Webpack編譯結果淺析

如今Webpack已經是一個不可或缺的前端構建工具,藉助這個構建工具,我們可以使用比較新的技術(瀏覽器不能直接支持)來開發。

你是否好奇你寫的代碼經過Webpack構建之後會生成什麼東西?是否有時調試遇到莫名其妙的問題?

本文不講如何進行配置,只是基於幾個基礎的例子,簡要分析一下 [email protected] 構建後的代碼結構,當然了,並不全面,時間問題能力問題還不能理解到位。

代碼比較長,生成的代碼也比較晦澀比較繞,也可能條理不順,客官坐好咧~

一、Webpack的運行機制

Webpack的運行過程實際上可以歸納爲這個步驟

讀取配置參數 -> 相關事件綁定(插件參與) ->  識別各入口Entry模塊 -> 編譯文件(loader參與)-> 生成文件

首先讀取我們的配置文件如 webpack.config.js,然後事件流就參與進來綁定相關的事件,Webpack中的事件使用 Tapable 來管理,在這一階段,除了綁定webpack內置的一大堆事件之外,還支持自定義的一些事件處理。

配置中的 plugins部分,實際上也可以看作是一些自定義的事件處理,因爲插件將在定義的”相關時刻“插入到編譯過程中處理資源,這裏的”相關時刻“指的就是 訂閱-發佈 模式中的發佈環節

webpack支持多個入口模塊,所以還需要進行各入口模塊的分析(這裏的入口模塊只能爲JS模塊),比如以下兩個入口模塊

分析完入口模塊,接下來分析該模塊的依賴,並使用相關loader進行編譯(如果需要loader的話),真正的編譯環節是在這裏。

期間會使用AST抽象語法樹來分析語法,直到編譯完成,輸出到相應的文件中

可以來看看這篇文章 Webpack運行機制

二、Webpack編譯結果

由最簡單的例子開始

2.1 無依賴的單個模塊

./main.js

console.log('main');

./webpack.config.js

module.exports = {
    // entry: './main',
    entry: {
        main: './main'
    },

    mode: 'none',

    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
};

注意,在webpack4中默認的mode對 development和production進行了一些特殊配置,爲了簡化,這裏就設置成none

編譯一個文件,將在dist目錄中生成

./dist/main.js

 1 /******/ (function(modules) { // webpackBootstrap
 2 /******/     // The module cache
 3 /******/     var installedModules = {};
 4 /******/
 5 /******/     // The require function
 6 /******/     function __webpack_require__(moduleId) {
 7 /******/
 8 /******/         // Check if module is in cache
 9 /******/         if(installedModules[moduleId]) {
10 /******/             return installedModules[moduleId].exports;
11 /******/         }
12 /******/         // Create a new module (and put it into the cache)
13 /******/         var module = installedModules[moduleId] = {
14 /******/             i: moduleId,
15 /******/             l: false,
16 /******/             exports: {}
17 /******/         };
18 /******/
19 /******/         // Execute the module function
20 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 /******/
22 /******/         // Flag the module as loaded
23 /******/         module.l = true;
24 /******/
25 /******/         // Return the exports of the module
26 /******/         return module.exports;
27 /******/     }
28 /******/
29 /******/
30 /******/     // expose the modules object (__webpack_modules__)
31 /******/     __webpack_require__.m = modules;
32 /******/
33 /******/     // expose the module cache
34 /******/     __webpack_require__.c = installedModules;
35 /******/
36 /******/     // define getter function for harmony exports
37 /******/     __webpack_require__.d = function(exports, name, getter) {
38 /******/         if(!__webpack_require__.o(exports, name)) {
39 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
40 /******/         }
41 /******/     };
42 /******/
43 /******/     // define __esModule on exports
44 /******/     __webpack_require__.r = function(exports) {
45 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
46 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
47 /******/         }
48 /******/         Object.defineProperty(exports, '__esModule', { value: true });
49 /******/     };
50 /******/
51 /******/     // create a fake namespace object
52 /******/     // mode & 1: value is a module id, require it
53 /******/     // mode & 2: merge all properties of value into the ns
54 /******/     // mode & 4: return value when already ns object
55 /******/     // mode & 8|1: behave like require
56 /******/     __webpack_require__.t = function(value, mode) {
57 /******/         if(mode & 1) value = __webpack_require__(value);
58 /******/         if(mode & 8) return value;
59 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
60 /******/         var ns = Object.create(null);
61 /******/         __webpack_require__.r(ns);
62 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
63 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
64 /******/         return ns;
65 /******/     };
66 /******/
67 /******/     // getDefaultExport function for compatibility with non-harmony modules
68 /******/     __webpack_require__.n = function(module) {
69 /******/         var getter = module && module.__esModule ?
70 /******/             function getDefault() { return module['default']; } :
71 /******/             function getModuleExports() { return module; };
72 /******/         __webpack_require__.d(getter, 'a', getter);
73 /******/         return getter;
74 /******/     };
75 /******/
76 /******/     // Object.prototype.hasOwnProperty.call
77 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
78 /******/
79 /******/     // __webpack_public_path__
80 /******/     __webpack_require__.p = "";
81 /******/
82 /******/
83 /******/     // Load entry module and return exports
84 /******/     return __webpack_require__(__webpack_require__.s = 0);
85 /******/ })
86 /************************************************************************/
87 /******/ ([
88 /* 0 */
89 /***/ (function(module, exports) {
90 
91 
92 console.log('main');
93 
94 
95 /***/ })
96 /******/ ]);

可以看到首先是一個匿名函數,在87行時自執行傳入

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


console.log('main');


/***/ })
/******/ ]

這個是modules,表示有一個模塊需要加載

第3行使用 installedModules 來緩存已經加載的模塊

webpack由最初支持 commonjs模塊規範,到後來要支持es6的模塊等,爲了兼容不同的模塊機制,定義了一個 __webpack_require__ 函數作爲webpack內部的require

/******/     // The require function
/******/     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, // 模塊ID
/******/             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; // 返回模塊的導出項目
/******/     }

其中,這個調用非常重要

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

結合匿名函數傳入的參數來看,modules[moduleId] 其實就是這個

(function(module, exports) {


console.log('main');


/***/ })

第一個參數 module.exports 實際上就是上面模塊的導出項,是爲了保證this能正確地指向module,第二第三個參數按着順序來,第四個參數一般用於依賴

因爲這裏 main.js沒有依賴其他模塊,所以沒有傳進來

最後 return module.exports; 實際上就是返回了模塊的導出項,在上面的84行中,入口模塊被引入 。從而自動地加載第一個模塊並執行

return __webpack_require__(__webpack_require__.s = 0); // __webpack_require__.s爲入口文件,此處引用模塊ID

另外再看其它代碼,

/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules; // 將模塊存起來
/******/
/******/     // expose the module cache
/******/     __webpack_require__.c = installedModules; // 將已經加載的模塊存起來

/******/     // __webpack_public_path__
/******/     __webpack_require__.p = ""; // 設置的 publicPath

這裏沒什麼可說的,這裏的publicPath對應於 output中的配置,如

output: {
        publicPath: './dist/',
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },

另外

/******/     // define getter function for harmony exports
/******/     __webpack_require__.d = function(exports, name, getter) {
/******/         if(!__webpack_require__.o(exports, name)) {
/******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/         }
/******/     };
/******/
/******/     // define __esModule on exports
/******/     __webpack_require__.r = function(exports) {
/******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/         }
/******/         Object.defineProperty(exports, '__esModule', { value: true });
/******/     };

/******/     // Object.prototype.hasOwnProperty.call
/******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

這裏 __webpack_require__.o 這裏只是hasOwnProperty的包裝

__webpack_require__.d 這裏是對exports定義一個屬性(當前模塊未用到,暫且如此,理解不到位)

__webpack_require__.r 這裏是對es6模塊中的export的支持(當前模塊未用到,暫且如此,理解不到位)

還有這個,這個就更難理解了

/******/     // create a fake namespace object
/******/     // mode & 1: value is a module id, require it
/******/     // mode & 2: merge all properties of value into the ns
/******/     // mode & 4: return value when already ns object
/******/     // mode & 8|1: behave like require
/******/     __webpack_require__.t = function(value, mode) {
/******/         if(mode & 1) value = __webpack_require__(value);
/******/         if(mode & 8) return value;
/******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/         var ns = Object.create(null);
/******/         __webpack_require__.r(ns);
/******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/         return ns;
/******/     };
/******/
/******/     // 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;
/******/     };

__webpack_require__.t 暫時不說明了,還看不懂怎麼調用的..

__webpack_require__.n 這個主要也是爲 es6模塊服務的,也沒能理解好,知道的可以在評論區留言哈~

2. 有依賴的單個模塊

先使用最基礎的commonjs模塊規範  require, exports ,module.exports 有助於理解上面那個模塊的導出項目

./main.js

let number = require('./number');

console.log('main', number);

./number.js

let n = 10;

exports.n = n;

編譯後,生成的文件變化的只是匿名函數傳入的部分

./dist/main.js

// 省略

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


let number = __webpack_require__(1);

console.log('main', number);


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


let n = 10;

exports.n = n;


/***/ })
/******/ ]);

注意到前面的數字即是模塊的ID,也可圖中的一致

這裏__webpack_require__參數被傳進來,main.js中引入number這個模塊 __webpack_require__(1);

number模塊中 exports.n = n,注意這裏的 exports即是調用時的第二個參數

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

所以此時 n屬性被存入module的export導出項中,從而__webpack_require__(1) 就能獲取這個導出項

換種方式,使用es6的模塊導出

更改 ./number.js

let n = 10;

export {
    n
};

編譯後 ./dist/main.js

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


let number = __webpack_require__(1);

console.log('main', number);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });

let n = 10;




/***/ })
/******/ ]);

可以看到模塊1變了,爲了兼容 export ,使用 __webpack_require__.r 定義了它爲es6模塊,再使用__webpack_require__.d 將 n保存到模塊的導出項中

__webpack_require__.d 函數中的 getter即爲 這裏的 function() { return n; },通過設置爲對象的get屬性,可以獲取到 n這個返回值

var o = {};

Object.defineProperty(o, 'abc', {
    get: function() {
        return 123;
    }
});

console.log(o.abc); // 123

所以將 let n = 10 定義在後面也是沒問題的,因爲getter是在number模塊被調用返回之後才使用的

接着,我們把引入依賴文件改爲import

./main.js

import {n} from './number';

console.log('main', n);

編譯後 ./dist/main.js

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

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);



console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["n"]);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });

let n = 10;




/***/ })
/******/ ]);

同樣的,這時main模塊用到了es6的模塊引入方式,所以 __webpack_require__.r(__webpack_exports__);

var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

這個 __webpack_require__(1) 實際上就是 number模塊的模塊導出項,自然就能取到屬性 n 了

接下來,着眼那個 default字眼,繼續更換模塊的導入導出方式

./main.js

import n from './number';

console.log('main', n);

./number.js

let n = 10;

export default n;

./dist/main.js

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

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);



console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["default"]);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);

let n = 10;

/* harmony default export */ __webpack_exports__["default"] = (n);


/***/ })
/******/ ]);

可以看到,變化只是屬性變成了default

再來一種 es6的方式

./main.js

import n from './number';

console.log('main', n);

./number.js

import {str as n} from './str';

export default n;

./str.js

export var str = 10;

編譯後

./dist/main.js

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

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);



console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["default"]);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _str__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);



/* harmony default export */ __webpack_exports__["default"] = (_str__WEBPACK_IMPORTED_MODULE_0__["str"]);


/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str", function() { return str; });
var str = 10;


/***/ })
/******/ ]);

可以看到 {str as n} 也是沒什麼影響的,通過上面的例子應該基本能理解模塊的依賴了

3. 多個入口模塊

如果不提取多模塊之間的公共部分,多個入口模塊和單個的不同之處就是多了一個文件而已,它們是獨立的。

所以這裏就不多說了

4. 異步加載模塊

webpack支持使用require.ensure來異步加載模塊

./main.js

console.log('main');

setTimeout(() => {
    require([], (require) => {
        let number = require('./number');

        console.log(number.n);
    });
}, 1000);

./number.js

let n = 10;

export {
    n
};

webpack.config.js中要加上 publicPath,防止異步模塊加載路徑出錯

output: {
        publicPath: './dist/',
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }

編譯後,生成的 1.js即爲異步的模塊number

./dist/1.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });


let n = 10;




/***/ })
]]);

可以看到,這裏首先獲取 (window["webpackJsonp"] = window["webpackJsonp"] || []), 再調用 push 傳入模塊及其依賴

jsonp類似我們跨域中的動態插入腳本,這裏也是一樣,動態插入一個script標籤,把src設置好就加載這個異步模塊了

push參數中第一個爲當前異步模塊

看看 ./dist/main.js

  1 /******/ (function(modules) { // webpackBootstrap
  2 /******/     // install a JSONP callback for chunk loading
  3 /******/     function webpackJsonpCallback(data) {
  4 /******/         var chunkIds = data[0];
  5 /******/         var moreModules = data[1];
  6 /******/
  7 /******/
  8 /******/         // add "moreModules" to the modules object,
  9 /******/         // then flag all "chunkIds" as loaded and fire callback
 10 /******/         var moduleId, chunkId, i = 0, resolves = [];
 11 /******/         for(;i < chunkIds.length; i++) {
 12 /******/             chunkId = chunkIds[i];
 13 /******/             if(installedChunks[chunkId]) {
 14 /******/                 resolves.push(installedChunks[chunkId][0]);
 15 /******/             }
 16 /******/             installedChunks[chunkId] = 0;
 17 /******/         }
 18 /******/         for(moduleId in moreModules) {
 19 /******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
 20 /******/                 modules[moduleId] = moreModules[moduleId];
 21 /******/             }
 22 /******/         }
 23 /******/         if(parentJsonpFunction) parentJsonpFunction(data);
 24 /******/
 25 /******/         while(resolves.length) {
 26 /******/             resolves.shift()();
 27 /******/         }
 28 /******/
 29 /******/     };
 30 /******/
 31 /******/
 32 /******/     // The module cache
 33 /******/     var installedModules = {};
 34 /******/
 35 /******/     // object to store loaded and loading chunks
 36 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
 37 /******/     // Promise = chunk loading, 0 = chunk loaded
 38 /******/     var installedChunks = {
 39 /******/         0: 0
 40 /******/     };
 41 /******/
 42 /******/
 43 /******/
 44 /******/     // script path function
 45 /******/     function jsonpScriptSrc(chunkId) {
 46 /******/         return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js"
 47 /******/     }
 48 /******/
 49 /******/     // The require function
 50 /******/     function __webpack_require__(moduleId) {
 51 /******/
 52 /******/         // Check if module is in cache
 53 /******/         if(installedModules[moduleId]) {
 54 /******/             return installedModules[moduleId].exports;
 55 /******/         }
 56 /******/         // Create a new module (and put it into the cache)
 57 /******/         var module = installedModules[moduleId] = {
 58 /******/             i: moduleId,
 59 /******/             l: false,
 60 /******/             exports: {}
 61 /******/         };
 62 /******/
 63 /******/         // Execute the module function
 64 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 65 /******/
 66 /******/         // Flag the module as loaded
 67 /******/         module.l = true;
 68 /******/
 69 /******/         // Return the exports of the module
 70 /******/         return module.exports;
 71 /******/     }
 72 /******/
 73 /******/     // This file contains only the entry chunk.
 74 /******/     // The chunk loading function for additional chunks
 75 /******/     __webpack_require__.e = function requireEnsure(chunkId) {
 76 /******/         var promises = [];
 77 /******/
 78 /******/
 79 /******/         // JSONP chunk loading for javascript
 80 /******/
 81 /******/         var installedChunkData = installedChunks[chunkId];
 82 /******/         if(installedChunkData !== 0) { // 0 means "already installed".
 83 /******/
 84 /******/             // a Promise means "currently loading".
 85 /******/             if(installedChunkData) {
 86 /******/                 promises.push(installedChunkData[2]);
 87 /******/             } else {
 88 /******/                 // setup Promise in chunk cache
 89 /******/                 var promise = new Promise(function(resolve, reject) {
 90 /******/                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
 91 /******/                 });
 92 /******/                 promises.push(installedChunkData[2] = promise);
 93 /******/
 94 /******/                 // start chunk loading
 95 /******/                 var head = document.getElementsByTagName('head')[0];
 96 /******/                 var script = document.createElement('script');
 97 /******/                 var onScriptComplete;
 98 /******/
 99 /******/                 script.charset = 'utf-8';
100 /******/                 script.timeout = 120;
101 /******/                 if (__webpack_require__.nc) {
102 /******/                     script.setAttribute("nonce", __webpack_require__.nc);
103 /******/                 }
104 /******/                 script.src = jsonpScriptSrc(chunkId);
105 /******/
106 /******/                 onScriptComplete = function (event) {
107 /******/                     // avoid mem leaks in IE.
108 /******/                     script.onerror = script.onload = null;
109 /******/                     clearTimeout(timeout);
110 /******/                     var chunk = installedChunks[chunkId];
111 /******/                     if(chunk !== 0) {
112 /******/                         if(chunk) {
113 /******/                             var errorType = event && (event.type === 'load' ? 'missing' : event.type);
114 /******/                             var realSrc = event && event.target && event.target.src;
115 /******/                             var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')');
116 /******/                             error.type = errorType;
117 /******/                             error.request = realSrc;
118 /******/                             chunk[1](error);
119 /******/                         }
120 /******/                         installedChunks[chunkId] = undefined;
121 /******/                     }
122 /******/                 };
123 /******/                 var timeout = setTimeout(function(){
124 /******/                     onScriptComplete({ type: 'timeout', target: script });
125 /******/                 }, 120000);
126 /******/                 script.onerror = script.onload = onScriptComplete;
127 /******/                 head.appendChild(script);
128 /******/             }
129 /******/         }
130 /******/         return Promise.all(promises);
131 /******/     };
132 /******/
133 /******/     // expose the modules object (__webpack_modules__)
134 /******/     __webpack_require__.m = modules;
135 /******/
136 /******/     // expose the module cache
137 /******/     __webpack_require__.c = installedModules;
138 /******/
139 /******/     // define getter function for harmony exports
140 /******/     __webpack_require__.d = function(exports, name, getter) {
141 /******/         if(!__webpack_require__.o(exports, name)) {
142 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
143 /******/         }
144 /******/     };
145 /******/
146 /******/     // define __esModule on exports
147 /******/     __webpack_require__.r = function(exports) {
148 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
149 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
150 /******/         }
151 /******/         Object.defineProperty(exports, '__esModule', { value: true });
152 /******/     };
153 /******/
154 /******/     // create a fake namespace object
155 /******/     // mode & 1: value is a module id, require it
156 /******/     // mode & 2: merge all properties of value into the ns
157 /******/     // mode & 4: return value when already ns object
158 /******/     // mode & 8|1: behave like require
159 /******/     __webpack_require__.t = function(value, mode) {
160 /******/         if(mode & 1) value = __webpack_require__(value);
161 /******/         if(mode & 8) return value;
162 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
163 /******/         var ns = Object.create(null);
164 /******/         __webpack_require__.r(ns);
165 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
166 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
167 /******/         return ns;
168 /******/     };
169 /******/
170 /******/     // getDefaultExport function for compatibility with non-harmony modules
171 /******/     __webpack_require__.n = function(module) {
172 /******/         var getter = module && module.__esModule ?
173 /******/             function getDefault() { return module['default']; } :
174 /******/             function getModuleExports() { return module; };
175 /******/         __webpack_require__.d(getter, 'a', getter);
176 /******/         return getter;
177 /******/     };
178 /******/
179 /******/     // Object.prototype.hasOwnProperty.call
180 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
181 /******/
182 /******/     // __webpack_public_path__
183 /******/     __webpack_require__.p = "./dist/";
184 /******/
185 /******/     // on error function for async loading
186 /******/     __webpack_require__.oe = function(err) { console.error(err); throw err; };
187 /******/
188 /******/     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
189 /******/     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
190 /******/     jsonpArray.push = webpackJsonpCallback;
191 /******/     jsonpArray = jsonpArray.slice();
192 /******/     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
193 /******/     var parentJsonpFunction = oldJsonpFunction;
194 /******/
195 /******/
196 /******/     // Load entry module and return exports
197 /******/     return __webpack_require__(__webpack_require__.s = 0);
198 /******/ })
199 /************************************************************************/
200 /******/ ([
201 /* 0 */
202 /***/ (function(module, exports, __webpack_require__) {
203 
204 
205 
206 console.log('main');
207 
208 setTimeout(() => {
209     __webpack_require__.e(/* AMD require */ 1).then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = []; ((require) => {
210         let number = __webpack_require__(1);
211 
212         console.log(number.n);
213     }).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);}).catch(__webpack_require__.oe);
214 }, 1000);
215 
216 
217 /***/ })
218 /******/ ]);

這下蹦出了許多代碼,從這裏開始會比較繞,需要有耐心!

按照代碼執行順序來分析,思路就清晰了

38行中定義了installedChunks這個新變量,它指代依賴模塊(不僅包括此處的異步模塊,也包括後續會說到的公共模塊,runtime模塊等),而上面installedModules指的是所有的模塊

/******/     // object to store loaded and loading chunks
/******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/     // Promise = chunk loading, 0 = chunk loaded
/******/     var installedChunks = {
/******/         0: 0
/******/     };

前面的0表示模塊ID,在這裏指的就是 ./main.js這個入口模塊了,它初始的狀態就被webpack設置成已加載

/******/     // script path function
/******/     function jsonpScriptSrc(chunkId) {
/******/         return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js"
/******/     }

這裏就是異步模塊的路徑了,({}[chunkId]||chunkId) 這個只是爲了防止出錯做的處理

__webpack_require__ 函數的內容沒變

75行多了一個 __webpack_require__.e 用來加載異步模塊,這個稍後再講

繼續到182行開始

/******/     // __webpack_public_path__
/******/     __webpack_require__.p = "./dist/";
/******/
/******/     // on error function for async loading
/******/     __webpack_require__.oe = function(err) { console.error(err); throw err; };
/******/
/******/     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/     jsonpArray.push = webpackJsonpCallback;
/******/     jsonpArray = jsonpArray.slice();
/******/     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/     var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/     // Load entry module and return exports
/******/     return __webpack_require__(__webpack_require__.s = 0);

這裏的publicPath就是我們剛剛設置的

__webpack_require__.oe 只是用於處理錯誤

初始會判斷是否有window["webpackJsonp"]存在,有的話就緩存起來,並將this的指向設置好 jsonpArray.push.bind(jsonpArray)

要理清楚 jsonpArray.push ,它不是簡單的數組,所以有些繞,它指向了第3行webpackJsonpCallback這個函數

如果初始已經有待加載的依賴模塊,則在for循環中直接加載。此處初始階段是沒有值的,所以可以直接略過

要看明白webpackJsonpCallback這個函數,得從調用它的地方開始,在216行中開始調用

setTimeout(() => {
    __webpack_require__.e(/* AMD require */ 1).then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = []; ((require) => {
        let number = __webpack_require__(1);

        console.log(number.n);
    }).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);}).catch(__webpack_require__.oe);
}, 1000);
/******/     // This file contains only the entry chunk.
/******/     // The chunk loading function for additional chunks
/******/     __webpack_require__.e = function requireEnsure(chunkId) {
/******/         var promises = []; // promise隊列,支持模塊加載完成後多個異步回調
/******/
/******/
/******/         // JSONP chunk loading for javascript
/******/
/******/         var installedChunkData = installedChunks[chunkId];
                // 未加載
/******/         if(installedChunkData !== 0) { // 0 means "already installed".
/******/
/******/             // a Promise means "currently loading".
                    // 加載中,則支持下一個回調加入
/******/             if(installedChunkData) {
/******/                 promises.push(installedChunkData[2]);
/******/             } else {
                        // 初始化一個promise來加載
/******/                 // setup Promise in chunk cache
/******/                 var promise = new Promise(function(resolve, reject) {
                            // 將resolve和reject存入模塊中,方便其他地方調用
/******/                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
/******/                 });
                        // installedChunkData的第三項即爲一個promise對象,並存入promises隊列中
/******/                 promises.push(installedChunkData[2] = promise);
/******/
/******/                 // start chunk loading
/******/                 var head = document.getElementsByTagName('head')[0];
/******/                 var script = document.createElement('script');
/******/                 var onScriptComplete;
/******/
/******/                 script.charset = 'utf-8';
/******/                 script.timeout = 120;
/******/                 if (__webpack_require__.nc) {
/******/                     script.setAttribute("nonce", __webpack_require__.nc);
/******/                 }
                        // 設置異步模塊的路徑
/******/                 script.src = jsonpScriptSrc(chunkId);
/******/
/******/                 onScriptComplete = function (event) {
/******/                     // avoid mem leaks in IE.
/******/                     script.onerror = script.onload = null;
/******/                     clearTimeout(timeout);
/******/                     var chunk = installedChunks[chunkId];
/******/                     if(chunk !== 0) {
/******/                         if(chunk) {
/******/                             var errorType = event && (event.type === 'load' ? 'missing' : event.type);
/******/                             var realSrc = event && event.target && event.target.src;
/******/                             var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')');
/******/                             error.type = errorType;
/******/                             error.request = realSrc;
                                    // 調用reject
/******/                             chunk[1](error);
/******/                         }
/******/                         installedChunks[chunkId] = undefined;
/******/                     }
/******/                 };
/******/                 var timeout = setTimeout(function(){
/******/                     onScriptComplete({ type: 'timeout', target: script });
/******/                 }, 120000);
/******/                 script.onerror = script.onload = onScriptComplete;
                        // 在head標籤中插入腳本
/******/                 head.appendChild(script);
/******/             }
/******/         }
/******/         return Promise.all(promises);
/******/     };

一秒鐘後加載這個異步模塊 ./1.js ,該模塊加載完成後就開始執行

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });


let n = 10;




/***/ })
]]);

此時的 window["webpackJsonp"] 已經被這句代碼影響,jsonpArray.push = webpackJsonpCallback; 所以push實際上調用的是 webpackJsonpCallback函數

/******/     // install a JSONP callback for chunk loading
/******/     function webpackJsonpCallback(data) {
/******/         var chunkIds = data[0]; // 依賴的模塊ID,此時是[1]
/******/         var moreModules = data[1]; // 依賴的模塊內容
/******/
/******/
/******/         // add "moreModules" to the modules object,
/******/         // then flag all "chunkIds" as loaded and fire callback
/******/         var moduleId, chunkId, i = 0, resolves = [];
                // 遍歷依賴的模塊進行加載
/******/         for(;i < chunkIds.length; i++) {
/******/             chunkId = chunkIds[i];
/******/             if(installedChunks[chunkId]) {
/******/                 resolves.push(installedChunks[chunkId][0]); // 存儲將要執行的resolve
/******/             }
/******/             installedChunks[chunkId] = 0; // 標記已加載
/******/         }
/******/         for(moduleId in moreModules) {
/******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/                 modules[moduleId] = moreModules[moduleId]; // 更新模塊組
/******/             }
/******/         }
/******/         if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/         while(resolves.length) {
/******/             resolves.shift()(); // 執行所有resolve
/******/         }
/******/
/******/     };

如果多依賴一個呢

./main.js

console.log('main');

setTimeout(() => {
    require(['./str'], (require) => {
        let number = require('./number');

        console.log(number.n);
    });
}, 1000);

這時只有 ./1.js改變了,差不不大,一樣的道理

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str", function() { return str; });
var str = 10;


/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });


let n = 10;




/***/ })
]]);

5. 提取公共模塊

./webpack.config.js

entry: {
        main: './main',
        test: './test'
    },

optimization: {
        // 提取公共部分爲common.js,使勁地提取吧.. 
        splitChunks: {
            name: 'common',
            chunks: 'all',
            minSize: 1
        }
    },

./main.js

import './chunk';

import {n} from './number';

console.log('main', n);

./test.js

import './chunk';

console.log('test');

編譯後

./dist/common.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
/***/ (function(module, exports) {

console.log('chunk');


/***/ })
]]);

可以看到 chunk模塊(ID爲1)被共用,被提取出來

再看看 ./dist/test.js

  1 /******/ (function(modules) { // webpackBootstrap
  2 /******/     // install a JSONP callback for chunk loading
  3 /******/     function webpackJsonpCallback(data) {
  4 /******/         var chunkIds = data[0];
  5 /******/         var moreModules = data[1];
  6 /******/         var executeModules = data[2];
  7 /******/
  8 /******/         // add "moreModules" to the modules object,
  9 /******/         // then flag all "chunkIds" as loaded and fire callback
 10 /******/         var moduleId, chunkId, i = 0, resolves = [];
 11 /******/         for(;i < chunkIds.length; i++) {
 12 /******/             chunkId = chunkIds[i];
 13 /******/             if(installedChunks[chunkId]) {
 14 /******/                 resolves.push(installedChunks[chunkId][0]);
 15 /******/             }
 16 /******/             installedChunks[chunkId] = 0;
 17 /******/         }
 18 /******/         for(moduleId in moreModules) {
 19 /******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
 20 /******/                 modules[moduleId] = moreModules[moduleId];
 21 /******/             }
 22 /******/         }
 23 /******/         if(parentJsonpFunction) parentJsonpFunction(data);
 24 /******/
 25 /******/         while(resolves.length) {
 26 /******/             resolves.shift()();
 27 /******/         }
 28 /******/
 29 /******/         // add entry modules from loaded chunk to deferred list
 30 /******/         deferredModules.push.apply(deferredModules, executeModules || []);
 31 /******/
 32 /******/         // run deferred modules when all chunks ready
 33 /******/         return checkDeferredModules();
 34 /******/     };
 35 /******/     function checkDeferredModules() {
 36 /******/         var result;
 37 /******/         for(var i = 0; i < deferredModules.length; i++) {
 38 /******/             var deferredModule = deferredModules[i];
 39 /******/             var fulfilled = true;
 40 /******/             for(var j = 1; j < deferredModule.length; j++) {
 41 /******/                 var depId = deferredModule[j];
 42 /******/                 if(installedChunks[depId] !== 0) fulfilled = false;
 43 /******/             }
 44 /******/             if(fulfilled) {
 45 /******/                 deferredModules.splice(i--, 1);
 46 /******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
 47 /******/             }
 48 /******/         }
 49 /******/         return result;
 50 /******/     }
 51 /******/
 52 /******/     // The module cache
 53 /******/     var installedModules = {};
 54 /******/
 55 /******/     // object to store loaded and loading chunks
 56 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
 57 /******/     // Promise = chunk loading, 0 = chunk loaded
 58 /******/     var installedChunks = {
 59 /******/         2: 0
 60 /******/     };
 61 /******/
 62 /******/     var deferredModules = [];
 63 /******/
 64 /******/     // The require function
 65 /******/     function __webpack_require__(moduleId) {
 66 /******/
 67 /******/         // Check if module is in cache
 68 /******/         if(installedModules[moduleId]) {
 69 /******/             return installedModules[moduleId].exports;
 70 /******/         }
 71 /******/         // Create a new module (and put it into the cache)
 72 /******/         var module = installedModules[moduleId] = {
 73 /******/             i: moduleId,
 74 /******/             l: false,
 75 /******/             exports: {}
 76 /******/         };
 77 /******/
 78 /******/         // Execute the module function
 79 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 80 /******/
 81 /******/         // Flag the module as loaded
 82 /******/         module.l = true;
 83 /******/
 84 /******/         // Return the exports of the module
 85 /******/         return module.exports;
 86 /******/     }
 87 /******/
 88 /******/
 89 /******/     // expose the modules object (__webpack_modules__)
 90 /******/     __webpack_require__.m = modules;
 91 /******/
 92 /******/     // expose the module cache
 93 /******/     __webpack_require__.c = installedModules;
 94 /******/
 95 /******/     // define getter function for harmony exports
 96 /******/     __webpack_require__.d = function(exports, name, getter) {
 97 /******/         if(!__webpack_require__.o(exports, name)) {
 98 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
 99 /******/         }
100 /******/     };
101 /******/
102 /******/     // define __esModule on exports
103 /******/     __webpack_require__.r = function(exports) {
104 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
105 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
106 /******/         }
107 /******/         Object.defineProperty(exports, '__esModule', { value: true });
108 /******/     };
109 /******/
110 /******/     // create a fake namespace object
111 /******/     // mode & 1: value is a module id, require it
112 /******/     // mode & 2: merge all properties of value into the ns
113 /******/     // mode & 4: return value when already ns object
114 /******/     // mode & 8|1: behave like require
115 /******/     __webpack_require__.t = function(value, mode) {
116 /******/         if(mode & 1) value = __webpack_require__(value);
117 /******/         if(mode & 8) return value;
118 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
119 /******/         var ns = Object.create(null);
120 /******/         __webpack_require__.r(ns);
121 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
122 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
123 /******/         return ns;
124 /******/     };
125 /******/
126 /******/     // getDefaultExport function for compatibility with non-harmony modules
127 /******/     __webpack_require__.n = function(module) {
128 /******/         var getter = module && module.__esModule ?
129 /******/             function getDefault() { return module['default']; } :
130 /******/             function getModuleExports() { return module; };
131 /******/         __webpack_require__.d(getter, 'a', getter);
132 /******/         return getter;
133 /******/     };
134 /******/
135 /******/     // Object.prototype.hasOwnProperty.call
136 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
137 /******/
138 /******/     // __webpack_public_path__
139 /******/     __webpack_require__.p = "./dist/";
140 /******/
141 /******/     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
142 /******/     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
143 /******/     jsonpArray.push = webpackJsonpCallback;
144 /******/     jsonpArray = jsonpArray.slice();
145 /******/     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
146 /******/     var parentJsonpFunction = oldJsonpFunction;
147 /******/
148 /******/
149 /******/     // add entry module to deferred list
150 /******/     deferredModules.push([3,1]);
151 /******/     // run deferred modules when ready
152 /******/     return checkDeferredModules();
153 /******/ })
154 /************************************************************************/
155 /******/ ({
156 
157 /***/ 3:
158 /***/ (function(module, __webpack_exports__, __webpack_require__) {
159 
160 "use strict";
161 __webpack_require__.r(__webpack_exports__);
162 /* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
163 /* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_chunk__WEBPACK_IMPORTED_MODULE_0__);
164 
165 
166 
167 console.log('test');
168 
169 
170 
171 /***/ })
172 
173 /******/ });

先看150行,初始不再馬上加載入口模塊,而是先將入口模塊和其依賴的公共模塊保存起來,再進行處理加載

/******/     // add entry module to deferred list
/******/     deferredModules.push([3,1]); // 這裏的3爲test模塊,1爲chunk公共模塊
/******/     // run deferred modules when ready
/******/     return checkDeferredModules();
/******/     function checkDeferredModules() {
/******/         var result;
                // deferredModules的結構長這樣 [[3,1]],對每一項進行處理
/******/         for(var i = 0; i < deferredModules.length; i++) {
/******/             var deferredModule = deferredModules[i];
/******/             var fulfilled = true;
                    // 從第二項開始,爲依賴的模塊
/******/             for(var j = 1; j < deferredModule.length; j++) {
/******/                 var depId = deferredModule[j];
                        // 依賴的模塊未加載
/******/                 if(installedChunks[depId] !== 0) fulfilled = false;
/******/             }
                    // 已經加載,則清除,並開始加載入口模塊,deferredModule的第一項即爲這裏的test入口模塊
/******/             if(fulfilled) {
/******/                 deferredModules.splice(i--, 1);
/******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/             }
/******/         }
/******/         return result;
/******/     }

注意到這裏也有 webpackJsonpCallback 函數,不過它的參數數組中有三項,第三項 var executeModules = data[2]; 暫時還沒用到,先略過

/******/         // add entry modules from loaded chunk to deferred list
/******/         deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/         // run deferred modules when all chunks ready
/******/         return checkDeferredModules();

上面這個,主要是爲了兼容公共模塊和入口模塊的兼容順序,什麼意思呢?

假如沒有這段代碼,那麼這樣是可行的

<script type="text/javascript" src="./dist/common.js"></script>
<script type="text/javascript" src="./dist/main.js"></script>

但common放後面就不行

<script type="text/javascript" src="./dist/main.js"></script>
<script type="text/javascript" src="./dist/common.js"></script>

common放在後面會導致初始調用checkDeferredModules時 公共模塊的fulfilled爲false,此時將無法加載入口模塊

所以需要在webpackJsonpCallback中再判斷處理一次

6. 提取runtime運行時模塊

上面代碼中,./dist/main.js 和 ./dist/test.js 都有很多運行時的代碼,我們可以將其提取出來,一併放到 common.js中

./webpack.config.js

optimization: {
        // 提取runtime代碼到common.js文件中
        runtimeChunk: {
            name: 'common'
        },
        // 提取公共部分爲common.js,使勁地提取吧..
        splitChunks: {
            name: 'common',
            chunks: 'all',
            minSize: 1
        }
    },

編譯後,看看 ./dist/test.js 乾淨了許多

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[2],{

/***/ 3:
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_chunk__WEBPACK_IMPORTED_MODULE_0__);



console.log('test');



/***/ })

},[[3,1]]]);

不過,注意到這裏push的參數多了第三項 [[3,1]],根據上面的分析,這個1應該就是公共模塊了

來看看 ./dist/common.js

  1 /******/ (function(modules) { // webpackBootstrap
  2 /******/     // install a JSONP callback for chunk loading
  3 /******/     function webpackJsonpCallback(data) {
  4 /******/         var chunkIds = data[0];
  5 /******/         var moreModules = data[1];
  6 /******/         var executeModules = data[2];
  7 /******/
  8 /******/         // add "moreModules" to the modules object,
  9 /******/         // then flag all "chunkIds" as loaded and fire callback
 10 /******/         var moduleId, chunkId, i = 0, resolves = [];
 11 /******/         for(;i < chunkIds.length; i++) {
 12 /******/             chunkId = chunkIds[i];
 13 /******/             if(installedChunks[chunkId]) {
 14 /******/                 resolves.push(installedChunks[chunkId][0]);
 15 /******/             }
 16 /******/             installedChunks[chunkId] = 0;
 17 /******/         }
 18 /******/         for(moduleId in moreModules) {
 19 /******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
 20 /******/                 modules[moduleId] = moreModules[moduleId];
 21 /******/             }
 22 /******/         }
 23 /******/         if(parentJsonpFunction) parentJsonpFunction(data);
 24 /******/
 25 /******/         while(resolves.length) {
 26 /******/             resolves.shift()();
 27 /******/         }
 28 /******/
 29 /******/         // add entry modules from loaded chunk to deferred list
 30 /******/         deferredModules.push.apply(deferredModules, executeModules || []);
 31 /******/
 32 /******/         // run deferred modules when all chunks ready
 33 /******/         return checkDeferredModules();
 34 /******/     };
 35 /******/     function checkDeferredModules() {
 36 /******/         var result;
 37 /******/         for(var i = 0; i < deferredModules.length; i++) {
 38 /******/             var deferredModule = deferredModules[i];
 39 /******/             var fulfilled = true;
 40 /******/             for(var j = 1; j < deferredModule.length; j++) {
 41 /******/                 var depId = deferredModule[j];
 42 /******/                 if(installedChunks[depId] !== 0) fulfilled = false;
 43 /******/             }
 44 /******/             if(fulfilled) {
 45 /******/                 deferredModules.splice(i--, 1);
 46 /******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
 47 /******/             }
 48 /******/         }
 49 /******/         return result;
 50 /******/     }
 51 /******/
 52 /******/     // The module cache
 53 /******/     var installedModules = {};
 54 /******/
 55 /******/     // object to store loaded and loading chunks
 56 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
 57 /******/     // Promise = chunk loading, 0 = chunk loaded
 58 /******/     var installedChunks = {
 59 /******/         1: 0
 60 /******/     };
 61 /******/
 62 /******/     var deferredModules = [];
 63 /******/
 64 /******/     // The require function
 65 /******/     function __webpack_require__(moduleId) {
 66 /******/
 67 /******/         // Check if module is in cache
 68 /******/         if(installedModules[moduleId]) {
 69 /******/             return installedModules[moduleId].exports;
 70 /******/         }
 71 /******/         // Create a new module (and put it into the cache)
 72 /******/         var module = installedModules[moduleId] = {
 73 /******/             i: moduleId,
 74 /******/             l: false,
 75 /******/             exports: {}
 76 /******/         };
 77 /******/
 78 /******/         // Execute the module function
 79 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 80 /******/
 81 /******/         // Flag the module as loaded
 82 /******/         module.l = true;
 83 /******/
 84 /******/         // Return the exports of the module
 85 /******/         return module.exports;
 86 /******/     }
 87 /******/
 88 /******/
 89 /******/     // expose the modules object (__webpack_modules__)
 90 /******/     __webpack_require__.m = modules;
 91 /******/
 92 /******/     // expose the module cache
 93 /******/     __webpack_require__.c = installedModules;
 94 /******/
 95 /******/     // define getter function for harmony exports
 96 /******/     __webpack_require__.d = function(exports, name, getter) {
 97 /******/         if(!__webpack_require__.o(exports, name)) {
 98 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
 99 /******/         }
100 /******/     };
101 /******/
102 /******/     // define __esModule on exports
103 /******/     __webpack_require__.r = function(exports) {
104 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
105 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
106 /******/         }
107 /******/         Object.defineProperty(exports, '__esModule', { value: true });
108 /******/     };
109 /******/
110 /******/     // create a fake namespace object
111 /******/     // mode & 1: value is a module id, require it
112 /******/     // mode & 2: merge all properties of value into the ns
113 /******/     // mode & 4: return value when already ns object
114 /******/     // mode & 8|1: behave like require
115 /******/     __webpack_require__.t = function(value, mode) {
116 /******/         if(mode & 1) value = __webpack_require__(value);
117 /******/         if(mode & 8) return value;
118 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
119 /******/         var ns = Object.create(null);
120 /******/         __webpack_require__.r(ns);
121 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
122 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
123 /******/         return ns;
124 /******/     };
125 /******/
126 /******/     // getDefaultExport function for compatibility with non-harmony modules
127 /******/     __webpack_require__.n = function(module) {
128 /******/         var getter = module && module.__esModule ?
129 /******/             function getDefault() { return module['default']; } :
130 /******/             function getModuleExports() { return module; };
131 /******/         __webpack_require__.d(getter, 'a', getter);
132 /******/         return getter;
133 /******/     };
134 /******/
135 /******/     // Object.prototype.hasOwnProperty.call
136 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
137 /******/
138 /******/     // __webpack_public_path__
139 /******/     __webpack_require__.p = "./dist/";
140 /******/
141 /******/     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
142 /******/     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
143 /******/     jsonpArray.push = webpackJsonpCallback;
144 /******/     jsonpArray = jsonpArray.slice();
145 /******/     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
146 /******/     var parentJsonpFunction = oldJsonpFunction;
147 /******/
148 /******/
149 /******/     // run deferred modules from other chunks
150 /******/     checkDeferredModules();
151 /******/ })
152 /************************************************************************/
153 /******/ ([
154 /* 0 */,
155 /* 1 */
156 /***/ (function(module, exports) {
157 
158 console.log('chunk');
159 
160 
161 /***/ })
162 /******/ ]);

58行直接將 chunk模塊設置爲已加載了,因爲它現在處於common模塊中,初始就是已加載

/******/     var installedChunks = {
/******/         1: 0
/******/     };

而150行上面不再出現 deferredModules的賦值,它由 ./dist/test.js 的第三個參數傳入來更新

var executeModules = data[2];
.
.
.
/******/         // add entry modules from loaded chunk to deferred list
/******/         deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/         // run deferred modules when all chunks ready
/******/         return checkDeferredModules();

7. 開發一個loader,加載模塊

loader會參與到模塊的編譯中,並輸出到生成的文件裏。這裏用個例子來說明一下

開發一個loader,原理很簡單,其實就是傳入參數,就可以自行處理了

./loader.js

const loaderUtils = require('loader-utils');

/**
 * 簡單的loader
 * @param  {[type]} content [description]
 * @return {[type]}         [description]
 */
module.exports = function(content) {
    // 獲取loader的參數
    let options = loaderUtils.getOptions(this);

    console.log('loader-options', options);
    console.log(content.split(/\r\n|\r|\n/g));

    // 做一些處理,並返回即可
    this.callback(null, JSON.stringify(content.split(/\r\n|\r|\n/g)));
};

./webpack.config.js

...
module: {
        rules: [{
            test: /\.css$/,
            loaders: [{
                loader: path.resolve('./loader.js'),
                options: {
                    css: 123
                }
            }]
        }]
    },

./test.css

.home {
    width: 100px;
    height: 200px;
}

./main.js

import './test.css';

console.log('main');

編譯後

./dist/main.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var _test_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_test_css__WEBPACK_IMPORTED_MODULE_0__);



console.log('main');


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

["",".home {","    width: 100px;","    height: 200px;","}",""]

/***/ })
],[[0,0]]]);

這裏的模塊0其實就是 ./main.js了,模塊1是 test.css,可以看到 css經過loader解析之後,內容是會扔到生成的文件裏面的

[[0,0]] 是webpack初始化生成的,這裏不必理會

8. 開發一個插件plugin,加載模塊

使用一個插件,看看插件是怎麼和編譯過程結合起來的

爲了簡便,這裏就自行開發一個簡單的插件

開發插件可以類似webpack那樣,基於 tapable進行開發,使用 訂閱-發佈 模式

先配置一些 ./webpack.config.js

const webpack = require('webpack');
const path = require('path');

const todayPlugin = require('./todayPlugin.js');

module.exports = {
    
    ...

    plugins: [
        new todayPlugin({
            test: 123
        })
    ]
};

./todayPlugin.js

// 使用SyncHook
const {SyncHook} = require('tapable');

/**
 * 自定義的插件
 */
class todayPlugin {
    constructor(options) {
        // 獲取插件的參數
        this.options = options;

        console.log('plugin-options', this.options);
    }

    /**
     * 提供webpack對插件進行調用
     * @param  {[type]} compiler [description]
     * @return {[type]}          [description]
     */
    apply(compiler) {
        // 實例化,創建一個hook
        compiler.hooks.todayHook = new SyncHook(['day']);

        // 事件訂閱,這裏的day參數需要和實例化時傳遞的參數一致
        compiler.hooks.todayHook.tap('logToday', (day) => {
            console.log('today', day);
        });

        // 選擇在webpack的compiler done觸發時做處理
        compiler.hooks.done.tap('setToday', () => {
            // 觸發我們的事件(即事件發佈)
            compiler.hooks.todayHook.call(new Date);
        });
    }
}

module.exports = todayPlugin;

編譯後

在生成的文件中,並沒有看到蹤跡

當然了,也不能由此就得出結論插件不會影響到生成的文件,只是看起來如此

編譯結果就分析到這裏了,說實話,非常亂 .......

具體到底是由源碼裏面哪段代碼控制的,就不得而知了,源碼實在是龐大,目前定位到兩個比較關鍵的文件,腦殼不疼的時候再看吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章