# 配置
# Entry
想让源文件加入到构建流程中去被 Webpack 控制,配置 entry
# Output
想自定义输出文件的位置和名称,配置 output
。
# Module
配置 loader 的 解析 js css 等
想自定义解析和转换文件的策略,配置 module
,通常是配置 module.rules
里的 Loader。
# Reslove
想要自定义寻找依赖模块的策略 (找 npm 包的)
# Plubin
其他操作
# 基本概念
在了解 Webpack 原理前,需要掌握以下几个核心概念,以方便后面的理解:
- Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
- Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
# 流程
- 初始化参数 :通过配置文件和 shell 完成初始化参数
- 开始编译 : 用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
- 确定入口 : 找到 enter 的所有入口
- 编译模块 : 从入口文件开始 调用 loader 来进行编译,然后找到文件引用的文件,递归调用,直到所有入口文件引用的文件处理过
- 完成模块编译 :得到每个模块的依赖关系
- 输出资源 :根据依赖关系,把每个模块生成对应的 chunk,再把每个 chunk 生成单独的文件添加到输出列表,通过一个立即执行函数接受一个数组 数组中都是函数 来模拟 require 执行第 0 个元素(就是 main.js) 来完成输出
- 完成输出资源 :根据配置输出到对应的位置和名称
# 流程细节
构建分为三大阶段
- 初始化 :启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
- 编译 :从 enter 出发,根据 Module 串行调用对应的 Loader 去翻译文件内容,然后再找出 Module 依赖的 Module,递归编译
- 输出 :编译后的 module 组合成 chunk 把 chunk 转化成文件,输出
如果是一次流程 走到输出就结束了 但是如果监听模式,就会重复编译,如下
# loader 实现
在 Webpack 中,loader 是用于转换模块文件的功能。Loader 本质上是一个函数,它接收源文件作为输入,进行处理后返回结果。通过 loader,Webpack 可以处理 JavaScript 以外的其他类型的文件,例如 CSS、图片、以及现代 JavaScript 特性(如 ES6、TypeScript)。
- 创建 loader 文件:编写一个 JavaScript 文件,实现 loader 的逻辑。
- 配置 Webpack:在 Webpack 配置文件中添加自定义 loader。
- 运行 Webpack:确保 loader 被应用到指定的文件上。
// uppercase-loader.js
module.exports = function (source) {
// `source` 是文件的内容
if (typeof source === "string") {
return source.toUpperCase();
}
return source;
};
# plugin 实现
在 Webpack 中,插件(plugin)是用来扩展其功能的机制。插件可以处理更多复杂的任务,例如优化打包文件、注入环境变量、甚至生成 HTML 文件。与 loader 不同,loader 主要是处理单个文件的转换,而插件则是处理整个构建过程的各个环节。
- 创建插件类:编写一个 JavaScript 类,实现插件的逻辑。
- 配置 Webpack:在 Webpack 配置文件中使用自定义插件。
- 运行 Webpack:确保插件被正确应用。
在不同的 hook 中插入逻辑
// MyPlugin.js
class MyPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
// 在编译开始时执行
compiler.hooks.compile.tap("MyPlugin", () => {
console.log("编译开始");
});
// 在编译完成时执行
compiler.hooks.done.tap("MyPlugin", (stats) => {
console.log("编译完成!");
console.log("Options:", this.options);
});
// 在生成资源时执行
compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => {
const assetNames = Object.keys(compilation.assets);
console.log("生成的资源:", assetNames);
// 可以修改资源或生成新的资源
const content = "Hello, this is a new file!";
compilation.assets["new-file.txt"] = {
source: () => content,
size: () => content.length,
};
callback();
});
}
}
module.exports = MyPlugin;