自己实现Webpack 的 Loader 和 Plugin

194 阅读6分钟

✊不积跬步,无以至千里;不积小流,无以成江海

由于之前实习的时候,最“有趣”的一个业务是自己编写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的定义以及使用规则

Writing a Plugin | webpack

自己实现

自己实现loader

在编写 loader 时,理解其本质至关重要。loader 实际上是一个函数,其中 this 的上下文由 webpack 提供,因此不能使用箭头函数。

这个函数接收一个参数,表示 webpack 传递给 loader 的文件内容。this 对象由 webpack 提供,使得 loader 可以访问所需的各种信息。

loader 支持同步和异步操作。对于异步操作,可以通过 this.callback 返回结果,返回值要求是 stringBuffer 类型。

代码如下所示:

    // 导出一个函数,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-loadercss-loaderstyle-loader 等多个 loader 的链式调用来实现的。

自己实现plugin

Webpack 在编译过程中会生成两个关键对象:

  • 编译器(Compiler) :这个对象囊括了整个 Webpack 环境的配置细节,包括加载器(loader)、插件(plugin)以及各种选项。此外,它还管理着 Webpack 整个生命周期中的钩子,这些钩子会在编译的不同阶段触发。
  • 编译(Compilation) :每次有文件发生变化,Webpack 都会创建一个新的编译对象。这个对象包含了当前模块的资源、生成的编译输出、发生变化的文件以及依赖关系的追踪信息。编译对象通常作为插件的回调函数参数传递,插件可以利用这些信息来对编译过程进行定制。

自定义插件的开发规范

如果你想开发自己的 Webpack 插件,需要遵循以下规则:

  • 插件的定义:插件必须是一个函数或者具有 apply 方法的对象。通过 apply 方法,插件可以访问到编译器实例,从而对编译过程进行干预。
  • 对象引用:传递给每个插件的编译器和编译对象都是同一个实例。为了避免冲突,不建议直接修改这些对象。
  • 异步操作:如果插件涉及异步操作,一定要在任务完成后调用回调函数通知 Webpack,否则编译过程会卡住。

简而言之,编译器是 Webpack 的配置中心,而编译对象则代表了一次具体的编译过程。插件通过这两个对象可以对 Webpack 的行为进行定制。在开发插件时,需要注意插件的定义方式、对象引用的处理以及异步操作的处理。

用过哪些

以下是 20 个常用的 Webpack loader:

  1. babel-loader:将 ES6+ 代码转换为向后兼容的 JavaScript 代码。
  2. ts-loader:用于加载和编译 TypeScript 代码。
  3. css-loader:解析 CSS 文件,支持模块化导入 CSS。
  4. style-loader:将 CSS 插入到页面中,通常与 css-loader 一起使用。
  5. sass-loader:处理 Sass/SCSS 文件,将其编译为 CSS。
  6. less-loader:处理 Less 文件,编译为 CSS。
  7. postcss-loader:对 CSS 进行后处理,例如添加浏览器前缀等。
  8. file-loader:处理文件的导入,将文件复制到输出目录,并返回文件的 URL。
  9. url-loader:类似于 file-loader,但可以在文件较小的时候将其转为 DataURL。
  10. html-loader:处理 HTML 文件中的资源引用,例如图片和脚本。
  11. mini-css-extract-plugin.loader:提取 CSS 到单独的文件中,而不是使用 style-loader 插入到页面中。
  12. image-webpack-loader:对图片进行优化处理。
  13. eslint-loader:在构建过程中进行代码检查,确保代码符合 ESLint 规则。
  14. terser-webpack-plugin.loader:对 JavaScript 代码进行压缩。
  15. vue-loader:用于处理 Vue.js 单文件组件。
  16. pug-loader:处理 Pug 模板文件,编译为 HTML。
  17. handlebars-loader:处理 Handlebars 模板文件。
  18. raw-loader:将文件作为字符串导入。
  19. json-loader:加载 JSON 文件。
  20. worker-loader:用于加载 Web Worker 文件。

以下是 20 个常用的 Webpack 插件:

  1. HtmlWebpackPlugin:生成 HTML 文件,并自动引入打包后的资源。
  2. MiniCssExtractPlugin:提取 CSS 到单独的文件中。
  3. CleanWebpackPlugin:在每次构建前清理输出目录。
  4. CopyWebpackPlugin:复制静态文件到输出目录。
  5. DefinePlugin:定义全局变量。
  6. ProvidePlugin:自动加载模块,为每个模块注入变量。
  7. UglifyJsPlugin(已逐渐被 terser-webpack-plugin 替代):压缩 JavaScript 代码。
  8. terser-webpack-plugin:现代的 JavaScript 代码压缩插件。
  9. OptimizeCSSAssetsPlugin:压缩 CSS 资源。
  10. BundleAnalyzerPlugin:分析打包后的文件大小和组成。
  11. HotModuleReplacementPlugin:实现热模块替换。
  12. CaseSensitivePathsPlugin:确保文件路径区分大小写。
  13. WebpackManifestPlugin:生成资源清单文件。
  14. IgnorePlugin:忽略特定的模块或文件。
  15. NamedModulesPlugin:显示模块的相对路径,方便调试。
  16. NamedChunksPlugin:为 chunk 分配可读的名称。
  17. CompressionPlugin:对输出文件进行压缩,例如生成.gz 文件。
  18. ProgressPlugin:显示构建进度。
  19. WebpackBar:提供美观的构建进度条。
  20. EnvironmentPlugin:设置环境变量。