国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

webpack啟動(dòng)代碼源碼解讀

zengdongbao / 482人閱讀

摘要:它是如何用原生實(shí)現(xiàn)模塊間的依賴管理的呢對(duì)于按需加載的模塊,它是通過(guò)什么方式動(dòng)態(tài)獲取的打包完成后那一堆開(kāi)頭的代碼是用來(lái)干什么的本文將圍繞以上個(gè)問(wèn)題,對(duì)照著源碼給出解答。

歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:

一、前言

雖然每天都在用webpack,但一直覺(jué)得隔著一層神秘的面紗,對(duì)它的工作原理一直似懂非懂。它是如何用原生JS實(shí)現(xiàn)模塊間的依賴管理的呢?對(duì)于按需加載的模塊,它是通過(guò)什么方式動(dòng)態(tài)獲取的?打包完成后那一堆/******/開(kāi)頭的代碼是用來(lái)干什么的?本文將圍繞以上3個(gè)問(wèn)題,對(duì)照著源碼給出解答。

如果你對(duì)webpack的配置調(diào)優(yōu)感興趣,可以看看我之前寫的這篇文章:webpack調(diào)優(yōu)總結(jié)

二、模塊管理

先寫一個(gè)簡(jiǎn)單的JS文件,看看webpack打包后會(huì)是什么樣子:

// main.js
console.log("Hello Dickens");

// webpack.config.js
const path = require("path");
module.exports = {
  entry: "./main.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist")
  }
};

在當(dāng)前目錄下運(yùn)行webpack,會(huì)在dist目錄下面生成打包好的bundle.js文件。去掉不必要的干擾后,核心代碼如下:

// webpack啟動(dòng)代碼
(function (modules) { 
    // 模塊緩存對(duì)象
    var installedModules = {};

    // webpack實(shí)現(xiàn)的require函數(shù)
    function __webpack_require__(moduleId) {
        // 檢查緩存對(duì)象,看模塊是否加載過(guò)
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }

        // 創(chuàng)建一個(gè)新的模塊緩存,再存入緩存對(duì)象
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };

        // 執(zhí)行模塊代碼
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        
        // 將模塊標(biāo)識(shí)為已加載
        module.l = true;

        // 返回export的內(nèi)容
        return module.exports;
    }

    ...

    // 加載入口模塊
    return __webpack_require__(__webpack_require__.s = 0);
})
([
    /* 0 */
    (function (module, exports) {
        console.log("Hello Dickens");
    })
]);

代碼是一個(gè)立即執(zhí)行函數(shù),參數(shù)modules是由各個(gè)模塊組成的數(shù)組,本例子只有一個(gè)編號(hào)為0的模塊,由一個(gè)函數(shù)包裹著,注入了moduleexports2個(gè)變量(本例沒(méi)用到)。

核心代碼是__webpack_require__這個(gè)函數(shù),它的功能是根據(jù)傳入的模塊id,返回模塊export的內(nèi)容。模塊id由webpack根據(jù)文件的依賴關(guān)系自動(dòng)生成,是一個(gè)從0開(kāi)始遞增的數(shù)字,入口文件的id為0。所有的模塊都會(huì)被webpack用一個(gè)函數(shù)包裹,按照順序存入上面提到的數(shù)組實(shí)參當(dāng)中。

模塊export的內(nèi)容會(huì)被緩存在installedModules中。當(dāng)獲取模塊內(nèi)容的時(shí)候,如果已經(jīng)加載過(guò),則直接從緩存返回,否則根據(jù)id從modules形參中取出模塊內(nèi)容并執(zhí)行,同時(shí)將結(jié)果保存到緩存對(duì)象當(dāng)中。緩存對(duì)象數(shù)據(jù)結(jié)構(gòu)如下:

我們?cè)偬砑右粋€(gè)文件,在入口文件處導(dǎo)入,再來(lái)看看生成的啟動(dòng)文件是怎樣的。

// main.js
import logger from "./logger";

console.log("Hello Dickens");
logger();

//logger.js
export default function log() {
    console.log("Log from logger");
}

啟動(dòng)文件的模塊數(shù)組:

[
    /* 0 */
    (function (module, __webpack_exports__, __webpack_require__) {

        "use strict";
        Object.defineProperty(__webpack_exports__, "__esModule", {
            value: true
        });
        /* harmony import */
        var __WEBPACK_IMPORTED_MODULE_0__logger__ = __webpack_require__(1);

        console.log("Hello Dickens");

        Object(__WEBPACK_IMPORTED_MODULE_0__logger__["a" /* default */ ])();
    }),
    /* 1 */
    (function (module, __webpack_exports__, __webpack_require__) {
    
        "use strict";
        /* harmony export (immutable) */
        __webpack_exports__["a"] = log;

        function log() {
            console.log("Log from logger");
        }
    })
]

可以看到現(xiàn)在有2個(gè)模塊,每個(gè)模塊的包裹函數(shù)都傳入了module, __webpack_exports__, __webpack_require__三個(gè)參數(shù),它們是通過(guò)上文提到的__webpack_require__注入的:

// 執(zhí)行模塊代碼
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

執(zhí)行的結(jié)果也保存在緩存對(duì)象中了。

執(zhí)行流程如下圖所示:

三、按需加載

再對(duì)代碼進(jìn)行改造,來(lái)研究webpack是如何實(shí)現(xiàn)動(dòng)態(tài)加載的:

// main.js
console.log("Hello Dickens");

import("./logger").then(logger => {
    logger.default();
});

logger文件保持不變,編譯后比之前多出了1個(gè)chunk。

bundle_asy的內(nèi)容如下:

(function (modules) {
    // 加載成功后的JSONP回調(diào)函數(shù)
    var parentJsonpFunction = window["webpackJsonp"];

    // 加載成功后的JSONP回調(diào)函數(shù)
    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
        var moduleId, chunkId, i = 0,
            resolves = [],
            result;

        for (; i < chunkIds.length; i++) {
            chunkId = chunkIds[i];
            
            // installedChunks[chunkId]不為0且不為undefined,將其放入加載成功數(shù)組
            if (installedChunks[chunkId]) {
                // promise的resolve
                resolves.push(installedChunks[chunkId][0]);
            }
            
            // 標(biāo)記模塊加載完成
            installedChunks[chunkId] = 0;
        }

        // 將動(dòng)態(tài)加載的模塊添加到modules數(shù)組中,以供后續(xù)的require使用
        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()();
        }
    };

    // 模塊緩存對(duì)象
    var installedModules = {};

    // 記錄正在加載和已經(jīng)加載的chunk的對(duì)象,0表示已經(jīng)加載成功
    // 1是當(dāng)前模塊的編號(hào),已加載完成
    var installedChunks = {
        1: 0
    };

    // require函數(shù),跟上面的一樣
    function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }

        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };

        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        module.l = true;

        return module.exports;
    }

    // 按需加載,通過(guò)動(dòng)態(tài)添加script標(biāo)簽實(shí)現(xiàn)
    __webpack_require__.e = function requireEnsure(chunkId) {
        var installedChunkData = installedChunks[chunkId];

        // chunk已經(jīng)加載成功
        if (installedChunkData === 0) {
            return new Promise(function (resolve) {
                resolve();
            });
        }

        // 加載中,返回之前創(chuàng)建的promise(數(shù)組下標(biāo)為2)
        if (installedChunkData) {
            return installedChunkData[2];
        }

        // 將promise相關(guān)函數(shù)保持到installedChunks中方便后續(xù)resolve或reject
        var promise = new Promise(function (resolve, reject) {
            installedChunkData = installedChunks[chunkId] = [resolve, reject];
        });
        installedChunkData[2] = promise;

        // 啟動(dòng)chunk的異步加載
        var head = document.getElementsByTagName("head")[0];
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.charset = "utf-8";
        script.async = true;
        script.timeout = 120000;
        if (__webpack_require__.nc) {
            script.setAttribute("nonce", __webpack_require__.nc);
        }
        script.src = __webpack_require__.p + "" + chunkId + ".bundle_async.js";
        script.onerror = script.onload = onScriptComplete;
        var timeout = setTimeout(onScriptComplete, 120000);

        function onScriptComplete() {
            script.onerror = script.onload = null;
            
            clearTimeout(timeout);
            
            var chunk = installedChunks[chunkId];
            
            // 正常的流程,模塊加載完后會(huì)調(diào)用webpackJsonp方法,將chunk置為0
            // 如果不為0,則可能是加載失敗或者超時(shí)
            if (chunk !== 0) {
                if (chunk) {
                    // 調(diào)用promise的reject
                    chunk[1](new Error("Loading chunk " + chunkId + " failed."));
                }
                installedChunks[chunkId] = undefined;
            }
        };
        
        head.appendChild(script);
        
        return promise;
    };

    ...
    
    // 加載入口模塊
    return __webpack_require__(__webpack_require__.s = 0);
})
([
    /* 0 */
    (function (module, exports, __webpack_require__) {

        console.log("Hello Dickens");

        // promise resolve后,會(huì)指定加載哪個(gè)模塊
        __webpack_require__.e /* import() */(0)
            .then(__webpack_require__.bind(null, 1))
            .then(logger => {
                logger.default();
            });
    })
]);

這里用戶記錄異步模塊加載狀態(tài)的對(duì)象installedChunks的數(shù)據(jù)結(jié)構(gòu)如下:

當(dāng)chunk加載完成后,對(duì)應(yīng)的值是0。在加載過(guò)程中,對(duì)應(yīng)的值是一個(gè)數(shù)組,數(shù)組內(nèi)保存了promise的相關(guān)信息。

掛在到window下面的webpackJsonp函數(shù)是動(dòng)態(tài)加載模塊代碼下載后的回調(diào),它會(huì)通知webpack模塊下載完成并將模塊加入到modules當(dāng)中。

__webpack_require__.e函數(shù)是動(dòng)態(tài)加載的核心實(shí)現(xiàn),它通過(guò)動(dòng)態(tài)創(chuàng)建一個(gè)script標(biāo)簽來(lái)實(shí)現(xiàn)代碼的異步加載。加載開(kāi)始前會(huì)創(chuàng)建一個(gè)promise存到installedChunks對(duì)象當(dāng)中,加載成功則調(diào)用resolve,失敗則調(diào)用reject。resolve后不會(huì)傳入模塊本身,而是通過(guò)__webpack_require__來(lái)加載模塊內(nèi)容,require的模塊id由webpack來(lái)生成:

__webpack_require__.e /* import() */(0)
    .then(__webpack_require__.bind(null, 1))
    .then(logger => {
        logger.default();
    });

這里之所以要加上default是因?yàn)橛龅桨葱杓虞d時(shí),如果使用的是ES Module,webpack會(huì)將export default編譯成__webpack_exports__對(duì)象的default屬性(感謝@MrCanJu的指正)。詳細(xì)請(qǐng)看動(dòng)態(tài)加載的chunk的代碼,0.bundle_asy的內(nèi)容如下:

webpackJsonp([0], [
    /* 0 */
    ,
    /* 1 */
    (function (module, __webpack_exports__, __webpack_require__) {

        "use strict";
        Object.defineProperty(__webpack_exports__, "__esModule", {
            value: true
        });
        /* harmony export (immutable) */
        __webpack_exports__["default"] = log;

        function log() {
            console.log("Log from logger");
        }
    })
]);

代碼非常好理解,加載成功后立即調(diào)用上文提到的webpackJsonp方法,將chunkId和模塊內(nèi)容傳入。這里要分清2個(gè)概念,一個(gè)是chunkId,一個(gè)moduleId。這個(gè)chunk的chunkId是0,里面只包含一個(gè)module,moduleId是1。一個(gè)chunk里面可以包含多個(gè)module。

執(zhí)行流程如下圖所示:

四、總結(jié)

本文通過(guò)分析webpack生成的啟動(dòng)代碼,講解了webpack是如何實(shí)現(xiàn)模塊管理和動(dòng)態(tài)加載的,希望對(duì)你有所幫助。

如果你對(duì)webpack的配置調(diào)優(yōu)感興趣,可以看看我之前寫的這篇文章:webpack調(diào)優(yōu)總結(jié)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/97982.html

相關(guān)文章

  • webpack-dev-middleware@1.12.2 源碼解讀

    摘要:如果此時(shí)我們不想把文件輸出到內(nèi)存里,可以通過(guò)修改的源代碼來(lái)實(shí)現(xiàn)。服務(wù)啟動(dòng)成功。。。根據(jù)請(qǐng)求的,拼接出 ? webpack-dev-middleware 是express的一個(gè)中間件,它的主要作用是以監(jiān)聽(tīng)模式啟動(dòng)webpack,將webpack編譯后的文件輸出到內(nèi)存里,然后將內(nèi)存的文件輸出到epxress服務(wù)器上;下面通過(guò)一張圖片來(lái)看一下它的工作原理: showImg(https:...

    yearsj 評(píng)論0 收藏0
  • redux源碼閱讀--主模塊

    摘要:主模塊的入口模塊就是。主要就做兩件事引入個(gè)功能模塊,并掛載至同一個(gè)對(duì)象上,對(duì)外暴露。在非環(huán)境下壓縮代碼,給予警告。后續(xù)的源碼解讀和測(cè)試?yán)涌梢躁P(guān)注源碼解讀倉(cāng)庫(kù) 主模塊 redux的入口模塊就是src/index.js。這個(gè)文件的代碼十分簡(jiǎn)單。主要就做兩件事: 引入個(gè)功能模塊,并掛載至同一個(gè)對(duì)象上,對(duì)外暴露。 在非production環(huán)境下壓縮代碼,給予警告。 下面是模塊的源碼(只包...

    testHs 評(píng)論0 收藏0
  • webpack-dev-server 源碼解析

    摘要:應(yīng)用源碼分析解讀結(jié)論熱更新的流程在構(gòu)建項(xiàng)目時(shí)會(huì)創(chuàng)建服務(wù)端基于和客戶端通常指瀏覽器,項(xiàng)目正式啟動(dòng)運(yùn)行時(shí)雙方會(huì)通過(guò)保持連接,用來(lái)滿足前后端實(shí)時(shí)通訊。服務(wù)端源碼的關(guān)鍵部分每一個(gè)都是沒(méi)有屬性,表示沒(méi)有發(fā)生變化。 webpack-dev-server 簡(jiǎn)介 Use webpack with a development server that provides live reloading. Th...

    darkbaby123 評(píng)論0 收藏0
  • 淺談webpack之plugin,webpack-manifest-plugin源碼解讀

    摘要:注冊(cè)方法之后,當(dāng)執(zhí)行了當(dāng)前的,那么掛載正在當(dāng)前上的方法就會(huì)被執(zhí)行。比如在開(kāi)始編譯之前,就能觸發(fā)鉤子,就用到了當(dāng)前的。上面都是前置知識(shí),下面通過(guò)解讀一個(gè)源碼來(lái)鞏固下。先看一段簡(jiǎn)單的源碼。,是眾多的的一個(gè),官網(wǎng)的解釋是編譯創(chuàng)建之后,執(zhí)行插件。 通過(guò)解讀webpack-manifest-plugin,了解下plugin機(jī)制 先簡(jiǎn)單說(shuō)一下這個(gè)插件的功能,生成一份資源清單的json文件,如下 s...

    william 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<