大佬教程收集整理的这篇文章主要介绍了打包工具的核心原理(转自:https://juejin.im/entry/5b223ebd518825748b569bda),大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
打包工具就是负责把一些分散的小模块,按照一定的规则整合成一个大模块的工具。与此同时,打包工具也会处理好模块之间的依赖关系,最终这个大模块将可以被运行在合适的平台中。
打包工具会从一个入口文件开始,分析它里面的依赖,并且再进一步地分析依赖中的依赖,不断重复这个过程,直到把这些依赖关系理清挑明为止。
从上面的描述可以看到,打包工具最核心的部分,其实就是处理好模块之间的依赖关系。
为了简单起见,minipack项目直接使用ES modules规范,接下来我们新建三个文件,并且为它们之间建立依赖:
它们的依赖关系非常简单:entry.js→message.js→name.js,其中entry.js将会成为打包工具的入口文件。
但是,这里面的依赖关系只是我们人类所理解的,如果要让机器也能够理解当中的依赖关系,就需要借助一定的手段了。
@H_772_72@依赖关系解析新建一个js文件,命名为@minipack.js,首先引入必要的工具
<span style="color: #0000ff">const
fs = require(<span style="color: #800000">'<span style="color: #800000">fs<span style="color: #800000">'<span style="color: #000000">)接下来,我们会撰写一个函数,这个函数接收一个文件作为模块,然后读取它里面的内容,分析出其所有的依赖项。当然,我们可以通过正则匹配模块文件里面的import
关键字,但这样做非常不优雅,所以我们可以使用babylon
这个js解析器把文件内容转化成抽象语法树(AST),直接从AST里面获取我们需要的信息。
得到了AST之后,就可以使用babel-traverse
去遍历这棵AST,获取当中关键的“依赖声明”,然后把这些依赖都保存在一个数组当中。
最后使用babel-core
的transformFromAst
方法搭配babel-preset-env
插件,把ES6语法转化成浏览器可以识别的ES5语法,并且为该js模块分配一个ID。
<span style="color: #008000">//<span style="color: #008000"> 转化成AST
<span style="color: #0000ff">const ast =<span style="color: #000000"> babylon.parse(content,{
sourceType: <span style="color: #800000">'<span style="color: #800000">module<span style="color: #800000">'<span style="color: #000000">,});
<span style="color: #008000">//<span style="color: #008000"> 该文件的所有依赖
<span style="color: #0000ff">const dependencies =<span style="color: #000000"> []
<span style="color: #008000">//<span style="color: #008000"> 获取依赖声明
<span style="color: #000000"> traverse(ast,{
ImportDeclaration: ({ node }) =><span style="color: #000000"> {
dependencies.push(node.source.value);
}
})
<span style="color: #008000">//<span style="color: #008000"> 转化ES6语法到ES5
<span style="color: #0000ff">const {CodE} = transformFromAst(ast,<span style="color: #0000ff">null<span style="color: #000000">,{
presets: [<span style="color: #800000">'<span style="color: #800000">env<span style="color: #800000">'<span style="color: #000000">],})
<span style="color: #008000">//<span style="color: #008000"> 分配ID
<span style="color: #0000ff">const id = ID++
<span style="color: #008000">//<span style="color: #008000"> 返回这个模块
<span style="color: #0000ff">return<span style="color: #000000"> {
id,filename,dependencies,code,}
}
运行createAsset('./example/entry.js')
,输出如下:
可见entry.js
文件已经变成了一个典型的模块,且依赖已经被分析出来了。接下来我们就要递归这个过程,把“依赖中的依赖”也都分析出来,也就是下一节要讨论的建立依赖关系图集。
新建一个名为createGragh()
的函数,传入一个入口文件的路径作为参数,然后通过createAsset()
解析这个文件使之定义成一个模块。
接下来,为了能够挨个挨个地对模块进行依赖分析,所以我们维护一个数组,首先把第一个模块传进去并进行分析。当这个模块被分析出还有其他依赖模块的时候,就把这些依赖模块也放进数组中,然后继续分析这些新加进去的模块,直到把所有的依赖以及“依赖中的依赖”都完全分析出来。
与此同时,我们有必要为模块新建一个@H_257_35@mapping属性,用来储存模块、依赖、依赖ID之间的依赖关系,例如“ID为0的A模块依赖于ID为2的B模块和ID为3的C模块”就可以表示成下面这个样子:
搞清楚了个中道理,就可以开始编写函数了。
<span style="color: #008000">//<span style="color: #008000"> 遍历数组,分析每一个模块是否还有其它依赖,若有则把依赖模块推进数组
<span style="color: #0000ff">for (<span style="color: #0000ff">const<span style="color: #000000"> asset of queuE) {
asset.mapping =<span style="color: #000000"> {}
<span style="color: #008000">//<span style="color: #008000"> 由于依赖的路径是相对于当前模块,所以要把相对路径都处理为绝对路径
<span style="color: #0000ff">const dirname =<span style="color: #000000"> path.dirname(asset.fileName)
<span style="color: #008000">//<span style="color: #008000"> 遍历当前模块的依赖项并继续分析
asset.dependencies.forEach(relativePath =><span style="color: #000000"> {
<span style="color: #008000">//<span style="color: #008000"> 构造绝对路径
<span style="color: #0000ff">const absolutePath =<span style="color: #000000"> path.join(dirname,relativePath)
<span style="color: #008000">//<span style="color: #008000"> 生成依赖模块
<span style="color: #0000ff">const child =<span style="color: #000000"> createAsset(absolutePath)
<span style="color: #008000">//<span style="color: #008000"> 把依赖关系写入模块的mapping当中
asset.mapping[relativePath] =<span style="color: #000000"> child.id
<span style="color: #008000">//<span style="color: #008000"> 把这个依赖模块也推入到queue数组中,以便继续对其进行以来分析
<span style="color: #000000"> queue.push(child)
})
}
<span style="color: #008000">//<span style="color: #008000"> 最后返回这个queue,也就是依赖关系图集
<span style="color: #0000ff">return<span style="color: #000000"> queue
}
尝试运行一下createGraph('./example/entry.js')
,就能够看到如下的输出:
现在依赖关系图集已经构建完成了,接下来就是把它们打包成一个单独的,可直接运行的文件啦!
@H_772_72@进行打包上一步生成的依赖关系图集,接下来将通过CommomJS
规范来实现加载。由于篇幅关系,本文不对CommomJS
规范进行扩展,有兴趣的读者可以参考@阮一峰 老师的一篇文章https://link.juejin.im/?target=http%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2015%2F05%2Fcommonjs-in-browser.html" rel="nofollow noopener noreferrer" target="_blank">《浏览器加载 CommonJS 模块的原理与实现》,说得非常清晰。简单来说,就是通过构造一个立即执行函数(function () {})()
,手动定义@H_257_35@module,exports
和require
变量,最后实现代码在浏览器运行的目的。
接下来就是依据这个规范,通过字符串拼接去构建代码块。
${mod.iD}: [ function (require,module,exports) { ${mod.codE} },${JSON.Stringify(mod.mapping)},],
<span style="color: #0000ff">const result =<span style="color: #000000"> `
(function(modules) {
function require(id) {
<span style="color: #0000ff">const [fn,mapping] =<span style="color: #000000"> modules[id];
function localrequire(Name) {
</span><span style="color: #0000ff">return</span><span style="color: #000000"> require(mapping[name]);
}
</span><span style="color: #0000ff">const</span> module =<span style="color: #000000"> { exports : {} };
fn(localrequire,module.exports);
</span><span style="color: #0000ff">return</span><span style="color: #000000"> module.exports;
}
require(</span><span style="color: #800080">0</span><span style="color: #000000">);
})({${modules}})
`
<span style="color: #0000ff">return<span style="color: #000000"> result
}
最后运行bundle(createGraph('./example/entry.js'))
,输出如下:
function localrequire(Name) {
</span><span style="color: #0000ff">return</span><span style="color: #000000"> require(mapping[name]);
}
</span><span style="color: #0000ff">const</span> module =<span style="color: #000000"> { exports: {} };
fn(localrequire,module.exports);
</span><span style="color: #0000ff">return</span><span style="color: #000000"> module.exports;
}
require(
<span style="color: #800080">0<span style="color: #000000">); </span><span style="color: #0000ff">var</span> _message = require(<span style="color: #800000">"</span><span style="color: #800000">./message.js</span><span style="color: #800000">"</span><span style="color: #000000">);
</span><span style="color: #0000ff">var</span> _message2 =<span style="color: #000000"> _interoprequireDefault(_messagE);
function _interoprequireDefault(obj) { </span><span style="color: #0000ff">return</span> obj && obj.__esModule ? obj : { <span style="color: #0000ff">default</span><span style="color: #000000">: obj }; }
console.log(_message2.</span><span style="color: #0000ff">default</span><span style="color: #000000">);
},{ </span><span style="color: #800000">"</span><span style="color: #800000">./message.js</span><span style="color: #800000">"</span>: <span style="color: #800080">1</span><span style="color: #000000"> },</span><span style="color: #800080">1</span><span style="color: #000000">: [
function (require,exports) {
</span><span style="color: #800000">"</span><span style="color: #800000">use Strict</span><span style="color: #800000">"</span><span style="color: #000000">;
Object.defineProperty(exports,</span><span style="color: #800000">"</span><span style="color: #800000">__esModule</span><span style="color: #800000">"</span><span style="color: #000000">,{
value: </span><span style="color: #0000ff">true</span><span style="color: #000000">
});
</span><span style="color: #0000ff">var</span> _name = require(<span style="color: #800000">"</span><span style="color: #800000">./name.js</span><span style="color: #800000">"</span><span style="color: #000000">);
exports.</span><span style="color: #0000ff">default</span> = <span style="color: #800000">"</span><span style="color: #800000">Hello </span><span style="color: #800000">"</span> + _name.name + <span style="color: #800000">"</span><span style="color: #800000">!</span><span style="color: #800000">"</span><span style="color: #000000">;
},{ </span><span style="color: #800000">"</span><span style="color: #800000">./name.js</span><span style="color: #800000">"</span>: <span style="color: #800080">2</span><span style="color: #000000"> },</span><span style="color: #800080">2</span><span style="color: #000000">: [
function (require,{
value: </span><span style="color: #0000ff">true</span><span style="color: #000000">
});
</span><span style="color: #0000ff">var</span> name = exports.name = <span style="color: #800000">'</span><span style="color: #800000">world</span><span style="color: #800000">'</span><span style="color: #000000">;
},{},})</span></pre>
这段代码将能够直接在浏览器运行,输出“Hello world!”。
至此,整一个打包工具已经完成。
@H_772_72@归纳总结经过上面几个步骤,我们可以知道一个模块打包工具,第一步会从入口文件开始,对其进行依赖分析,第二步对其所有依赖再次递归进行依赖分析,第三步构建出模块的依赖图集,最后一步根据依赖图集使用CommonJS
规范构建出最终的代码。明白了当中每一步的目的,便能够明白一个打包工具的运行原理
以上是大佬教程为你收集整理的打包工具的核心原理(转自:https://juejin.im/entry/5b223ebd518825748b569bda)全部内容,希望文章能够帮你解决打包工具的核心原理(转自:https://juejin.im/entry/5b223ebd518825748b569bda)所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。