webpack 笔记

一、自定义插件

{
  apply(compiler) {
    compiler.hooks.emit.tap('ReplaceRequires', (compilation) => {
      const replaceMap = {
        'vue': 'vue',
        'vue-router': 'VueRouter',
        'vuex': 'Vuex',
        'axios': 'axios',
        'lodash': '_',
        'element-ui': 'ELEMENT',
        'moment': 'moment'
      }
      
      for (const filename in compilation.assets) {
        if (filename.endsWith('.js')) {
          let source = compilation.assets[filename].source()
          
          Object.entries(replaceMap).forEach(([pkg, variable]) => {
            const regex1 = new RegExp(`require\\("${pkg}"\\)`, 'g')
            const regex2 = new RegExp(`require\\('${pkg}'\\)`, 'g')
            source = source.replace(regex1, variable)
            source = source.replace(regex2, variable)
          })
          
          compilation.assets[filename] = {
            source: () => source,
            size: () => source.length
          }
        }
      }
    })
  }
}

1. apply 方法的作用

基本作用

apply 方法是 webpack 插件的入口点。当 webpack 启动时,会调用所有插件的 apply 方法。

执行时机

时机: webpack 启动阶段,在读取配置后、开始编译前
调用者: webpack 编译器(compiler)
参数: webpack 的 compiler 实例

代码示例

class MyPlugin {
  apply(compiler) {
    // 在这里注册各种钩子
    console.log('插件被应用了!')
  }
}

// webpack 内部大致这样调用:
const compiler = new Compiler()
plugins.forEach(plugin => {
  plugin.apply(compiler)  // 这里调用 apply 方法
})

2. compiler.hooks.emit.tap 的作用

Compiler Hooks 系统

webpack 使用 Tapable 库实现钩子系统,整个构建过程被分为多个阶段:

初始化 → 编译 → 生成资源 → 输出 → 完成

emit 钩子的位置

environment → afterEnvironment → beforeRun → run → beforeCompile → compile → 
thisCompilation → compilation → make → afterCompile → 
shouldEmit → emit → afterEmit → done

emit 钩子的特点

时机: 在资源生成后,写入文件系统之前
参数: compilation 对象(包含所有编译资源)
用途: 修改最终输出的文件内容

执行流程

compiler.hooks.emit.tap('插件名称', (compilation) => {
  // 这个回调函数在 emit 阶段执行
  // compilation.assets 包含所有要输出的文件
})

3. 完整的执行时序

// 1. webpack 启动
const webpack = require('webpack')
const config = require('./webpack.config.js')

// 2. 创建 compiler
const compiler = webpack(config)

// 3. webpack 内部调用插件的 apply 方法
//    ↓↓↓ 执行 apply(compiler) ↓↓↓

// 4. webpack 开始编译流程
compiler.run((err, stats) => {
  // 编译完成后的回调
})

// 编译过程中的钩子调用顺序:
// beforeRun → run → beforeCompile → compile → 
// 编译模块... → 
// afterCompile → shouldEmit → 
// ↓↓↓ 执行 emit 钩子 ↓↓↓
// emit → afterEmit → done

4. compilation.assets 结构

compilation.assets 是一个对象,包含所有要输出的文件:

在新版 webpack 中,Compilation.assets 在 emit 阶段已经被冻结,不能直接修改。如果想要修改需要在更早的钩子中进行资源处理。eg:compiler.hooks.compilation.tap("ReplaceRequiresPlugin"

{
  'dist/my-component.js': {
    source: function() { return '生成的JS代码...' },
    size: function() { return 1024 }
  },
  'dist/my-component.css': {
    source: function() { return '生成的CSS代码...' },
    size: function() { return 512 }
  }
}

5.其他相关钩子

apply(compiler) {
  // 编译开始时
  compiler.hooks.compile.tap('MyPlugin', (params) => {
    console.log('开始编译')
  })
  
  // 编译创建时
  compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
    console.log('创建新的编译')
  })
  
  // 生成资源时(推荐使用这个)
  compiler.hooks.emit.tap('MyPlugin', (compilation) => {
    console.log('准备生成文件')
  })
  
  // 文件写入后
  compiler.hooks.afterEmit.tap('MyPlugin', (compilation) => {
    console.log('文件已写入')
  })
}

二、webpack compiler 和 compilation区别

1. Compiler(编译器)

定义:
compiler 是 Webpack 的全局编译器对象,代表了整个 Webpack 编译过程的生命周期。它在 Webpack 启动时创建,且全局唯一,直到编译结束才会被销毁。

作用:

  • 保存了 Webpack 的配置信息(如 entry、output、module、plugins 等)。
  • 提供了 Webpack 生命周期中所有钩子函数(如 entryOption、run、compile、emit、done 等),用于在编译的不同阶段执行自定义逻辑。
  • 负责统筹整个编译流程,触发各个阶段的钩子,并创建 compilation 对象。

获取方式:

  • 在 Webpack 插件中,通过 apply(compiler) 方法的参数获取。
  • 在配置文件的 plugins 中,自定义插件函数的 this 指向 compiler(如前文示例中的 this.hooks.emit)。

2. Compilation(编译实例)

定义:
compilation 是单次编译过程的实例对象,代表了一次具体的编译过程(从入口文件解析到输出资源生成的完整流程)。

作用:

  • 包含了当前编译的所有资源信息(如模块 modules、依赖 chunks、输出资源 assets 等)。
  • 提供了与单次编译相关的钩子(如 optimize、chunkAsset、moduleAsset 等),用于在本次编译中修改资源、处理模块依赖等。
  • 负责具体的编译工作:解析模块、处理依赖、优化代码、生成输出文件等。

特点:

  • 并非全局唯一:每次触发编译(如开发模式下修改文件后自动重新编译)都会创建一个新的 compilation 对象。
  • 依赖于 compiler:compilation 由 compiler 在 compile 钩子阶段创建,并传递给后续的钩子函数。

核心区别总结

维度 Compiler(编译器) Compilation(编译实例)
生命周期 全局唯一,从 Webpack 启动到退出 单次编译有效,每次编译创建新实例
核心内容 保存全局配置和生命周期钩子 保存单次编译的模块、依赖、资源等具体信息
作用范围 统筹整个编译流程,管理所有编译实例 负责单次编译的具体工作(解析、优化、输出)
钩子类型 全局生命周期钩子(如 run、done) 单次编译相关钩子(如 optimize、emit)

通俗类比

  • 可以把 compiler 理解为工厂老板:负责制定规则(配置)、管理整个工厂的生命周期(从开工到收工),并决定何时启动生产(编译)。
  • 把 compilation 理解为单次生产流程:每次生产(编译)时,老板(compiler)会启动一个生产流程(compilation),流程中包含原材料(模块)、加工过程(依赖处理)、最终产品(输出资源),生产结束后流程实例就失效了。

通过两者的配合,Webpack 实现了从配置解析到资源输出的完整流程,插件可以通过它们的钩子函数介入编译的各个环节,实现自定义功能。

参考:
webpack
豆包
deepseek

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容