browserify 是一个编译工具,是目前比较流行的模块打包工具之一(另外一个是webpack),browserify 是基于流式思想设计;可以通过命令行,也可以通过API来使用,是模块化的逆过程,但是推动模块化的快速发展,它可以使用类似于node的require()的方式来组织浏览器端的JavaScript代码,通过预编译可以让前端的JavaScript直接使用npm安装一些库
全局安装:npm install -g browserify
使用:browserify main.js > compiled.js
也就意味着main.js这个文件和依赖的的其他文件都会编译进compiled.js
编译好的文件的使用:
<script src="./compiled.js">script>
总体过程可以分为两个部分:预编译阶段和执行阶段
预编译阶段
1.从入口模块开始,分析代码中 require
函数的调用
2.生成AST
3.根据AST找到每个模块require的模块名
4.得到每个模块的依赖关系
5.包装每个模块,生成用于执行的
执行阶段
从入口模块开始执行,递归执行所有的require的模块,得到依赖对象
具体分析
由于浏览器原生是没有原生的require函数,所以require函数是需要自己实现的,因此第一步我们需要知道一个模块的代码中,哪些地方用了require
函数,依赖了什么模块,browserify实现的原理是为了代码文件生成抽象语法树(AST,也称语法树,也就是语法分析的产物),根据AST,就可以找到require函数依赖的模块
举个例子,如下面的代码
var velement = require('./velement');
var diff = require('./diff');
var patches = diff(vdom, newVdom);
生成的抽象语法树的样子:查看这里
代码如下:
var diff = require('./diff');
var patches = diff(vdom, newVdom);
生成的语法树如下:网站
与之对应的JSON结构:网站
{
"type": "Program",
"start": 0,
"end": 245,
"body": [
{
"type": "VariableDeclaration",
"start": 180,
"end": 209,
"declarations": [
{
"type": "VariableDeclarator",
"start": 184,
"end": 208,
"id": {
"type": "Identifier",
"start": 184,
"end": 188,
"name": "diff"
},
"init": {
"type": "CallExpression",
"start": 191,
"end": 208,
"callee": {
"type": "Identifier",
"start": 191,
"end": 198,
"name": "require" },
"arguments": [
{
"type": "Literal",
"start": 199,
"end": 207,
"value": "./diff",
"raw": "'./diff'" }
]
}
}
],
"kind": "var"
},
{
"type": "VariableDeclaration",
"start": 210,
"end": 244,
"declarations": [
{
"type": "VariableDeclarator",
"start": 214,
"end": 243,
"id": {
"type": "Identifier",
"start": 214,
"end": 221,
"name": "patches"
},
"init": {
"type": "CallExpression",
"start": 224,
"end": 243,
"callee": {
"type": "Identifier",
"start": 224,
"end": 228,
"name": "diff" },
"arguments": [
{
"type": "Identifier",
"start": 229,
"end": 233,
"name": "vdom" },
{
"type": "Identifier",
"start": 235,
"end": 242,
"name": "newVdom" }
]
}
}
],
"kind": "var"
}
],
"sourceType": "module"
}
在上面的结构中,也可以很轻易的看到使用require的地方,就在第一个init里面,由此也可以发现,它的type为callExpression,callee的name为require,arguments的value为引入的模块的名称,所以也就可以根据这些特点找到require的模块名,得到每个模块的依赖关系之后,生成一个依赖字典,这个字典记录了哪些模块,以及模块各自依赖的模块,通过依赖字典,就很容易得知代码中的依赖关系。
下面就需要代码执行了,也就是要实现浏览器并不支持的export和require,所以就需要对原有的模块进行包装,所以就需要实现export和require,export的实现,创建一个对象作为该模块的export,require的实现就要依赖之前的依赖字典,根据传入的模块名,在依赖字典里找到依赖的函数模块,然后一直执行(递归执行)
在browserify生成的JavaScript文件当中,会添加以下require的实现代码,并传递给每个模块函数
(function e(t,n,r){ //t是传入的依赖字典,n是一个空对象,用于保存所有新创建的模块(exports对象)
function s(o,u){
if(!n[o]){
if(!t[o]){
var a=typeof require=="function"&&require;
if(!u&&a)
return a(o,!0);
if(i)
return i(o,!0);
var f=new Error("Cannot find module '"+o+"'");
throw f.code="MODULE_NOT_FOUND",f
}
var l=n[o]={exports:{}};
t[o][0].call(l.exports,function(e){
var n=t[o][1][e];
return s(n?n:e)
},l,l.exports,e,t,n,r)
}
return n[o].exports
}
var i=typeof require=="function"&&require;
for(var o=0;oreturn s
})