Talk Lazy Loading JS Modules on ForwardJS 2017
JavaScript CSS HTML
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
css
font
img
js
src
template
.gitignore
Gruntfile.js
README.md
config.json
index.html
package.json

README.md

Lazy loading JS modules in the browser

Tiago Garcia @ ForwardJS
Jul 27th, 2017


Tiago Garcia


Avenue Code

  • Offices in SF & NY (US), São Paulo & Belo Horizonte (BR)
  • Primary verticals:
    Retail & Financial services
  • Partners with MuleSoft, Adobe, Chef, Oracle and AWS
  • Project Management, Business Analysis, Development, QA, DevOps, Coaching
  • *[avenuecode.com/careers](https://www.avenuecode.com/careers)*

Part I


Agenda

  • Part I: JavaScript Lazy-loading for dummies
    • SPA for performance?
    • Lazy-loading 101
    • Do I need Lazy-loading?
    • How to Lazy load?

SPA for performance?


SPA for performance?

  • Moving you server-side app to SPA would improve the page performance:
    • Requests returning JSON instead of HTML
    • Rendering a new view instead of a page reload
    • Routing and state management on the client-side
  • But that may not be enough!
    • Have you ever checked if you application is downloading more stuff that is being actually used?
    • All those performance gains can fall short if you download all your stuff at once!

Lazy-loading 101

  • E-commerce A (server-side rendered) has 5 pages: home, browse, product, cart, checkout
    • Each page has 300 KB HTML + 100 KB JS
    • The complete flow will download:
      • *400 KB* for each page
      • *2 MB* total

Lazy-loading 101

  • E-commerce B (SPA) has the same 5 pages
    • 1 actual page + 4 views
    • Home page has 300 KB HTML + 100 KB JS
    • Each of the views has 50 KB JSON + 150 KB JS
    • The complete flow will download:
      • *1.2 MB* total (vs *2 MB*)
      • 80% just on home page: *1 MB* (vs *400 KB*)
    • However, if you use Lazy-loading:
      • 400 KB on home page
      • 200 KB each view after

What is Lazy-loading?

  • Lazy loading is a design pattern about deferring the initialization (loading/fetching/allocation) of a resource (code/data/asset) until the point at which it is needed.
  • Its main goal is to improve efficiency when a significant amount of resources is not needed at first.
  • Lazy-loading is targeted to increase performance and save on memory consumption and processing power.
  • It's an EAA pattern from Martin Fowler.

Do I need Lazy-loading?

  • Above the fold
    • what you see first when you open a page
    • part of the Critical Rendering Path
    • must be rendered during the page load time
    • thus, it can't be lazy loaded
  • Below the fold
    • everything else, needs scrolling or user interaction
    • won't be displayed during the page load time
    • it doesn't need to be rendered with the page load
    • thus, it can be lazy loaded

Do I need Lazy-loading?

  • Ask yourself: is there any chunk of code/library that only runs:
    • below the fold (as a reviews panel)?
    • after some event (as a button click)?
    • upon a certain condition (as an uncommon widget)?
  • If you answered yes, you may profit from lazy load and potentially improve your page performance.
  • Just defer the downloading of those chunks of code/libraries until the trigger is executed.

Do I need Lazy-loading?

  • Lazy loading isn't recommended for certain scenarios:
    • Supporting network limitations (offline mode)
    • Web-based mobile apps (Web Views)
    • Apps that can't be paused (games)
    • Specific UX requirements (single loading screen)

How to Lazy load?

  • DON'T include all your scripts in the page at once.
  • DON'T import all your modules on the top of your file.
  • Carefully decide WHEN to import or require your modules and libraries:
    • Below the fold (scroll listener)
    • Event callbacks (user interactions / network calls)
    • Conditionally (for uncommon scenarios)
    • After some time (chat overlays)
  • This talk is about JS but you can also lazy load images, fonts and CSS.

Part II


Agenda

  • Part II: Blazing loading
    • Modules
    • Lazy-loading in CommonJS
    • Lazy-loading in ES Modules
    • Webpack 2

Module

  • Structural design pattern
  • Purpose: to define reusable components with private/public variables and functions
  • Pre-condition: a chunk of code with a return point
  • Post-condition: a definition that represents that chunk of code as a module

Global object

  • Encapsulation through closures -> function scope
  var Kennel = (function() {
    var getBarkStyle = function(isHowler) {
      return isHowler? 'woooooow!': 'woof, woof!';
    };
    return {
      Dog: function(name, breed) {
        this.name = name;
        this.bark = getBarkStyle(breed === 'husky');
      },
      Wolf: function(name) {
        this.name = name;
        this.bark = getBarkStyle(true);
      }
    };
  })(); // IIFE
var myDog = new Kennel.Dog('Sherlock', 'beagle');
console.log(myDog.name + ': ' + myDog.bark); // Sherlock: woof, woof!

Revealing Module

  • Flexibility to switch items from private to public scope.
  let Kennel = (function() {
    let getBarkStyle = isHowler => isHowler ? 'woooooow!' : 'woof, woof!';
    let Dog = function(name, breed) {
      this.name = name;
      this.bark = getBarkStyle(breed === 'husky');
    };
    let Wolf = function(name) {
      this.name = name;
      this.bark = getBarkStyle(true);
    };
    return {
      Dog: Dog,
      Wolf: Wolf
    };
  })(); // IIFE

Why to use Modules?

  • Spaghetti code is bad for reusability, readability, code organization and is very brittle (side-effects).
  • Modules can fix these all if properly implemented.
  • A module can be delivered as a dependency for another module.
  • Modules can be packaged and deployed separately from each other, mitigating the "butterfly effect".
  • Modules bring cohesion up and coupling down.

Module loaders

  • On the previous example, Kennel is a global var:
    • fragile (any posterior code can modify/redefine it)
    • not scalable (what if you define 100 modules?)
    • counter-productive (you have to manually resolve your dependencies)
  • Enter module loaders:
    • Container for module registration under aliases
    • Dependency injection
    • Modules loading on demand
  • Included with your Module standard of choice!

CommonJS

  • Export your module interface with module.exports
  • Import on the client using require(dependency)
  // dog.js
  var Dog = function(name, breed) {
    this.name = name;
    this.breed = breed;
  };
  Dog.prototype.bark = function() {
    return this.name + ': ' + getBarkStyle(this.breed);
  };
  function getBarkStyle(breed) {
    return breed === 'husky'? 'woooooow!': 'woof, woof!';
  };
  module.exports = Dog;

Lazy-loading in CommonJS

// main.js
document.getElementById('loadDogButton')
        .addEventListener('click', function(e) {
  // Lazy-loading dog module
  require.ensure([], function(require) {
    var Dog = require('./dog'),
        dogContainer = document.getElementById('dogContainer');

    var sherlock = new Dog('Sherlock', 'beagle');
    dogContainer.innerHTML += sherlock.bark();

    var whisky = new Dog('Whisky', 'husky');
    dogContainer.innerHTML += '<br/>' + whisky.bark();
  });
});

ES Modules

  • ES2015+ offers native Modules which are quite a bit similar to CommonJS.
// dog.js
let getBarkStyle = breed => {
  return breed === 'husky'? 'woooooow!': 'woof, woof!';
};

export class Dog {
  constructor(name, breed) {
    this.name = name;
    this.breed = breed;
  }

  bark() {
    return `${this.name}: ${getBarkStyle(this.breed)}`;
  };
}

Using ES Modules


System.js

  • System.js is a module loader which supports AMD, CommonJS, ES and global scripts.
  • It performs asynchronous module loading using a Promises-based API.
  • Promises can be chained and combined.
  • Promises.all can load multiple modules in parallel.
  • System.js 0.2.0 ships with dynamic import() operator.

Lazy-loading ES Modules

// main.js
document.getElementById('loadDogButton')
        .addEventListener('click', e => {
  // Lazy-loading dog module
  import('dog').then(Dog => {
    let dogContainer = document.getElementById('dogContainer');

    let sherlock = new Dog('Sherlock', 'beagle');
    dogContainer.innerHTML += sherlock.bark();

    let whisky = new Dog('Whisky', 'husky');
    dogContainer.innerHTML += `<br/>${whisky.bark()}`;
  })).catch(err => {
    console.log("Module loading failed");
  });
});

Webpack 2

  • Webpack is a module bundler constituted of Entries, Outputs, Loaders and Plugins.
  • Webpack 2 offers native ES modules and System.js support.
  • Loads JS modules in AMD, CommonJS and ES modules (and also TypeScript and CoffeeScript).
  • Performs bundling (importing the dependency graph in the right order).
  • Lazy-loads JS and CSS (code splitting).
  • Performs tree-shaking.

Challenge


Conclusion


Questions?


Thanks!