定义
jack.js
export default () => {
console.log('fafa');
};
mark.js
export const val = 'sd';
export default () => {
console.log('jaja');
};
index.js
const name = 'jack';
import('./mark').then(mark => mark());
import('./jack').then(jack => jack());
export default name;
使用 webpack 打包
(function(modules) {
// webpackBootstrap
// install a JSONP callback for chunk loading
function webpackJsonpCallback(data) {}
// The module cache
var installedModules = {};
// 用来存储加载过和加载中的 chunk
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
var installedChunks = {
main: 0,
};
// script path function
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + '' + chunkId + '.bundle.js';
}
// The require function
function __webpack_require__(moduleId) {}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function requireEnsure(chunkId) {};
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// 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 });
};
__webpack_require__.t = function(value, mode) {};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
// __webpack_public_path__
__webpack_require__.p = '';
// 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 = './src/index.js'));
})(
/************************************************************************/
{
/***/ './src/index.js':
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! exports provided: default */
/***/ function(module, __webpack_exports__, __webpack_require__) {
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\nconst name = \'jack\';\n__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./mark */ "./src/mark.js")).then(mark => mark());\n\n/* harmony default export */ __webpack_exports__["default"] = (name);\n\n\n//# sourceURL=webpack:///./src/index.js?',
);
/***/
},
},
);
jack.js 打包出的内容
(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
[1],
{
/***/ './src/mark.js':
/*!*********************!*\
!*** ./src/mark.js ***!
\*********************/
/*! exports provided: val, default */
/***/ function(module, __webpack_exports__, __webpack_require__) {
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "val", function() { return val; });\nconst val = \'sd\'\n\n/* harmony default export */ __webpack_exports__["default"] = (() => {\n console.log(\'jaja\')\n});\n\n\n//# sourceURL=webpack:///./src/mark.js?',
);
/***/
},
},
]);
取全局的 webpackJsonp 数组,如果没有,定义为空数组,并调用 push 方法,参数一个数组,第一项对应异步模块的 chunkId,第二项就是模块的内容
总体流程
首先,通过立即执行函数,跳过一系列的方法定义,到下面地方开始运行
// ... some code
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;
return __webpack_require__((__webpack_require__.s = './src/index.js'));
可以看到,jsonpArray 是全局的 webpackJsonp数组,并对这个数组的 push 方法等于 webpackJsonpCallback, 这样根据上面异步 chunk 里的代码,可以看出模块加载了以后,就会执行 webpackJsonpCallback 方法.
加载 index.js. 看下 index.js 对应的 eval 函数内部的内容.
__webpack_require__.r(__webpack_exports__);
const name = 'jack';
__webpack_require__
.e(/*! import() */ 1)
.then(__webpack_require__.bind(null, /*! ./mark */ './src/mark.js'))
.then(mark => mark());
__webpack_require__
.e(/*! import() */ 0)
.then(__webpack_require__.bind(null, /*! ./jack */ './src/jack.js'))
.then(jack => jack());
/* harmony default export */
__webpack_exports__['default'] = name;
可以看到 import().then() 被转换为了 __webpack_require__.e. 这个函数的第一个参数就是按需加载对应 chunk 的 chunkId。
接下来看看 __webpack_require__.e 的具体实现
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if (installedChunkData !== 0) {
// 0 表示该chunk已加载过了
// 如果 installedChunkData 是 promise,即为真,则表示模块正在加载中
// push installedChunkData[2] 到 promises 数组
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// installedChunks[chunkId] 赋值为一个数组,前两项是 promise 的 resolve reject
// installedChunkData = installedChunks[chunkId]
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
// installedChunkData[2]赋值为上面创建的promise
promises.push((installedChunkData[2] = promise));
// 通过创建 script 标签形式下载 chunk script
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute('nonce', __webpack_require__.nc);
}
// jsonpScriptSrc 拼接 script 地址,webpack public path + chunkId + .bundle.js
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
onScriptComplete = function(event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
// 不为 0 表示下载出错
if (chunk !== 0) {
if (chunk) {
var errorType =
event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message =
'Loading chunk ' +
chunkId +
' failed.\n(' +
errorType +
': ' +
realSrc +
')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
installedChunks[chunkId] = undefined;
}
};
// 超时处理
var timeout = setTimeout(function() {
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
// 使用 Promise.all 处理
return Promise.all(promises);
};
通过创建 script 标签加载对应 chunkId 的 chunk,加载完以后,执行对应 chunk 内的内容.
也就是上面提到的 webpackJsonpCallback
function webpackJsonpCallback(data) {
var chunkIds = data[0];
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]) {
// installedChunks[chunkId][0] 之前是 __webpack_reuqire__.e 里赋值为 promise 的 resolve
// 放入 resolves
resolves.push(installedChunks[chunkId][0]);
}
// 赋值为 0 表示加载完成
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
// 同步到 modules 中
modules[moduleId] = moreModules[moduleId];
}
}
if (parentJsonpFunction) parentJsonpFunction(data);
while (resolves.length) {
resolves.shift()(); // => promise resolve()
}
}
通过执行 promise 的 resolve(),这样我们的Promise.all(promises); 全部 resolve 之后,就可以执行 .then 后面的方法了.
整理一下
- 通过
__webpack_require__ 加载入口文件
- 遇到异步模块(import()),转换为使用
__webpack_require__.e 加载
__webpack_require__.e 使用创建 script 标签的方式加载异步模块,并将每个异步的 chunk 创建 promise,并放入 installedChunk
- 模块被加载后执行
webpackJsonpCallback 方法,改变 nstalledChunk 对应的 chunkId 加载状态为 0,即表示加载过,然后同步到 modules(所有模块存放的缓存)中,执行之前创建的 promise 的 resolve()
- then 里面就可以使用
__webpack_require使用异步模块
定义
jack.jsmark.jsindex.js使用 webpack 打包
jack.js打包出的内容取全局的
webpackJsonp数组,如果没有,定义为空数组,并调用 push 方法,参数一个数组,第一项对应异步模块的 chunkId,第二项就是模块的内容总体流程
首先,通过立即执行函数,跳过一系列的方法定义,到下面地方开始运行
可以看到,
jsonpArray是全局的webpackJsonp数组,并对这个数组的 push 方法等于webpackJsonpCallback, 这样根据上面异步 chunk 里的代码,可以看出模块加载了以后,就会执行webpackJsonpCallback方法.加载 index.js. 看下 index.js 对应的 eval 函数内部的内容.
可以看到
import().then()被转换为了__webpack_require__.e. 这个函数的第一个参数就是按需加载对应 chunk 的 chunkId。接下来看看
__webpack_require__.e的具体实现通过创建 script 标签加载对应 chunkId 的 chunk,加载完以后,执行对应 chunk 内的内容.
也就是上面提到的
webpackJsonpCallback通过执行 promise 的 resolve(),这样我们的
Promise.all(promises);全部 resolve 之后,就可以执行.then后面的方法了.整理一下
__webpack_require__加载入口文件__webpack_require__.e加载__webpack_require__.e使用创建 script 标签的方式加载异步模块,并将每个异步的 chunk 创建 promise,并放入 installedChunkwebpackJsonpCallback方法,改变 nstalledChunk 对应的 chunkId 加载状态为 0,即表示加载过,然后同步到 modules(所有模块存放的缓存)中,执行之前创建的 promise 的 resolve()__webpack_require使用异步模块