BooheeFE / weekly

📝 薄荷前端周刊 Boohee Front End Team Weekly
760 stars 40 forks source link

2018/11/20 - webpack原理与实践(二):实现一个webpack插件 #25

Open lance10030 opened 5 years ago

lance10030 commented 5 years ago

[TOC]

webpack原理与实践(二):实现一个webpack插件

关于 loaderplugin

  1. webpack 本身只能处理js文件。那如何处理如 css内联图像html 等这些文件了呢。这就需要用 loader 来进行转化。
  2. 通常 loader 功能比较单一,只专注于语言的转化。但是我们会有像压缩,分离文件这样的需求,这就需要通过插件来实现

实现一个插件

插件本身为一个构造函数,除了自己定义的方法外,会有一个 apply 方法 , apply 方法中传入全局唯一的 compiler 对象。

基本结构

class FileFilterPlugin {
    constructor(options){
        this.options = options;
    }
    apply(compiler) {

    }
}
  1. 插件 options 是你在使用插件的时候new 一个插件时传入的配置。通常做法是你可以有一些默认的配置,通过 Object.assign 来合并传入的配置和默认的配置,来得到最终的配置项。
  2. 第二个重点就是 apply 方法,该方法会传入一个 compile 对象。compile 对象和 compilation 对象是 webpack 打包过程中最重要的两个对象。他们都继承自 Tapable 关于,通过 Tapable 注入钩子进行流程管理。compile 对象每一次编译全局唯一,并且包含了compilation对象。一个 compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。compilation 对象也提供了很多事件回调供插件做扩展。在开发模式下,每次热更新都会生成一个新的compilation对象。

准备工作

前一篇文章中已经讲过 webpack 执行的一个流程了。我们需要知道插件应该挂载在执行过程中的哪个阶段,各个阶段都做了什么。这里就给大家贴几张总结的比较到位的图(来自《深入浅出webpack》):

image image image

然后你需要了解:

class Compiler extends Tapable {
    constructor(context) {
        super();
        this.hooks = {
            /** @type {SyncBailHook<Compilation>} */
            shouldEmit: new SyncBailHook(["compilation"]),
            /** @type {AsyncSeriesHook<Stats>} */
            done: new AsyncSeriesHook(["stats"]),
            /** @type {AsyncSeriesHook<>} */
            additionalPass: new AsyncSeriesHook([]),
            /** @type {AsyncSeriesHook<Compiler>} */
            beforeRun: new AsyncSeriesHook(["compiler"]),
            /** @type {AsyncSeriesHook<Compiler>} */
            run: new AsyncSeriesHook(["compiler"]),
            /** @type {AsyncSeriesHook<Compilation>} */
            emit: new AsyncSeriesHook(["compilation"]),
            /** @type {AsyncSeriesHook<Compilation>} */
            afterEmit: new AsyncSeriesHook(["compilation"]),

            /** @type {SyncHook<Compilation, CompilationParams>} */
            thisCompilation: new SyncHook(["compilation", "params"]),
            /** @type {SyncHook<Compilation, CompilationParams>} */
            compilation: new SyncHook(["compilation", "params"]),
            ......
            }

    }

}

在插件apply 方法的回调中我们可以传入 compilation 对象. 如果是异步, 通常还会传入一个回调函数,对资源进行处理.

例子

下面会根据实际开发中的问题举一个完整的例子.

首先是第一中解决方案.我们找到了一个暴露 webpack 钩子的插件: event-hooks-webpack-plugin 该插件通过传入调用的钩子名称和相应的回调函数,在回调函数中执行钩子对应阶段可以执行的操作.插件本身很强大,并且代码很简单,但是需要你了解webpack的原理才能使用,也就是有一定的门槛.我先把我们的用法粘贴出来:

new EventHooksPlugin({
  emit: (compilation) => {
    // compilation.chunks 存放所有代码块,是一个数组
    compilation.chunks.forEach(function(chunk) {
      // chunk 代表一个代码块
      chunk.files.forEach(function(filename) {
        // compilation.assets 存放当前所有即将输出的资源,是一个对象
        let regex = /\.scss$/;
        if (regex.test(filename)) {
          delete compilation.assets[filename];
        }
      });
    });
  }
})

这里我们调用的钩子是 emit 从上文粘贴的对该钩子的介绍我们知道这是最后一个可以改变输出文件的钩子.传入的回调中我们对 scss 文件做了删除处理.到这里我们的目标实现了.但是同时也在思考: 本身这个插件使用时有门槛的,我们能不能自己写一个简单的插件来替代他, 让配置变得简单,不用了解webpack内部的实现就能使用.于是我们自己写了一个插件:

module.exports = class FileFilerPlugin {
    constructor(options) {
        this.options = options;
    }

    apply(compiler) {
        compiler.hooks.emit.tap(' FileFilerPlugin', compilation => {
        // compilation.chunks 存放所有代码块,是一个数组
        compilation.chunks.forEach((chunk) => {
        // chunk 代表一个代码块
          chunk.files.forEach(function(filename) {
            // compilation.assets 存放当前所有即将输出的资源,是一个对象
            // let regex = /\.scss$/;
            let regex = this.options.deleteFileReg
            if (regex.test(filename)) {
              delete compilation.assets[filename];
            }
          });
        });
      })
    }
};

如上,我们只需要在配置中传入需要删除的文件的正则表达式就能实现目标:

 new FileFilterPlugin({
      deleteFileReg: /\.scss$/
    }),

这个插件的实际上就是把我们第一个插件的配置转移到了插件源码中进行实现.但是针对这个场景的使用就变得简单了很多.

参考

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~

heboliufengjie commented 4 years ago

有点简单了,继续