欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~
作者:QQ会员技术团队
接玩转webpack(一)上篇:webpack的基本架构和构建流程
文件生成阶段
这个阶段的主要内容,是根据 chunks 生成最终文件。主要有三个步骤:模板 hash 更新,模板渲染 chunk,生成文件
Compilation
在实例化的时候,就会同时实例化三个对象:MainTemplate
, ChunkTemplate
,ModuleTemplate
。这三个对象是用来渲染 chunk 对象,得到最终代码的模板。第一个对应了在 entry 配置的入口 chunk 的渲染模板,第二个是动态引入的非入口 chunk 的渲染模板,最后是 chunk 中的 module 的渲染模板。
在开始渲染之前,Compilation
实例会调用 createHash
方法来生成这次构建的 hash。在 webpack 的配置中,我们可以在 output.filename
中配置 [hash]
占位符,最终就会替换成这个 hash。同样,createHash
也会为每一个 chunk 也创建一个 hash,对应 output.filename
的 [chunkhash]
占位符。
每个 hash 的影响因素比较多,首先三个模板对象会调用 updateHash
方法来更新 hash,在内部还会触发任务点 hash
,传递 hash 到其他插件。 chunkhash 也是类似的原理:
// https://github.com/webpack/webpack/blob/master/lib/Compilation.js
class Compilation extends Tapable {
// 其他代码..
createHash() {
// 其他代码..
const hash = crypto.createHash(hashFunction);
if(outputOptions.hashSalt)
hash.update(outputOptions.hashSalt);
this.mainTemplate.updateHash(hash);
this.chunkTemplate.updateHash(hash);
this.moduleTemplate.updateHash(hash);
// 其他代码..
for(let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const chunkHash = crypto.createHash(hashFunction);
if(outputOptions.hashSalt)
chunkHash.update(outputOptions.hashSalt);
chunk.updateHash(chunkHash);
if(chunk.hasRuntime()) {
this.mainTemplate.updateHashForChunk(chunkHash, chunk);
} else {
this.chunkTemplate.updateHashForChunk(chunkHash, chunk);
}
this.applyPlugins2("chunk-hash", chunk, chunkHash);
chunk.hash = chunkHash.digest(hashDigest);
hash.update(chunk.hash);
chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
}
this.fullHash = hash.digest(hashDigest);
this.hash = this.fullHash.substr(0, hashDigestLength);
}
}
当 hash 都创建完成之后,下一步就会遍历 compilation.chunks
来渲染每一个 chunk。如果一个 chunk 是入口 chunk,那么就会调用 MainTemplate
实例的 render 方法,否则调用 ChunkTemplate
的 render 方法:
// https://github.com/webpack/webpack/blob/master/lib/Compilation.js
class Compilation extends Tapable {
// 其他代码..
createChunkAssets() {
// 其他代码..
for(let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
// 其他代码..
if(chunk.hasRuntime()) {
source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates);
} else {
source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates);
}
file = this.getPath(filenameTemplate, {
noChunkHash: !useChunkHash,
chunk
});
this.assets[file] = source;
// 其他代码..
}
}
}
这里注意到 ModuleTemplate
实例会被传递下去,在实际渲染时将会用 ModuleTemplate
来渲染每一个 module,其实更多是往 module 前后添加一些"包装"代码,因为 module 的源码实际上是已经渲染完毕的(还记得前面的 loaders 应用吗?)。
MainTemplate
的渲染跟 ChunkTemplate
的不同点在于,入口 chunk 的源码中会带有启动 webpack 的代码,而非入口 chunk 的源码是不需要的。这个只要查看 webpack 构建后的文件就可以比较清楚地看到区别:
// 入口 chunk
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [], result;
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ };
/******/ // 其他代码..
/******/ })(/* modules代码 */);
// 动态引入的 chunk
webpackJsonp([0],[
/* modules代码.. */
]);
当每个 chunk 的源码生成之后,就会添加在 Compilation
实例的 assets
属性中。
assets
对象的 key 是最终要生成的文件名称,因此这里要用到前面创建的 hash。调用 Compilation
实例内部的 getPath
方法会根据配置中的 output.filename
来生成文件名称。
assets
对象的 value 是一个对象,对象需要包含两个方法,source
和 size
分别返回文件内容和文件大小。
当所有的 chunk 都渲染完成之后,assets
就是最终更要生成的文件列表。此时 Compilation
实例还会触发几个任务点,例如 addtional-chunk-assets
,addintial-assets
等,在这些任务点可以修改 assets
属性来改变最终要生成的文件。
完成上面的操作之后,Compilation
实例的 seal
方法结束,进入到 Compiler
实例的 emitAssets
方法。Compilation
实例的所有工作到此也全部结束,意味着一次构建过程已经结束,接下来只有文件生成的步骤。
在 Compiler
实例开始生成文件前,最后一个修改最终文件生成的任务点 emit
会被触发:
// 监听 emit 任务点,修改最终文件的最后机会
compiler.plugin("emit", (compilation, callback) => {
let data = "abcd"
compilation.assets["newFile.js"] = {
source() {
return data
}
size() {
return data.length
}
}
})
当任务点 emit
被触发之后,接下来 webpack 会直接遍历 compilation.assets
生成所有文件,然后触发任务点 done
,结束构建流程。
总结
经过全文的讨论,我们将 webpack 的基本架构以及核心的构建流程都过了一遍,希望在阅读完全文之后,对大家了解 webpack 原理有所帮助。
最后再次说明,本文内容是由个人理解和整理,如果有不正确的地方欢迎大家指正。如果需要转载,请注明出处。
下一篇文章将会讲解 webpack 核心的对象,敬请期待。
本文来源于 小时光茶社 微信公众号
相关阅读
玩转webpack(一)上篇:webpack的基本架构和构建流程
Webpack + vue 之抽离 CSS 的正确姿势
使用Yeoman generator来规范工程的初始化
此文已由作者授权腾讯云技术社区发布,转载请注明原文出处
原文链接:https://cloud.tencent.com/com...