webpack plugin原理以及自定义plugin

通过插件我们可以拓展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务。

plugin工作原理:

webpack工作就像是生产流水线,要通过一系列处理流程后才能将源文件转为输出结果,在不同阶段做不同的事,插件就是通过插入在某个阶段,执行一些特定的事情。

webpack通过tapable来组织这条复杂的生产线,webpack在执行的过程中会广播事件,插件只需要监听它关心的事件,就能加入到这条生产线中,改变运作。

代码角度来说:webpack在编译的过程中,会触发一系列的tapable钩子事件,插件要做的就是找到对应的钩子,往上面挂自己的任务,也就是注册事件,webpack在构建的时候,就会一起触发注册的事件。

webpack内部钩子函数

钩子函数的本质就是事件,为了方便我们直接接入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来,这些就是钩子。

Tapable

为webpack提供了统一的插件接口,类型定义,是webpack的核心功能库。webpack目前有十种ooks。Tapable统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:

tap:可以注册同步钩子和异步钩子

tapAsync:回调方式注册异步钩子

tapPromise:Promise方式注册异步钩子

plugin构建对象

Compiler 对象中保存着完整的webpack环境配置,每次启动webpack构件时,都只创建一次的对象,我们可以通过compiler获取到ebpack的主环境配置,比如:loader,plugin等。主要有以下属性(compiler 钩子 | webpack 中文文档 | webpack 中文文档 | webpack 中文网):

compiler.options 可以访问本次启动webpack时所有的配置文件,包括但不限于loader,entry,output,plugin等等完整配置信息。

compiler.inputFileSystem compiler.outputFileSystem 可进行文件操作,类似于node中fs模块。

compiler.hooks 可以注册Tapable的不同种类Hook,从而可以再compiler生命周期中植入不同的逻辑。

Compilation 对象代表一次资源的构建,compilation实例可以访问所有的模块以及他们的依赖。

一个compilation会构建依赖图中所有的模块进行编译。在编译阶段,模块会被加载(load),封存(seal),优化(optimize),分块(split),哈希(hash)和重新构建(restore)。主要有以下属性(compilation 钩子 | webpack 中文文档 | webpack 中文文档 | webpack 中文网):

compilation.modules 可以访问所有模块,打包的每一个文件都是一个模块。

compilation.chunks chunks即是多个modules组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。

compilation.assets 可以访问本次打包生成所有文件的结果。

compilation.hooks 可以注册Tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改。

写法如下:

//1.webpack加载webpack.config.js中所有配置,此时就会new TestPlugin(),执行插件的constructor
//2.创建compiler对象
//3.遍历所有plugins中插件,调用插件的apply方法,
//4.再去执行剩下的编译流程(触发各个hooks事件)
class TestPlugin{
    constructor(){}
    apply(compiler){
        //文档可知environment是同步,所以使用tap注册
        compiler.hooks.environment.tap("TestPlugin",()=>{

        })
        //emit是异步串行,按照顺序执行完才往下走
        compiler.hooks.emit.tap("TestPlugin",(compilation)=>{
        })
        compiler.hooks.emit.tapAsync("TestPlugin",(compilation,callback)=>{
            setTimeout(()=>{
                callback() //callback执行后才继续往下走
            },1000)
        })
        compiler.hooks.emit.tapPromise("TestPlugin",(compilation)=>{
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    resolve()
                },1000)
            })
        })
        //make是异步并行
        compiler.hooks.make.tapAsync("TestPlugin",(compilation,callback)=>{
            //compilation钩子函数要在make钩子里触发
            compilation.hooks.seal.tap("TestPlugin",()=>{

            })
            setTimeout(()=>{
                callback() //callback执行后才继续往下走
            },3000)
        }),
        compiler.hooks.make.tapAsync("TestPlugin",(compilation,callback)=>{
            setTimeout(()=>{
                callback() //callback执行后才继续往下走
            },2000)
        }),
        compiler.hooks.make.tapAsync("TestPlugin",(compilation,callback)=>{
            setTimeout(()=>{
                callback() //callback执行后才继续往下走
            },1000)
        })
    }
}
module.exports=TestPlugin

关于compiler以及compilation的调试:

首行断点

"scripts": {
    "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
  }

在TestPlugin中debugger

class TestPlugin{
    constructor(){}
    apply(compiler){
        debugger
        console.log(compiler);
        //文档可知environment是同步,所以使用tap注册
        compiler.hooks.environment.tap("TestPlugin",()=>{

        })
        ...

运行指令:npm run debug,启动后打开浏器控制台,点击node标志,进入调试模式

 点击下一个断点,就是我们的debugger的地方,可以看到compiler的内容,当然也包括compilation。


这里以下都是生产环境:

自定义bannerWebpackPlugin:给打包后文件添加注释:

需要使用compiler.hooks.afterCompile钩子函数,在compilation 结束和封印之后触发

class BannerWebpackPlugin{
    constructor(options={}){
        this.options = options
    }
    apply(compiler){
        compiler.hooks.afterCompile.tapAsync("BannerWebpackPlugin",(compilation,callback)=>{
            debugger
            // 1.获取即将输出的资源文件compiler.assets
            // 2.只保留js和css资源
            const extension = ['css','js']
            //assets文件都是 文件路径:文件内容的格式 所以做一下处理
            const assets = Object.keys(compilation.assets).filter(assetPath=>{
                let splitted = assetPath.split(".")
                return extension.includes(splitted[splitted.length-1])
            })
            // 3.遍历资源添加注释
            let prefix = `/*
            *author:${this.options.author}
            */`
            assets.forEach(asset=>{
                //找到原文件内容
                const source = compilation.assets[asset].source()
                //内容添加注释
                const content = prefix + source

                compilation.assets[asset]={
                    //调用source方法,返回内容
                    source(){
                        return content
                    },
                    //返回资源大小
                    size(){
                        return content.length
                    }
                }
            })
            callback()
        })
    }
}
module.exports = BannerWebpackPlugin
plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
    // new TestPlugin(),
    new BannerWebpackPlugin({
      author:"胖虎"
    })
  ],

自定义cleanWebpackPlugin:

我们希望每次打包以后都能够清空上次的打包内容。

我们在compiler.hooks.emit钩子函数触发,即将输出资源前清空,通过文件操作outputFileSystem操作文件

class CleanWebpackPlugin {
    constructor(){}
    apply(compiler){
        //获取打包目录
        const outputpath = compiler.options.output.path
        const fs = compiler.outputFileSystem
        compiler.hooks.emit.tapAsync("CleanWebpackPlugin",(compilation,callback)=>{
            //清空内容
            this.removeFiles(fs,outputpath)
            callback()
        })
    }
    removeFiles(fs,filePath){
        debugger
        console.log(filePath);
        // 想要删除打包目录下的所有文件,需要先删除这个包下的所有资源,然后再删除这个目录
        // 获取当前目录下所有的资源
        const files = fs.readdirSync(filePath)
        //遍历一个一个删除,判断文件还是文件夹,文件直接删除,文件夹递归
        files.forEach(file => {
            const path = `${filePath}/${file}`
            const fileStat = fs.statSync(path)
            if(fileStat.isDirectory()){
                this.removeFiles(fs,path)
            }else{
                fs.unlink(path,()=>{})
            }
        });
    }
}

module.exports=CleanWebpackPlugin
plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
    // new TestPlugin(),
    new BannerWebpackPlugin({
      author:"胖虎"
    }),
    new CleanWebpackPlugin()
  ],

自定义analyze-webpack-plugin, 分析webpack打包资源大小并输出分析文件。

也还是在compiler.hooks.emit钩子函数触发

class AnalyzeWebpackPlugin {
    constructor() { }
    apply(compiler) {
        compiler.hooks.emit.tapAsync("AnalyzeWebpackPlugin", (compilation, callback) => {
            const assets = Object.entries(compilation.assets)
            let content = `|资源名称|资源大小|
|---|---|`
            assets.forEach(([filename, file]) => {
                content += `\n|${filename}|${Math.floor(file.size()/1024)+"kb"}|`
            })
            // 生成一个md文件
            compilation.assets['analyze.md']={
                source(){
                    return content
                },
                size(){
                    return content.size
                }
            }
            callback()
        })
    }
}
module.exports=AnalyzeWebpackPlugin
plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
    // new TestPlugin(),
    new BannerWebpackPlugin({
      author:"胖虎"
    }),
    new CleanWebpackPlugin(),
    new AnalyzeWebpackPlugin()
  ],

效果:

 自定义inline-chunk-webpack-plugin

webapck打包生成的runtime文件太小了,额外发请求性能不太好,将其内联到js中,从而减少请求数量,需要借助html-webpack-plugin来实现,将runtime内联注入到index.html中。下面是html-webpack-plugin原理图。

 html-webpack-plugin有6个生命周期函数,我们在alterAssetTagGroups中(已经将文件分好组)来找到runtime文件,变成inline script标签。在compiler.hooks.compilation钩子触发。

生成runtime文件:

optimization: {
    splitChunks: {
      chunks: "all",
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}.js`,
    },
  },
const HtmlWebpackPlugin = require('safe-require')('html-webpack-plugin')
class InlineChunkWebpackPlugin {
    constructor(tests) { 
        this.tests=tests
    }
    apply(compiler) {
        compiler.hooks.compilation.tap("InlineChunkWebpackPlugin", (compilation, compilationParams) => {
            // 获取HtmlWebpackPlugin的钩子,注册
            HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => { //assets是获取的资源包含headTags以及bodyTags
                //     headTags: [
                //       {
                //         tagName: 'script',
                //         voidTag: false,
                //         meta: [Object],
                //         attributes: [Object]
                //       },
                //       {
                //         tagName: 'script',
                //         voidTag: false,
                //         meta: [Object],
                //         attributes: [Object]
                //       }
                //     ],
                //     bodyTags: [],
                // 我们需要修改:
                //     {
                //         tagName: 'script',
                //         innerHTML: runtime文件内容,
                //         closeTag: true
                //       }
                assets.headTags = this.getInlineChunk(assets.headTags, compilation.assets)
                assets.bodyTags = this.getInlineChunk(assets.bodyTags, compilation.assets)

            })
            // 删除已经被注入的文件
            HtmlWebpackPlugin.getHooks(compilation).afterEmit.tap("InlineChunkWebpackPlugin", (outputname,plugin) => { //assets是获取的资源包含headTags以及bodyTags
                Object.keys(compilation.assets).forEach(filePath=>{
                    if(this.tests.some(test=>test.test(filePath))){
                        delete compilation.assets[filePath]
                    }
                })

            })
        })

    }
    getInlineChunk(tags, assets) {
        return tags.map(tag => {
            if (tag.name != "script") return tag
            const filePath = tag.attributes.src
            if (!filePath) return tag
            if (!this.tests.some(test=>{test.test(filePath)})) return tag
            return {
                tagName: 'script',
                innerHTML: assets[filePath].source(),
                closeTag: true
            }
        });
    }
}
module.exports = InlineChunkWebpackPlugin
plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
    // new TestPlugin(),
    new BannerWebpackPlugin({
      author:"胖虎"
    }),
    new CleanWebpackPlugin(),
    new AnalyzeWebpackPlugin(),
    new InlineChunkWebpackPlugin([/runtime(.*)\.js$/g]) //可自定义输入文件正则来删除已注入的文件
  ],

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/17632.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

二、PEMFC基础之电化学与反应动力学

二、PEMFC基础之电化学与反应动力学 1.电流、电流密度2.反应速率常数3.交换电流密度4.电化学动力学奠基石B-V方程5.活化损失计算Tafel公式6.计算案例 1.电流、电流密度 由法拉第定律 i d Q d t n F d N d t i\frac{dQ}{dt}\frac{nFdN}{dt} idtdQ​dtnFdN​ j i A j\frac{…

【五一创作】ERP实施-委外业务-委外采购业务

委外业务主要有两种业务形态:委外采购和工序外协,委外采购主要是在MM模块中实现,工序外协主要由PP模块实现,工序外协中的采购订单创建和采购收货由MM模块实现。 委外采购概念 委外采购,有些企业也称为带料委外或者分包…

牛客刷SQL题Day5

SQL69 返回产品并且按照价格排序 select prod_name , prod_price from Products where prod_price between 3 and 6 select prod_name , prod_price from Products where 6>prod_price and prod_price >3 踩坑1: between......and.......包括边界。 踩坑2&am…

网卡丢失导致集群异常

假期晚上有个电话,说集群故障,应用无法连接,节点一可以ssh登录,节点二已无法正常登录了,在节点一上需要ssh 私网ip地址才可以登录节点二,虽不是重点客户,有问题还是需要积极处理。 首先看集群状…

Cartesi 2023 年 4 月回顾

查看你不想错过的更新 2023年5月1日,感谢Cartesi生态系统中所有了不起的构建者! 在一个激动人心的旅程之后,我们的首届全球线上黑客马拉松正式结束了!有超过200名注册建造者参加,见证了所有参与者展示的巨大才华和奉献…

有必要给孩子买台灯吗?分享四款高品质的护眼台灯

有必要使用护眼台灯,尤其是有近视现象的孩子们。 现在很多孩子小学就开始近视了,保护视力刻不容缓呀! 很多人不知道,其实劣质光线是最大的眼睛杀手 给孩子随便买便宜的台灯,看着一样能用,其实时间久了 对孩子眼睛的…

SpringCloud:ElasticSearch之RestClient查询文档

文档的查询同样适用RestHighLevelClient对象,基本步骤包括: 1)准备Request对象2)准备请求参数3)发起请求4)解析响应 1.快速入门 我们以match_all查询为例 1.1.发起查询请求 代码解读: 第一步…

【《中国工业经济》数据复现】数字化转型与企业分工:专业化还是纵向一体化

一.研究内容 本文使用机器学习方法刻画微观企业数字化水平,并在构建数理模型的基础上实证考察了企业数字化转型对企业分工的影响及其机理。结果表明,企业数字化转型显著提升了中国上市企业专业化分工水平。机制分析表明,数字化转型对企业专业…

ReentrantLock原理剖析

前言 本文主要讲解底层逻辑,基本不会贴代码,目的是让大家能够真正的知晓原理,对照着逻辑去理解代码看代码也会很快就能看懂。 在讲ReentrantLock原理之前,我们先回顾下ReentrantLock的基本用法。ReentrantLock是一个锁编程api&am…

考研机试刷题第二天:任意进制转任意进制【高进度短除法】

理一下思路&#xff1a; 看了y总的视频之后我觉得这道题其实只需要对上次写的进制转换微微做一下调整即可。 于是我写出了下面的代码 #include <iostream> #include <vector> #include <algorithm> #include <cstring>using namespace std;vector<…

Moonbeam操作指南|如何使用Gelato创建自动化任务

Gelato是一个Web3去中心化自动化网络&#xff0c;允许开发者横跨多个基于EVM兼容区块链上自动化和连接任意的智能合约执行。&#x1f4d1;阅读中文版详细操作教程 举例来说&#xff0c;我们将使用MetaMask作为钱包。同时&#xff0c;您的钱包余额中需要有一些GLMR用于支付自动…

基于海洋捕食者算法的极限学习机(ELM)回归预测-附代码

基于海洋捕食者算法的极限学习机(ELM)回归预测 文章目录 基于海洋捕食者算法的极限学习机(ELM)回归预测1.极限学习机原理概述2.ELM学习算法3.回归问题数据处理4.基于海洋捕食者算法优化的ELM5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;本文利用海洋捕食者算法对极限学习…

深度学习笔记--本地部署Mini-GPT4

目录 1--前言 2--配置环境依赖 3--下载权重 4--生成 Vicuna 权重 5--测试 6--可能出现的问题 1--前言 本机环境&#xff1a; System: Ubuntu 18.04 GPU: Tesla V100 (32G) CUDA: 10.0 项目地址&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4 2--配置环境依赖 …

python面试题

文章目录 赋值、深拷贝和浅拷贝有什么区别&#xff1f;元组和列表有什么不同&#xff1f;和is有什么不同&#xff1f;集合怎么转字典&#xff1f;字典怎么遍历&#xff1f;如何在Python中实现多线程&#xff1f;如何实现tuple和list的转换&#xff1f;实现删除一个list里面的重…

智能无人蜂群作战系统适应性进化模型仿真研究

源自&#xff1a;系统仿真学报 作者&#xff1a;李志强, 李元龙, 殷来祥, 马向平 摘 要 智能无人蜂群作战系统主要由有限行为能力的大规模作战个体组成&#xff0c;一般不具备应对复杂战场环境和作战对手变化的适应能力。采用遗传算法与增强学习相结合的方法探索构建基于个体…

Tre靶场通关过程(linpeas使用+启动项编辑器提权)

Tre靶场通关 通过信息收集获得到了普通用户账号密码&#xff0c;利用PEASS-ng的linpeas脚本进行提权的信息收集&#xff0c;根据已有信息进行提权。 靶机下载地址&#xff1a; https://download.vulnhub.com/tre/Tre.zip 信息收集 靶机IP探测&#xff1a;192.168.0.129 a…

vue2实现高德地图 JSAPI 2.0轨迹回放组件(MoveAnimation)

vue2实现高德地图 JSAPI 2.0轨迹回放组件(MoveAnimation) 声明: 本人是做java后端的,组件抽取不是很规范请大家见谅 前提: 需要注册高德开放平台,之后创建应用并且开通Web端(JS API)平台,然后拿到securityJsCode和key 实现效果: 1. 基础抽取 注意: 将securityJsCode和key修改为…

Hystrix线程池问题

背景&#xff1a;在一个以springcloud为基础架构的微服务项目中&#xff0c;活动期间并发量一大就会出现服务调用失败的问题。经定位发现&#xff0c;被调用服务中无对应的请求日志&#xff0c;继续通过日志查询确认是feign调用时出现服务降级&#xff0c;进入降级方法统一返回…

极化码的入门与探索

文章目录 极化码的基础先验知识二进制输入离散无记忆信道模型(Binary-input Discreten Memoryless Channel, B-DMC)二进制离散输入信道的ML判决和错误率B-DMC相关参数的定义和理解 两信道极化N信道极化的解释信道极化分解的蝶形结构补充&#xff1a;生成矩阵的结构 极化码的基础…

【2023 年第十三届 MathorCup 高校数学建模挑战赛】A 题 量子计算机在信用评分卡组合优化中的应用 42页论文及代码

相关信息 &#xff08;1&#xff09;建模思路 【2023 年第十三届 MathorCup 高校数学建模挑战赛】A 题 量子计算机在信用评分卡组合优化中的应用 详细建模过程解析及代码实现 【2023 年第十三届 MathorCup 高校数学建模挑战赛】 B 题 城市轨道交通列车时刻表优化问题 详细建…