Tiago Garcia @ ForwardJS
Jul 27th, 2017
- JavaScript P.I., dog daddy, vegan rollerblader,
not your typical Brazilian. - Tech Manager at [Avenue Code](http://www.avenuecode.com)
- Tech Lead at [Macys.com](http://www.macys.com)
- Twitter: [@tiagooo_romero](https://twitter.com/tiagooo_romero)
- Site: *[tiagorg.com](http://tiagorg.com)*
- *[tgarcia@avenuecode.com](mailto:tgarcia@avenuecode.com)*
- 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: JavaScript Lazy-loading for dummies
- SPA for performance?
- Lazy-loading 101
- Do I need Lazy-loading?
- How to Lazy load?
- 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!
- 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
- 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
- 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.
- 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
- 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.
- 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)
- 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: Blazing loading
- Modules
- Lazy-loading in CommonJS
- Lazy-loading in ES Modules
- Webpack 2
- 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
- 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!
- 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
- 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.
- 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!
- 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;
- Using Webpack's
require.ensure
// 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();
});
});
- Read more about Code Splitting
- 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)}`;
};
}
- ES Modules are shipping on evergreen browsers: Chrome 61, Edge 15, Firefox 54, Safari 10.1
- Yet for compatibility reasons you probably should still use a transpiler as Babel.js.
- You can adopt a module loader such as System.js.
- More on:
- 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.
- Using the dynamic import() operator
// 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 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.
- Fork from github.com/tiagorg/lazy-load-es2015-webpack2-challenge
- Take a moment to understand this implementation.
- Completely lazy-load
Animations
and its dependencies upon click of the "Merry Xmas" button. - Completely lazy-load
Messaging
after 5s the page has been loaded. - Verify your lazy-loadable bundles have been generated and are loading properly.
- Use the dynamic import() operator.
- Special thanks to ForwardJS crew and most importantly all the attendees!
- Slides: tiagorg.com/lazy-loading-js-modules
- Github: github.com/tiagorg/lazy-loading-js-modules
- More talks at tiagorg.com
- Follow me at @tiagooo_romero