Webpack 钩子介绍、手写 Webpack Plugin

news2024/11/17 3:50:15

目录

1. Plugin 用作和工作原理

1.1 Plugin 的作用

1.2 Plugin 的工作原理

2. Webpack 底层逻辑和钩子介绍

2.1 Webpack 内部执行流程

2.2 Webpack 内部钩子

2.2.1 钩子是什么

2.2.2 Tapable —— 为 Webpack 提供 Plugin 钩子 数据类型接口 定义

2.2.3 Compiler Hooks(继承了 Tapable )

2.2.4 Compilation Hooks(继承了 Tapable )

2.2.5 JavascriptParser Hooks(继承了 Tapable )

3. 手写 Webpack Plugin

3.1 Plugin 基本结构

3.2 在 html 项目中,使用自定义插件

3.3 在 Vue 项目中,使用自定义插件

3.4 开发 Webpack 文件清单插件

4. 常用的 Webpack Plugin

5. 参考文章


1. Plugin 用作和工作原理

1.1 Plugin 的作用

关于 Plugin 的作用,Webpack 官方是这样介绍的: 

Plugins expose the full potential of the webpack engine to third-party developers. Using staged build callbacks, developers can introduce their own behaviors into the webpack build process. 

我们可以通过插件,扩展 Webpack,加入自定义的构建行为,使 Webpack 可以执行更广泛的任务,拥有更强的构建能力。

简单来说,就是扩展 Webpack 功能

1.2 Plugin 的工作原理

关于 Plugin 的工作原理,「深入浅出 Webpack」是这样介绍的: 

webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。

插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。

webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
——「深入浅出 Webpack」

简单来说:

  • Webpack 通过内部的 事件流机制  ,保证了插件的有序性
  • Webpack 底层利用了 发布订阅模式,在运行过程中会广播事件
  • Webpack 插件只需要监听它所关心的事件,在特定的时机对资源做处理

站在代码逻辑的角度:

  • Webpack 在编译过代码程中,会触发一系列 Tapable 钩子事件
  • 插件需要找到相应的钩子,在上面添加自己的任务(注册事件)
  • 当 Webpack 构建时,插件注册的事件,会随着钩子的触发而执行

2. Webpack 底层逻辑和钩子介绍

开发 Plugin,需要用到一些 Webpack 底层的逻辑

2.1 Webpack 内部执行流程

一次完整的 Webpack 打包,大致是这样的过程:

  • 将 cli 命令行参数与 Webpack 配置文件 合并、解析得到参数对象 options,用于激活 webpack 加载项和插件
  • 把参数对象 options 传给 Webpack, 会创建生成 Compiler 对象,并初始化基础插件
  • 执行 Compiler 的 run 方法开始编译。每次执行 run 编译都会生成一个 Compilation 对象,也就是说,Compilation 对象负责一次编译过程
  • 触发 Compiler 的 make 方法分析入口文件,调用 compilation 的 buildModule 方法创建主模块对象
  • 生成入口文件 AST(抽象语法树),通过 AST 分析和递归加载依赖模块
  • 所有模块分析完成后,执行 compilation 的 seal 方法对每个 chunk 进行整理、优化、封装
  • 最后执行 Compiler 的 emitAssets 方法,把生成的文件输出到 output 的目录中

2.2 Webpack 内部钩子

2.2.1 钩子是什么

钩子的本质就是:事件

为了方便 开发者 直接介入和控制编译过程,Webpack 把编译过程中,触发的各类关键事件,封装成事件接口暴露了出来,这些接口被很形象地称做:hooks(钩子)

开发插件,离不开这些钩子

2.2.2 Tapable —— 为 Webpack 提供 Plugin 钩子 数据类型接口 定义

Tapable 暴露了三个方法给 Plugin,用于注入 不同类型的 自定义构建行为:

  • tap:可以注册同步钩子和异步钩子
  • tapAsync:回调方式注册异步钩子
  • tapPromise:Promise方式注册异步钩子

举个栗子~~~~~ 

 

Webpack 里的几个非常重要的对象,Compiler, Compilation 和 JavascriptParser 都继承了 Tapable 类,它们身上挂着丰富的钩子

Tapable 是 Webpack 的核心功能库

在 Tapable 源码中可以看到,Webpack 中目前有十种 hooks

// https://github.com/webpack/tapable/blob/master/lib/index.js

exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");

2.2.3 Compiler Hooks(继承了 Tapable )

  • Compiler 编译器模块,是创建 编译实例 的主引擎
  • 大多数面向用户的插件,都在 Compiler 上注册

Compiler 上暴露的一些常用的钩子:

钩子类型什么时候调用
runAsyncSeriesHook在编译器 开始读取记录前 执行
compileSyncHook在一个新的 compilation 创建之前执行
compilationSyncHook在一次 compilation 创建后执行插件
makeAsyncParallelHook完成一次编译前执行
emitAsyncSeriesHook在生成文件到 output 目录之前执行,回调参数:compilation
afterEmitAsyncSeriesHook在生成文件到 output 目录之后执行
assetEmittedAsyncSeriesHook生成文件的时候执行,提供访问产出文件信息的入口,回调参数:file,info
doneAsyncSeriesHook一次编译完成后执行,回调参数:stats

 

2.2.4 Compilation Hooks(继承了 Tapable )

  • Compilation 是 Compiler 用来创建一次新的编译过程的模块
  • 一个 Compilation 实例可以访问所有模块和它们的依赖
  • 在一次编译阶段,模块被加载、封装、优化、分块、散列和还原

Compilation 上暴露的一些常用的钩子:

钩子类型什么时候调用
buildModuleSyncHook在模块开始编译之前触发,可以用于修改模块
succeedModuleSyncHook当一个模块被成功编译,会执行这个钩子
finishModulesAsyncSeriesHook当所有模块都编译成功后被调用
sealSyncHook当一次 compilation 停止接收新模块时触发
optimizeDependenciesSyncBailHook在依赖优化的开始执行
optimizeSyncHook在优化阶段的开始执行
optimizeModulesSyncBailHook在模块优化阶段开始时执行,插件可以在这个钩子里执行对模块的优化,回调参数:modules
optimizeChunksSyncBailHook在代码块优化阶段开始时执行,插件可以在这个钩子里执行对代码块的优化,回调参数:chunks
optimizeChunkAssetsAsyncSeriesHook优化任何代码块资源,这些资源存放在 compilation.assets 上。一个 chunk 有一个 files 属性,它指向由一个 chunk 创建的所有文件。任何额外的 chunk 资源都存放在 compilation.additionalChunkAssets 上。回调参数:chunks
optimizeAssetsAsyncSeriesHook优化所有存放在 compilation.assets 的所有资源。回调参数:assets

2.2.5 JavascriptParser Hooks(继承了 Tapable )

  • Parser 解析器实例在 Compiler 编译器中产生,用于解析 Webpack 正在处理的每个模块
  • 可以用它提供的 Tapable 钩子,自定义解析过程

JavascriptParser 上暴露的一些常用的钩子:

钩子类型什么时候调用
evaluateSyncBailHook在计算表达式的时候调用
statementSyncBailHook为代码片段中每个 已解析的语句 调用的通用钩子
importSyncBailHook为代码片段中每个 import 语句调用,回调参数:statement,source
exportSyncBailHook为代码片段中每个 export 语句调用,回调参数:statement
callSyncBailHook解析一个 call 方法的时候调用,回调参数:expression
programSyncBailHook解析一个 表达式 的时候调用,回调参数:expression

 

3. 手写 Webpack Plugin

3.1 Plugin 基本结构

① 本质上是 Node 模块,该导出了一个 Javascript 方法 或 JavaScript 类

② 它的原型上,需要定义一个叫做 apply 的方法

③ 通过 compiler 获取 Webpack 内部的钩子,获取 Webpack 打包过程中的各个阶段;钩子分为同步和异步的钩子,异步钩子在功能完成后,必须执行对应的回调

④ 通过 compilation,操作 Webpack 内部实例特定数据

⑤ 功能完成后,调用 Webpack 提供的回调

 

3.2 在 html 项目中,使用自定义插件

新建项目,通过 npm init -y 创建 package.json,并在 script 中配置打包命令 "build": "webpack"

安装 webpack、webpack-cli

新建 MyPlugin.js 文件,声明一个 class,并导出(这就是自定义手写的 Webpack 插件)

通过 compiler 获取 webpack 内部的钩子 done,并在钩子中注入一条 控制台打印 的语句

根据上文 2.2.3 钩子介绍,done 会在一次编译完成后执行

所以这个插件会在每次打包结束时,向控制台输出 “♪(^∇^*) Lyrelion 打包已完成!”

// 自定义 MyPlugin 插件,该插件在打包完成后,在控制台输出 "打包已完成"

// 一个命名的 Javascript 方法 或 JavaScript 类
class MyPlugin {
    // 原型上需要定义 apply 的方法
    apply(compiler) {
        // 通过 compiler 获取 webpack 内部的钩子,获取 Webpack 打包过程中的各个阶段
        compiler.hooks.done.tap("My Plugin", (compilation, cb) => {
            // 通过 compilation,操作 Webpack 内部实例特定数据
            console.log("♪(^∇^*) Lyrelion 打包已完成!");
            // 分为同步和异步的钩子,异步钩子在功能完成后,必须执行对应的回调
            // cb();
        });
    }
}

module.exports = MyPlugin;

 

新建 webpack.config.js,引入自定义插件

const path = require('path');
const MyPlugin = require('./plugins/MyPlugin');

module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js',
        a: './src/a.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new MyPlugin(),
    ]
}

执行打包,控制台输入 yarn build,打包完成后,控制台输出效果: 

 

3.3 在 Vue 项目中,使用自定义插件

在 vue.config.js 引入该插件

const MyPlugin = require('./MyPlugin.js')

在 configureWebpack 的 plugins 列表中,注册该插件

module.exports = {

  configureWebpack: {

    plugins: [new MyPlugin()]

  }

};

执行项目打包命令,会在控制台输出 “♪(^∇^*) Lyrelion 打包已完成!”

3.4 开发 Webpack 文件清单插件

需求:

  • 每次 Webpack 打包后,自动产生一个打包文件清单
  • 清单上要记录 文件名、文件数量 等信息

基本思路:

  • 本操作需要在文件生成到 dist 目录之前进行,所以要注册 Compiler 上的 emit 钩子
  • emit 是一个异步串行钩子,用 tapAsync 来注册
  • 在 emit 回调函数里,可以拿到 compilation 对象,所有待生成的文件都在它的 assets 属性上;通过 compilation.assets 获取文件信息,并整理输出
  • 然后往 compilation.assets 添加这个新的文件

编写插件:

/**
 * 自定义 FileListPlugin 插件
 * 每次 Webpack 打包后,自动产生一个打包文件清单
 * 清单上要记录 文件名、文件数量 等信息
 */
class FileListPlugin {
    constructor(options) {
        // 获取插件配置项
        this.filename = options && options.filename ? options.filename : 'FILELIST.md';
    }

    apply(compiler) {
        // 注册 compiler 上的 emit 钩子
        compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {

            // 通过 compilation.assets 获取文件数量
            let len = Object.keys(compilation.assets).length;

            // 添加统计信息
            let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpack\n\n`;

            // 通过 compilation.assets 获取文件名列表
            for (let filename in compilation.assets) {
                content += `- ${filename}\n`;
            }

            // 往 compilation.assets 中添加清单文件
            compilation.assets[this.filename] = {
                // 写入新文件的内容
                source: function () {
                    return content;
                },
                // 新文件大小(给 webapck 输出展示用)
                size: function () {
                    return content.length;
                }
            }

            // 执行回调,让 webpack 继续执行
            cb();
        })
    }
}

module.exports = FileListPlugin;

 

配置 webpack.config.js:

const path = require('path');
const MyPlugin = require('./plugins/MyPlugin');
const FileListPlugin = require('./plugins/FileListPlugin');

module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js',
        a: './src/a.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new MyPlugin(),
        new FileListPlugin({
            filename: '_filelist.md'
        })
    ]
}

最终效果:

 

4. 常用的 Webpack Plugin

插件名称作用
html-webpack-plugin生成 html 文件,引入公共的 js 和 css 资源
webpack-bundle-analyzer对打包后的文件进行分析,生成资源分析图
terser-webpack-plugin代码压缩,移除 console.log 打印等
HappyPack Plugin开启多线程打包,提升打包速度
Dllplugin动态链接库,将项目中依赖的三方模块抽离出来,单独打包
DllReferencePlugin配合 Dllplugin,通过 manifest.json 映射到相关的依赖上去
clean-webpack-plugin清理上一次项目生成的文件
vue-skeleton-webpack-pluginvue 项目实现骨架屏

5. 参考文章

揭秘webpack plugin | ChampYin's BlogPlugin(插件) 是 webpack 生态的的一个关键部分。它为社区提供了一种强大的方法来扩展 webpack 和开发 webpack 的编译过程。本文将尝试探索 webpack plugin,揭秘它的工作原理,以及如何开发一个 plugin。https://champyin.com/2020/01/12/%E6%8F%AD%E7%A7%98webpack-plugin/

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

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

相关文章

C#,图像二值化(08)——灰度图像二值化,全局算法,全局阈值优化算法及其源代码

1、全局阈值算法 基于灰度直方图的优化迭代算法之一。 Iterative Scheduler and Modified Iterative Water-Filling In the downlink, the inter-cell interference is only function of the power levels and is independent of the user scheduling decisions. This suggest…

俺的2022年

年末将至,还是要写点总结性的内容,以回顾过去一年做的各种事情。工作之外从客观数据上看,今年的收入水平略差于去年,主要是工作外的收入有所减少,其核心原因是没有录制新的课程内容进行变现,原本的计划是&a…

【自学Python】Python介绍

Python教程 什么是编程语言 编程语言(programming language),是用来定义计算机程序的形式语言。它是一种被标准化的交流技巧,用来向计算机发出指令。 也可以说,计算机语言让程序员能够准确地定义计算机所需要使用的…

拓展交流空间,分享开发精彩 | 开发者说·DTalk 鉴赏

日月其迈,岁律更新,时间的洗礼让开发者们更加坚韧,持续探索,不断追求,同样也激励着我们为开发者提供更多的帮助与支持。不断迭代的技术产品是开发者们的趁手工具,定期更新的政策助力打造安全可靠的生态&…

基础数学(4)——线性回归复习

文章目录课程回顾基础知识回归模型的建模过程一元线性回归模型线性回顾进行极大似然估计(例题(必考))极大似然估计极大似然估计的性质线性性无偏性最优性(记住即可,没有推导)方差计算一元线性回…

智能制造 | AIRIOT智慧工厂管理解决方案

工厂生产运转中,设备数量多,环境复杂、企业往往需要承担很高的维修、保养、备件和人力成本。传统的工厂改革遇到了诸多前所未有的挑战: 1、管理系统较多,数据隔离,系统集成困难重重; 2、大量老旧设备无法联…

QT使用log4cpp日志库

文章目录QT使用log4cpp日志库1. 从官网下载log4cpp源码2. 编译项目3. 在QT中使用log4cpp4. log4cpp4.1. Category4.2. Appender4.3. Layout4.4. Priority4.5. 使用宏定义为日志加上文件名 函数名 行号等QT使用log4cpp日志库 1. 从官网下载log4cpp源码 log4cpp官方网址 下载后…

MyBatisPlus ---- 常用注解

MyBatisPlus ---- 常用注解1. TableNamea>问题b>通过TableName解决问题c>通过全局配置解决问题2. Tablelda>问题b>通过TableId解决问题c>TableId的value属性d>TableId的type属性e>雪花算法3. TableFielda>情况1b>情况24. TableLogica>逻辑删除…

LeetCodeday03

203.移除链表元素 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,5] 示例 2&#xff1…

基于springboot+mybatis+mysql+html实现医院预约挂号管理系统

基于springbootmybatismysqlhtml实现医院预约挂号管理系统一、系统简介二、系统主要功能界面2.1登陆2.2首页(留言板、我的预约)--用户2.3就诊预约--用户2.4我的预约--用户2.5我参与的评介--用户2.6我的预约日程--医生2.7对我的评介--医生2.8医生管理--管…

一文探索“预训练”的奥秘!

Datawhale干货 作者:王奥迪,单位:中国移动云能力中心2022年下半年开始,涌现出一大批“大模型”的优秀应用,其中比较出圈的当属AI作画与ChatGPT,刷爆了各类社交平台,其让人惊艳的效果&#xff0c…

[思维模式-19]:《复盘》-7- “积”篇 - 操作复盘- 如何做好复盘

目录 一、联想:复盘的五个误区与七个关键成功要素 1.1 五个误区 1.2 七个关键成功要素 二、复盘的25个“坑”及对策建议 2.1 回顾、评估阶段 2.2 分析、反思阶段 2.3 萃取、提炼阶段 2.4 转化、应用阶段 2.5 复盘引导阶段 三、有效复盘的三项核心技能 3.…

java常见问题处理

文章目录一、前言二、实战演练1、idea常用快捷键使用2、idea设置字体大小3、idea设置背景颜色-背景4、idea配置Maven5、idea中配置JDK6、idea中java.util变红报错IDEA中Sources、JavaDocs路径是红色的7、idea中使用mybatisPlus 自增主键失效,自增主键超大小知识点8、…

ZI-data RO-data RW-data Code BSS DATA

KEIL MDK 查看代码量、RAM使用情况--RO-data、RW-data、ZI-data的解释(转) - 酒醉的Tiger - 博客园源:KEIL MDK 查看代码量、RAM使用情况--RO-data、RW-data、ZI-data的解释KEIL RVMDK编译后的信息Program Size: Code86496 RO-datahttps://ww…

STM32MP157驱动开发——Linux 音频驱动

STM32MP157驱动开发——Linux 音频驱动一、简介1.CS42L51 简介2.I2S总线3.STM32MP1 SAI 总线接口二、驱动开发1.音频驱动1)修改设备树i2c 接口:1.8v电源管理:2)SAI 音频接口设备树3)sound 节点2.使能和修改内核的 CS42…

linux系统中字符设备驱动开发方法

大家好,今天主要和大家聊一聊,linux系统中的字符设备驱动实现的基本过程。 目录 第一:字符设备驱动简介 第二:字符设备驱动开发步骤 第三:编写字符设备驱动实验程序 第一:字符设备驱动简介 字符设备是Li…

130道基础OJ编程题之: 58 ~ 67 道

130道基础OJ编程题之: 58 ~ 67 道 文章目录130道基础OJ编程题之: 58 ~ 67 道0. 昔日OJ编程题:58. BC61 金字塔图案59. BC62 翻转金字塔图案60. BC63 菱形图案61. BC64 K形图案62. BC65 箭形图案63. BC66 反斜线形图案64. BC67 正斜线形图案65. BC68 X形图案66. BC69 空心正方形…

电商大促话术

每逢节日,各大电商平台为了迎合节日气氛,会有各种大促活动,客户咨询量都会较平日有所增加,为了接待更多的客户,客服要掌握一定的电商大促话术。 前言 每逢节日,各大电商平台为了迎合节日气氛,会…

Python--数据容器

文章目录一、数据容器数据容器特点对比二、序列三、列表(list)3.1、列表定义:3.2、列表下标索引3.3、列表常用方法:3.4、list遍历四、元组(tuple)4.1、元组定义4.2、元组的常用方法4.3、元组的遍历五、字符串(str)5.1、字符串定义5.2、常用方法5.3、字符…

Unity 3D 资源下载 || Unity 3D 综合案例

Unity 3D 资源下载 你也可以在 Unity 3D 中执行 Window → Asset Store 菜单命令直接访问 Unity 资源商店(Asset Store)。 Unity 资源商店简介 Unity 资源商店https://www.assetstore.unity3d.com/ 中提供了多种类的游戏媒体资源(人物模型…