重学webpack4之plugin开发

2020-11-26 16:08:56 浏览数 (1)

代码语言:javascript复制
// 每日前端夜话 第438篇
// 正文共:3000 字
// 预计阅读时间:12 分钟

❝本文由作者 xfz 授权发布 ❞

插件的运行环境

  • 插件没有像loader那样的独立的运行环境,run-loader
  • 只能在webpack里面运行

插件基本结构

代码语言:javascript复制
// 插件名称
class MyPlugin {
  constructor(options) {}
  // 插件运行方法apply
  apply(compiler) {
    // 插件hooks
    compiler.hooks.done.tap('My Plugin', (/* xxx */) => { 
      // 插件处理逻辑
    })
  }
}
module.exports = MyPlugin

  • 使用

plugins: [new MyPlugin()]

插件的错误处理

  • 参数校验阶段可以直接throw出错误
  • 通过compilation对象的warning和errors结构
代码语言:javascript复制
compileration.warning.push('warning')
compileration.errors.push('error')

通过compilation进行文件写入

  • Compilation上的 assets 可以用于文件写入
  • 文件写入需要使用 webpack-sources
代码语言:javascript复制
compilation.assets[name] = new RawSource('xxxx')

事件钩子会有不同的类型 SyncBailHook,AsyncSeriesHook,SyncHook等

如果是异步的事件钩子,那么可以使用 tapPromise 或者 tapAsync 来注册事件函数,

tapPromise 要求方法返回 Promise 以便处理异步,而 tapAsync 则是需要用 callback 来返回结果

代码语言:javascript复制
compiler.hooks.done.tapPromise('PluginName', (stats) => {
    return new Promise((resolve, reject) => {
    // 处理promise的返回结果 reject(err) : resolve()
});
compiler.hooks.done.tapAsync('PluginName', (stats, callback) => {
    callback( err)) 
});

除了同步和异步的,名称带有 parallel 的,注册的事件函数会并行调用,名称带有 bail 的,注册的事件函数会被顺序调用,直至一个处理方法有返回值名称带有 waterfall 的,

每个注册的事件函数,会将上一个方法的返回结果作为输入参数。有一些类型是可以结合到一起的,如 AsyncParallelBailHook,这样它就具备了更加多样化的特性

代码语言:javascript复制
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
    apply(compiler) {
        // 在 compiler的hooks中注册一个方法,当执行到该阶段时会调用
        compiler.hooks.run.tap(pluginName, compilation => {
            console.log("The webpack build process is starting!!!");
        });
    }
}

使用

plugins: [ new ConsoleLogOnBuildWebpackPlugin() ]

自己动手写写插件

my-plugin

代码语言:javascript复制
├── package.json
├── plugins
│   └── my-plugin.js
├── src
│   └── index.js
└── webpack.config.js

package.json

代码语言:javascript复制
{
  "name": "loader-order",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12"
  }
}

webpack.config.js

代码语言:javascript复制
const path = require('path');
const MyPlugin = require('./plugins/my-plugin')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'main.js'
  },
  plugins: [
    new MyPlugin('xfz')
  ]
}

my-plugin.js

代码语言:javascript复制
class MyPlugin { // 插件名称
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    console.log('my plugin is executed')
    compiler.hooks.done.tap('My Plugin', (/* xxx */) => {
      // 插件处理逻辑
      console.log('my plugin options:', this.options)
    })
  }
}

module.exports = MyPlugin

index.js

代码语言:javascript复制
const a = 1

npm run build,日志打印

代码语言:javascript复制
my plugin is executed
my plugin options: xfz

zip-plugin

代码语言:javascript复制
├── dist
│   ├── main.js
│   └── offline.zip
├── package.json
├── plugins
│   └── zip-plugin.js
├── src
│   └── index.js
└── webpack.config.js

package.json

代码语言:javascript复制
{
  "name": "zip-plugin",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "loader-utils": "^2.0.0",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "yazl": "^2.5.1"
  },
  "dependencies": {
    "jszip": "^3.5.0"
  }
}

webpack.config.js

代码语言:javascript复制
const path = require('path');
const ZipPlugin = require('./plugins/zip-plugin');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'main.js'
  },
  plugins: [
    new ZipPlugin({
      filename: 'offline'
    })
  ]
}

zip-plugin.js

代码语言:javascript复制
// 将文件压缩为zip包
const JSZip = require('jszip')
const RawSource = require('webpack-sources').RawSource
const path = require('path')

const zip = new JSZip()

class ZipPlugin {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    // 在生成文件的钩子上操作 
    compiler.hooks.emit.tapAsync('ZipPlugin', (compilation, callback) => {
      const folder = zip.folder(this.options.filename)

      for (let filename in compilation.assets) {
        const source = compilation.assets[filename].source()
        folder.file(filename, source)
      }

      zip.generateAsync({ type: 'nodebuffer'})
        .then(content => {
          const outputPath = path.join(
            compilation.options.output.path, 
            this.options.filename   '.zip'
          )

          const outputRelativePath = path.relative(
            compilation.options.output.path,
            outputPath
          )
          compilation.assets[outputRelativePath] = new RawSource(content)

          callback()
        })
    })
  }
}

module.exports = ZipPlugin

index.js

代码语言:javascript复制
const a = 1

npm run build,产出物

  • main.js
  • offline.zip

htmlAfterWebpackPlugin

  • 前端缓存,将打包出来的 runtime、vendor、index文件存储在localStorage中,增量更新
  • 初次加载时请求服务器,第二次加载则请求localStorage中存储的脚本

0 人点赞