如何编写自定义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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容