一、为什么需要模块化?
代码量骤增 => 分治管理的刚性需求
二、模块化方案需解决什么问题?
模块化要实现两个东西:模块加载与模块封装。面临的具体问题包括:
1、如何定义模块以确保模块的作用域独立,避免命名冲突?
2、如何管理模块间的依赖关系,避免重复加载与循环引用?
3、模块化的代码如何部署,以降低HTTP请求数?
4、如何实现按需加载?
5、如何在解决上述问题之后,保证性能且不影响debug?
三、原始的解决方案有何局限?
命名空间 + 立即执行函数 + script标签
局限性:
1、全局空间污染
2、需手动管理依赖,不具备可扩展性
3、无法实现按需加载
四、新的解决方案
1、CommonJS
CommonJS 起源于一个服务端项目 SeverJS,该项目意在通过模块化的开发模式, 解决 JS 作用域的问题。后来发展成了一个致力于构建 JS 生态圈的组织。
CommonJS 提供了一套模块加载的规范,其核心语法是通过 module.exports 暴露接口,通过 require() 加载资源。
CommonJS 规范采用同步加载,适用于服务端,但并不适用于浏览器环境(网络延迟、异步特性),因此在浏览器端出现了各类模块加载器,以解决模块加载的问题。
各类模块加载器提出了各自的模块封装的规范。其中 Sea.js 提出/实现的封装规范,就是 CMD 规范;RequireJS 提出/实现的封装规范,就是 AMD 规范。
2、Sea.js
模块封装:
define (function (require, exports, module) {
var a = require('./a') // 模块加载
a.doSomething();
// ……
var b = require('./b') // 依赖可以就近书写
b.doSomething();
// 通过 exports 对外提供接口
exports.doSomething = ...
// 或者通过 module.exports 提供整个接口
module.exports = ...
})
模块加载:
var $ = require('jquery');
3、RequireJS
模块封装:
define (['./a', './b'], function(a, b) {
a.doSomething();
// 此处略去 100 行
b.doSomething();
return function () { } //返回模块的值,可以是函数,也可以是对象
})
模块加载:
require (['./a', './b'], function (a, b) {
// do sth
})
区别
RequireJS:依赖前置,提前加载
Sea.js:依赖就近,延迟加载
4、UMD
一种兼容 CommonJS 和 AMD 的语法糖。事实上 RequireJS 和 Sea.js 就是相互支持的。
图片来源:https://www.leanpanda.com/blog/2015/06/28/amd-requirejs-commonjs-browserify/
5、打包工具:Browserify&Webpack
在模块化的开发方式下,模块加载器(Module loader)解决了模块的加载与依赖的自动管理,但是并没有解决 HTTP 请求数的问题。如何将分散的文件合并成一个或几个文件以减少 HTTP 请求,这就是打包工具(Module bundler)的作用。
打包工具的核心能力是对 js 代码进行合并,扩展能力是对 js 代码进行优化、编译和压缩。webpack 的特色是扩展得比较狠,通过各类插件,可以打包任意类型的文件。
打包工具的真正价值在哪里呢?仅仅是合并文件从而减少 HTTP 请求数吗?当然不止,打包工具的真正价值是在工程开发中,完成从开发状态到发布状态的自动化构建。
减少 HTTP 请求和按需加载其实是相互矛盾的,都打包成一个文件了,自然不需要考虑模块加载的问题。但是把所有文件都打包成一个文件,又显得有些冗余。所以 webpack 也支持 code spliting,把文件打成多个包。
6、终结者:ES6 Module
原生模块标准得到浏览器全面支持之时,就是所有模块封装方案灭亡之日。但打包工具(自动化构建工具)仍会继续存在。
五、模块打包的技术实现
图片来源:https://www.leanpanda.com/blog/2015/06/28/amd-requirejs-commonjs-browserify/
以上是 browserify 的实现,webpack 的实现也差不多,这里详细说明一下。
首先,webpack 会将每个 js 文件编译成一个函数:
webpack 会将文件路径映射为一个数字 id,入口文件默认 id 为 0,在入口文件中遇到的第一个加载的模块,id 为 1……,总之按照加载的顺序依次赋予一个 id。
webpack 函数用于整个模块的加载,所有模块函数会按照 id 的顺序组成一个数组,传给 webpack 进行加载。
最后运行的结果是: