美文网首页
如何写一个 webpack 插件

如何写一个 webpack 插件

作者: 莫帆海氵 | 来源:发表于2021-10-25 14:46 被阅读0次

插件提供给第三方开发人员可以完全深入处理 webpack 引擎处理的数据。

定义一个插件的要求

  • 定义一个函数或类
  • 包含一个 apply 方法的属性
  • 在 event hooks 上绑定 tap 方法
  • 处理 webpack 的各种数据
  • 最后调用 callback

插件基本的结构

// my-plugin.js
class MyPlugin {
  /**
   * 当 webpack 编译器在安装该插件时 apply 方法就会调用
   */
  apply(compiler) {
    compiler.hooks.done.tap(
      'MyPlugin',
      (stats) => {
        console.log('test', stats.compilation.modules[0])
      }
    )
  }
}

module.exports = MyPlugin


// webpack.config.js
var MyPlugin = require('./my-plugin')

module.exports = {
    // ...
    plugins: [
        new MyPlugin()
    ]
}

Tap TapAsync TapPromise

给 hooks 绑定回调的方法

xxx.hooks.xxx.tap('{some name}', () => {})

每个 hook 都需要通过 tap 方法绑定它的回调,hook 有同步和异步之分,同步的使用 tap 方法绑定,异步的使用 tapAsync 或 tapPromise 绑定。

可从下面文档中查找每个 hook 的类型,常用类型有 SyncHook、SyncBailHook、AsyncSeriesHook

https://webpack.js.org/api/compiler-hooks/#environment

// tap
compiler.hooks.environment.tap('a', () => {
  console.log('environment')
})

// tapAsync 必须调用作为最后一个参数的 callback,方法,否则就会在中断继续执行
compiler.hooks.run.tapAsync('b', (compiler, callback) => {
  console.log('run b')
  // callback()
})

// tapPromise 必须返回一个 promise
compiler.hooks.run.tapPromise('c', (compiler) => {
    return new Promise(resolve => {
        console.log('run c')
        resolve()
    })
}

如果给一个同步 hook 使用 tapAsync 方法会抛出如下错误

tapAsync is not supported on a SyncHook

compiler.hooks.environment.tapAsync('a', () => {
  console.log('environment')
})

如果 tapPromise 不返回一个 promise 也会抛出如下错误

Tap function (tapPromise) did not return promise (returned undefined)

compiler.hooks.run.tapPromise('b', (compiler) => {
    console.log('run b')
})

Compiler 和 Compilation

  • Compiler 编译器

每个 Webpack 的实例都会创建一个 Compiler 对象,Compiler 把加载、打包、写入这些操作委托给注册的插件,它的 hooks 属性能将插件注册到 Compiler 生命周期中的任何钩子事件上。

  • Compilation 编译

每个 Compiler 都会创建一个 Compilation 对象,同时只支持有一个并行的 Compilation 对象。每个 Compilation 对象代表编译的过程,它可以访问所有的模块及其依赖,在编译过程中模块会被加载、密封、优化、分块、散列和恢复,包含错误警告(如果有)、时间、模块和分块信息等内容

// webpack.js
const webpack = (options, callback) => {
    // ...
    
    const compiler = new Compiler(options.context)
    
    // ...
    
    if (callback) {     
        compiler.run(() => {
            callback()
        });
    }
    
    return compiler
}
// Compiler.js
class Compiler {
    // ...
    
    run() {
        // ...
        
        this.hooks.beforeRun.callAsync(this, err => {
            if (err) return finalCallback(err);

            this.hooks.run.callAsync(this, err => {
                if (err) return finalCallback(err);

                this.readRecords(err => {
                    if (err) return finalCallback(err);

                    this.compile(onCompiled);
                });
            });
        });
        
        // ...
    }
    
    compiler(callback) {
        // ...

        const compilation = new Compilation(this, params));

        this.hooks.make.callAsync...
        // ...
    }
}

Compilation 在 Compiler 某些钩子的回调的参数中可以获取到,eg:compilation、emit、done...

compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
  // ...
  compilation.xxx
});

https://v4.webpack.js.org/api/compiler-hooks/#emit

  • Compiler hooks 生命周期
compiler hooks lifecycle.png

生命周期主要包含 5 个部分,run -> compile -> make -> emit -> done

一个Demo

// For Webpack4
class CountAssetPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap('CountAssetPlugin', (compilation) => {      
      let fileCount = 0
      let totalFileSize = 0
      Object.entries(compilation.assets).forEach(([pathname, source]) => {
        fileCount++
        totalFileSize += source.size()
      })

      console.log(`file ${fileCount} size ${totalFileSize}`)
    })
  }
}

module.exports = CountAssetPlugin

// For Webpack4
const { ConcatSource } = require("webpack-sources");

class MyBannerPlugin {
  apply(compiler) {
        compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
        compilation.hooks.afterOptimizeChunkAssets.tap('MyPlugin', (chunks) => {
        
        // console.log(chunks.map(chunk => `${chunk.id}`))
        chunks.forEach(chunk => {
          chunk.files.forEach(file => {
            compilation.updateAsset(
              file,
              old => {
                return new ConcatSource(
                '/\* Powered by MT \*/',
                '\n',
                old
              )}
            )
          });
        });
      })

      compilation.hooks.afterOptimizeAssets.tap('MyPlugin', (assets) => {
        console.log(Object.keys(assets))
      })
    })
  }
}

module.exports = MyBannerPlugin

相关文章

网友评论

      本文标题:如何写一个 webpack 插件

      本文链接:https://www.haomeiwen.com/subject/qdbmoltx.html