运行流程
webpack 本质是一个JS Module Bundler, 用于将多个代码模块进行打包。bundler 从一个构建入口触发, 解析代码,分析出代码依赖关系, 然后将依赖的代码模块组合在一起, 在JS bundler 中, 还需要一些胶水代码让多个代码模块可以协同工作, 相会引用。
// entry.js
import { bar } from './bar.js';
// bar.js
const foo = require('./foo.js');
// 递归下去, 直到没有更多的依赖模块, 最终形成一颗模块依赖树
分析出依赖关系后, webpack 会利用JS Function 的特性提供一些代码来将各个模块整合到一起.。即将每一个模块装成一个JS Function,提供一个引用依赖模块的方法。下面例子中的webpackrequier__, 这样做, 既可以避免变量相互干扰, 又能有效控制执行顺序。
// 分别将各个依赖模块的代码用 modules 的⽅式组织起来打包成⼀个⽂件
================================entry======================================
================================moudles======================================
// entry.js
modules["./entry.js"] = function () {
const { bar } = __webpack__require__("./bar.js");
};
// bar.js
modules["./bar.js"] = function () {
const foo = __webpack__require__("./foo.js");
};
// foo.js
modules["./foo.js"] = function () {
// ...
};
================================output===========================
// 已经执⾏的代码模块结果会保存在这里
const installedModules = {};
function __webpack__require__(id) {
// 如果 installedModules 中有就直接获取
// 没有的话从 modules 中获取 function 然后执⾏,
//将结果缓存在 installedModules 中然后返回结果
}
- Compiler webpack 的运行入口, compiler对象代表完整的webpack 环境配置。这个对象在启动webpack 时被一次建立, 并配置好所有可操作的设置, 包括options、loader和plugin。当在webpack 环境中应用一个插件时, 插件将收到此compiler 对象的引用, 可以使用它来访问webpack 的主环境/
- Compilation 对象代表了一次资源版本构建。 当运行webpack 开发环境中间件时, 每当检测到一个文件变化, 就会创建一个新的compilation, 从而生成一组新的编译资源。 一个compilation 对象表示当前的模块资源、编译生成环境、变化的文件以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键步骤的回调, 以供插件做自定义处理时选择使用。
- Chunk, 即表示chunk 的类, 对于构建时需要的chunk 对象有compilation 创建后保存管理。(webpack中最核心的负责编译的compiler 和负责创建bundle 的compilation都是Tapable的实例)
- Module(webpack/lib/module.js), 用于表示代码的基础类, 衍生出很多子类用于处理不同的情况(webpack/lib/normalModule.js)关于代码模块的所有信息都会存在module实例中, 例如dependencies 记录代码模块的依赖。
当一个module 实例被创建后, 比较重要的一步是执行compilation.buildModule 这个方法,它会调用module 实例的build方法来创建module 实例需要的一些东西, 然后自身的runloaders 方法。runloader: loader-runner(webpack/loader-runner), 执行对应的loaders, 将代码源码内容一一交由配置中指定的loader处理后, 再把处理的结果保存起来。
- Parser, 基于acorn 来分析 AST 语法树, 解析出代码模块的依赖。
- Dependency, 解析时用于保存代代码模块对应的依赖使用的对象。 module 实例的build 方法在执行对应的loader时, 处理完模块代码自身的转换后, 继续调用parser 的实例来解析自身依赖的模块, 解析后的结果放到module.dependencies 中, 首先保存的是依赖的路径, 后续会经由compilation.processModuleDependencies 方法, 再来处理各个依赖模块, 递归区建立整个依赖。
- Template, 生成最终代码要使用到的代码模版, 像上述提到的胶水代码久是用对应的Tempalte来生成。
Template 基础类: lib/Template.js
常用的主要Template类: lib/MainTemplate.js
(webpack编译)钩子函数调用执行顺序
手写webpack
核心使用Tapable 实现插件的binding 和 applying
静态资源文件指纹
解决: 静态资源实现增量更新。
- hash。和整个项⽬⽬的构建相关,只要项⽬⽬⽂⽂件有修改,整个项⽬⽬构建的hash 值就会更更改
- chunkhash。和 webpack 打包的 chunk 有关,不不同的 entry 会⽣⽣成不不同的chunkhash 值
- contenthash。根据⽂⽂件内容来定义 hash ,⽂⽂件内容不不变,则 contenthash不不变
watch :轮询判断⽂件的最后编辑时间是否变化,某个⽂件发⽣了变化,并不会⽴
⽴刻告诉监听者,⽽⽽是先缓存起来,等 aggregateTimeout
1. 文件入口
entry
if (/.+\/([a-zA-Z]+-[a-zA-Z]+)(\.entry\.js$)/g.test(item) == true) {
const entrykey = RegExp.$1
_entry[entrykey] = item;
const [dist, template] = entrykey.split(“-");
}
2. 你不知道的module
2.1 webpack 多核支持
const HappyPack = require("happypack");
const os = require("os");
//开辟⼀个线程池
const happyThreadPoll = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports.plugins = [
new HappyPack({
id: "babel",
threadPool: happyThreadPoll,
loaders: [
{
loader: "babel-loader",
},
],
}),
];
2.2 loader 的原理分析
2.3 手写loader
- use: [‘bar-loader’, ‘foo-loader’] 时, loader 是以相反的顺序执行。
- 最后的loader最早调用, 传入原始的资源内容(代码/二进制文件/buffer处理)。 第一个loader 最后调用, 期望返回是JS 代码和sourcemap 对象中间的loader执行时, 传入的是上一个 loader 执行的结果。
- 多个loader 时遵循这样的执行顺序, 但对于大多数单个loader来说无需感知这一点, 只负责处理好接受的内容。
- 当loader 中的异步处理。 有一些loader 在执行过程中可能依赖外部I/O 的结果, 导致必须使用使用异步的方式处理, 需要在loader 执行时使用this.async() 来标识该 loader 是异步处理, 然后使用this.callback返回loader 处理结果。
2.4 AST 静态语法分析树
AST(抽象语法树), 或者语法树, 是源代码的抽象语法结构的树形表现形式。树上的每个节点都表示源代码中的一种结构。抽象在于, 此处的语法并不表示真是语法中的出现的每个细节。
webpack 优势在于能将所有资源都整合成模块(不仅js 文件)。需要一些loader, 比如url-loader 等来直接在源文件中引用各类资源。最后调用acorn(Esprime)解析经loader处理后的源文件生成抽象语法树AST。
Node {
type: 'VariableDeclaration', // 描述该语句的类型 --变量声明语句
start: 0,
end: 10,
declarations: [Array], // 声明的内容数组, 每一项也是一个对象
type: // 描述该无语的类型
id: // 描述变量名称的对象
type: // 定义
name: // 变量的名字
init: // 初始化变量值的对象
type: // 类型
vale: // 值“is tree” 不带引导
row: // "\"is tree"\" 带引号
kind: 'var' // 变量声明的关关键字 --var
}
var UglifyJs = require('uglify-js')
var code = 'var a = 1;';
var toplevel = UglifyJs.parse(code); // 语法树
var transformer = new UglifyJs.TreeTransformer(function (node) {
if (node instanceof UglifyJs.AST_Number) { // 查找需要修改的叶子节点
node.value = '0x'+Number(node.value).toString(16);
return node; // 返回一个新的叶子节点 替换原叶子节点
}
});
toplevel.transform(transformer);
var ncode = toplevel.print_to_string();
console.log(ncode); // var a=0x1;
3. Plugins
webpack实现插件机制的⼤体⽅式是:
「创建」 —— webpack在其内部对象上创建各种钩⼦;
「注册」 —— 插件将⾃⼰的⽅法注册到对应钩⼦上,交给webpack;
「调⽤」 —— webpack编译过程中,会适时地触发相应钩⼦,因此也就触发了插件的⽅法
loader
loader-utils
acorn
acorn-walk
magic-string
plugin
参考:
https://aotu.io/notes/2020/07/17/webpack-analize/index.html