Webpack學習-工做原理(下)

繼上篇文章介紹了Webpack的基本概念,完整流程,以及打包過程當中廣播的一些事件的做用,這篇文章主要講生成的chunk文件如何輸出成具體的文件。分同步和異步兩種狀況來分析輸出的文件使用的webpack版本:3.8.0javascript

模塊文件show.jshtml

function show(content) {
        window.document.getElementById('app').innerText = 'Hello,' + content;
    }

    // 經過 CommonJS 規範導出 show 函數
    module.exports = show;

同步加載

// main.js

import show from './show';

show('TWENTY-FOUR K');

生成的bundle文件

// webpackBootstrap啓動函數
// modules存放的是全部模塊的數組,每一個模塊都是一個函數
(function(modules) {
    var installedModules = {}; // 緩存安裝過的模塊,提高性能
    //去傳入的modules數組中加載對應moduleId(index)的模塊,與node的require語句類似
    function __webpack_require__(moduleId) {
        // 檢查是否已經加載過,若是有的話直接從緩存installedModules中取出
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // 若是沒有的話,就直接新建一個模塊,而且存進緩存installedModules
        var module = installedModules[moduleId] = {
            i: moduleId, // 對應modules的索引index,也就是moduleId
            l: false, // 標誌模塊是否已經加載
            exports: {}
        };
        // 執行對應模塊函數,而且傳入須要的參數
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        // 將標誌設置爲true
        module.l = true;
        // 返回這個模塊的導出值
        return module.exports;
    }
    // 儲存modules數組
    __webpack_require__.m = modules;
    // 儲存緩存installedModules
    __webpack_require__.c = installedModules;
    // 爲Harmony導出定義getter函數
    __webpack_require__.d = function(exports, name, getter) {
        if(!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                configurable: false,
                enumerable: true,
                get: getter
            });
        }
    };
    // 用於與非協調模塊兼容的getdefaultexport函數,將module.default或非module聲明成getter函數的a屬性上
    __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;
    };
    // 工具函數,hasOwnProperty
    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    // webpack配置中的publicPath,用於加載被分割出去的異步代碼
    __webpack_require__.p = "";
    // 使用__webpack_require__函數去加載index爲0的模塊,而且返回index爲0的模塊也就是主入口文件的main.js的對應文件,__webpack_require__.s的含義是啓動模塊對應的index
    return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
// 設置__esModule爲true,影響__webpack_require__.n函數的返回值
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// 同步加載index爲1的依賴模塊
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__show__ = __webpack_require__(1);
// 獲取index爲1的依賴模塊的export
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__show___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__show__);
// 同步加載


__WEBPACK_IMPORTED_MODULE_0__show___default()('wushaobin');

/***/ }),
/* 1 */
/***/ (function(module, exports) {

function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 經過 CommonJS 規範導出 show 函數
module.exports = show;


/***/ })
]);

異步加載

// main.js

// 異步加載 show.js
import('./show').then((show) => {
  // 執行 show 函數
  show('TWENTY-FOUR K');
});

經webpack打包會生成兩個文件0.bundle.js和bundle.js,怎麼作的緣由,是能夠吧show.js以異步加載形式引入,這也是分離代碼,達到減小文件體積的優化方法,兩個文件分析以下。java

0.bundle.js

// 加載本文件(0.bundle.js)包含的模塊, webpackJsonp用於從異步加載的文件中安裝模塊,掛載至全局(bundle.js)供其餘文件使用
webpackJsonp(
// 在其餘文件中存放的模塊id
[0],[
// 本文件所包含的模塊

/***/ (function(module, exports) {

function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 經過 CommonJS 規範導出 show 函數
module.exports = show;


/***/ })
]);

bundle.js

(function(modules) { // webpackBootstrap啓動函數
     // 安裝用於塊加載的JSONP回調
     var parentJsonpFunction = window["webpackJsonp"];
    // chunkIds 異步加載文件(0.bundle.js)中存放的須要安裝的模塊對應的chunkId
    // moreModules 異步加載文件(0.bundle.js)中存放須要安裝的模塊列表
    // executeModules 異步加載文件(0.bundle.js)中存放須要安裝的模塊安裝後須要執行的模塊對應的index
     window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
         // 將 "moreModules" 添加到modules對象中,
        // 將全部chunkIds對應的模塊都標記成已經加載成功
         var moduleId, chunkId, i = 0, resolves = [], result;
         for(;i < chunkIds.length; i++) {
             chunkId = chunkIds[i];
             if(installedChunks[chunkId]) {
                 resolves.push(installedChunks[chunkId][0]);
             }
             installedChunks[chunkId] = 0;
         }
         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()();
         }

     };

     // 緩存已經安裝的模塊
     var installedModules = {};

     // 儲存每一個chunk的加載狀態
    // 鍵爲chunk的id,值爲0表明加載成功
     var installedChunks = {
         1: 0
     };

     // 去傳入的modules數組中加載對應moduleId(index)的模塊,與node的require語句類似,同上,此處省略
     function __webpack_require__(moduleId) {
        ...
     }

     // 用於加載被分割出去的須要異步加載的chunk對應的文件,
    // chunkId須要異步加載的chunk對應的id,返回的是一個promise
     __webpack_require__.e = function requireEnsure(chunkId) {
        // 從installedChunks中獲取chunkId對應的chunk文件的加載狀態
         var installedChunkData = installedChunks[chunkId];
        // 若是加載狀態爲0,則表示該chunk已經加載成功,直接返回promise resolve
         if(installedChunkData === 0) {
             return new Promise(function(resolve) { resolve(); });
         }

        // installedChunkData不爲空且不爲0時表示chunk正在網絡加載中
         if(installedChunkData) {
             return installedChunkData[2];
         }

         // installedChunkData爲空,表示該chunk尚未加載過,去加載該chunk對應的文件
         var promise = new Promise(function(resolve, reject) {
             installedChunkData = installedChunks[chunkId] = [resolve, reject];
         });
         installedChunkData[2] = promise;

         // 經過dom操做,向html head中插入一個script標籤去異步加載chunk對應的javascript文件
         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;
        // HTMLElement 接口的 nonce 屬性返回只使用一次的加密數字,被內容安全政策用來決定此次請求是否被容許處理。
         if (__webpack_require__.nc) {
             script.setAttribute("nonce", __webpack_require__.nc);
         }
        // 文件的路徑由配置的publicPath、chunkid拼接而成
         script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
        // 設置異步加載的最長超時時間
         var timeout = setTimeout(onScriptComplete, 120000);
         script.onerror = script.onload = onScriptComplete;
        // 在script加載和執行完成時回調
         function onScriptComplete() {
             // 防止內存泄漏
             script.onerror = script.onload = null;
             clearTimeout(timeout);
            
             var chunk = installedChunks[chunkId];
            // 判斷chunkid對應chunk是否安裝成功
             if(chunk !== 0) {
                 if(chunk) {
                     chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
                 }
                 installedChunks[chunkId] = undefined;
             }
         };
         head.appendChild(script);

         return promise;
     };

     // 這裏會給__webpack_require__設置多個屬性和方法,同上,此處省略
     

     // 異步加載的出錯函數
     __webpack_require__.oe = function(err) { console.error(err); throw err; };

     // Load entry module and return exports
     return __webpack_require__(__webpack_require__.s = 0);
 })
/************************************************************************/
 ([
// 存放沒有通過異步加載的,隨着執行入口文件加載的模塊,也就是同步的模塊
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
// 經過__webpack_require__.e異步加載show.js對應的chunk
// 異步加載 show.js
__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then((show) => {
  // 執行 show 函數
  show('Webpack');
});


/***/ })
 ]);

總結

這裏咱們經過對比同步加載和異步加載的簡單應用,去分析兩種狀況webpack打包出來文件的差別性,咱們發現,對於異步加載的bundle.js與同步的bundle.js基本類似,都是模擬node.js的require語句去導入模塊,有所不一樣的是多了一個__webpack_require__.e函數,用來加載分割出的須要異步加載的chunk對應的文件,以及一個wepackJsonp函數,用於從異步加載的文件中去安裝模塊。node

相關文章
相關標籤/搜索