webpack的編譯&構建

上一篇文章webpack詳解中介紹了webpack基於事件流編程,是個高度的插件集合,總體介紹了webpack 的編譯流程。本文將單獨聊一聊最核心的部分,編譯&構建。javascript

webpack的編譯

重要的構建節點

webpack的構建中總會經歷以下幾個事件節點。html

  • before-run 清除緩存
  • run 註冊緩存數據鉤子
  • compile 開始編譯
  • make 從入口分析依賴以及間接依賴模塊,建立模塊對象
  • build-module 模塊構建
  • seal 構建結果封裝, 不可再更改
  • after-compile 完成構建,緩存數據
  • emit 輸出到dist目錄

其中make是整個構建中最核心的部分編譯,經過模塊工廠函數建立模塊,而後對模塊進行編譯。java

在make鉤子的編譯

Alt text
上圖中提到的 *ModuleFactory是指模塊工廠函數,之因此會有模塊工廠這樣的函數,還要從webpack中 entry的配置提及,在webpack的配置項中 entry支持以下類型:

  • 字符類型string
  • 字符數組類型[string]
  • 多頁面對象key-value類型object { <key>: string | [string] }
  • 也支持一個函數,返回構建的入口(function: () => string | [string] | object { <key>: string | [string] })

爲了處理之後不一樣類型的入口模塊,因此就須要個模塊工廠來處理不一樣的入口模塊類型。webpack

  • 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對模塊進行加載,而後利用進行解析,分析模塊依賴,遞歸構建。git

構建封裝seal

到構建封裝階段時候,代碼構建已經完畢,可是如何將這些代碼按照依賴引用邏輯組織起來,當瀏覽器將你構建出來的代碼加載到瀏覽器的時候,仍然可以正確執行。在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;
}])
複製代碼

上面說到了runtimemanifest就是在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

相關文章
相關標籤/搜索