Browserify的运行机制
发布在前端模块化工具研究2014年3月30日view:6287
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

enter image description here

Browserify的运行机制

Browserify允许你在浏览器中使用require,在某种程度上实现了”NPM Everywhere”,这也许就是它变得越来越受欢迎的原因。在本文中,我们将一起来探讨Browserify的运行机制。

Browserify使用一个叫做“入口文件”的概念来描述它会从什么地方开始读取依赖图,同时它的输出结果将会是一个文件包。总的来说,一个Browserify文件包就是一个IIFE,或者叫做即可调用函数表达式(Immediately Invoked Function Expression)。这种机制使得代码一经载入就能够立刻运行。

模块映射

传递给IIFE的第一个变量时一个映射,这个映射的键是不重复的数字,而值是一个包含两个元素的数组。下面是一个简单的映射的例子:

{
  1: [function (require, module, exports) {
    module.exports = 'DEP';

  }, {}],
  2: [function (require, module, exports) {
    require('./dep');

    module.exports = 'ENTRY';

  }, {"./dep": 1}]
} 

正如你所看到的,每一个模块都被赋予了一个独特的数字id。在上面的例子中,我们的enter.js文件被包裹起来并给赋予了一个为2的键值,同时dep.js的内容被包裹起来同时被赋予了一个为1的键值。

我们再来看看enter.js模块。数组的以一个元素是一个包裹着源代码的闭包,它由Browserify为你自动生成。这个包裹的目的是防止作用于污染以及确保你的代码可以获取那些由Node环境提供的变量。我们在下一部分中将更深入的研究这个包裹函数。

数组的第二个元素是另一个映射,但是这个映射代表的事你的模块的依赖模块。由于enter.js依赖dep.js,在源代码中具体表示为require('./dep');,于是这个映射对象中就包含一个键位./dep而值为1的映射。当然,1就是模块映射中dep.js的键。由于dep.js不依赖于任何其他的模块,它的依赖映射就是一个空对象{}。

这是一个非常简单的例子,但是你可以从中了解到我们的依赖树是如何重新创建我们的代码。最终的输出由browser-pack生成,它是Browserify内建的一个函数。

生成闭包

既然我们已经了解了不同的文件之间是如何包含并与其它文件之间相关联的,现在我们来看看一个单独的文件以及他的内容是如何被扩充的。enter.js文件的源代码很简单:

require('./dep');

module.exports = 'ENTRY';  

但是在最终生成的文件包总它的形式是:

function (require, module, exports) {
  require('./dep');

  module.exports = 'ENTRY';
}  

Browserify将我们的代码包括在一个闭包中,这个闭包指明了几个非常重要的参数。在Node中,我们使用require方法来同步载入依赖模块。然而在浏览器端,载入模块的方式完全不同。在这里,没有require可以使用,因此Browserify为我们实现了这个方法,只要它包含在闭包中,我们就可以使用这个方法。

如果你熟悉CommonJS的语法,那么你一定能了解module和exports两个参数的作用。CommonJS使用module和exports变量来指明将什么值暴露到模块外部。在我们的entry.js中,我们通过将字符串”ENTRY”赋值给module.exports来指明将该字符串暴露到模块外部。乳沟我们想要暴露多个值,我们可以直接使用exports,比如exports.foo = “FOO”; exports.bar = “BAR”。再次申明,这些东西并不是浏览器内部的原生方法。但是Browserify实现了它。

缓存

提供给IIFE的第二个参数是一个之前已经载入的定义好的模块的缓存。大多数情况下它都是空对象{},因此我们在这里不打算提它。你只需要知道基本上它就是另一个在别处定义的模块映射,并在Browserify开始运行时传递到你的文件包中。

入口模块

传递给文件包IIFE的最后一个参数是一个包含模块id的数组,它被用做创建我们的依赖图的起点。在上面的例子中,entry.js被赋予了id 2。因此第三个参数就是[2]。它之所以是一个数组是因为你可以指明多个入口文件,这在运行测试时非常常见,但是一般情况下不太常用。

让全部代码运行起来

既然现在你已经了解了传递的参数分别是什么了,我们就来谈谈IIFE中的F究竟做了些什么。这个函数来源于browser-pack项目。具体来说,是prelude.js文件。你应该去看看这个文件中到底说了些什么(它的注释非常详细),但是它并不像看起来那样容易。

基本上来说,每一个入口模块的id都被传递给了一个函数,这个函数将在缓存中寻找这些id。如果找到了,这个模块的exports属性将被返回,实现依赖。如果id在缓存中找不到,那么Browserify将会检查我们的模块映射。

当一个id在我们的映射中被找到时,相应的闭包将会被调用,同时会给它传递require,module以及exports参数,这个模块也会被添加到缓存中。这个模块同时也会被赋予它的依赖映射中的模块,因此我们的require会得到满足。

如果我们的模块映射中没有找到id,Browserify将会查找之前载入的所有文件包。如果还是没找到,那么它将会抛出一个错误。

总结

以上的内容就是Browserify运行机制的一个简化版本。除了能够在浏览器端使用require之外,Browserify还支持一些高级的特性例如模块别名,扩展模块,转换,以及别好东西。你应该在你的下一个项目中尝试使用Browserify,你一定能体会到在浏览器中使用成千上万个npm模块的乐趣。有谁会不喜欢Browserify呢?


本文译自How Browserify Works,原文地址http://benclinkinbeard.com/posts/how-browserify-works/

如果你觉得本文对你有帮助,请为我提供赞助 https://me.alipay.com/jabez128

评论
发表评论
暂无评论
WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
前端模块化工具研究

介绍前端模块化工具的基础知识和使用方式

友情链接 大搜车前端团队博客
我的收藏