这篇博文介绍了两个未来发展的趋势(HTTP/2 和原生模块)对模块打包产生了何种影响。
1. 为什么要用模块打包
绑定打包指的是把几个带有模块的文件组合到一起,成为一个单一文件。这么做有三个理由:
- 在加载所有的模块时,需要检索的文件数量可以降低。
- 压缩一个打包文件要比压缩多个单独的文件更有效率一些。
- 没用使用的 exports 会在打包过程中被移除,这样能极大程度地节省内存空间。
2. JavaScript 模块
随着 ECMAScript 6 的提出, JavaScript 终于支持内置的模块功能了。(在本文余下内容中,我都将用 JavaScript 模块指代它)尽管如此, JavaScript 的模块化现在还处于一个有点儿尴尬的位置:
一方面, ES6 对模块化进行了语法和语义的标准化。 ES6 制定的规则成为了比较流行的模块书写格式,它的静态结构能自动省略没使用的 exports (JavaScript 中也称这种技术为 “tree-shaking”)
而另一方面,关于如何加载 JavaScript 模块的标准化进程仍在制定过程中,并且截至目前也还没有原生的 JavaScript 引擎支持它们。也就是说,目前唯一使用JavaScript模块的方式是将其编译为一种非原生的格式,比较流行的解决方案有:browserify, webpack, jspm和Rollup。
3. 模块打包的未来发展趋势。
接下来,我们一起看看未来将有很大发展空间的两个潜力股,以及它们对 JavaScript 模块打包的影响。
3.1 潜力股一号种子选手: HTTP/2
HTTP/2 正有条不紊地发展壮大。在上文列举的三个使用模块打包的理由当中,HTTP/2 主要影响其中第一条理由:HTTP/2 跟 HTTP/1相比 ,能极大程度地降低每个网页请求的代价,也就是说下载单个文件和下载多个文件相比,并不能显著地提升性能。既然如此,那我们可以使用更小型的、增量式更新方法。使用打包的话,就不得不下载整个打包后的文件。而不用打包的话,我们可以只下载内容发生改变的那部分(而其它部分的文件仍保存在浏览器缓存中)。
但是 HTTP/2 并不影响使用模块打包的第二和第三个原因。所以以后我们可能会使用混合式的方法,既有利于增量式更新,又能减少下载内容的大小。
3.2 潜力股二号种子选手: 原生 JavaScript 模块
一旦引擎支持原生的 JavaScript 模块的话,这是否会给打包带来影响呢?就连原生运行在浏览器上的 AMD 模块,它都有一套定制化的绑定格式(和自己的小型加载器)。那么原生的 JavaScript 模块比 AMD 模块有什么过人之处吗?答案应该是肯定的。Rollup 支持将多个 JS 模块打包成一个 JS 模块。
1 2 3 4 5 6 7 |
// lib.js export function foo() {} export function bar() {} // main.js import {foo} from './lib.js'; console.log(foo()); |
以下面两个 JS 模块为例:
1 2 3 4 5 6 7 |
// lib.js export function foo() {} export function bar() {} // main.js import {foo} from './lib.js'; console.log(foo()); |
Rollup 能将这两个 JS 模块绑定成下面的一个 JS 模块 (注意未使用的 export bar 被删除了):
1 2 3 |
function foo() {} console.log(foo()); |
创始人 Rich Harris 坦言,起初,没人想到JavaScript模块会引入打包机制。
当初我研究 Rollup 的时候,连我自己都没想到它能成功。
JS模块处理 imports的方式有助于打包: imports 不是 exports 的复制变量, 只能对 imports 执行只读操作。
在Rollup 官网上,你可以体验一下它的功能。
4. 延伸阅读
- “Building for HTTP/2” 作者 Rebecca Murphey (主要介绍了在新版 HTTP 中的最佳实践发生的彻底改变)
- Chap. “Modules” in “Exploring ES6” (主要介绍了 ES6 modules 的工作机制)
- “Babel and CommonJS modules” (介绍了如何通过 Babel 实现 交叉编译的 ES6 模块与 CommonJS 模块互通)