上一篇文章webpack詳解中介紹了webpack基於事件流編程,是個高度的插件集合,總體介紹了webpack 的編譯流程。本文將單獨聊一聊最核心的部分,編譯&構建。javascript
webpack的構建中總會經歷以下幾個事件節點。html
其中make
是整個構建中最核心的部分編譯,經過模塊工廠函數建立模塊,而後對模塊進行編譯。java
上圖中提到的*ModuleFactory
是指模塊工廠函數,之因此會有模塊工廠這樣的函數,還要從webpack中entry
的配置提及,在webpack的配置項中entry
支持以下類型:webpack
string
[string]
object { <key>: string | [string] }
(function: () => string | [string] | object { <key>: string | [string] })
爲了處理之後不一樣類型的入口模塊,因此就須要個模塊工廠來處理不一樣的入口模塊類型。git
string|object { <key>: string }
[string]|object { <key>: [string] }
(function: () => string | [string] | object { <key>: string | [string] })
上圖中爲了簡單說明構建的流程,就以最直接的singleEntry
類型提及,對於此類入口模塊,webpack均使用NormalModuleFactory
來建立模塊,這個建立的模塊的類型叫NormalModule
,在NormalModule
中實現了模塊的構建方法build
,使用runLoaders
對模塊進行加載,而後利用進行解析,分析模塊依賴,遞歸構建。github
到構建封裝階段時候,代碼構建已經完畢,可是如何將這些代碼按照依賴引用邏輯組織起來,當瀏覽器將你構建出來的代碼加載到瀏覽器的時候,仍然可以正確執行。在webpack中經過Manifest
記錄各個模塊的詳細要點,經過Runtime
來引導,加載執行模塊代碼,特別是異步加載。web
Runtime
如上所述,咱們這裏只簡略地介紹一下。runtime,以及伴隨的 manifest 數據,主要是指:在瀏覽器運行時,webpack 用來鏈接模塊化的應用程序的全部代碼。runtime 包含:在模塊交互時,鏈接模塊所需的加載和解析邏輯。包括瀏覽器中的已加載模塊的鏈接,以及懶加載模塊的執行邏輯。編程
Manifest
那麼,一旦你的應用程序中,形如 index.html 文件、一些 bundle 和各類資源加載到瀏覽器中,會發生什麼?你精心安排的 /src 目錄的文件結構如今已經不存在,因此 webpack 如何管理全部模塊之間的交互呢?這就是 manifest 數據用途的由來……
當編譯器(compiler)開始執行、解析和映射應用程序時,它會保留全部模塊的詳細要點。這個數據集合稱爲 "Manifest",當完成打包併發送到瀏覽器時,會在運行時經過 Manifest 來解析和加載模塊。不管你選擇哪一種模塊語法,那些 import 或 require 語句如今都已經轉換爲 webpack_require 方法,此方法指向模塊標識符(module identifier)。經過使用 manifest 中的數據,runtime 將可以查詢模塊標識符,檢索出背後對應的模塊。segmentfault
定義了一個當即執行函數,聲明瞭__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
若是對您的團隊或者項目有幫助,請給個Star支持一下哈~