webpack plugin原理以及自定义plugin

news2024/10/5 16:32:59

通过插件我们可以拓展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]) //可自定义输入文件正则来删除已注入的文件
  ],

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

核磁机器学习 | 基于机器学习的fMRI分类

导读 本文通过提取最显著的特征,将大脑图像分类为正常和异常,并对大脑各种状态的磁共振成像(MRI)进行了研究。本文描述了一种基于小波变换的方法,首先对图像进行分解,然后使用各种特征选择算法从MRI图像中提取最显著的大脑特征。…

[Netty] 面试问题 1 (十八)

文章目录 1.Netty的特点2.Netty应用场景3. Netty核心组件4.Netty的线程模型5. EventloopGroup和EventLoop6.Netty 的零拷贝7.Netty 长连接和心跳机制8.Netty 服务端和客户端的启动过程9.Netty 的 Channel 和 EventLoop10.Netty 的 ChannelPipeline11.Netty 中的 ByteBuf12.Nett…

数据分析01——Anaconda安装/Anaconda中的pip换源/jupyter配置

0、前言: 数据分析三大模块知识:numpy(数组计算)、pandas(基于numpy开发,用于数据清洗和数据分析)、matplotlib(实现数据可视化) 1、Anaconda安装: 安装Ana…

Spring常见面试题总结(2023最新版)

文章目录 1、谈谈你对Spring的理解?1.1 发展历程1.2 Spirng的组成1.3 Spring的好处 2、Autowired和Resource的区别2.1 共同点:2.2 Autowired2.3 Resource2.3.1 Resource的装配顺序 3、Spring常用注解3.1、给容器中注入组件3.1.1 包扫描组件标注注解3.1.2…

Faster-RCNN跑自己的数据集(详细过程)FPN学习

1、下载b站 :霹雳吧啦Wz 的代码 github链接:https://github.com/WZMIAOMIAO/deep-learning-for-image-processing 对应视频链接:2-自定义DataSet_哔哩哔哩_bilibili 2、配置环境,安装相应的包。 或者如果有报错可以直接更新重新…

数据结构之栈的详解

文章目录 一.什么是栈二. 栈的使用2.1栈的基本操作2.2 栈的基本使用 三.栈的实现3.1 数组实现栈的方式3.2 链式栈的实现 四.栈的应用4.1 括号匹配4.2 逆波兰表达式求值什么是逆波兰表达式 4.3 出栈入栈次序匹配4.4 最小栈五.总结 一.什么是栈 栈是一种先入后出(FILO)的线性表数…

【刷题笔记】结构体内存对齐举例+统计回文

一、结构体内存对齐举例 题目: 下面存在两个结构体: struct One {double d;char c;int i; } struct Two {char c;double d;int i; } 在#pragma pack(4)和#pragma pack(8)的情况下,结构体的大小分别是? 分析: C/C中结构…

mysql8.0性能对比以及新特性

MySQL8.0 性能测试与新特性介绍 性能对比 测试内容 测试mysql5.7和mysql8.0 分别在读写、只读、只写模式((oltp_read_write,oltp_read_only,oltp_write_only))下不同并发时的性能(tps&#x…

《微服务实战》 第一章 Java线程池技术应用

前言 介绍Java的线程、线程池等操作 1、Java创建线程方式回顾 1.1、继承Thread类(只运行一次) public class ThreadTest extends Thread{Overridepublic void run() {System.out.println(Thread.currentThread().getName());}public static void main(String[] args) {new …

【python】keras包:深度学习( MLP多层感知器 Multi-Layer Perceptron)

MLP多层感知器 Multi-Layer Perceptron Part 1. 算法逻辑 实现经典问题——如何通过图像区分猫和狗 神经网络:建立模型,模仿人的思考机制 将“机器学习_逻辑回归”按照神经元的逻辑,组成逻辑网络。 解释: 假设自变量x[]和应变…

档案库房建设需要遵守的一些规定

各单位在建设档案室时需要对照《机关档案管理规定》《档案馆建筑设计规范》关于档案库房的相关标准,对库房的位置、面积、承重、安全等方面进行全面考虑,建设符合国家规定的档案库房。 档案库房建设需要遵守什么规定? 一、《机关档案管理规定…

Transformer的位置编码

1. 什么是位置编码,为什么要使用位置编码 简单来说位置编码就是给一个句子中的每个token一个位置信息,通过位置编码可以明确token的前后顺序关系。 对任何语言来说,句子中词汇的顺序和位置都是非常重要的。它们定义了语法,从而定…

DP(9)--插头DP

DP(9)--插头DP /* Mondriaan’s Dream题目大意:在 N*M 的棋盘内铺满 1*2 或 2*1 的多米诺骨牌,求方案数。 砖只有横放和竖放两种状态,把横放记为两个0,竖放记为上1下0,逐格DP,每次无论前一格…

Kali Linux 配置动态/静态 IP

[笔者系统版本] [Kali]: Kali Linux 2023.1 [Kernel]: kernel 6.1.0 [Desktop]: Xfce 4.18.1 1. Kali Linux 配置动态 IP (1). 首先查看网卡接口名称。 (2). 编辑网络接口配置文件。 (3). 网络接口配置文件的默认内容是这样的。 (4). 新增配置内容如下; 指定网卡…

ChatGPT :十几个国内免费可用 ChatGPT 网页版

前言 ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI 研发的聊天机器人程序 ,于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言…

浅析智慧充电桩云平台的技术设计方案

自从我国提出“新基建”以来,充电基础设施产业也成为行业的话题与关注焦点。据数据统计,2021年,中国新能源汽车保有量达到784万辆,预计2025年,中国新能源汽车保有量达到2672万辆,2025年充电桩数量将达到654…

SwiftUI 新 Alert 弹出窗口帮你实现文本输入之梦

概览 小伙伴们都知道,弹出 Alert 不能包含文本输入框是 SwiftUI 的阿喀琉斯之踵(Achilles’ Heel) 。当然,这说的有些夸张了。😉 不过,Alert 不能包含 TextField 在某些情况下着实不方便。于是乎,从 SwiftUI 3.0&…

[pgrx开发postgresql数据库扩展]附1.存储过程的优缺点与数据库扩展函数

俗话说:天下大势,分久必合,合久必分。 最早的软件系统开发,讲究的就是一个全栈——在最早期的桌面软件时代,数据、用户界面和业务逻辑是完全混在一起的,讲究的就是一个一体化……那个年代也诞生了大量的码农…

MySQL基础(三)基本的SELECT语句

1. SQL概述 1.1 SQL背景知识 1946 年,世界上第一台电脑诞生,如今,借由这台电脑发展起来的互联网已经自成江湖。在这几十年里,无数的技术、产业在这片江湖里沉浮,有的方兴未艾,有的已经几幕兴衰。但在这片浩…

同步辐射散射数据处理:从测量到分析的全流程解析

同步辐射散射数据处理:从测量到分析的全流程解析 同步辐射(Synchrotron radiation,SR)是指粒子在强磁场中受到加速或转向时所放出的辐射。这种辐射是一种非常强烈、具有非常高能量和亮度的电磁辐射。同步辐射散射(Sync…