写webpack 插件

527 阅读2分钟

Webpack插件主要工作流程

  1. 读取命令行或配置文件webpack.config.js参数
  2. 实例化Compiler,注册plugin配置中的插件
  3. 执行Compiler.run()方法进行编译,过程中实例化一个Compilation对象进行构建打包操作Webpack内部数据,执行callback()回调
  4. 打包生成代码块chunk及各类资源输出到output路径

CompilerCompilation继承自Tapable,整个流程使用TapableHook机制管理打包

编写Webpack插件必备条件

  • Webpack插件是一个函数或者类
  • 该方法或者类包含一个apply方法,接收compiler对象参数

实现一个简单的Webpack插件例子

  • 目的是将script标签 插入html,并且给src值添加 时间戳例子来源网络,进行改造) 1、方法一
  • 编译时获取plugin配置,实例化SetScriptTimestampPlugin对象
  • 利用CompilerCompilaion对象,监听html-webpack-pluginbeforeAssetTagGeneration钩子,在把js插入到html之前进行拦截
  • 遍历HtmlWebpackPluginassets参数获取打包后的js,添加时间戳,将修改后的js文件重新赋值给 HtmlWebpackPlugin.assets.js
  • 调用callback回调函数完成流程
// SetScriptTimestampPlugin.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

class SetScriptTimestampPlugin {
    constructor(options) {
        this.options = options;
        this.name = "SetScriptTimestampPlugin"
    }
    
    apply(compiler) {
        compiler.hooks.compilation.tap(this.name, (compilation, callback) => {
                const run = this.run.bind(this, compilation);
                if (compilation.hooks.htmlWebpackPluginAfterHtmlProcessing) {
                    // html-webpack-plugin v3 插件
                    compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync(this.name, run);
                } else {
                    // html-webpack-plugin v4
                 HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(this.name, run);
                }
            }
        );
    }

   run(compilation, HtmlWebpackPluginData, callback) {
        const jsList = HtmlWebpackPluginData.assets.js
        const timeStrip = `?${new Date().getTime()}`
        HtmlWebpackPluginData.assets.js = jsList.map(item => {
            item += timeStrip
            return item
        })
        callback(null, HtmlWebpackPluginData)
    }
}

module.exports = SetScriptTimestampPlugin;
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SetScriptTimestampPlugin = require('../plugins/SetScriptTimestampPlugin');
const path = require('path');       

const config = {
    mode: 'development',        
    entry: {    
        main: path.resolve(__dirname,'../src/index.js'),
        index: path.resolve(__dirname,'../src/entry.js')
    },
    output: {  
        path: path.resolve(__dirname,'../dist'),
        filename:'[name].bundle.js'
    },
   
    plugins: [
        new HtmlWebpackPlugin({filename: "index.html",template: './src/index.html'}), 
        new SetScriptTimestampPlugin(), // 引入SetScriptTimestampPlugin插件
    ],
    devServer: {        
        contentBase: path.join(__dirname, '../dist'), 
        publicPath: '/',
        port: 1111,   
    }
};
module.exports = config;

2、方法二

  • 监听Compiler.hooks.emit类,此时状态已完成编译,生成资源到output目录之前
  • 遍历compilationnamedChunks(还是chunks也可以????)获取输出块的js名字,加上时间戳(可以打印看看compilation的属性)
  • 获取compilation.assetshtml文件内容,正则匹配原来的script标签,替换成src有时间戳的script标签,最后返回修改后的文件
// SetScriptTimestampPlugin.js
class SetScriptTimestampPlugin {
    constructor(options) {
        this.options = options;
        this.name = "SetScriptTimestampPlugin"
    }
     apply(compiler) {
        compiler.hooks.emit.tap(this.name, (compilation) => {
                Object.keys(compilation.assets).forEach((data) => {
                    if (data.includes(".html")) {
                    	let content = compilation.assets[data].source() // 欲处理的文本
                        let newScriptStr = ""
                    	const timeStr = `?${new Date().getTime()}`
                        const reg = new RegExp("<script\\b[^>]*>[\\s\\S]*<\\/script>")
                        
                        compilation.namedChunks.forEach(item => {
                            newScriptStr += `<script src="${item.files[0] + timeStr}"></script>`
                        })
                        content = content.replace(reg, newScriptStr)
                        compilation.assets[data] = {
                            source() {
                                return content
                            },
                            size() {
                                return content.length
                            }
                        }
                    }
                })
            }
        );
    }
}

module.exports = SetScriptTimestampPlugin;
  • 执行结果

最后

  • CompilerCompilation存在很多钩子,在编写插件的时候会不清楚何时调用什么钩子
  • 官网直接调用的Compiler.plugin为啥本地会报错喃 - 版本问题-WEBPACK 4迁移说明
  • compiler.hooks.emit.tap 还是 compiler.hooks.emit.tapAsync?
  • 例子很简单,感觉整个编译过程还是挺复杂的,革命尚未成功,同志仍需努力!

参考

《手写一个prefetch-webpack-plugin插件》

《Webpack构建流程》