Webpack探索【16】--- 懶加載構建原理詳解(模塊如何被組建&如何加載)&源碼解讀

本文主要說明Webpack懶加載構建和加載的原理,對構建後的源碼進行分析。javascript

一 說明

本文以一個簡單的示例,經過對構建好的bundle.js源碼進行分析,說明Webpack懶加載構建原理。html

本文使用的Webpack版本是4.32.2版本。java

注意:以前也分析過Webpack3.10.0版本構建出來的bundle.js,經過和此次的Webpack 4.32.2版本對比,核心的構建原理基本一致,只是將模塊索引id改成文件路徑和名字、模塊代碼改成了eval(moduleString)執行的方式等一些優化改造。webpack

二 示例

1)Webpack.config.js文件內容:web

 1 const path = require('path');
 2 const HtmlWebpackPlugin = require('html-webpack-plugin');
 3 const CleanWebpackPlugin = require('clean-webpack-plugin');
 4 
 5 module.exports = {
 6     entry: {
 7         app: './src/index.js'
 8     },
 9     output: {
10         filename: '[name].bundle.js',
11         chunkFilename: '[name].bundle.js',
12         path: path.resolve(__dirname, 'dist')
13     },
14     plugins: [
15         new CleanWebpackPlugin(['dist']),
16         new HtmlWebpackPlugin({
17             title: 'Output Management'
18         })
19     ],
20     mode: 'development' // 'production' 用於配置開發仍是發佈模式
21 };

2)建立src文件夾,添加入口文件index.js:npm

 1 function component() {
 2     var element = document.createElement('div');
 3     var button = document.createElement('button');
 4     var br = document.createElement('br');
 5 
 6     button.innerHTML = 'Click me and look at the console!';
 7     element.innerHTML = 'Hello webpack'; // _.join(['Hello', 'webpack'], ' ');
 8     element.appendChild(br);
 9     element.appendChild(button);
10 
11     button.onclick = (
12         e => {
13             // 注意:下邊的註釋不寫的話,打包出來的print文件包名就不是print.bundle.js,而是0.bundle.js
14             import(/* webpackChunkName: "print" */'./print').then(
15                 module => {
16                     var print = module.default;
17                     print();
18                 }
19             )
20         }
21     );
22 
23     return element;
24 }
25 
26 document.body.appendChild(component());

3)在src目錄下建立print.js文件:json

1 export default () => {
2     console.log('Button Clicked: Here\'s "some text"!');
3 }

 

4)package.json文件內容:數組

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "webpack": "webpack",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^0.1.18",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.32.2",
    "webpack-cli": "^3.3.2"
  },
  "dependencies": {
    "lodash": "^4.17.4"
  }
}

三 執行構建

執行構建命令:npm run webpackpromise

在dist目錄下生成了兩個文件:app.bundle.js和print.bundle.js。緩存

app.bundle.js源碼以下(下邊代碼是將註釋去掉、壓縮的代碼還原後的代碼):

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

print.bundle.js的源碼以下:

 1 (window["webpackJsonp"] = window["webpackJsonp"] || []).push([ // 注意:這個push實際是webpackJsonpCallback方法
 2     ["print"],
 3     {
 4         "./src/print.js": (function(module, __webpack_exports__, __webpack_require__) {
 5             "use strict";
 6             __webpack_require__.r(__webpack_exports__);
 7             __webpack_exports__["default"] = (() => {
 8                 console.log('Button Clicked: Here\'s "some text"!');
 9             });
10         })
11     }
12 ]);

 四 源碼解讀

說明:懶加載構建和和上一篇的基礎構建原理中有不少相同的代碼,這裏再也不重複說明,本文主要詳細說明其中增長的懶加載方面的內容。

app.bundle.js是構建好的入口文件,裏邊就是一個自執行函數,基本結構和上一篇基礎構建源碼中一致,這裏再也不詳細說明。下邊是使用懶加載模塊構建後,增長的內容,這裏詳細說明這些內容:

 1 (function (modules) {
 2     function webpackJsonpCallback(data) {...};
 3 
 4     // The module cache
 5     var installedModules = {};
 6 
 7     // object to store loaded and loading chunks
 8     // undefined = chunk not loaded, null = chunk preloaded/prefetched
 9     // Promise = chunk loading, 0 = chunk loaded
10     var installedChunks = {
11         "app": 0
12     };
13 
14     // script path function
15     function jsonpScriptSrc(chunkId) {...}
16 
17     // The require function
18     function __webpack_require__(moduleId) {...}
19 
20     // This file contains only the entry chunk.
21     // The chunk loading function for additional chunks
22     __webpack_require__.e = function requireEnsure(chunkId) {...};
23     
24     // .... 
25     
26     // on error function for async loading
27     __webpack_require__.oe = function (err) {
28         console.error(err);
29         throw err;
30     };
31 
32     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
33     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); // 複製一個數組的push方法,這個方法的this是jsonpArray
34     jsonpArray.push = webpackJsonpCallback; // TODO: 爲何要複寫push,而不是直接增長一個新方法名?
35     jsonpArray = jsonpArray.slice(); // 拷貝一個新數組
36     for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
37     var parentJsonpFunction = oldJsonpFunction;
38 
39     // Load entry module and return exports
40     return __webpack_require__(__webpack_require__.s = "./src/index.js");
41 })
42 /************************************************************************/
43 ({
44     "./src/index.js": (function (module, exports, __webpack_require__) {...})
45 });

咱們詳細分析下新增的這些代碼。

4.1 installedChunks緩存變量

根據註釋,該對象變量主要緩存各個獨立的js文件模塊的加載狀態。

該對象的key就是chunkId,而chunkId實際就是文件名去掉.bundle.js後剩餘的內容,例如:print.bundle.js的chunkId就是print。

根據值的不一樣標誌着key對應的文件加載狀態主要有如下幾種:

undefined:key對應的文件未加載;

null:key對應的文件延遲加載;

數組:正在加載(注意,這裏的註釋有點不許確,這個數組實際存儲的是一個promise的實例,以及對應的reject和resolve);

0:已經加載過了。

這個變量的核心做用:當一個懶加載模塊被多個文件依賴時,若是該模塊已經被加載過了,就不會被其它模塊加載了。判斷方法就是經過該緩存變量判斷的。具體源碼能夠在__webpack_require__.e函數中看到:

 1 __webpack_require__.e = function requireEnsure(chunkId) {
 2     var promises = [];
 3 
 4     // JSONP chunk loading for javascript
 5     var installedChunkData = installedChunks[chunkId];
 6     if (installedChunkData !== 0) { // 0 means "already installed".
 7 
 8         // a Promise means "currently loading".
 9         if (installedChunkData) {
10             promises.push(installedChunkData[2]);
11         } else {
12             // ... 
13             // 建立一個<script>標籤,將路徑設置爲懶加載文件路徑,並插入HTML,實現該懶加載文件的加載。
14         }
15     }
16     return Promise.all(promises);
17 };

 

4.2 __webpack_require__.e函數

該函數主要做用就是建立一個<script>標籤,而後將chunkId對應的文件經過該標籤加載。

源代碼以下:

 1 __webpack_require__.e = function requireEnsure(chunkId) {
 2         var promises = [];
 3 
 4         // JSONP chunk loading for javascript
 5 
 6         var installedChunkData = installedChunks[chunkId];
 7         if (installedChunkData !== 0) { // 0 means "already installed".
 8 
 9             // a Promise means "currently loading".
10             if (installedChunkData) {
11                 promises.push(installedChunkData[2]);
12             } else {
13                 // setup Promise in chunk cache
14                 var promise = new Promise(function (resolve, reject) {
15                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
16                 });
17                 promises.push(installedChunkData[2] = promise);
18 
19                 // start chunk loading
20                 var script = document.createElement('script');
21                 var onScriptComplete;
22 
23                 script.charset = 'utf-8';
24                 script.timeout = 120;
25                 if (__webpack_require__.nc) {
26                     script.setAttribute("nonce", __webpack_require__.nc);
27                 }
28                 script.src = jsonpScriptSrc(chunkId);
29 
30                 // create error before stack unwound to get useful stacktrace later
31                 var error = new Error();
32                 onScriptComplete = function (event) {
33                     // avoid mem leaks in IE.
34                     script.onerror = script.onload = null;
35                     clearTimeout(timeout);
36                     var chunk = installedChunks[chunkId];
37                     if (chunk !== 0) {
38                         if (chunk) {
39                             var errorType = event && (event.type === 'load' ? 'missing' : event.type);
40                             var realSrc = event && event.target && event.target.src;
41                             error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
42                             error.type = errorType;
43                             error.request = realSrc;
44                             chunk[1](error);
45                         }
46                         installedChunks[chunkId] = undefined;
47                     }
48                 };
49                 var timeout = setTimeout(function () {
50                     onScriptComplete({type: 'timeout', target: script});
51                 }, 120000);
52                 script.onerror = script.onload = onScriptComplete;
53                 document.head.appendChild(script);
54             }
55         }
56         return Promise.all(promises);
57     };

主要作了以下幾個事情:

1)判斷chunkId對應的模塊是否已經加載了,若是已經加載了,就再也不從新加載;

2)若是模塊沒有被加載過,但模塊處於正在被加載的過程,再也不重複加載,直接將加載模塊的promise返回。

爲何會出現這種狀況?

例如:咱們將index.js中加載print.js文件的地方改造爲下邊屢次經過ES6的import加載print.js文件:

 1 button.onclick = (
 2     e => {
 3         
 4         import('./print').then(
 5             module => {
 6                 var print = module.default;
 7                 print();
 8             }
 9         );
10 
11         import('./print').then(
12             module => {
13                 var print = module.default;
14                 print();
15             }
16         )       
17     }
18 );

 從上邊代碼能夠看出,當第一import加載print.js文件時,尚未resolve,就又執行第二個import文件了,而爲了不重複加載該文件,就經過將這裏的判斷,避免了重複加載。

3)若是模塊沒有被加載過,也不處於加載過程,就建立一個promise,並將resolve、reject、promise構成的數組存儲在上邊說過的installedChunks緩存對象屬性中。而後建立一個script標籤加載對應的文件,加載超時時間是2分鐘。若是script文件加載失敗,觸發reject(對應源碼中:chunk[1](error),chunk[1]就是上邊緩存的數組的第二個元素reject),並將installedChunks緩存對象中對應key的值設置爲undefined,標識其沒有被加載。

4)最後返回promise

注意:源碼中,這裏返回的是Promise.all(promises),分析代碼發現promises好像只可能有一個元素。可能還沒遇到多個promises的場景吧。留待後續研究。

 4.3 自執行函數體代碼分析

整個app.bundle.js文件是一個自執行函數,該函數中執行的代碼以下:

1     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
2     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); // 複製一個數組的push方法,這個方法的this是jsonpArray
3     jsonpArray.push = webpackJsonpCallback; // TODO: 爲何要複寫push,而不是直接增長一個新方法名?
4     jsonpArray = jsonpArray.slice(); // 拷貝一個新數組
5     for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 6 var parentJsonpFunction = oldJsonpFunction;

這段代碼主要作了以下幾個事情:

1)定義了一個全局變量webpackJsonp,改變量是一個數組,該數組變量的原生push方法被複寫爲webpackJsonpCallback方法,該方法是懶加載實現的一個核心方法,具體代碼會在下邊分析。

該全局變量在懶加載文件中被用到。在print.bundle.js中:

1 (window["webpackJsonp"] = window["webpackJsonp"] || []).push([ // 注意:這個push實際是webpackJsonpCallback方法
2     ["print"],
3  { 4 "./src/print.js": (function(module, __webpack_exports__, __webpack_require__) {...}) 5  } 6 ]);

2)將數組的原生push方法備份,賦值給parentJsonpFunction變量保存。

注意:該方法的this是全局變量webpackJsonp,也就是說parentJsonpFunction('111')後,全局數組變量webpackJsonp就增長了一個'111'元素。

該方法在webpackJsonpCallback中會用到,是將懶加載文件的內容保存到全局變量webpackJsonp中。

3)上邊第一步中複寫push的緣由?

多是由於在懶加載文件中,調用了複寫後的push,執行了原生push的功能,所以,爲了更形象的表達該意思,所以直接複寫了push。

但我的認爲這個不太好,不易讀。直接新增一個_push或者extendPush,這樣是否是讀起來就很簡單了。

4.4 webpackJsonpCallback函數分析

該函數是懶加載的一個比較核心代碼。其代碼以下:

 1     function webpackJsonpCallback(data) {
 2         var chunkIds = data[0]; 3 var moreModules = data[1]; 4 5 // add "moreModules" to the modules object, 6 // then flag all "chunkIds" as loaded and fire callback 7 var moduleId, chunkId, i = 0, resolves = []; 8 for (; i < chunkIds.length; i++) { 9 chunkId = chunkIds[i]; 10 if (installedChunks[chunkId]) { 11 resolves.push(installedChunks[chunkId][0]); 12  } 13 installedChunks[chunkId] = 0; 14  } 15 for (moduleId in moreModules) { 16 if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { 17 modules[moduleId] = moreModules[moduleId]; 18  } 19  } 20 if (parentJsonpFunction) parentJsonpFunction(data); 21 22 while (resolves.length) { 23  resolves.shift()(); 24  } 25 };

參數說明:

參數是一個數組。有兩個元素:第一個元素是要懶加載文件中全部模塊的chunkId組成的數組;第二個參數是一個對象,對象的屬性和值分別是要加載模塊的moduleId和模塊代碼函數。

該函數主要作的事情以下:

1)遍歷參數中的chunkId:

判斷installedChunks緩存變量中對應chunkId的屬性值:若是是真,說明模塊正在加載,由於從上邊分析中能夠知道,installedChunks[chunkId]只有一種狀況是真,那就是在對應的模塊正在加載時,會將加載模塊建立的promise的三個信息搞成一個數組[resolve, reject, proimise]賦值給installedChunks[chunkId]。將resolve存入resolves變量中。

將installedChunks中對應的chunkId置爲0,標識該模塊已經被加載過了。

2)遍歷參數中模塊對象全部屬性:

將模塊代碼函數存儲到modules中,該modules是入口文件app.bundle.js中自執行函數的參數。

這一步很是關鍵,由於執行模塊加載函數__webpack_require__時,獲取模塊代碼時,就是經過moduleId從modules中查找對應模塊代碼。

3)調用parentJsonpFunction(原生push方法)將整個懶加載文件的數據存入全局數組變量window.webpackJsonp。

4)遍歷resolves,執行全部promise的resolve:

當執行了promise的resolve後,纔會走到promise.then的成功回調中,查看源碼能夠看到:

 1             button.onclick = (
 2                 e => { 3 __webpack_require__.e("print") 4 .then(__webpack_require__.bind(null, "./src/print.js")) 5  .then( 6 module => { 7 var print = module.default; 8  print(); 9  } 10  ) 11  } 12 );

resolve後,執行了兩個then回調:

第一個回調是調用__webpack_require__函數,傳入的參數是懶加載文件中的一個模塊的moduleId,而這個moduleId就是上邊存入到modules變量其中一個。這樣就經過__webpack_require__執行了模塊的代碼。並將模塊的返回值,傳遞給第二個then的回調函數;

第二個回調函數是真正的onclick回調函數的業務代碼。

5)重要思考:

從這個函數能夠看出:

調用__webpack_require__.e('print')方法,實際只是將對應的print.bundle.js文件加載和建立了一個異步的promise(由於並不知道何時這個文件才能執行完,所以須要一個異步promise,而promise的resolve會在對應的文件加載時執行,這樣就能實現異步文件加載了),並無將懶加載文件中保存的模塊代碼執行。

在加載對應print.bundle.js文件代碼時,經過調用webpackJsonpCallback函數,實現觸發加載文件時建立的promise的resolve。

resolve觸發後,會執行promise的then回調,這個回調經過__webpack_require__函數執行了真正須要模塊的代碼(注意:若是print.bundle.js中有不少模塊,只會執行用到的模塊代碼,而不是執行全部模塊的代碼),執行完後將模塊的exports返回給promise的下一個then函數,該函數也就是真正的業務代碼了。

綜上,能夠看出,webpack實際是經過promise,巧妙的實現了模塊的懶加載功能。

 5 懶加載構建原理圖

 

相關文章
相關標籤/搜索