上一篇文章webpack詳解中介紹了webpack基於事件流程式設計,是個高度的外掛集合,整體介紹了webpack 的編譯流程。本文將單獨聊一聊最核心的部分,編譯&構建。
webpack的編譯
重要的構建節點
webpack的構建中總會經歷如下幾個事件節點。
- before-run 清除快取
- run 註冊快取資料鉤子
- compile 開始編譯
- make 從入口分析依賴以及間接依賴模組,建立模組物件
- build-module 模組構建
- seal 構建結果封裝, 不可再更改
- after-compile 完成構建,快取資料
- emit 輸出到dist目錄
其中make
是整個構建中最核心的部分編譯,通過模組工廠函式建立模組,然後對模組進行編譯。
在make鉤子的編譯
上圖中提到的*ModuleFactory
是指模組工廠函式,之所以會有模組工廠這樣的函式,還要從webpack中entry
的配置說起,在webpack的配置項中entry
支援如下型別:
- 字元型別
string
- 字元陣列型別
[string]
- 多頁面物件key-value型別
object { <key>: string | [string] }
- 也支援一個函式,返回構建的入口
(function: () => string | [string] | object { <key>: string | [string] })
為了處理以後不同型別的入口模組,所以就需要個模組工廠來處理不同的入口模組型別。
- singleEntry:
string|object { <key>: string }
- multiEntry:
[string]|object { <key>: [string] }
- dynamicEntry:
(function: () => string | [string] | object { <key>: string | [string] })
上圖中為了簡單說明構建的流程,就以最直接的singleEntry
型別說起,對於此類入口模組,webpack均使用NormalModuleFactory
來建立模組,這個建立的模組的型別叫NormalModule
,在NormalModule
中實現了模組的構建方法build
,使用runLoaders
對模組進行載入,然後利用進行解析,分析模組依賴,遞迴構建。
構建封裝seal
到構建封裝階段時候,程式碼構建已經完畢,但是如何將這些程式碼按照依賴引用邏輯組織起來,當瀏覽器將你構建出來的程式碼載入到瀏覽器的時候,仍然能夠正確執行。在webpack中通過Manifest
記錄各個模組的詳細要點,通過Runtime
來引導,載入執行模組程式碼,特別是非同步載入。
###Runtime
如上所述,我們這裡只簡略地介紹一下。runtime,以及伴隨的 manifest 資料,主要是指:在瀏覽器執行時,webpack 用來連線模組化的應用程式的所有程式碼。runtime 包含:在模組互動時,連線模組所需的載入和解析邏輯。包括瀏覽器中的已載入模組的連線,以及懶載入模組的執行邏輯。
###Manifest
那麼,一旦你的應用程式中,形如 index.html 檔案、一些 bundle 和各種資源載入到瀏覽器中,會發生什麼?你精心安排的 /src 目錄的檔案結構現在已經不存在,所以 webpack 如何管理所有模組之間的互動呢?這就是 manifest 資料用途的由來……
當編譯器(compiler)開始執行、解析和對映應用程式時,它會保留所有模組的詳細要點。這個資料集合稱為 “Manifest”,當完成打包併傳送到瀏覽器時,會在執行時通過 Manifest 來解析和載入模組。無論你選擇哪種模組語法,那些 import 或 require 語句現在都已經轉換為 webpack_require 方法,此方法指向模組識別符號(module identifier)。通過使用 manifest 中的資料,runtime 將能夠查詢模組識別符號,檢索出背後對應的模組。
定義了一個立即執行函式,宣告瞭__webpack_require__
,對各種模組進行載入。
(function(modules) { // webpackBootstrap
var installedModules = {}; // cache module
function __webpack_require__(moduleId) { // 模組載入
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module[`default`]; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, `a`, getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = 0);
})([/**modules*/])
複製程式碼
上面提到的程式碼片段便是webpack構建後在瀏覽器中執行的引導程式碼。也就是上面提到的runtime
。它是個立即執行函式,那麼入參modules
便是上面的Manifest
,組織各個模組的依賴邏輯。
(function(modules){
// ...
// runtime
function __webpack_require__(moduleId) {
// 載入邏輯
}
// ...
})([function (module, exports, __webpack_require__) {
var chunk1 = __webpack_require__(1);
var chunk2 = __webpack_require__(2);
}, function (module, exports, __webpack_require__) {
__webpack_require__(2);
var chunk1 = 1;
exports.chunk1 = chunk1;
}, function (module, exports) {
var chunk2 = 1;
exports.chunk2 = chunk2;
}])
複製程式碼
上面說到了runtime
和manifest
就是在seal
階段注入的
class Compilation extends Tapable {
seal(callback) {
this.hooks.seal.call();
// ...
if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
this.hooks.beforeChunkAssets.call();
this.createChunkAssets();
}
// ...
}
createChunkAssets() {
// ...
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
// ...
const template = chunk.hasRuntime()
? this.mainTemplate
: this.chunkTemplate; // 根據是否有runTime選擇模組,入口檔案是true, 需要非同步載入的檔案則沒有
const manifest = template.getRenderManifest({
// 生成manifest
chunk,
hash: this.hash,
fullHash: this.fullHash,
outputOptions,
moduleTemplates: this.moduleTemplates,
dependencyTemplates: this.dependencyTemplates
}); // [{ render(), filenameTemplate, pathOptions, identifier, hash }]
// ...
}
}
複製程式碼
通過template
最後將程式碼組織起來,上面看到的構建後的程式碼就是mainTemplate
生成的。
寫在最後
通過template
生成最後程式碼,構建已經完成,接下來就是將程式碼輸出到dist
目錄。
最後
騰訊IVWEB團隊的工程化解決方案feflow已經開源:Github主頁:https://github.com/feflow/feflow