1. webpack简介
webpack是当下最热门的前端资源模块化管理和打包工具。简单来说,通过指定入口文件,按照依赖和规则,将许多松散的模块打包成符合环境部署的前端资源。
通过加载器(loader)、插件(plugin),webpack可以将任何形式的资源,比如 CommonJs 模块、AMD 模块、ES6 模块、CSS、图片、JSON、Coffeescript、LESS 等,转译成模块进行解析。
面对工程中成百上千个模块,webpack究竟是如何将它们有序地组织在一起,并按照我们预想的顺序运行在浏览器上的呢?
接下来让我们带着这些问题来开始。首先模块之间是有依赖的,webpack是怎么有序组织起来的呢?
2. webpack如何处理模块依赖
这里我就不赘叙了,直接上干货图理解(笔者呕心沥血花了近一周时间,边看源码边整理出来的,该源码版本为:webpack v5.21.1, webpack v4.5.0。后面版本迭代,部分函数名可能会有变动,但主流程思路和逻辑应该是不变的)。简单来说,webpack是利用compiler来控制整个编译流程,Compilation来专门编译构建,最后转交给compiler将文件打包输出到指定目录。
其中处理模块依赖的,主要是由Compilation addModuleTree触发,调用模块工厂ModuleFactory来完成解析。接下来,我们着重来看下它的编译打包部分。
3. 不同模块方法的打包及源码解析
首先我们打包一个amd规范的文件示例来看下。(所有示例代码均可在这里找到)
准备一个最简单的js文件
// src/index.js
const logA = () =>console.log('hello webpack');export default logA;
修改webpack配置文件的入口为src/index.js
// webpack.config.js
...
entry: "./src/index.js",
...
修改webpack配置文件的打包方式为amd,
// webpack.prod.config.js output: { path: path.resolve(__dirname, "../dist"), filename: 'amd.js', library: "myDemo", libraryTarget: 'amd' },
// 打包生成的文件: 打包内容,最外层封装不同define("myDemo", [], (() => (() => {
"use strict";
var e = {
352 : (e, r, o) = >{
o.r(r),
o.d(r, {
default:
() = >t
});
const t = function() {
return console.log("hello webpack")
}
}
},
r = {};
function o(t) {
if (r[t]) return r[t].exports;
var n = r[t] = {
exports: {}
};
return e[t](n, n.exports, o),
n.exports
}
return o.d = (e, r) =>{
for (var t in r) o.o(r, t) && !o.o(e, t) && Object.defineProperty(e, t, {
enumerable: !0,
get: r[t]
})
},
o.o = (e, r) => Object.prototype.hasOwnProperty.call(e, r),
o.r = e => {
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
value: "Module"
}),
Object.defineProperty(e, "__esModule", {
value: !0
})
},
o(352)
})()));
我们可以看到,设置不同的打包规范,最终webpack打包出来的文件模式都很固定模式。
再对照前面 工作机制的梳理图,compilation.seal调用后(也就是模块构建完成后),会调用create ChunkAssets(实际核心是调用template)将这些模块重组成js代码。
createChunkAssets(callback) {
//...
asyncLib.forEach( this.chunks, (chunk, callback) => {
let manifest = this.getRenderManifest({...}) // 获取渲染列表 //... asyncLib.forEach( manifest, (fileManifest, callback) => {
//...
let source = fileManifest.render(); // 开始渲染 //...
this.emitAsset(file, source, assetInfo); // 打包文件并输出到指定目录 //...
},
callback);
},
callback);}
调用了fileManifest的render函数,其实就是mainTemplate。
mainTemplate转交给template去处理,即调用Template.renderChunkModules,
并返回了一个ConcatSource
的资源。其中有固定的模板,也有调用的模块。
static renderChunkModules(renderContext, modules, renderModule, prefix = "") {
// 1. 初始化source
var source = new ConcatSource();
// 2. 处理模块依赖更新allModules source,并渲染模块 const allModules = modules.map(module => { return { id: chunkGraph.getModuleId(module), source: renderModule(module) || "false" }; });
// 3. 模板渲染source,这里简化显示
source.add("{\n"); for (let i = 0; i < allModules.length; i++) { const module = allModules[i]; if (i !== 0) { source.add(",\n"); } source.add(`\n/***/ ${JSON.stringify(module.id)}:\n`); source.add(module.source); } source.add(`\n\n${prefix}}`); return source; }
模块工厂(ModuleFactory)给module配备了generator, 生成替换代码。在generate阶段的时候,会触发请求RuntimeTemplate
,用于替换运行时的代码。详情请参考该具体编译流程(原链接在这里)
最后通过emitAssets输出到指定目录中文件中去。
4. 总结
嗯,其实webpack做的事情很多,源码也很绕,阅读得花费些功夫...初步了解大致工作流程后,下一阶段着重看下loader及plugin的调用处理模块之间依赖的部分。待续。。。(我先去喝杯茶,喘口气儿)
文中所提的代码规范示例仓库:github.com/JeanZhao/we…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!