如何编写自定义webpack plugin?

参考文章:https://webpack.docschina.org/api/compiler-hooks/
webpack插件包括:

  • 具名function或class
  • 原型上有apply方法
  • 在特定钩子函数执行代码
  • 处理compiler或compilation对象上的数据
  • 调用webpack提供的回调
// A JavaScript class.
class MyExampleWebpackPlugin {
  // Define `apply` as its prototype method which is supplied with compiler as its argument
  apply(compiler) {
    // Specify the event hook to attach to
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        console.log('This is an example plugin!');
        console.log(
          'Here’s the `compilation` object which represents a single build of assets:',
          compilation
        );

        // Manipulate the build using the plugin API provided by webpack
        compilation.addModule(/* ... */);

        callback();
      }
    );
  }
}
apply(compiler) {
        let skeletons;
        // compatible with webpack 4.x
        if (compiler.hooks) {
            compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, cb) => {
                if (!compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) {
                    console.error('VueSkeletonWebpackPlugin must be placed after HtmlWebpackPlugin in `plugins`.');
                    return;
                }

                this.generateSkeletonForEntries(this.extractEntries(compiler.options.entry), compiler, compilation)
                    .then(skeletonResults => {
                        skeletons = skeletonResults.reduce((cur, prev) => Object.assign(prev, cur), {});
                        cb();
                    })
                    .catch(e => console.log(e));

                compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync(PLUGIN_NAME, (htmlPluginData, callback) => {
                    this.injectToHtml(htmlPluginData, skeletons);
                    callback(null, htmlPluginData);
                });
            });
        }
        else {
            compiler.plugin('make', (compilation, cb) => {
                this.generateSkeletonForEntries(this.extractEntries(compiler.options.entry), compiler, compilation)
                    .then(skeletonResults => {
                        skeletons = skeletonResults.reduce((cur, prev) => Object.assign(prev, cur), {});
                        cb();
                    })
                    .catch(e => console.log(e));
                
                compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, callback) => {
                    this.injectToHtml(htmlPluginData, skeletons);
                    callback(null, htmlPluginData);
                });
            });
        }
    }

compiler所有钩子函数详见:https://webpack.docschina.org/api/compiler-hooks/
compiler钩子函数使用示例:
emit : 生成资源到output之前,在需要更改输出的文件时很有用,比如去掉所有的console.log。
make :compilation 结束之前执行。
compilation :compilation 创建之后执行。
afterPlugins :在初始化内部插件集合完成设置之后调用。

对于初始化插件需传参的情况,在constructor中定义。以下使用了schema-utils来校验传入的参数是否符合规范。

import { validate } from 'schema-utils';
import schema from 'path/to/schema.json';
class Plugin {
  constructor(options) {
    validate(schema, options, {
      name: 'Plugin Name',
      baseDataPath: 'options',
    });
    this.options = options;
  }
  apply(compiler) {
    // Code...
  }
}
export default Plugin;

插件调用顺序

插件按照在配置文件中的定义顺序依次执行,并且插件必须调用钩子函数中的cb方法才会执行下一个插件。
比如,自动生成一些js文件。在生成文件之后再执行GenerateRenderIndexPlugin插件。

// generateRenderApiPlugin.js
class GenerateRenderApiPlugin {
    apply(compiler) {
        compiler.hooks.beforeRun.tapAsync('GenerateRenderApiPlugin', (compilation, cb) => {
            // 该插件中需要执行一些异步操作
            let actions = [Promise.resolve()]
            Promise.all(actions).then(() => {
                cb();
            }).catch((e) => {
                // cb()
            })
        })

    }
}
//  webpack.config.js
 plugins: [
        new GenerateRenderApiPlugin(),
        new GenerateRenderIndexPlugin()
]

将字符串变成可执行代码:

vm.runInThisContext(`let a = 1`)
console.log(a);

输出格式化的代码到文件:

const beautify = require('js-beautify').js;
util.promisify(fs.writeFile)(`${outputPath}/${prefix}.js`, beautify(apiContent, { indent_size: 2, space_in_empty_paren: true }), 'utf8').then((data) => {

                    console.log('test write success')
                })

字符串全部替换:

const replaceAll = (target, reg, slot) => {
    return target.replace(reg, slot);
}
let reg = new RegExp(/customR/g);
replaceAll(`let {a} = customR('./a.js')`, reg, 'require');

插件传参

const { validate } = require('schema-utils')
const schema = {
    type: "object",
    properties: {
        test: {
            type: "string",
        },
    },
};
class RemoveConsolePlugin {
    constructor(options) {
        console.log('init plugin');
        validate(schema, options, {
            name: 'Plugin Name',
            baseDataPath: 'options',
        });
        this.options = options;
    }
    apply(compiler) {
        if (compiler.hooks) {
            console.log('webpack 4');
        } else {
            console.log('webpack 5');
        }
    }
}
module.exports = RemoveConsolePlugin;

你可能感兴趣的:(如何编写自定义webpack plugin?)