webpack进阶 -- 自定义Plugin,Loader封装打包优化

介绍

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。在 Webpack 处理应用程序时,它会在内部构建一个依赖图(dependency graph),这个依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle。在这个过程中,Webpack 可以通过使用 loaderplugin 来扩展其功能。

Loader

Loader 让 Webpack 能够去处理那些非 JavaScript 文件(Webpack 自身只理解 JavaScript)。Loader 可以将所有类型的文件转换为 Webpack 能够处理的有效模块,然后你就可以利用 Webpack 的打包能力,对它们进行处理。简单来说,loader 用于对模块的源代码进行转换。Loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于"任务运行者",并且可以将文件从不同的语言(如 TypeScript 转换为 JavaScript)转换为 JavaScript,或将内联图像转换为 data URL。Loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

Plugin

Plugin 用于 bundle 文件的优化,资源管理和环境变量注入等一系列的任务。作用于整个构建过程,插件可以用来执行范围更广的任务,如打包优化、资源管理和环境变量的注入。简单来说,插件可以用于执行任何其他 loader 无法完成的任务。它们直接作用于整个构建过程,从打包优化和压缩,一直到重新定义环境中的变量等。

Loader 和 Plugin 的区别
  • Loader 用于转换某些类型的模块,它们作用于单个文件。
  • Plugin 影响整个构建过程,能够执行更广泛的任务,比如打包优化、资源管理和环境变量注入。
example

Loader 示例:使用 babel-loader 来转换 ES6+ 的代码到 ES5。

module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env']}}}]
}

Plugin 示例:使用 HtmlWebpackPlugin 来生成一个 HTML 文件,并自动注入打包后的脚本。

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {plugins: [new HtmlWebpackPlugin({template: './src/index.html'})]
};

Webpack 的强大之处在于其高度的可配置性,通过合理地使用 loaderplugin,你可以极大地提升开发效率和项目的性能。

自定义Loader

concept

loader本质上是一个函数,loader的执行顺序在use数组里面是从下往上执行,里面有一个pitch方法,use数组中pitch方法的执行顺序是从上往下执行,因此我们如果想先执行某些功能,可以先在pitch方法中定义

在 Webpack 的 loader 文件中,this 关键字指的是 loader 上下文(loader context)。Webpack 为每个 loader 提供了一个上下文环境,这个环境包含了很多有用的方法和属性,允许开发者在 loader 中与 Webpack 的构建过程进行交互。
Loader 上下文中的一些常用属性和方法包括:
this.callback: 一个允许 loader 异步返回结果的函数。当你需要异步处理内容时,可以使用这个方法。
this.async: 一个函数,当调用时,会返回 this.callback 函数,用于异步模式。
this.resourcePath: 当前正在处理的文件的绝对路径。
this.query: loader 的选项对象。如果你在 webpack 配置中对某个 loader 传递了选项,这些选项会通过 this.query 访问到。
this.data: 在 pitch 阶段和普通阶段之间共享的数据对象。
this.loaders: 当前处理文件所使用的 loader 数组。
this.emitFile: 允许在输出目录中生成文件的方法。

example

假设你正在编写一个简单的 loader,它的作用是将文件内容转换为大写

module.exports = function(source) {// 使用 this.context 访问 loader 上下文const callback = this.async(); // 如果需要异步处理,获取异步回调函数if (!callback) {// 同步处理return source.toUpperCase();} else {// 异步处理setTimeout(() => {callback(null, source.toUpperCase());}, 1000);}
};

在这个例子中,我们使用了 this.async 来获取一个回调函数,用于异步返回处理结果。这是 this 在 loader 中的一个常见用法。

自定义babel-loader

创建校验规则 babelSchema.json

{"type": "object","properties": {"presets": {"type": "array"}},"addtionalProperties": true
}

创建loader babelLoader.js

const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');
const babel = require('@babel/core');
const util = require('util');const babelSchema = require('./babelSchema.json');// babel.transform用来编译代码的方法
// 是一个普通异步方法
// util.promisify将普通异步方法转化成基于promise的异步方法
const transform = util.promisify(babel.transform);module.exports = function (content, map, meta) {// 获取loader的options配置const options = getOptions(this) || {};// 校验babel的options的配置validate(babelSchema, options, {name: 'Babel Loader'});// 创建异步const callback = this.async();// 使用babel编译代码transform(content, options).then(({code, map}) => callback(null, code, map, meta)).catch((e) => callback(e))}

babelLoader使用 webpack.config.js

const path = require('path');module.exports = {module: {rules: [{test: /\.js$/,loader: 'babelLoader',options: {presets: ['@babel/preset-env']}}]},// 配置loader解析规则:我们的loader去哪个文件夹下面寻找(这里表示的是同级目录的loaders文件夹下面寻找)resolveLoader: {modules: ['node_modules',path.resolve(__dirname, 'loaders')]}}
warn

当编写 loader 时,应避免使用箭头函数定义 loader,因为箭头函数不绑定自己的 this,这会导致无法访问 loader 上下文。

自定义plugin

concept

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。在Webpack的工作过程中,它提供了一系列的钩子(hooks)来让开发者在构建流程的不同阶段介入,从而可以自定义构建的行为。Webpack 使用了 Tapable 来实现这些钩子系统。Tapable 是一个类似于 Node.js 的 EventEmitter,用于自定义事件的发布和订阅,但它专为同步和异步的钩子设计。

Webpack 的编译器(Compiler)和编译(Compilation)对象提供了许多钩子,以下是一些常用的编译器钩子:

Compiler Hooks

Compiler 模块是 webpack 的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自 Tapable 类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler 上注册。

entryOption:在 webpack 选项中的 entry 配置项处理之后,执行插件。

afterPlugins:设置完初始插件之后,执行插件。

afterResolvers:设置完解析器之后,执行插件。

environment:环境准备好之后,执行插件。

afterEnvironment:环境完全准备好之后,执行插件。

beforeRun:在编译器开始读取记录之前,执行插件。

run:开始读取记录之前,执行插件。

emit:生成资源到 output 目录之前,执行插件。

afterEmit:生成资源到 output 目录之后,执行插件。

done:编译完成。

failed:编译失败。

这些钩子可以用于执行各种任务,比如清理/dist目录、生成自定义报告、自动部署等。

compilation Hooks

Compilation 模块会被 Compiler 用来创建新的 compilation 对象(或新的 build 对象)。 compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。
Compilation 类扩展(extend)自 Tapable,Compilation 钩子是在每次构建过程中触发的,它们提供了对编译(compilation)对象的访问,这个对象包含了当前的模块资源、编译生成资源、变化的文件等信息。以下是一些常用的 Compilation 钩子:

buildModule: 在开始构建模块之前触发。

succeedModule: 当一个模块成功构建后触发。

failedModule: 当一个模块构建失败后触发。

finishModules: 当所有模块构建完成后触发。

seal: 在封装之前触发。封装(sealing)是优化和生成最终资源之前的一个步骤。

optimize: 在优化阶段开始时触发。

emit: 在生成资源到 output 目录之前触发,可以用来访问和修改输出资源。

afterEmit: 在生成资源到 output 目录之后触发。

done: 在编译完成后触发。

example

要在插件中使用这些钩子,你需要在插件类的 apply 方法中订阅它们。例如,如果你想在每个模块构建完成后做一些事情,你可以这样做
模块构建成功提示插件

class MyPlugin {apply(compiler) {compiler.hooks.compilation.tap('MyPlugin', (compilation) => {compilation.hooks.succeedModule.tap('MyPlugin', (module) => {console.log(`模块构建成功: ${module.identifier()}`);});});}
}

在这个例子中,MyPlugin 是一个自定义插件,它在每个模块构建成功后打印模块的标识符。Webpack 的插件系统和钩子机制非常强大,允许开发者高度自定义构建过程。理解和正确使用这些钩子对于开发高效、可维护的 webpack 插件至关重要。

打包检测非法字符插件

// 检测非法字符的插件
class CheckBetaEnvPlugin {constructor(options) {this.options = options || {};}apply(compiler) {compiler.hooks.emit.tapAsync('CheckBetaEnvPlugin', (compilation, callback) => {// 遍历所有即将输出的资源Object.keys(compilation.assets).forEach((filename) => {const source = compilation.assets[filename].source();// 检查资源内容是否包含beta环境字段if (source.includes(this.options.searchText)) {console.warn(`警告: 文件 ${filename} 包含beta环境字段: ${this.options.searchText}`);}});callback();});}}module.exports = CheckBetaEnvPlugin;
module.exports = {plugins: [new CheckBetaEnvPlugin({searchText: 'beta-api'})]
}

它会在Webpack的emit阶段检查所有即将输出的文件,寻找非法字符(例如,我们这里假设beta-api是我们要检测的)

warn

使用钩子时,确保你的插件逻辑不会意外地影响到构建过程的性能和结果。钩子的使用和订阅需要遵循 webpack 的插件开发规范。钩子的异步版本通常以 Async 结尾,使用它们时需要特别注意异步逻辑的处理。

webpack打包优化

打包流程
  1. 初始化(Initialization)
    配置解析:Webpack 启动后会读取配置文件(默认是 webpack.config.js),合并命令行传入的参数,形成最终的配置对象。
    插件初始化:根据配置初始化插件,插件可以监听Webpack打包过程中的各个节点,执行相应的任务。
  2. 编译(Compilation)
    创建编译对象:根据配置初始化一个 compiler 对象,它包含了Webpack环境所有的配置信息,这个对象在Webpack启动时被一次性创建,并被用于后续的编译。
    确定入口:根据配置中的 entry 属性,找出所有的入口文件。
  3. 构建模块(Building)
    从入口开始解析:对每一个入口和其依赖进行递归解析。
    加载模块:根据模块类型,调用对应的 loader 对模块进行转换,比如将 ES6 转换为 ES5,将 LESS/SASS 转换为 CSS。
    模块转换:在加载模块的过程中,应用各种 Loader 对模块进行转换,最终转换为 JavaScript 代码。
  4. 完成模块编译并输出(Compilation & Output)
    构建模块关系图:根据入口文件和模块之间的依赖关系,构建出一个模块关系图。获取依赖关系是通过babel处理应用代码为抽象语法书(AST),不断递归获取依赖关系图。
    输出资源:根据入口和模块之间的依赖关系,组合成一个个包含多个模块的 Chunk,再将每个 Chunk 转换成一个单独的文件加入到输出列表,这个过程称为 chunking。
    写入文件系统:根据配置中的 output 属性,将文件内容写入到文件系统中。
  5. 完成(Finish)
    插件执行更多任务:在整个编译过程结束后,插件可以继续执行一些任务,比如优化、压缩等。
    完成打包:至此,Webpack 完成了整个打包过程。
    Webpack 的打包流程是高度可配置的,通过 Loader 和 Plugin,开发者可以灵活地控制每一步的具体行为,以适应不同项目的需求。
优化方式

1 减少解析的文件和目录(减少不必要的工作)缩小文件范围Loader

优化loader配置
test、include、exclude三个配置项来缩⼩loader的处理范围
推荐include

rules: [{test: /\.css$/,include: path.resolve(__dirname, "./src"),use: ["style-loader", "css-loader"],},{test: /\.less$/,include: path.resolve(__dirname, "./src"),use: [// "style-loader",MiniCssExtractPlugin.loader,{loader: "css-loader",options: {//css modules 开启modules: true,},},{loader: "postcss-loader",},"less-loader",],},{test: /\.(png|jpe?g|gif)$/,include: path.resolve(__dirname, "./src"),use: {loader: "url-loader",options: {name: "[name]_[hash:6].[ext]",outputPath: "images/",//推荐使用url-loader 因为url-loader支持limit//推荐小体积的图片资源转成base64limit: 12 * 1024, //单位是字节 1024=1kb},},},{test: /\.js$/,include: path.resolve(__dirname, "./src"),exclude: /node_modules/,use: {loader: "babel-loader",},},],

2 借助MiniCssExtractPlugin完成抽离css

npm install mini-css-extract-plugin -Dconst MiniCssExtractPlugin = require("mini-css-extract-plugin");{test: /\.less$/,use: [// "style-loader", // 不再需要style-loader,⽤MiniCssExtractPlugin.loader代替MiniCssExtractPlugin.loader,"css-loader", // 编译css"postcss-loader","less-loader" // 编译less]},
plugins: [new MiniCssExtractPlugin({filename: "css/[name]_[contenthash:6].css",chunkFilename: "[id].css"})]

3 Tree Shaking 配合代码分割优化(optimization.splitChunks)

Webpack 的代码分割(Code Splitting)是一种优化技术,旨在将代码拆分成多个小块(chunks),然后按需加载或并行加载这些小块,以减少初始加载时间和提高应用性能。

在webpack配置文件添加splitChunks:

//webpack.config.js
module.exports = {// ...optimization: {splitChunks: {chunks: 'all', // 对所有模块进行优化minSize: 20000, // 生成 chunk 的最小体积(以 bytes 为单位)minChunks: 1, // 在分割之前,模块被引用的最少次数maxAsyncRequests: 30, // 按需加载时的最大并行请求数maxInitialRequests: 30, // 入口点的最大并行请求数automaticNameDelimiter: '~', // 默认情况下,webpack 将使用块的来源和名称生成名称(例如 vendors~main.js)cacheGroups: { // 配置缓存组vendors: {test: /[\\/]node_modules[\\/]/, // 控制此缓存组选择的模块。匹配 node_modules 目录下的模块priority: -10 // 一个模块可以属于多个缓存组。优化将优先考虑具有更高优先级的缓存组。},default: {minChunks: 2, // 覆盖外部配置的 minChunkspriority: -20,reuseExistingChunk: true // 如果当前块包含已从主束拆分的模块,则将重用它而不是生成新的块。}}}}
};

chunks: 指定哪些 chunks 将被选中进行优化。可以是 async(只对异步加载的模块进行分割)、initial(只对入口文件进行分割)、all(对所有模块进行分割)。
minSize: 生成的每个 chunk 的最小大小。
minChunks: 分割前必须共享模块的最小 chunks 数。它控制的是每个模块什么时候被抽离出去:当模块被不同entry引用的次数大于等于这个配置值时,才会被抽离出去。它的默认值是1。也就是任何模块都会被抽离出去(入口模块其实也会被webpack引入一次)。
maxAsyncRequests: 按需加载时的最大并行请求数。
maxInitialRequests: 入口点的最大并行请求数。
automaticNameDelimiter: 生成的文件名的分隔符。
cacheGroups: 缓存组可以继承和/或覆盖来自 splitChunks的任何选项。每个缓存组都有自己的配置,可以细粒度地控制如何分割代码。cacheGroups是splitChunks配置的核心,对代码的拆分规则全在cacheGroups缓存组里配置。
缓存组的每一个属性都是一个配置规则
priority:priority属性的值为数字,可以为负数。作用是当缓存组中设置有多个拆分规则,而某个模块同时符合好几个规则的时候,则需要通过优先级属性priority来决定使用哪个拆分规则。

4 压缩css,压缩Html等

npm install cssnano -D
npm i optimize-css-assets-webpack-plugin -D
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");new OptimizeCSSAssetsPlugin({cssProcessor: require("cssnano"), //引⼊cssnano配置压缩选项cssProcessorOptions: {discardComments: { removeAll: true }}
})

其他优化方式

使⽤externals优化cdn静态资源

我们可以将⼀些JS⽂件存储在CDN上(减少Webpack打包出来的js体积),在index.html中通过标签引⼊,如果用标签引入但是我们希望在使⽤时,仍然可以通过import的⽅式去引⽤(如:import $ from ‘jquery’ ),并且希望webpack不会对其进⾏打包,此时就可以配置 externals 。

//webpack.config.js
module.exports = {//...externals: {//jquery通过script引⼊之后,全局中即有了 jQuery 变量'jquery': 'jQuery'}
}

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

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

相关文章

Docker-compsoe部署prysm-beacon-chain + geth服务(geth版本v1.14.0)

1、创建目录结构 ~ # mkdir -p /data/docker-compose/eth ~ # cd /data/docker-compose/eth /data/docker-compose/eth# mkdir beacondata eth ethdata prysm2、编写prysm-beacon-chain Dockerfile和启动脚本文件 /data/docker-compose/eth# vim Dockerfile /data/docker-…

Git的系统级设置

一、如何查看当前系统登录的GIt用户信息 1.看全局Git配置文件 cat ~/.gitconfig 2.查看系统级Git配置文件 cat /etc/gitconfig 3.使用Git命令查看配置的用户名和电子邮件地址 git config --global user.name git config --global user.email 4.如果你想查看所有Git配置&…

(delphi11最新学习资料) Object Pascal 学习笔记---第11章第2节 ( 多个接口与方法别名)

11.2.3 多个接口与方法别名 ​ 接口的另一个非常重要的特点是一个类可以实现多个接口。下面的 TAthlete 类就演示了这一点,该类同时实现了 IWalker 和 IJumper 接口: TAthlete class(TInterfacedObject, IWalker, IJumper) privateFJumpImpl: TJumpe…

旅行卡使用秘籍:告别出游烦恼

在现代社会,随着人们生活水平的不断提高,旅游已经成为越来越多人的休闲方式。而为了让旅行更加方便、实惠,各种旅游卡应运而生。然而,很多人在使用旅游卡时,对于其使用规则并不是非常清楚。接下来,我们就来…

如何把公章盖在电子档文件上?

将公章盖在电子档文件上,尤其是确保其法律效力和安全性,通常涉及以下步骤: 准备工作 获取合法的电子公章:确保你拥有公司或机构正式授权的电子公章图像,且该图像经过了必要的加密或数字签名处理,以确保其…

使用海外云手机为亚马逊店铺引流

在全球经济一体化的背景下,出海企业与B2B外贸企业愈发重视海外市场的深耕,以扩大市场份额。本文旨在探讨海外云手机在助力亚马逊店铺提升引流效果方面的独特作用与优势。 海外云手机,一种基于云端技术的虚拟手机,能够在单一硬件上…

Centos系统实用运维命令记录(持续更新)

本文记录Centos服务器常用的运维命令,备忘 查询当前内存占用最高(前10)的进程列表和占用比例,进程ID ps -eo pid,comm,%mem,cmd --sort-%mem | head -n 11注: 内存警报时定位问题时非常有用 查询占用某个端口号的进程id lsof -i :9000注: 后面的9000…

Ai绘画工具Stable Diffusion提示词如何使用

Stable Diffusion是一种基于深度学习的AI绘画工具,它可以根据用户提供的提示词(prompt)生成相应的图像。提示词是引导AI生成图像的关键,正确的使用和调试提示词对于获得满意的结果至关重要。本文将介绍如何使用和调试Stable Diffu…

let命令

let 命令 let 与 var 二者区别: 作用域不同:变量提升(Hoisting):临时性死区重复声明: 联系:举例说明: 块级作用域 块级作用域的关键字使用 var(无块级作用域)…

JavaScript手写专题——图片懒加载、滚动节流、防抖手写

图片懒加载场景:在一些图片量比较大的网站(比如电商网站首页,或者团购网站、小游戏首页等),如果我们尝试在用户打开页面的时候,就把所有的图片资源加载完毕,那么很可能会造成白屏、卡顿等现象&a…

算法练习第五十八天|739. 每日温度、496. 下一个更大元素

739. 每日温度 496. 下一个更大元素 I 每日温度 class Solution {public int[] dailyTemperatures(int[] temperatures) {int length temperatures.length;int[] res new int[length];//保留下标Deque<Integer> stack new LinkedList<>();stack.push(0);for(in…

ICode国际青少年编程竞赛- Python-2级训练场-range函数

ICode国际青少年编程竞赛- Python-2级训练场-range函数 1、 for i in range(4):Dev.turnLeft()Dev.step(i 1)2、 for i in range(4):Spaceship.step(i 1)Spaceship.turnRight()3、 for i in range(4):Spaceship.step(i 1)Dev.step(2)Dev.step(-2)4、 for i in range(1, 5)…

上海市计算机学会竞赛平台2022年1月月赛丙组三角形的判定

题目描述 给定三个正整数 &#x1d44e;a&#xff0c;&#x1d44f;b 与 &#x1d450;c&#xff0c;请判断它们是否能组成一个三角形。 输入格式 第一行&#xff1a;三个整数表示 &#x1d44e;a&#xff0c;&#x1d44f;b 与 &#x1d450;c。 输出格式 如果可以组成一…

【深度学习4】pip、conda换源

pip、conda换源 1. pip换源2. conda 换源 1. pip换源 临时使用镜像源安装&#xff0c;如下所示&#xff1a;some-package 为你需要安装的包名 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ some-package设置pip默认镜像源&#xff0c;升级 pip 到最新的版本 (&…

【深度学习】【Lora训练2】StabelDiffusion,Lora训练过程,秋叶包,Linux,SDXL Lora训练

文章目录 一、如何为图片打标1.1. 打标工具1.1.1. 秋叶中使用的WD1.41.1.2. 使用BLIP21.1.3. 用哪一种 二、 Lora训练数据的要求2.1 图片要求2.2 图片的打标要求 三、 Lora的其他问题qa1qa2qa3qa4qa5 四、 对图片的处理细节4.1. 图片尺寸问题4.2. 图片内容选取问题4.3. 什么是一…

攻击基础-问题

目录 1. windows登录的明文密码&#xff0c;存储过程是怎么样的&#xff0c;密文存在哪个文件下&#xff0c;该文件是否可以打开&#xff0c;并且查看到密文 2. 我们通过hashdump 抓取出 所有用户的密文&#xff0c;分为两个模块&#xff0c;为什么&#xff1f; 这两个模块分别…

2024年3月份宠物行业线上市场数据分析:市场呈现出精细化、品质化趋势

近些年来&#xff0c;养宠物的家庭越来越多&#xff0c;宠物经济也逐渐衍生开来。宠物经济主要是围绕宠物产生的一整条产业链&#xff0c;包括宠物食品、宠物家居、宠物美容和最近火起来的宠物保险等多个领域。目前随着居民人均收入的提高&#xff0c;宠物市场也得到稳步发展。…

【linux】海量小文件的存储方案

在介绍海量文件存储之前&#xff0c;需要先介绍一下常见的系统里面文件是如何存储的 文件inode 在linux下&#xff0c;每个文件或者目录&#xff0c;都会分配一个inode(index node)&#xff0c;它不存储具体的文件内容&#xff0c;而是记录该文件的基础信息。每个inode大小一…

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Slider的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Slider的使用及说明 文章编号&#xff1a;…

Java | Leetcode Java题解之第80题删除有序数组中的重复项II

题目&#xff1a; 题解&#xff1a; class Solution {public int removeDuplicates(int[] nums) {int n nums.length;if (n < 2) {return n;}int slow 2, fast 2;while (fast < n) {if (nums[slow - 2] ! nums[fast]) {nums[slow] nums[fast];slow;}fast;}return sl…