最近由于一篇分享手淘过年项目中采用到的前端技术的影响,重新研究了一下项目中CSS的架构
.本来打算写一篇文章,但是写到一半突然发现自己像在写文档介绍一样,所以后来就放弃了。但是觉得过程中研究的 Webpack
倒是可以单独拿出来讲一讲
在这里非常感谢印记中文 团队翻译的 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.js
webpack 中 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
文件
这里吐槽一个问题,require.ensure 第一个参数是一个尴尬的存在,写和不写根本没差,如果你填了的这个参数,webpack 会帮你把文件加载近来,但是不执行。一堆不执行的代码是没有意义的,你想让他执行就必须 require() 一遍,但是执行 require 也会帮你加载文件。所以根本没差,但是里面可能涉及到模块加载顺序的问题,这块我没深入研究了,因为 require.ensure() 使用的场景越来越小了
// 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 文档
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。