九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
webpack 啟動(dòng)代碼源碼解讀

作者 | dabai

來(lái)源 |https://segmentfault.com/a/1190000016524677


前言


雖然每天都在用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',
    pathpath.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,
            lfalse,
            exports: {}
        };

        // 執(zhí)行模塊代碼
        modules[moduleId].call(module.exports, 
        modulemodule.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ù)包裹著,注入了module和exports2個(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)中(將在下文講解)。


我們?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', {
            valuetrue
        });
        /* 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
modulemodule.exports
__webpack_require__);


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


按需加載


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


// main.js
console.log('Hello Dickens');
import('./logger').then(logger => {
    logger();
});


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 <>
            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 = {
        10
    };

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

        var module = installedModules[moduleId] = {
            i: moduleId,
            lfalse,
            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(null1))
            .then(logger => {
                logger();
            });
    })
]);


掛在到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(null1))
    .then(logger => {
        logger();
    });


接下來(lái)看下動(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', {
            valuetrue
        });
        /* 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。


總結(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é)。


本文完~




本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
深入理解 webpack 文件打包機(jī)制
web前端教程分享js中的模塊化二
三大應(yīng)用場(chǎng)景調(diào)研,Webpack 新功能 Module Federation 深入解析
探索 模塊打包 exports和require 與 export和import 的用法和區(qū)別
webpack 入門
webpack多頁(yè)面開(kāi)發(fā)與懶加載hash解決方案
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服