初识 webpack 原理——自定义插件

时间:2022-07-25
本文章向大家介绍初识 webpack 原理——自定义插件,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

背景

接到一个关于 webpack 的有意思需求——整理各个组件之间的依赖关系表

简单来讲,如下图,如果组件 B 或者 组件 C 更新了,那么我们就需要提示组件 A 去做更新

初识 webpack 原理

webpack 的核心概念

entry: webpack 的编译入口•module: 模块,在 webpack 中,一切皆为模块,一个模块对应一个文件Chunk: 代码块,一个 chunk 由多个模块组合而成,用于代码的合并与分割•Loader: 模块转换器,可以当做是一个翻译机器,可以将一些内容转换成新的内容,以便我们的浏览器能够识别•Plugin: 扩展插件。webpack 运行的各个阶段,都会广播出去相对应的事件,插件可以监听到这些事件的发生,在特定的时机做相对应的事情

注意加粗的部分,我们之后就会用到

流程概括

如图所示,我们可以看到 webpack 的整一个编译流程。在这个过程中,我们上面提到的各个核心概念都发挥着重要的作用

entry 帮忙确认入口•loader 帮忙将模块 module 加工•最后输出资源的时候,根据入口(entry)和模块 (module) 的关系,组装成包含多个模块的 chunk,每个 chunk 实际上对应输出的一个文件

那么在大家都那么忙碌的时候, plugin 在做什么呢?

首先看开始编译的时候,webpack 会用上一步初始化得到的参数用来初始化 Compiler 对象(这是一个很重要的概念,我们稍后会讲),同时会加载所有配置的插件,并执行对象中的 run 方法开始执行编译

上面我们提到,webpack 会在特定的时间点广播事件。在开始编译之后,plugin 就像一个机动员工,哪里需要就往哪里跑。它可以监听特定的事件然后处理特定的逻辑,并且 plugin 可以调用 webpackAPI 改变 webpack 的运行结果

对于我们平时使用 webpack 而言,webpack 就是一个黑盒子,我们不用关注于它内部的事情,但它也提供了 plugin 作为桥梁,开发者可以很好的运用 plugin 去修改 webpack 的编译结果,不得不感叹一下它的设计巧妙

各个阶段暴露的事件

这里不详细列出所有的事件,感兴趣的可以直接前往官网查看

注:官网文档称之为 Compiler HooksCompilation Hooks,翻译过来就是 compilation 钩子和 Compilation 钩子

我们看看我们这次需求需要用到的事件,在上面的流程图中,在输出资源到输出完成是修改资源的绝佳时机,这个时候 webpack 会广播一个 emit 事件,代表确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容

编写一个 plugin

以上,我们已经找到合适的时机并可以获取到相关的资源文件,现在就是要怎么利用好信息,完成我们的组件关系依赖图了

自定义一个 plugin 实际上很简单,基础的如下所示:

class BasicPlugin{
  // 在构造函数中获取用户给该插件传入的配置
  constructor(options){
  }

  // Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象
  apply(compiler){
    compiler.plugin('compilation',function(compilation) {
    })
  }
}

// 导出 Plugin
module.exports = BasicPlugin;

使用的时候:

const BasicPlugin = require('./BasicPlugin.js');
module.export = {
  plugins:[
    new BasicPlugin(options),
  ]
}

webpack 启动后,它会去执行我们配置的 new BasicPlugin(options) 初始化一个插件实例。在初始化 compiler 对象之后,会调用 basicPlugin .apply(compiler) 方法将 compiler 传入,插件获得 compiler 对象后,就可以通过 compiler.plugin('事件名', 回调函数) 的方式进行监听 webpack 广播出来的事件了

举个例子,目前的项目是 demo 项目是直接使用 vue 搭建,我现在定义了一个 HelloWorldPlugin 如下:

class HelloWorldPlugin {
  constructor (options) {
    console.log(options)
  }
  apply (compiler) {
    console.log(`Hello World`)
    // compilation('编译器'对'编译ing'这个事件的监听)
    compiler.plugin('compile', function () {
      console.log(`The compiler is starting to compile...-----`)
    })
    // compilation('编译器'对'编译ing'这个事件的监听)
    compiler.plugin('compilation', function (compilation) {
      console.log(`The compiler is starting a new compilation...-----`)
      compilation.plugin('optimize', function () {
        console.log('The compilation is starting to optimize files...')
      })
    })
    compiler.plugin('done', function () {
      console.log(`done......`)
    })
  }
}

module.exports = HelloWorldPlugin

webpack.prod.conf.js 中,我们引入插件

const HelloWorldPlugin = require('./plugin/helloPlugin')

并在 plugins 配置中加入配置

new HelloWorldPlugin({
      name: 'helloPlugin',
      des: '我是一段配置'
})

执行 npm run build

可以看到,我们可以监听到 webpack 的各个阶段了。

两个重要对象——compiler 和 compilation

compiler 对象包含 webpack 所有的配置信息,包括 optionspluginsloader等等,这个对象在 webpack 启动的时候被初始化,是全局唯一的,我们可以理解成它是 webpack 实例•compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等等。当 webpack 以开发模式运行时,每一个文件变化,一个新的 compilation 就会被创建

两者的区别在于:Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译

回到最初的问题

其实我们这次是不需要修改到 webpack 的结果的,我们只需要获得最后每个 chunk 中包含哪些文件路径即可,获取之后,具体要怎么操作,跟具体的业务相关,这里就不方便细聊了,以下是 demo 示例:

compiler.plugin('emit', function (compilation, callback) {
  // compilation.chunks 存放所有代码块,是一个数组
  compilation.chunks.forEach(function (chunk) {
    // chunk 代表一个代码块
    // 代码块由多个模块组成,通过 chunk.forEachModule 能读取组成代码块的每个模块
    chunk.forEachModule(function (module) {
      // module 代表一个模块
      // module.fileDependencies 存放当前模块的所有依赖的文件路径,是一个数组
      module.fileDependencies.forEach(function (filepath) {
        console.log(filepath)
      })
    })
  })
  // 这是一个异步事件,要记得调用 callback 通知 Webpack 本次事件监听处理结束。
  // 如果忘记了调用 callback,Webpack 将一直卡在这里而不会往后执行。
  callback()
})

参考

https://www.webpackjs.com/api/compiler-hooks/

https://www.webpackjs.com/api/compilation-hooks/

https://webpack.wuhaolin.cn/5%E5%8E%9F%E7%90%86/5-1%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E6%A6%82%E6%8B%AC.html

https://webpack.wuhaolin.cn/5%E5%8E%9F%E7%90%86/5-4%E7%BC%96%E5%86%99Plugin.html