编写插件

基础插件

编写示例

class BasicPlugin {
    // 从构造函数中获取用户设置的配置
    constructor (options) {

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

        })
    }
}

// 导出 Plugin
module.exports = BasicPlugin

使用示例

const BasicPlugin = require('./BasicPlugin.js')

module.exports = {
    plugins: [
        new BasicPlugin(options)
    ]
}

Compiler 和 Compilation

  • Compiler

    Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例

  • Compilation

    Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象

  • 区别

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

事件流

开发插件时注意事项

  • 只要能拿到 Compiler 或 Compilation 对象,就能广播出新的事件,所以在新开发的插件中也能广播出事件,给其它插件监听使用

  • 传给每个插件的 Compiler 和 Compilation 对象都是同一个引用。也就是说在一个插件中修改了 Compiler 或 Compilation 对象上的属性,会影响到后面的插件

  • 有些事件是异步的,这些异步的事件会附带两个参数,第二个参数为回调函数,在插件处理完任务时需要调用回调函数通知 Webpack,才会进入下一处理流程(v3.8.1)

常用 API

读取输出资源、代码块、模块及其依赖

class Plugin {
    apply (compiler) {
        compliler.hooks.emit('Plugin', compilation => {
            // 存放所有代码块,是一个数组
            compilation.chunks.forEach(function (chunk) {
                // chunk 代表一个代码块
                // 代码块有多个模块组成,通过 chunk.forEachModule 能读取组成代码块的每个模块
                chunk.forEachModule(module => {
                    // module 代表一个模块
                    // module.fileDependencies 存在当前模块的所有依赖的文件路径,是一个[]
                    module.fileDependenciew.forEach(filepath => {

                    })
                })
            })
        })

        // Webpack 会根据 Chunk 去生成输出的文件资源,每个 Chunk 都对应一个及其以上的输出文件
        // 例如在 Chunk 中包含了 CSS 模块并且使用了 ExtractTextPlugin 时,
        // 该 Chunk 就会生成 .js 和 .css 两个文件
        chunk.files.forEach(function (filename) {
          // compilation.assets 存放当前所有即将输出的资源
          // 调用一个输出资源的 source() 方法能获取到输出资源的内容
          let source = compilation.assets[filename].source();
        })
    }
}
  • chunk 字段

{
  id: 0,
  ids: [ 0 ],
  debugId: 1003,
  name: 'app',
  preventIntegration: false,
  entryModule: {
    // NormalModule
    {
      dependencies: [],
      blocks: [],
      variables: [],
      type: '',
      ...
    }
  },
  _modules: Set {
    // CssModule
    {
      dependencies: [],
      blocks: [],
      type: 'css/mini-extract'
    },
    // NormalModule
    {
      type: 'javascript/auto'
    }
    // ConcatenatedModule
    {
      type: 'javascript/esm'
    }
    ...
  },
  files: [],
  rendered: true,
  hash: 'hash 32',
  contentHash:
}
  • module 字段

{
  dependencies: [],
  blocks: [],
  variables: [],
  type: '',
  context: '' | null,
  debugId: 1041,
  hash: 'hash 32',
  renderedHash: 'hash 20',
  resolveOptions: {} ,
  factoryMeta: {},
  warnings: [],
  errors: [],
  buildMeta: {},
  buildInfo: {
    strict: true,
    cacheable: true,
    moduleArgument: undefined,
    exportsArgument: '__webpack_exports__',
    fileDependencies: Set {},
    contextDependencies: Set {},
    assets: undefined,
  },
  reasons: [
    {
      // ModuleReason
      module: [NormalModule],
      dependency: [],
      explanation: undefined,
      _chunks: null
    }
  ],
  _chunks: Set {
    {
      // Chunk
       id: 0,
       ids: [Array],
       debugId: 1003,
       name: 'app',
       preventIntegration: false,
       entryModule: [NormalModule],
       _modules: [SortableSet],
       filenameTemplate: undefined,
       _groups: [SortableSet],
       files: [Array],
       rendered: true,
       hash: '4be1571b7464f812b1b2e7cc00a6bc04',
       contentHash: [Object],
       renderedHash: '4be1571b7464f812b1b2',
       chunkReason: undefined,
       extraAsync: false,
       removedModules: undefined },
     _lastActiveSortFn: null,
     _sortFn: [Function: sortById],
     _cache: undefined,
     _cacheOrderIndependent: undefined
     }
  },
  id: 15,
  index: 21,
  index2: 28,
  depth: 1,
  issuer: null,
  profile: undefined,
  prefetched: false,
  built: true,
  used: true,
  usedExports: true,
  optimizationBailout: [],
  _rewriteChunkInReasons: undefined,
  useSourceMap: false,
  _source: null,
  rootModule: {
    // NormalModule
    {
      dependencies: [],
      ...
    }
  }

}

监听文件变化

// // 当依赖的文件发生变化时会触发 watch-run 事件
compiler.hooks.watchRun.tap('Plugin', watching => {
     // 获取发生变化的文件列表
    const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes
    // changedFiles 格式为键值对,键为发生变化的文件路径。
    if (changedFiles[filePath] !== undefined) {
      // filePath 对应的文件发生了变化
    }
})
compiler.hooks.afterCompile.tap('Plugin', (compilation) => {
  // 把 HTML 文件添加到文件依赖列表,好让 Webpack 去监听 HTML 模块文件,在 HTML 模版文件发生变化时重新启动一次编译
  compilation.fileDependencies.push(filePath)
})

修改输出资源

compiler.hooks.emit.tap('Plugin', (compilation) => {
  // 设置名称为 fileName 的输出资源
  compilation.assets[fileName] = {
    // 返回文件内容
    source: () => {
      // fileContent 既可以是代表文本文件的字符串,也可以是代表二进制文件的 Buffer
      return fileContent;
      },
    // 返回文件大小
      size: () => {
      return Buffer.byteLength(fileContent, 'utf8');
    }
  }

  // 读取 compilation.assets
  // 读取名称为 fileName 的输出资源
  const asset = compilation.assets[fileName]
  // 获取输出资源的内容
  asset.source()
  // 获取输出资源的文件大小
  asset.size()
})

判断 Webpack 使用了哪些插件

// 判断当前配置使用使用了 ExtractTextPlugin,
// compiler 参数即为 Webpack 在 apply(compiler) 中传入的参数
function hasExtractTextPlugin(compiler) {
  // 当前配置所有使用的插件列表
  const plugins = compiler.options.plugins;
  // 去 plugins 中寻找有没有 ExtractTextPlugin 的实例
  return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null;
}

Last updated