上一篇文章webpack詳解中介紹了webpack基於事件流編程,是個高度的插件集合,總體介紹了webpack 的編譯流程。本文將單獨聊一聊最核心的部分,編譯&構建。javascript
webpack的構建中總會經歷以下幾個事件節點。html
其中make
是整個構建中最核心的部分編譯,經過模塊工廠函數建立模塊,而後對模塊進行編譯。java
*ModuleFactory
是指模塊工廠函數,之因此會有模塊工廠這樣的函數,還要從webpack中
entry
的配置提及,在webpack的配置項中
entry
支持以下類型:
string
[string]
object { <key>: string | [string] }
(function: () => string | [string] | object { <key>: string | [string] })
爲了處理之後不一樣類型的入口模塊,因此就須要個模塊工廠來處理不一樣的入口模塊類型。webpack
string|object { <key>: string }
[string]|object { <key>: [string] }
(function: () => string | [string] | object { <key>: string | [string] })
上圖中爲了簡單說明構建的流程,就以最直接的singleEntry
類型提及,對於此類入口模塊,webpack均使用NormalModuleFactory
來建立模塊,這個建立的模塊的類型叫NormalModule
,在NormalModule
中實現了模塊的構建方法build
,使用runLoaders
對模塊進行加載,而後利用進行解析,分析模塊依賴,遞歸構建。git
到構建封裝階段時候,代碼構建已經完畢,可是如何將這些代碼按照依賴引用邏輯組織起來,當瀏覽器將你構建出來的代碼加載到瀏覽器的時候,仍然可以正確執行。在webpack中經過Manifest
記錄各個模塊的詳細要點,經過Runtime
來引導,加載執行模塊代碼,特別是異步加載。github
###Runtime 如上所述,咱們這裏只簡略地介紹一下。runtime,以及伴隨的 manifest 數據,主要是指:在瀏覽器運行時,webpack 用來鏈接模塊化的應用程序的全部代碼。runtime 包含:在模塊交互時,鏈接模塊所需的加載和解析邏輯。包括瀏覽器中的已加載模塊的鏈接,以及懶加載模塊的執行邏輯。web
###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