✊不积跬步,无以至千里;不积小流,无以成江海
由于之前实习的时候,最“有趣”的一个业务是自己编写manifest.json来描述应用清单从而能够打开相应格式的文件,其实约等于一个plugin。了解到面试中对源码也会有一些考察,就萌生了一些想法能否自己写一些源码这一类的小项目。做了一些survey之后发现webpack的loader和之前写的manifest文件有一定的相似程度,就决定了写loader/plugin这一个诉求。因此有了这个小项目:自己实现webpack的loader和plugin。
loader的原理是什么
loader相当于对模块的源代码进行转换,在使用import或“加载”模块时预处理文件。
webpack中关于loader的定义
loader | webpack 中文文档 | webpack中文文档 | webpack中文网 (webpackjs.com)
webpack中关于如何编写loader
编写 loader | webpack 中文文档 | webpack中文文档 | webpack中文网 (webpackjs.com)
实现方法:
实现方法1: 获取当前文件的绝对路径,当文件中某一部分符合命名要求时,执行use。
test: /.js$/,
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {
/* ... */
},
},
],
方法2: 利用多个目录搜索loaders,并进行匹配。
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')],
},
plugin的原理是什么
在开发插件时,最重要的两个资源是"编译器"(complier)和“一次编译”(compilation)。了解他们的角色是扩展 Webpack 引擎的重要第一步。
举例来说,如果我开车上班,那么车就是complier;而开车上班这件事就是complication。车的各种配置,型号,性能等等就是complier的细节。开车时候遇见的事,遇到的人,这些发生的情况就是complication。
webpack中关于plugin的定义以及使用规则
自己实现
自己实现loader
在编写 loader 时,理解其本质至关重要。loader 实际上是一个函数,其中 this 的上下文由 webpack 提供,因此不能使用箭头函数。
这个函数接收一个参数,表示 webpack 传递给 loader 的文件内容。this 对象由 webpack 提供,使得 loader 可以访问所需的各种信息。
loader 支持同步和异步操作。对于异步操作,可以通过 this.callback 返回结果,返回值要求是 string 或 Buffer 类型。
代码如下所示:
// 导出一个函数,source为webpack传递给loader的文件源内容
module.exports = function(source) {
const content = doSomeThing2JsString(source);
// 如果 loader 配置了 options 对象,那么this.query将指向 options
const options = this.query;
// 可以用作解析其他模块路径的上下文
console.log('this.context');
/*
* this.callback 参数:
* error:Error | null,当 loader 出错时向外抛出一个 error
* content:String | Buffer,经过 loader 编译后需要导出的内容
* sourceMap:为方便调试生成的编译后内容的 source map
* ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
*/
this.callback(null, content); // 异步
return content; // 同步
}
在编写 loader 时,建议保持功能的专一性。比如,将 less 文件转换为 css 文件并不是一个简单的过程,而是通过 less-loader、css-loader 和 style-loader 等多个 loader 的链式调用来实现的。
自己实现plugin
Webpack 在编译过程中会生成两个关键对象:
- 编译器(Compiler) :这个对象囊括了整个 Webpack 环境的配置细节,包括加载器(loader)、插件(plugin)以及各种选项。此外,它还管理着 Webpack 整个生命周期中的钩子,这些钩子会在编译的不同阶段触发。
- 编译(Compilation) :每次有文件发生变化,Webpack 都会创建一个新的编译对象。这个对象包含了当前模块的资源、生成的编译输出、发生变化的文件以及依赖关系的追踪信息。编译对象通常作为插件的回调函数参数传递,插件可以利用这些信息来对编译过程进行定制。
自定义插件的开发规范
如果你想开发自己的 Webpack 插件,需要遵循以下规则:
- 插件的定义:插件必须是一个函数或者具有
apply方法的对象。通过apply方法,插件可以访问到编译器实例,从而对编译过程进行干预。 - 对象引用:传递给每个插件的编译器和编译对象都是同一个实例。为了避免冲突,不建议直接修改这些对象。
- 异步操作:如果插件涉及异步操作,一定要在任务完成后调用回调函数通知 Webpack,否则编译过程会卡住。
简而言之,编译器是 Webpack 的配置中心,而编译对象则代表了一次具体的编译过程。插件通过这两个对象可以对 Webpack 的行为进行定制。在开发插件时,需要注意插件的定义方式、对象引用的处理以及异步操作的处理。
用过哪些
以下是 20 个常用的 Webpack loader:
babel-loader:将 ES6+ 代码转换为向后兼容的 JavaScript 代码。ts-loader:用于加载和编译 TypeScript 代码。css-loader:解析 CSS 文件,支持模块化导入 CSS。style-loader:将 CSS 插入到页面中,通常与css-loader一起使用。sass-loader:处理 Sass/SCSS 文件,将其编译为 CSS。less-loader:处理 Less 文件,编译为 CSS。postcss-loader:对 CSS 进行后处理,例如添加浏览器前缀等。file-loader:处理文件的导入,将文件复制到输出目录,并返回文件的 URL。url-loader:类似于file-loader,但可以在文件较小的时候将其转为 DataURL。html-loader:处理 HTML 文件中的资源引用,例如图片和脚本。mini-css-extract-plugin.loader:提取 CSS 到单独的文件中,而不是使用style-loader插入到页面中。image-webpack-loader:对图片进行优化处理。eslint-loader:在构建过程中进行代码检查,确保代码符合 ESLint 规则。terser-webpack-plugin.loader:对 JavaScript 代码进行压缩。vue-loader:用于处理 Vue.js 单文件组件。pug-loader:处理 Pug 模板文件,编译为 HTML。handlebars-loader:处理 Handlebars 模板文件。raw-loader:将文件作为字符串导入。json-loader:加载 JSON 文件。worker-loader:用于加载 Web Worker 文件。
以下是 20 个常用的 Webpack 插件:
HtmlWebpackPlugin:生成 HTML 文件,并自动引入打包后的资源。MiniCssExtractPlugin:提取 CSS 到单独的文件中。CleanWebpackPlugin:在每次构建前清理输出目录。CopyWebpackPlugin:复制静态文件到输出目录。DefinePlugin:定义全局变量。ProvidePlugin:自动加载模块,为每个模块注入变量。UglifyJsPlugin(已逐渐被terser-webpack-plugin替代):压缩 JavaScript 代码。terser-webpack-plugin:现代的 JavaScript 代码压缩插件。OptimizeCSSAssetsPlugin:压缩 CSS 资源。BundleAnalyzerPlugin:分析打包后的文件大小和组成。HotModuleReplacementPlugin:实现热模块替换。CaseSensitivePathsPlugin:确保文件路径区分大小写。WebpackManifestPlugin:生成资源清单文件。IgnorePlugin:忽略特定的模块或文件。NamedModulesPlugin:显示模块的相对路径,方便调试。NamedChunksPlugin:为 chunk 分配可读的名称。CompressionPlugin:对输出文件进行压缩,例如生成.gz 文件。ProgressPlugin:显示构建进度。WebpackBar:提供美观的构建进度条。EnvironmentPlugin:设置环境变量。