背景
在上一篇文章里面,我们简单介绍了浏览器端 AMD 模块化方案。这里我们再来看看当时存在的另外一种方案 CommonJs。CommonJs 最开始是用于 node 服务端的模块化方案,browserify 为了使 node 端的代码可以在浏览器运行,对 CommonJs 的 require 语法进行了打包处理。
browserify 简介
browserify 从名字就能看出来,这是由 browser 加后缀 ify 组成的单词,字面意思就是"使浏览器化"。把什么"使浏览器化"呢?其实就是node 代码。官网中有这么一句话
Browserify lets you require('modules') in the browser by bundling up all of your dependencies.
意思是,我们可以在浏览器端使用 node 的 require 方式编写代码,browserify 会将代码中的依赖进行打包,生成的代码就可以在浏览器运行了。
browserify 总体上来说,包括两个方面的作用:
1、使 node 特定的 api 能够通过 polyfills 的方式在浏览器运行,比如说 process.nextTick() 。
2、就是通过打包,使运用了 CommonJs 规范的代码能在浏览器端运行。
这里,我们主要看一下第二点,看看它是怎么做的?
打包
首先通过 pnpm add browserify -D
的方式安装依赖,然后在 scrpits 添加
json
"build": "browserify index.js -o bundle.js"
新建几个测试文件
csharp
add.js
subtract.js
calc.js
index.js
文件内容如下
js
// add.js
module.exports = function add(a, b) {
return a + b;
}
// subtract.js
module.exports = function sub(a, b) {
return a - b;
}
// calc.js
const add = require('./add');
const sub = require('./subtract');
module.exports = (a, b) => {
return add(a, b) * sub(a, b);
}
// index.js
const calc = require('./calc');
console.log(calc(1, 2));
内容比较简单,index.js 中会引入 calc.js,clac.js 中会引入 add.js 和 subtract.js。 运行 pnpm build
以后,生成了 bundle.js。
js
// bundle.js
(function () {
// 省略加载函数
})()(
{
1: [
function (require, module, exports) {
module.exports = function add(a, b) {
return a + b;
};
},
{},
],
2: [
function (require, module, exports) {
const add = require("./add");
const sub = require("./subtract");
module.exports = (a, b) => {
return add(a, b) * sub(a, b);
};
},
{ "./add": 1, "./subtract": 4 },
],
3: [
function (require, module, exports) {
const calc = require("./calc");
console.log(calc(1, 2));
},
{ "./calc": 2 },
],
4: [
function (require, module, exports) {
module.exports = function sub(a, b) {
return a - b;
};
},
{},
],
},
{},
[3]
);
简单看一下,可以看出 browserify 将每一个模块使用函数进行了包裹,并变成了一个大对象的 value,通过这种方式将之前的 4 个文件打包变成了一个文件。
我们再来看一下早期的 webpack 怎么打包的,下面是 webpack version 1 的打包结果。
javascript
(function (modules) {
// 省略加载函数
})([
/* 0 */
function (module, exports, __webpack_require__) {
const calc = __webpack_require__(1);
console.log(calc(1, 2));
},
/* 1 */
function (module, exports, __webpack_require__) {
const add = __webpack_require__(2);
const sub = __webpack_require__(3);
module.exports = (a, b) => {
return add(a, b) * sub(a, b);
};
},
/* 2 */
function (module, exports) {
module.exports = function add(a, b) {
return a + b;
};
},
/* 3 */
function (module, exports) {
module.exports = function sub(a, b) {
return a - b;
};
},
]);
可以看出 browserify 和早期的 webpack 的结果是比较类似的,webpack 将 browserify 中的大对象换成了数组。如果看文章比较多的话,会发现目前网上一些关于 webpack 的文章,依然是这种数组的形式。
番外
上面这两种打包结果都有一个不太被人关注的缺陷,就是会为每一个 module 模块包裹一个函数。这会造成两个问题。
1、性能问题。每一个 module 模块都会被函数包裹,意味着 javascript 代码运行的时候,需要为每一个模块创建一个新的函数作用域。这就意味着有更多的性能消耗。The cost of small modules 这篇文章专门探讨了这个问题,当模块数量在1000 个以上的时候,性能下降非常明显。
2、打包结果变大。这个就比较容易理解了,由于会为每一个模块包裹一个函数,那么如果将项目代码拆分的越细,模块就会越多,包裹的函数就会越多,导致最后的打包结果就会越大。
这两个问题其实就是 rich harris 在这篇采访中提到的问题,也是 rollup 诞生的背景。
所以,当有人问 webpack 和 rollup 有什么区别的时候,很多人会说 rollup 用来打包库,webpack 用来打包前端代码。但是为什么呢?原因就在这里。
后来随着打包工具的进化,webpack 在新版的时候借鉴了rollup 的功能,解决了这个问题。同时 rollup 也添加了代码分割这种前端项目需要的功能。
关于 webpack 和 rollup 的详细内容,我们后面再说吧。
总结
这里我们首先简单介绍了 browserify,然后使用 browserify 进行打包,分析了打包结果。最后对比了 browserify 和 早期 webpack 的打包结果,并揭露了这种方式的弊端。