Another RayJune

JavaScript Module 进化史

简明扼要回顾一下 JavaScript Module 的进化史。

Inline Script

即在 script 标签中书写代码,常用于初学者。但是它有以下缺点:

  • Lack of Code Reusability: If we need to add another page and need some of the functions from this page, we will have to copy and paste the code
  • Lack of Dependency Resolution: You are responsible for having add, reduce and sum functions come before main part of the script.
  • Pollution of global namespace: All the functions and variables will reside on global scope.

所以我们基本不用这样的形式。

Script tags

即采用 script src=”…” 的形式来引入JS文件。

比起 Inline Script 已然是好了许多,解决了加载多文件的问题,但是还是没有解决变量全局污染以及加载文件顺序的问题:

  • Lack of Dependency Resolution: The order of the files are important. You are responsible for including add.js, reduce.js and sum.js files before main.js file.
  • Pollution of global namespace: All the functions and variables are still in global scope.

Module Object and IIFE(Module Pattern)

通过模块对象和立即执行函数的形式,我们可以只创建一个对象,将其他函数方法添加到这个对象上即可:

1
2
3
4
5
6
7
8
9
10
var myApp = {};

(function(){
myApp.add = function(a, b) {
return a + b;
}
myApp.sayHello = function(name) {
return 'hello ~' + name;
}
})();

但是它仍不完美:

  • Lack of Dependency Resolution: The order of the files are still important. myApp.js file must come before any other files. And main.js file must come after all other library files.
  • Pollution of global namespace: Number of global variable is now 1, but is not zero yet.

CommonJS

在 09 年 node.js 的出现使得 CommonJS 这种方法的实现成为可能,CommonJS 是一种规范。

就像 ECMA 是 JS 语法的规范, W3C 是 JavaScript web API (such as DOM or DOM events) 的规范一样,CommonJS 定义了 common APIs for web server, module, desktop and command line applications.

主要提供了导入导出的样式规范:

1
2
3
4
5
6
7
// add.js
module.exports = function add(a, b){
return a+b;
}

// another file
var add = require(‘./add’);

如果你写 node.js 的话, 就会发现 node.js 正是使用了 CommonJS 的规范样式。

Asynchronous Module Definition(AMD)

CommonJS 也有它的问题,它没有解决好异步加载的问题,比如当你执行一个 var add=require(‘add’); 时,这个网页会被停止运行直到这个文件加载完毕,这显然是很不好的。

为了实现异步加载其他 module 文件,AMD 出现了

1
2
3
define([‘add’, ‘reduce’], function(add, reduce){
return function(){...};
});

define 这个函数提供了回调函数,实现了异步加载,大大提高了 module 的加载效率。

short break

这样看来,CommonJS 和 AMD 已经解决了我们最主要的两个问题:

  • dependency resolution
  • pollution of global scope

这里再没有全局变量的污染,我们只需要考虑每个文件对每个 module 的依赖关系就好了。

RequireJS

讲完了 CommonJS 和 AMD 的规范后,我们该来看看这两种规范的具体实例了吧?

RequireJS 就是一个实现 AMD 规范的小而美的库,如何使用呢? 看看官网的文档吧:) RequireJS get start

Browserify

讲完了 AMD 的实现库,我们再来看看 CommonJS 的具体实例。

不是说 CommonJS 只能同步加载吗,为什么还用它呢?

Browserify 完美解决了 CommonJS 的加载问题,我们来看看它是怎么解决的吧:

Browserify 是一个模块打包器,它会遍历你的项目 module 依赖,而后将他们和你的 main.js 一起打包为一个文件,直接用script src=”bundle.js” 加载bundle.js 即可。

而 Browserify 比较 RequireJS 而言使用方式更加简单: Just npm it

1
npm install -g browserify

安装后记得配置 package.json 文件

1
2
3
"scripts": {
"build": "browserify main.js -o bundle.js"
},

而后运行

1
npm run build

即可生成 bundle.js 文件。

ES6 module syntax

终于,ES6 推出的 module syntax 解决了 JS-module 这个争端,它使用起来是如此的方便、简洁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.js
import sum from "./sum";

var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];

// sum.js
import add from './add';
import reduce from './reduce';

export default function sum(arr){
return reduce(arr, add);
}

// add.js
export default function add(a,b){
return a + b;
}

Webpack

Webpack 是一个超级模块打捆器,和 Browserify 一样,它也是通过遍历文件中的 module 依赖关系将 所有要依赖的 module 打包成一个或多个文件。

不过它的强大之处在于 可配置性、可拓展性, 什么 CommonJS, AMD and ES6 modules 都可以打包。

而且它还有三个秘密武器:

  • Code Split: When you have multiple apps sharing same modules. Webpack can bundle your code into two or more files. For example, if you have two apps, app1 and app2, and both shares many modules. With Browserify, you would have app1.js and app2.js. And both contain all the dependency modules. But with Webpack, you can create app1.js, app2.js, and shared-lib.js. Yes, you will have to load 2 files from html page. But with hashed filename, browser cache and CDN, it can reduce initial loading time.
  • Loader: With custom loaders, you can load any file into your source. You can use ‘reuiqre()’ syntax to load not just JavaScript files, but also css, CoffeeScript, Sass, Less, HTML for template, images, etc.
  • Plugin: Webpack plugins manipulate your bundles before it is written into files. There are many community built plugins. For example, there are bundle for adding banners to bundled code, adding source map and splitting a bundle into chunks, and more.

或许它一开始比较难上手,但是…

既然选择了远方,就应该风雨兼程,不是吗 :》

Thanks

参考代码:github 上的代码
参考资料:medium上sungthecoder写的文章

文章标题:JavaScript Module 进化史

文章作者:RayJune

时间地点:上午9:02,于又玄图书馆

原始链接:https://www.rayjune.me/2017/09/28/JavaScript-Module-developing-history/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。