入门好文:http://zhaoda.net/webpack-handbook/
现状
在单页应用,每个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的js代码,这给前端开发和资源组织带来了巨大的挑战。
模块化系统的演进
<script>标签
最原始的js文件加载方式,如果把每个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,不同模块的接口调用都是一个作用域。
这种原始的加载方式暴露了一些显而易见的弊端:
全局作用域下容易造成变量冲突
偶要文件只能按照<script>的书写顺序进行加载
开发人员必须主观解决模块和代码库的依赖关系
在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪
CommonJS
服务器端的nodejs遵循commonjs规范,该规范的核心思想是允许模块通过require方法来同步加载所要依赖的其他模块,然后通过exports或module.exports来导出需要暴露的接口。
优点:
服务器端模块便于重用
npm中已经有将近20万个可以使用的模块包
简单并容易使用
缺点:
同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步jia在的。
不能非阻塞的并行加载多个模块
AMD
规范其实只有一个主要接口,它在声明模块的时候指定所有的依赖,对于依赖的模块提前执行,依赖前置。
优点:
适合在浏览器环境中异步加载模块
可以并行加载多个模块
缺点:
提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅
不符合通用的模块化思维方式,是一种妥协的实现
实现:requireJs
CMD
规范跟AMD很相似,尽量保持简单,并与CommonJS和nodejs的modules规范保持了很大的兼容性。
优点:
依赖就近,延迟执行
可以很容易在nodejs执行
缺点:
依赖SPM打包,模块的加载逻辑偏重
实现:sea.js
ES6模块
ES6标准增加了js语言层面的模块体系定义。ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。
优点:
容易进行静态分析
面相未来的ECMAScript标准
缺点:
原生浏览器端还没有实现该标准
全新的命令字,新版的nodejs才支持
实现:
Babel
CommonJS规范
CommonJS规范是为了解决js的作用域问题而定义的模块形式,可以使每个模块在他自身的命名空间中执行。该规范的主要内容是,模块必须通过module.exports导出对外的变量或接口,通过require()来导入其他模块的输出到当前模块作用域中。
// moduleA.js
module.exports =function(value){
return value * 2;
}
// moduleB.js
var multiplyBy2 =require('./moduleA');
var result = multiplyBy2(4);
CommonJS是同步加载模块,但其实也有浏览器端的实现,其原理是现将所有模块都定义好并通过id索引,这样就可以方便的在浏览器环境中解析了。
更多关于CommonJS规范的内容请查看:
http://wiki.commonjs.org/wiki/CommonJS
AMD规范
ADM异步模块定义是为浏览器环境设计的,因为CommonJS模块系统是同步加载的,当前浏览器环境还没有准备好同步加载模块的条件。
AMD定义了一套js模块依赖异步加载标准,来解决同步加载的问题。
模块通过define函数定义在闭包中。
格式:
define(id?:String, dependencies?:String[], factory:Function|Object);
id是模块的名字,他是可选的参数。
dependencies制定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数一次传入factory中。如果没有指定dependencies,那么他的默认值是["require", "exports", "module"]
define(function(require, exports, module){})
factory是最后一个参数,它包裹了模块的具体实现,他是一个函数或者对象。如果是函数,那么他的返回值就是模块的输出接口或值。
用例:
定义一个名为myModule的模块,它依赖jQuery模块:
define('myModule', ['jquery'],function($){
// $ 是 jquery 模块的输出
$('body').text('hello world');
});
//使用
require(['myModule'],function(myModule){});
注意:在webpack中,模块名只有局部作用域,在require.js中模块名是全局作用域,可以在全局引用
定义一个没有id值的匿名模块,通常作为应用的启动函数:
define(['jquery'],function($){
$('body').text('hello world');
});
依赖多个模块的定义:
define(['jquery','./math.js'],function($, math){
// $ 和 math 一次传入 factory
$('body').text('hello world');
});
模块输出:
define(['jquery'],function($){
var HelloWorldize =function(selector){
$(selector).text('hello world');
};
// HelloWorldize 是该模块输出的对外接口
returnHelloWorldize;
});
在模块定义内部引用依赖:
define(function(require){
var $ =require('jquery');
$('body').text('hello world');
});