玩轉webpack(一)下篇:webpack的基本架構和構建流程

歡迎你們前往騰訊雲社區,獲取更多騰訊海量技術實踐乾貨哦~javascript

做者:QQ會員技術團隊vue

玩轉webpack(一)上篇:webpack的基本架構和構建流程java

文件生成階段

這個階段的主要內容,是根據 chunks 生成最終文件。主要有三個步驟:模板 hash 更新,模板渲染 chunk,生成文件webpack

Compilation 在實例化的時候,就會同時實例化三個對象:MainTemplate, ChunkTemplateModuleTemplate。這三個對象是用來渲染 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 是一個對象,對象須要包含兩個方法,sourcesize 分別返回文件內容和文件大小。

當全部的 chunk 都渲染完成以後,assets 就是最終更要生成的文件列表。此時 Compilation 實例還會觸發幾個任務點,例如 addtional-chunk-assetsaddintial-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...

相關文章
相關標籤/搜索