Samgao0312 / Blog

MIT License
1 stars 1 forks source link

【积点成势】重学Webpack (2) —— Loader与Plugin #149

Open Samgao0312 opened 2 years ago

Samgao0312 commented 2 years ago

1、Loader (加载器)

1.1 什么是 Loader

Loader本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

默认情况下,在遇到import或者load加载模块的时候,webpack只支持对js文件打包。像css、sass、png等这些类型的文件的时候,webpack则无能为力,这时候就需要配置对应的loader进行文件内容的解析。在加载模块的时候,执行顺序如,如下图所示。 image

关于配置Loader的方式,有常见的三种方式: - 配置方式(推荐):在 webpack.config.js文件中指定 loader - 内联方式:在每个 import 语句中显式指定 loader - Cli 方式:在 shell 命令中指定它们

配置方式

关于Loader的配置,我们通常是写在 module.rules 属性中,属性介绍如下:

下面是一个module.rules的示例代码:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
          { loader: 'sass-loader' }
        ]
      }
    ]
  }
};

1.2 Loader特性

从上述代码可以看到,在处理 css 模块的时候,use属性中配置了三个 loader 分别处理 css 文件。因为 loader 支持链式调用,链中的每个 loader 会处理之前已处理过的资源,最终变为 js 代码。 顺序为相反的顺序执行,即上述执行方式为sass-loader、css-loader、style-loader。

除此之外,loader的特性还有如下:

可以通过 loader 的预处理函数,为 JS 生态系统提供更多能力。用户现在可以更加灵活地引入细粒度逻辑,例如:压缩、打包、语言翻译和更多其他特性。

1.3 webpack中常见的loader

在页面开发过程中,除了需要导入一些场景 JS 文件外,还需要配置响应的 loader 进行加载。Webpack常见的Loader如下:

下面以css-loader为例子,来说明Loader的使用过程。首先,我们在项目中安装css-loader插件。

npm install --save-dev css-loader

然后将规则配置到module.rules中,比如:

rules: [
  ...,
 {
  test: /\.css$/,
    use: {
      loader: "css-loader",
      options: {
     // 启用/禁用 url() 处理
     url: true,
     // 启用/禁用 @import 处理
     import: true,
        // 启用/禁用 Sourcemap
        sourceMap: false
      }
    }
 }
]


2、Plugin(插件)

2.1 什么是plugin

Plugin就是插件,基于事件流框架Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

Webpack中的 Plugin 也是如此,Plugin赋予其各种灵活的功能,例如:打包优化、资源管理、环境变量注入等,它们会运行在 webpack 的不同阶段(钩子 / 生命周期),贯穿了 Webpack 整个编译周期。 image

使用的时候,通过配置文件导出对象中 plugins 属性传入 new 实例对象即可。

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 访问内置的插件
module.exports = {
  ...
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};

2.2 特性

Plugin从本质上来说,就是一个具有 apply 方法 JS 对象。apply 方法会被 webpack compiler 调用,并且在整个编译生命周期都可以访问 compiler 对象。

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log('webpack 构建过程开始!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

我们可以使用上面的方式,来获取 Plugin 在编译生命周期钩子。如下:

2.3 常见的Plugin

Weebpack中,常见的plugin有如下一些:

下面通过clean-webpack-plugin来看一下插件的使用方法。首先,需要安装clean-webpack-plugin插件。

npm install --save-dev clean-webpack-plugin

然后,引入插件即可使用。

const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
 ...
  plugins: [
    ...,
    new CleanWebpackPlugin(),
    ...
  ]
}

3、Loader 和 Plugin 的区别

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 JS,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Plugin 就是插件,基于事件流框架Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

4、如何自定义 Loader 和 Plugin

4.1 自定义 Loader

我们知道,Loader本质上来说就是一个函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader 设为一个箭头函数。该函数接受一个参数,为 webpack 传递给 loader 的文件源内容。

同时,函数中 this 是由 webpack 提供的对象,能够获取当前 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; // 同步
}

4.2 自定义 Plugin

Webpack的Plugin是基于事件流框架Tapable,由于webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务。

同时,Webpack编译会创建两个核心对象:compiler和compilation。

如果需要自定义Plugin,也需要遵循一定的规范: - 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问compiler实例 - 传给每个插件的 compiler 和 compilation 对象都是同一个引用,因此不建议修改 - 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住

下面是一个自定Plugin的模板:

class MyPlugin {
    // Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象
  apply (compiler) {
    // 找到合适的事件钩子,实现自己的插件功能
    compiler.hooks.emit.tap('MyPlugin', compilation => {
        // compilation: 当前打包构建流程的上下文
        console.log(compilation);

        // do something...
    })
  }
}

在 emit 事件被触发后,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内容。