解析 Webpack中import、require、按需加载的执行过程
最近由于一篇分享手淘过年项目中采用到的前端技术的影响,重新研究了一下项目中CSS的架构.本来打算写一篇文章,但是写到一半突然发现自己像在写文档介绍一样,所以后来就放弃了。但是觉得过程中研究的 Webpack 倒是可以单独拿出来讲一讲
搭建一个简单环境
- npm init
- npm install css-loader html-webpack-plugin style-loader webpack webpack-cli
// Webpack 4.0
const htmlPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "[name].js",
path: __dirname + "/dist"
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
},
]
}
]
},
plugins: [
new htmlPlugin({
title: "Test Webpack",
filename: "index.html"
})
]
};一个基本的配置就搭建好了,详细的配置内容我就不介绍了, 然后我们在 src/index.js 上面写我们的测试代码, 在 dist/main.js 看一下 Webpack 实现的原理,那么目前我们的项目结构是这样子的
|-- project
|-- dist
|-- src
|-- index.js
|-- node_modules
|-- webpack.config.jswebpack 中 require 和 import 的执行过程
在进入按需加载的讲解之前,我们需要看一个问题 require 和 import在 Webpack 的执行过程是怎样的呢 ?现在我们在 src建立两个文件 index.js、module-es6.js 和 module-commonjs.js。我们通过这三个文件解析 require 和 import 的执行过程
首先我们要区分的是 CommonJS 和 ES6 模块导出之间的区别,在 CommonJS 中你导出模块方式是改变 module.exports,但是对于 ES6 来说并不存在 module 这个变量,他的导出方式是通过一个关键词 export来实现的。在我们书写 JS文件的时候,我们发现无论是以 CommomJS 还是 ES6 的形式导出都可以实现,这是因为 Webpack做了一个兼容处理
我们建立一个小 DEMO 来查看一下,我们现在上面建立的三个文件的代码如下
// index.js
// import moduleDefault, { moduleValue } from "./module-es6.js";
// import moduleDefault, { moduleValue1, moduleValue2 } from "./module-commanjs.js";// module-es6.js export let moduleValue = "moduleValue" //ES6模块导出 export default "ModuleDefaultValue"
// module-commonjs.js exports.moduleValue1 = "moduleValue1" exports.moduleValue2 = "moduleValue2"
现在我们打开 index.js 中加载 module-commonjs.js 的代码,首先会先给当前模块打上 ES6模块的标识符,在 index 则会产生两个变量 A 和 B. A 保存 module-commonjs 的导出的结果,B 则是兼容 CommonJS中没有 ES6通过 export default导出的结果,其值跟 A一样. 用B来兼容 export default 的结果
然后我们重新注释代码,再打开 index.js 中加载 module-es6.js 的代码
这次和上面一样会先给当前模块打上 ES6模块的标识符,然后去加载 module-es6,获取他的导出值。但是浏览器是不识别 export 这个关键词的所以 Webpack 会对的代码进行解释,首先给 module.exports 设定导出的值,如果是 export default 会直接赋值给 module.exports,如果是其他形式,则给module.exports的导出的key设定一个 getter,该 getter 的返回值就是导出的结果
而对于require来说整个执行过程其实过程和import是一样的。
对于 Webpack 来说只要你使用了 import 或者 export等关键字, 他就会给 module.exports添加一个__esModule : true 来识别这是一个 ES6的模块,通过这个值来做一些特殊处理
如果觉得我上面讲的不太明白 那可以看看下面这些代码
let commonjs = {
"./src/index.js": function(module, __webpack_exports__, __webpack_require__) {
"use strict";
//给当前模块打上 `ES6`模块的标识符
__webpack_require__.r(__webpack_exports__); //给当前模块打上 `ES6`模块的标识符
// 执行 ./src/module-commonjs.js 的代码 获取导出值
var A = __webpack_require__("./src/module-commonjs.js");
// 根据 ./src/module-commonjs.js 是否为ES6模块 给返回值增加不同的 getter函数
var B = __webpack_require__.n(A);
},
"./src/module-commonjs.js": function(module, exports) {
exports.moduleValue1 = "moduleValue1";
exports.moduleValue2 = "moduleValue2";
}
};
let es6 = {
"./src/index.js": function(module, __webpack_exports__, __webpack_require__) {
"use strict";
//给当前模块打上 `ES6`模块的标识符
__webpack_require__.r(__webpack_exports__);
// 执行 ./src/module-commonjs.js 的代码 获取导出值
var A = __webpack_require__("./src/module-es6.js");
},
"./src/module-es6.js": function(module, __webpack_exports__, __webpack_require__) {
//给当前模块打上 `ES6`模块的标识符
__webpack_require__.r(__webpack_exports__);
// 设置 __webpack_exports__.moduleValue 的 getter
__webpack_require__.d(__webpack_exports__, "moduleValue", function() {
return moduleValue;z
});
__webpack_exports__["default"] = "ModuleDefaultValue";
let moduleValue = "moduleValue";
}
};按需加载的执行过程
看完上面的 require 和 import,我们回到 按需加载 这个执行过程. Webpack 的按需加载是通过 import() 或者 require.ensure()来实现的,有些读者可能对于 require.ensure 比较熟悉,所以我们先看看 require.ensure 的执行过程,
现在我们修改建立一个 module-dynamic.js文件,然后修改 index.js文件
// index.js
setTimeout(function() {
require.ensure([], function() {
let d = require("./module2")
});
}, 1000);
// module2.js
module.exports = {
name : "Jason"
}执行 require.ensure(dependencies,callback,errorCallback,chunkName) 实际上会返回一个 promise , 里面的实现逻辑是 先判断 dependencies 是否已经被加载过,如果加载过则取缓存值的 promise, 如果没有被加载过 则生成一个 promise 并将 promise 里面的 resolve,reject 和 promise本身 存入一个数组,然后缓存起来.接着生成一个 script 标签,填充完信息之后添加到HTML文件上,其中的 script 的 src属性 就是我们按需加载的文件(module2),Webpack 会对这个 script 标签监听 error 和 load时间,从而做相应的处理。
Webpack打包过程中会给 module2 添加一些代码,主要就是主动触发 window["webpackJsonp"].push这个函数,这个函数会传递
两个参数 文件ID 和 文件内容对象,其中 文件标示如果没有配置的话,会按载入序号自动增长,文件内容对象实际上就是上文说的 require.ensure第一个参数dependencies的文件内容,或者是 callback,errorCallback里面需要加载的文件,以 key(文件路径) --- value(文件内容)的形式出现.里面执行的事情其实就是执行上面创建的promise的resolve函数,让require.ensure里面的callback执行,之后的执行情况就跟我上面将 requir 和 import 一样了
当然其实讲了那么长的 require.ensure并没有什么用,因为这个函数已经被 import() 取代了,但是考虑到之前的版本应该有很多人都是用 require.ensure 方法去加载的,所以还是讲一下,而且其实 import 的执行过程跟 require.ensure 是一样的,只不过用了更友好的语法而已,所以关于 import 的执行流程我也没啥好讲的了,感兴趣的人看一下两者的 API介绍就好了。
到这里就正式讲完了,如果有大牛路过看到有不对的地方,希望能帮我指出来.非常谢谢!!!
然后再次感谢印记中文 团队翻译的 Webpack 文档