歡迎你們前往騰訊雲社區,獲取更多騰訊海量技術實踐乾貨哦~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...