欢迎你们前往腾讯云社区,获取更多腾讯海量技术实践干货哦~javascript
做者:QQ会员技术团队vue
接玩转webpack(一)上篇:webpack的基本架构和构建流程java
这个阶段的主要内容,是根据 chunks 生成最终文件。主要有三个步骤:模板 hash 更新,模板渲染 chunk,生成文件webpack
Compilation
在实例化的时候,就会同时实例化三个对象:MainTemplate
, ChunkTemplate
,ModuleTemplate
。这三个对象是用来渲染 chunk 对象,获得最终代码的模板。第一个对应了在 entry 配置的入口 chunk 的渲染模板,第二个是动态引入的非入口 chunk 的渲染模板,最后是 chunk 中的 module 的渲染模板。git
在开始渲染以前,Compilation
实例会调用 createHash
方法来生成此次构建的 hash。在 webpack 的配置中,咱们能够在 output.filename
中配置 [hash]
占位符,最终就会替换成这个 hash。一样,createHash
也会为每个 chunk 也建立一个 hash,对应 output.filename
的 [chunkhash]
占位符。github
每一个 hash 的影响因素比较多,首先三个模板对象会调用 updateHash
方法来更新 hash,在内部还会触发任务点 hash
,传递 hash 到其余插件。 chunkhash 也是相似的原理:web
// 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 构建后的文件就能够比较清楚地看到区别:app
// 入口 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...