请选择 进入手机版 | 继续访问电脑版

技术控

    今日:34| 主题:53287
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Lazy-loading ES2015 modules in the browser

[复制链接]
下一秒落寞 发表于 2016-10-1 11:29:42
153 1

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
Over the last few years, developers have been relentlessly moving their server-side sites to the client-side on the premise that the page performance would be improved.
    However, this may not be enough. Have you ever considered that your site may be downloading more stuff than is being actually used? Meet Lazy-loading , a design pattern about deferring the initialization (loading/fetching/allocation) of a resource (code/data/asset) until the point at which it is needed.
    At the same time, ES2015 is already production-ready through transpilers such as Babel . Now you no longer need to fight the AMD vs CommonJS war - described on my article The mind-boggling universe of JavaScript Module strategies - since you can simply write ES2015 modules and have them transpiled and delivered to the browser, while supporting your existing CommonJS or AMD modules.
    This article demonstrates how to load ES2015 modules synchronously (during the page load) and asynchronously (performing lazy-loading) using System.js .
   Page load vs Lazy-loading

   When developing JavaScript code to be executed on the browser, you always have to decide WHEN you want it to be executed.
    There is always some chunk of code that must run during the page load , as for instance the structural setup of an SPA using frameworks such as Angular, Ember, Backbone or React. Such code must be referenced on the main HTML document returned to the browser after a page request, most likely through one or more <script> tags.
   On the other hand, you might have some more chunks of code from features that should only be executed if certain triggering conditions happen. Classical examples are:
   
       
  • Content below the fold, such as a Reviews panel, which shows up after the user scrolls down the page.   
  • Content displayed as a consequence of triggering an event, such as a Zoomer overlay, which shows up after the user clicks on the image.   
  • Unusual/infrequent content, such as a Free Shipping widget, which only applies to a fraction of the pages   
  • Content that shows up after some time, such as a customer service chat box.   
   This way, for a given feature like those ones above, if its triggering condition never happens, its chunk of code won't ever be executed. Hence, that chunk of code is definitely not needed during the page load and it can be deferred.
   In order to defer it, you simply need to leave that code out of the chunk of code which gets downloaded and executed during the page load - so that it would only be downloaded and executed on demand, when its triggering condition happens for the first time.
    This approach of asynchronously loading deferred code, or lazy-loading , plays an important role in improving the page performance, in terms of reducing the page load time and the Speed Index .
    In order to learn more about the Speed Index and the performance impacts on Page load vs Lazy-loading, check out my article Leveling up: Simple steps to optimize the Critical Rendering Path .
   The pitfalls of AMD

    The AMD standard was created for asynchronous module loading on the browser, being one of the first successful alternatives to the spaghetti mess known as global JavaScript files scattered around your page. According to the Require.js documentation :
   The AMD format comes from wanting a module format that was better than today’s “write a bunch of script tags with implicit dependencies that you have to manually order” and something that was easy to use directly in the browser.
    It is based on empowering the Module Design Pattern with a module loader, dependency injection, alias resolution and asynchronous capabilities. One of it main usages is to perform lazy-loading of modules.
   Despite being a formidable idea, it brings some inherent complexity: namely, the need to understand runtime module timelines, which was previously unnecessary. This means that developers need to know when each asynchronous module is expected to do its work.
   By failing to understand this, developers got into situations that may work sometimes and may not work some other times, due to race conditions, which can be quite difficult to debug. Because of such things, AMD lost quite a bit of its momentum and traction, unfortunately.
    In order to learn more about AMD pitfalls, check out Moving Past RequireJS .
   ES2015 modules 101

   Before we go any further, let's go over ES2015 modules. If you are already familiar with them, here's a quick refresher.
   Modules have been finally adopted as an official part of the JavaScript language in ES2015. They are powerful yet simple to grasp, standing on the shoulders of the CommonJS modules giants.
   Scope

   Basically, a ES2015 module will live in its own file. All its "globals" variables will be scoped to just this file. Modules can export data and also import other modules.
   Exporting and importing

    Export a ES2015 module's interface through the keyword export before each item you want to export (a variable, function or class). In the following example, we are exporting Dog and Wolf :
  
  1. // zoo.js
  2. var getBarkStyle = function(isHowler) {  
  3.   return isHowler? 'woooooow!': 'woof, woof!';
  4. };
  5. export class Dog {  
  6.   constructor(name, breed) {
  7.     this.name = name;
  8.     this.breed = breed;
  9.   }
  10.   bark() {
  11.     return `${this.name}: ${getBarkStyle(this.breed === 'husky')}`;
  12.   };
  13. }
  14. export class Wolf {  
  15.   constructor(name) {
  16.     this.name = name;
  17.   }
  18.   bark() {
  19.     return `${this.name}: ${getBarkStyle(true)}`;
  20.   };
  21. }
复制代码
   Let's see how to import this module in a Mocha/Chai unit test, using the syntax import <object> from <path> . As for <object> we can take advantage of another ES2015 feature here - destructuring objects - where we can basically create our own object from items being imported from a module. We can then decide to just import expect from chai as well as Dog and Wolf from Zoo .
  
  1. // zoo_spec.js
  2. import { expect } from 'chai';  
  3. import { Dog, Wolf } from '../src/zoo';
  4. describe('the zoo module', () => {  
  5.   it('should instantiate a regular dog', () => {
  6.     var dog = new Dog('Sherlock', 'beagle');
  7.     expect(dog.bark()).to.equal('Sherlock: woof, woof!');
  8.   });
  9.   it('should instantiate a husky dog', () => {
  10.     var dog = new Dog('Whisky', 'husky');
  11.     expect(dog.bark()).to.equal('Whisky: woooooow!');
  12.   });
  13.   it('should instantiate a wolf', () => {
  14.     var wolf = new Wolf('Direwolf');
  15.     expect(wolf.bark()).to.equal('Direwolf: woooooow!');
  16.   });
  17. });
复制代码
  Default

    If you only have one item to export, you can use export default to export your item as an object instead of exporting a container object with your item inside:
  
  1. // cat.js
  2. export default class Cat {  
  3.   constructor(name) {
  4.     this.name = name;
  5.   }
  6.   meow() {
  7.     return `${this.name}: You gotta be kidding that I'll obey you, right?`;
  8.   }
  9. }
复制代码
  Importing default modules is simpler, as object destructuring is no longer needed. You can simply directly import the item from the module.
  
  1. // cat_spec.js
  2. import { expect } from 'chai';  
  3. import Cat from '../src/cat';
  4. describe('the cat module', () => {  
  5.   it('should instantiate a cat', () => {
  6.     var cat = new Cat('Bugsy');
  7.     expect(cat.meow()).to.equal('Bugsy: You gotta be kidding that I\'ll obey you, right?');
  8.   });
  9. });
复制代码
   In order to learn more about ES2015 modules, check out Exploring ES6 book — Modules .
   ES2015 Module Loader and System.js

    As surprising as it may be, ES2015 doesn't actually have a module loader specification. There was a popular proposal for a dynamic module loader - es6-module-loader - which inspired System.js . This proposal has been retreated, but a new Loader specification is in the works by WhatWG , hopefully for ES2017.
    Nevertheless, System.js is today one of the most frequently used module loader implementations which support ES2015. It supports ES2015, AMD, CommonJS and global scripts in the browser and NodeJS. It provides an asynchronous module loader (to pair with Require.js) and ES2015 transpiling through Babel , Traceur or Typescript .
    System.js implements asynchronous module loading using a Promises-based API. This is a very powerful and flexible approach, since promises can be chained and combined: so for instance, if you want to load multiple modules in parallel, you can use Promises.all and just fire your listener when all the promises have been resolved.
   Importing modules synchronously and asynchronously

    In order to illustrate the loading of modules in both synchronous and asynchronous fashion I've created a sample project, which will synchronously load our Cat module during the page load, and lazy-load the Zoo module once the user clicks on a button. The code is available on my Github project lazy-load-es2015-systemjs .
    Let's have a look at the main chunk of code which is loaded during the page load, our main.js .
    First, notice how it performs synchronous loading of Cat through import . After that, it creates an instance of Cat , invokes its method meow() and append the result to the DOM:
  
  1. // main.js
  2. // Importing Cat module synchronously
  3. import Cat from 'cat';
  4. // DOM content node
  5. let contentNode = document.getElementById('content');
  6. // Rendering cat
  7. let myCat = new Cat('Bugsy');  
  8. contentNode.innerHTML += myCat.meow();
复制代码
   Lastly, notice the asynchronous loading of Zoo through System.import('zoo') , and finally, the instances of Dog and Wolf invoking their method bark() and again appending the results to the DOM:
  
  1. // Button to lazy load Zoo
  2. contentNode.innerHTML += `<p><button id='loadZoo'>Lazy load <b>Zoo</b></button></p>`;
  3. // Listener to lazy load Zoo
  4. document.getElementById('loadZoo').addEventListener('click', e => {
  5.   // Importing Zoo module asynchronously
  6.   System.import('zoo').then(Zoo => {
  7.     // Rendering dog
  8.     let myDog = new Zoo.Dog('Sherlock', 'beagle');
  9.     contentNode.innerHTML += `${myDog.bark()}`;
  10.     // Rendering wolf
  11.     let myWolf = new Zoo.Wolf('Direwolf');
  12.     contentNode.innerHTML += `<br/>${myWolf.bark()}`;
  13.   });
  14. });
复制代码
  Voilà

    When the page is first loaded, the only modules which are loaded are Cat and Main :
   

Lazy-loading ES2015 modules in the browser

Lazy-loading ES2015 modules in the browser

    Once the user clicks on the button, the Zoo module is then loaded:
   

Lazy-loading ES2015 modules in the browser

Lazy-loading ES2015 modules in the browser

   Conclusion

   Mastering the art of keeping the page load close to the minimal necessary and lazy-loading deferrable modules can definitely improve your page performance. AMD and CommonJS paved the way for ES2015 modules, which are available to you right now via transpilers. You can start loading your ES2015 modules with System.js, while the official Module Loader spec is not ready.
    For more information on this, check out the presentation Lazy Loading ES2015 modules in the browser given by the author at conferences such as: Front End Design Conference (St. Petersburg, FL), DevCon5 (New York, NY) and Abstractions (Pittsburgh, PA).
   

Lazy-loading ES2015 modules in the browser

Lazy-loading ES2015 modules in the browser



上一篇:数据平台技术指南
下一篇:Why Good Linux Sysadmins Use Markdown
非希风 发表于 2016-10-2 10:49:54
2016-10-02楼主还是蛮拼的。
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )

© 2001-2017 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表