Webpack
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。
webpack 的作用是将源代码编译(构建、打包)成最终代码
简单理解,就是把一堆玩具零件组装成一个完整的玩具。 Webpack又是如何把这一堆零件组成一个完整的东西呢?
一、Webpack打包内容分析
Webpack会帮助我们把一堆js文件进行打包,那打包的结果是什么呢?它与我们写的js文件有什么不同?
- Webpack打包的结果肯定是js文件,这是不容置疑的。
Webpack打包后的js文件与我们写的js文件有什么不同
我们写的js文件如下
js
/* index.js */
const a = require('./a.js');
const b = require('./b.js');
console.log(a+b)
/* a.js */
module.exports = 20;
/* b.js */
module.exports = 7;
执行webpack
命令后,打包结果如下:(简化了Webpack的打包结果
)
js
(function (modules) {
var installedModules = {};
function require(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, require);
return module.exports;
}
return require("./src/index.js");
})
({
"./src/a.js":
(function (module, exports) {
module.exports = 20;
}),
"./src/b.js":
(function (module, exports) {
module.exports = 7;
}),
"./src/index.js":
(function (module, exports, require) {
const a = require('./src/a.js');
const b = require('./src/b.js');
console.log(a + b)
})
});
我们发现webpack打包后的结果本质是一个立即执行函数 ,该函数接收一个对象作为实参,使用modules
形参接收。
下面我们将逐步解析这个立即执行函数做了什么
1.先看modules
形参里有什么?
js
{
"./src/a.js": (function (module, exports) {
module.exports = 20;
}),
"./src/b.js": (function (module, exports) {
module.exports = 7;
}),
"./src/index.js": (function (module, exports, require) {
const a = require('./src/a.js');
const b = require('./src/b.js');
console.log(a + b)
})
}
这是一个对象,该对象的键为我们写的文件路径,值为一个函数,该函数内容为我们写的js代码(在Webpack中,我们写的js代码为做为字符串放在eval()函数中执行,这里方便理解,放在函数体中
)
2.然后我们看这一段代码,它把require
函数传入一个"./src/index.js"
实参调用后的结果作为返回值
js
15 return require("./src/index.js");
3.再看require
函数里面做了什么事
webpack打包结果中,
require
函数为__webpack__require
在这里为了简写
js
// 7-11行
var module = installedModules[moduleId] = {
exports: {}
};
// modules[moduleId]: 获取到对应模块的函数,首先是获取到的"./src/index.js"对应的函数
// 然后使用call来改变this指向,并且传入三个参数
modules[moduleId].call(module.exports, module, module.exports, require);
return module.exports;
以上代码就是把该模块的this指向module.exports这个对象,module
和module.exports
作为模块的导出,把require
这个函数作为模块的导入,最后把这个模块的导出结果作为返回值
- 如果我们导入了多个相同的模块 ,该模块的代码是不是要执行多次 ?所以使用了
installedModules
这个变量来保存模块的导出结果。
js
// 2行
var installedModules = {};
...
// 4-6行
// 判断该模块是否缓存了导出结果
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 7-11行
// 定义该模块的导出
var module = installedModules[moduleId] = {
exports: {}
};
以上代码就完成了对模块的导出内容的缓存。
- 当没有缓存时,就执行该模块代码,然后缓存。
- 当有缓存时,直接取出缓存并返回,无需执行该模块代码。
总结: Webpack打包后的本质就是一个立即执行函数,把我们需要的模块转换为key:value
的键值对保存在一个对象里,然后作为参数传入到立即执行函数。Webpack
写了一个require
函数和module
对象用来模拟CommonJS模块化完成功能的实现。
二、Webpack是如何工作的?
目录
- 初始化
- 编译
- 打包
1.初始化
初始化就是把命令行参数和webpack.config.js
文件内容的配置进行合并,命令行参数会覆盖配置文件参数。
2.编译
Webpack在编译的过程中其实就做了一件事,就是把依赖收集,形成一个
moudles
参数
- 依赖收集流程图
Webpack会根据一个入口文件index.js
进行依赖收集,首先,根据文件ID(文件ID就是文件路径)检查这个模块是否被收集,如果被收集就不做任何处理,没有被收集就读取文件内容,进行抽象语法树分析,检查该模块依赖了哪些其他模块,把这些依赖模块存放到数组里,然后把require
替换为__webpack__require
,再把这个模块就添加到chunk
里面。最后把依赖模块数组的每一项传入到入口文件中进行递归调用。
- modules(chunk)
把编译好的模块存在在这里面
3.打包
打包阶段其实就很简单了,先生成一个js文件,文件内容为一个立即执行函数,然后把chunk
生成一个对象,键为文件ID,值为一个函数,函数体里包裹着该模块代码,最后chunk
作为参数传入到立即执行函数里面,这个立即执行函数里面提供了一个变量和一个函数,变量用来存储每个模块的导出结果,函数用来模拟require
导入函数。
总结
在Webpack中,每个阶段还有很多事要做,比如编译阶段会对每个模块生成一个hash,然后对总的chunk也生成一个hash,我只是大致描述了一下Webpack的核心
三、关于loader和plugin
可能有人会问Webpack中最强大的loader和plugin在这里为什么没有提?
loader和plugin这些东西并不是Webpack的核心,它们只是一些工具和插件,没有它们Webpack照样能够运行,有了它们会增强Webpack某一方面的功能。
比如六味地黄丸,只是对某一方面增强,并不会影响本身。
完结
我是一个小菜,希望我们大家能够友好沟通,一起完善对Webpack
知识点的掌握。
也希望大佬们能够指出该文章的缺点和错误,还有您宝贵的意见,希望我们能够一起进步!
谢谢大家!