前言
前端性能优化方面, 很多时候我们需要减小主包大小, 提升首屏加载渲染时间, 这时候我们就需要异步的延迟加载模块,webpack提供了运行时加载模块函数 import()
, import函数的作用就是 动态的加载模块。调用 import 地方,被视为分割点,意思是,被请求的模块和它引用的所有子模块,会分割到一个单独的 chunk 中
详情可以看webpack官方对import()的使用方法解释 传送门
import()
具体做了什么 我们先搭一个demo
搭建DEMO
mkdir webpack-import-demo
cd webpack-import-demo
yarn init -y
yarn add webpack webpack-cli html-webpack-plugin --dev
根目录下创建配置文件 webpack.config.js
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
title: 'webpack-import-demo',
}),
],
};
package.json
中添加scripts
省的每次都敲一堆命令行
"scripts": {
"build": "webpack --mode=development --config webpack.config.js"
},
然后 根目录创建scr/index.js
设置定时器 3秒后去加载./dynamic.js
并执行他
// scr/index.js
setTimeout(()=>{
import('./dynamic').then(({default:text}) => {
alert(text)
})
},3000)
创建src/dynamic.js
// src/dynamic.js
const text = '我是运行时异步载入的文本'
export default text
然后我们yarn build
看下效果
OJBK 编译完成 我们在浏览器打开index.html
康康
good good 3秒后才去加载 ./dynamic.js
并执行他
让我们康康编译后的代码是啥样 做了些啥
打开dist/mian.js
让我们研究下他是怎么做的
草!(一种植物) 这么多 还有eval 这怎么读!!
我们在webpack.config.js
中添加devtool: false
然后重新执行 yarn build
打开dist/mian.js
good good 终于读起来通顺了
开始分析
main.js
可以看到 这个文件其实就是一个IIFE(自执行函数),上面定义了一堆webpack运行时用的各种函数等等,最后面就是编译后我们main.js
中的内容了,
我们写的 import('./dynamic')
被编译成了__webpack_require__.e(/*! import() */ "src_dynamic_js").then(__webpack_require__.bind(__webpack_require__, /*! ./dynamic */ "./src/dynamic.js"))
我们开始往上找 先看看__webpack_require__
是干啥的
__webpack_require__
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
搜嘎 原来是通过模块ID查找模块的函数, 如果模块在缓存中就直接从缓存中返回 , 如果不在缓存中那就创建一个并且执行__webpack_modules__[moduleId]
这个方法 然后返回这个模块
我们再看看__webpack_require__.e
是啥
__webpack_require__.e
继续查找 __webpack_require__.e
的定义
/******/ /* webpack/runtime/ensure chunk */
/******/ (() => {
/******/ __webpack_require__.f = {};
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = (chunkId) => {
/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
/******/ __webpack_require__.f[key](chunkId, promises);
/******/ return promises;
/******/ }, []));
/******/ };
/******/ })();
原来 __webpack_require__.e
返回了一个promise, 再看里面 Object.keys(__webpack_require__.f)
, 提取 f
对象上的key,但是我们顺着作用域往上找 看到__webpack_require__.f = {}
, 什么! 是个空对象?
心机之蛙一直摸你肚子!
那他一定在之前对 __webpack_require__.f
有添加属性!不然要这个Object.keys
干嘛! 继续顺着找,
果然!
他在
__webpack_require__.f
上添加了一个名为 'j'的函数, 这个函数接收一个chunkId
和一个promises
, 主要作用就是判断 installedChunks
中有没有包含[chunkId]
这个key 如果米有加载过 那就成一个promise,并且把[resolve, reject]
赋值给installedChunkData
,然后通过__webpack_require__.l
大概就是生成一个script
标签添加进head,以JSONP的方式开始加载这个资源,执行完就把这个script 删掉
但是!!!!!!
这个promise的resolve
啥时候执行的??? 加载资源的回调函数loadingEnded
中没有执行 resolve`啊!
这代码跑不通了啊 找!!
哈啊!在这呢 原来在webpackJsonpCallback
中执行的呀
那继续找webpackJsonpCallback
在哪执行的
找到了
他在全局定义了一个参数 self["webpackChunkwebpack_import_demo"]
(self
也就是window
对象),并且重写了push
方法 这样在调用self["webpackChunkwebpack_import_demo"].push
的时候webpackJsonpCallback
也就执行了! 那这个push在哪调用的呢??
可以看到 运行到这的时候 加载了src_dynamic_js.main.js
src_dynamic_js.main.js
"use strict";
(self["webpackChunkwebpack_import_demo"] = self["webpackChunkwebpack_import_demo"] || []).push([["src_dynamic_js"],{
/***/ "./src/dynamic.js":
/*!************************!*\
!*** ./src/dynamic.js ***!
\************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
const text = '我是运行时异步载入的文本'
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (text);
/***/ })
}]);
可以看到这个js也是个IIFE
哎 这self["webpackChunkwebpack_import_demo"].push
这不就调用了吗 传入了一个数组,数组第一项为['./src/dynamic.js']
第二项为一个对象 对象内有一个方法 方法名叫'./src/dynamic.js'
ok 再回到main.js
再看webpackJsonpCallback
接收两个参数 parentChunkLoadingFunction和data
第一个参数不用管 这个函数是柯里化过 就是chunkLoadingGlobal
,柯里化不懂的不用管他,我们只需要知道 data
就是我们./src/dynamic.js
在执行push的时候传的值就行
然后对data解构
var [chunkIds, moreModules, runtime] = data;
moreModules
就是传入的那个对象, 然后用 for...in
对其遍历,把moreModules
本身含有的所有键值都复制给__webpack_require__.m
也就是__webpack_modules__
,他俩是一个对象, 因为moreModules中只有一个'./src/dynamic.js'
方法,所以此时, __webpack_require__.m = __webpack_modules__ = { ' ./src/dynamic.js' : function( xxx ){ xxxx }}
,
终于!!!
__webpack_require__.e(/*! import() */ "src_dynamic_js")
这段跑完了,模块注册完成了,他的then可以执行了
then里面执行 __webpack_require__
传入了 ./src/dynamic.js
这个key 然后__webpack_modules__['./src/dynamic.js'](module, module.exports, __webpack_require__)执行, 执行中通过 __webpack_require__.d
把 default
定义到module.exports
并设置 getter
,然后把module.exports
返回出去
此时,最后那个then就能获取到模块的返回了!!!!!
收工