[Webpack] 打包优化 二

news/2025/3/16 17:09:06/文章来源:https://www.cnblogs.com/xiaobai-y/p/18775262
 

optimization

optimization这个配置项内部的很多优化策略都是在生产环境默认开启的,启用这些规则从一定程序上来讲可以显著减少 bundle 体积,优化代码生成,帮助长期缓存等。

optimization.minimize

optimization.minimize指定 webpack 默认使用 terser-webpack-plugin 来压缩 JS 代码,或者使用其它在optimization.minimizer定义的插件。在生产环境下,这个配置项默认是true

optimization.minimizer

optimization.minimizer指定一个插件数组,其中包含使用的插件配置项。

比如我们希望将css文件压缩,可以使用插件CssMinimizerWebpackPlugin

CssMinimizerWebpackPlugin

pnpm add css-minimizer-webpack-plugin -D

注意:该插件需要联合MiniCssExtractPlugin一起使用才有意义

optimization: {minimizer: [new CssMinimizerPlugin(),"..." //使用 '...' 来访问默认值,如果没有默认值terser就会失去意义],
},

我们也可以自定义单独指定一个针对terser-webpack-plugin的配置项,比如,默认webpack5使用terser打包之后会多出一些LICENSE的txt文件。

如果希望去除这些文件,可以单独配置terser-webpack-plugin

const TerserPlugin = require('terser-webpack-plugin');optimization: {minimize: true,minimizer: [new CssMinimizerPlugin(),new TerserPlugin({parallel: true, // 开启多进程并行压缩terserOptions: {format: {comments: false, // 去掉注释},},extractComments: false, // 不将注释提取到单独的文件中}),],
}

console.log删除

new TerserPlugin({parallel: true, // 开启多进程并行压缩terserOptions: {compress: {drop_console: true,drop_debugger: true,pure_funcs: ["console.log", "console.error"]},format: {comments: false, // 去掉注释},},extractComments: false, // 不将注释提取到单独的文件中
}),

不过需要注意一个问题,删除console.log以及去掉注释,这些配置和devtoolsource-map的一些配置产生冲突,比如:如果你设置为devtool: 'eval-cheap-module-source-map',那么TerserPluginconsole.log等删除以及注释的删除将不起作用,因为devtool: 'eval-cheap-module-source-map'本身就会产生文件内的注释

performance

performance是 webpack 内置的负责进行打包性能评估的配置,它提示当前项目内部可能需要进行优化的点,将提示信息输出在控制台。默认如果一个资源超过 250kb,webpack 会在打包的时候输出到控制台提示信息。

如果将performance直接设置成false,就可以禁用它。其他的的配置项还有:

  • performance.hints:指定 webpack 是否在控制台打印性能提示信息,只有三个可选值:
    • warning:仅作为提示
    • error:把提示信息当成错误来输出
    • false:禁用性能提示信息
  • performance.maxEntrypointSize:配置提示入口文件的大小的限制,默认是250000字节,当入口文件超过这个限制就会显示性能提示
  • performance.maxAssetSize:当 webpack 生成的 bundle 等文件超过performance.maxAssetSize指定的大小时,就会发出性能提示信息,默认是250000字节。

cache

缓存生成的 webpack 模块和 chunk,来改善构建速度。cache 会在开发模式默认开启被设置成 type: 'memory' 。

我们可以配置cache: filesystem,来缓存生成的 webpack 模块和 chunk,改善构建速度。

cache: {type: 'filesystem',buildDependencies: {config: [__filename], // 依赖的模块版本发生改变时重建缓存内容},
},

文件默认生成在node_modules/.cache/webpack,可以通过cacheDirectory属性修改,不过一般不会改动

这样可以明显加快webpack5的构建速度

注意:如果构建的时候出现错误,特别是一些过期插件导致的问题。修改错误之后。建议手动删除.cache下的缓存文件,再重新进行构建。不然可能会由于读取缓存文件,导致继续出现之前的错误。

生产环境首次构建

生产环境生成cache之后

开发环境首次运行

开发环境生成cache之后

虽然webpack5 cache开发环境和生产环境都能生效,不过开发环境效果更好,如果生产环境是在CI/CD自动化持续集成/部署下开发。例如:项目使用Jenkins打包并构建镜像,cache将失去意义,毕竟每次构建镜像都相当于第一次。不过一般情况下,本机开发效果还是非常明显的。

模块解析规则优化

rule.include 和 rule.exclude

在使用 loader 的时候通过include或者exclude属性传入必要的路径和文件,避免全局匹配,缩小Loader对文件的搜索范围,避免不必要的转译,可以提升 webpack 构建的速度。

babel-loader忽略node_modules内部的模块

{test: /\.m?jsx?$/,exclude: /node_modules/,use: [{loader: "babel-loader",options: {cacheDirectory: true, // 开启babel编译缓存cacheCompression: false, // 缓存文件不要压缩},}]
},

在匹配图片文件的时候,指定具体的文件夹

{test: /\.(png|jpe?g|gif|webp|avif)(\?.*)?$/, include: path.resolve(__dirname, '../src/assets/images'), //仅包含图片文件夹type: "asset", // webpack5内置的通用资源处理模块,默认8kb以下的资源会被转换成base64位parser: {dataUrlCondition: {maxSize: 8 * 1024, // 小于8kb转base64资源内联}},generator:{ filename:'images/[name].[contenthash:6][ext]', // 输出到images目录下},
},

cache缓存副本

配置cache缓存Loader对文件的编译副本,很多Loader/Plugin都提供一个可用编译缓存的选项,通过包括cache前缀,比如babel-loadereslint-webpack-plugin

// babel-loader
{test: /\.m?jsx?$/,exclude: /node_modules/,use: [{loader: "babel-loader",options: {cacheDirectory: true, // 开启babel编译缓存cacheCompression: false, // 缓存文件不要压缩},}]
},// eslint-webpack-plugin
new ESLintPlugin({extensions: ['.js','.jsx','.vue','.ts','.tsx'],// 指定检查文件的根目录context: path.resolve(__dirname, "../src"),exclude: "node_modules", // 默认值cache: true, // 开启缓存// 缓存目录cacheLocation: path.resolve(__dirname,"../node_modules/.cache/.eslintcache"),
}),   

Resovle.alias

配置别名,不单单可以简化模块引入,其实也可以定向指定所需文件路径,提高文件搜索速度

resolve: {alias: {'@': path.resolve(__dirname, '../src'), // @ 代表 src 目录vue$: 'vue/dist/vue.runtime.esm-bundler.js' }
},

Resovle.extensions

解析的文件类型列表,引入文件时不需要加后缀

resolve: {// 引入文件时不需要加后缀。// 这里只配置ts,js,json和vue, 其他文件引入都要求带后缀,可以稍微提升构建速度extensions: ['.ts', '.js', '.json','.vue'],
},

thread-loader

thread-loader是 webpack 团队提供的一个 webpack loader.

thread-loader利用的是 nodejs 的worker pool,也就是nodejs的事件轮询机制;当使用 node 启动 webpack 打包程序的时候,webpack 主程序会运行在事件循环的主线程上,还有 worker pool 负责处理高成本的任务。

安装

pnpm add thread-loader -D

使用

在其它 loader 的配置前面添加thread-loader,则它们将会在一个 worker pool 中运行。每个 worker 都是一个独立的 node.js 进程,开启 worker 本身会产生额外开销的,每一个 worker 都会产生大于600ms的延迟。也就是说,如果构建时间小于600ms,开启worker pool 就没有意义了,得不偿失

module: {rules: [{test: /\.m?jsx?$/,exclude: /node_modules/,use: [{loader: "thread-loader",},......]},......]
}

区分环境

在开发过程中,切忌在开发环境使用生产环境才会用到的工具,如在开发环境下,应该排除 [fullhash]/[chunkhash]/[contenthash] 等工具。

同样,在生产环境,也应该避免使用开发环境才会用到的工具,如 webpack-dev-server 等插件

const nodeEnv = process.env.NODE_ENV;
const isProduction = nodeEnv === 'production';module.exports = {entry: path.resolve(__dirname, '../src/index.ts'), // 入口文件output: {path: path.resolve(__dirname, '../dist'), // 打包后的目录filename: isProduction ? 'js/[name].[chunkhash:6].js' : 'js/[name].js', chunkFilename: isProduction ? 'js/[name].[chunkhash:8].js' : 'js/[name].chunk.js', },plugins: [......isProduction && new MiniCssExtractPlugin({// 定义输出文件名和目录filename: "css/[name].[contenthash:6].css",}),]
}

作用提升

作用提升可以分析模块间的依赖关系,把打包好的模块合并到一个函数中,好处是减少函数声明与内存花销。

webpack中只需将打包环境设置为生产环境就能让作用提升生效,或显式设置concatenateModules

直接生产环境即可:
mode: "production"or显示设置:
optimization: {concatenateModules:true,
}

合理配置 externals

externals可以直接将某些模块在打包过程中剔除,这样减少 webpack 打包时候的工作量,从而加快构建速度。而且一般配置了externals,我们都是和CDN配合使用。

externals: {"vue": "Vue","vue-router": "VueRouter","element-plus": 'ElementPlus',"@vueuse/core": "VueUse","echarts": "echarts","vue-echarts": "VueECharts",
}
<linkrel="stylesheet"href="https://cdn.jsdelivr.net/npm/element-plus@2.3.12/dist/index.min.css"/><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.1/dist/csp/style.min.css"><script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vue-router@4.2.4/dist/vue-router.global.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@vueuse/shared@10.4.1/index.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@vueuse/core@10.4.1/index.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/element-plus@2.3.12/dist/index.full.min.js"></script><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.1/dist/index.umd.min.js"></script>

这里配置的key一般是代码导入语句中import xxx from 'vue',也就是from后面的值,value其实就是引入CDN之后,挂载到window上的对应的全局变量

注意:现实开发中,应该避免使用免费的CDN

另外,externals的模块如果还和其他模块有关联,要么全部externals,要么就都不要,因为挂载到window上的对象在使用上和node_modules中的使用情况容易不一致

DllPlugin 动态链接库

DllPlugin 与 externals 的作用相似,都是将依赖抽离出去,节约打包时间。区别是 DllPlugin 是将依赖单独打包,这样以后每次只构建业务代码,而 externals 是将依赖转化为 CDN 的方式引入。并且同时还大幅度提升了构建的速度。"DLL" 一词代表微软最初引入的动态链接库

当公司没有很好的 CDN 资源或不支持 CDN 时,就可以考虑使用 DllPlugin ,替换掉 externals。

或者公司使用CI/CD持续集成/部署的时候,希望提升构建速度,可以考虑使用 DllPlugin

使用DllPlugin大致分为四步:

1、创建定制生成dll文件的 webpack.dll.config.js 配置文件

const webpack = require("webpack");
const path = require("path");module.exports = {// 需要抽离的依赖entry: {vendor: ["vue","vue-router","element-plus","echarts","@vueuse/core","vue-echarts",],},mode: "production",output: {filename: "[name].dll.js", // 输出路径和文件名称library: "[name]", // 全局变量名称,其他模块会从此变量上获取里面模块path: path.resolve(__dirname, "../dll"), // 输出目录路径(这里定义的是根目录下的dll文件夹)},plugins: [new webpack.DllPlugin({name: "[name]", // 全局变量名称:减小搜索范围,与output.library结合使用path: path.resolve(__dirname, "../dll/[name]-manifest.json"), // 输出目录路径}),],
};

2、配置 package.json 脚本

"scripts": {......"build:dll":"webpack -c build/webpack.dll.config.js"
},

3、使用 CopyPlugin 插件,将生成的dll相关文件拷贝到最终打包的目录

new CopyPlugin({patterns: [{from: path.resolve(__dirname, '../dll'), // 生成的dll文件夹to: path.resolve(__dirname, '../dist/dll'), // 最终打包目录toType: 'dir'},......],
}),

4、使用 DllReferencePlugin 将打包生成的 dll 文件,引用到需要的预编译的依赖上来,并通过 html-webpack-tags-plugin 在打包时自动插入 dll 文件

安装插件

pnpm add html-webpack-tags-plugin -D

生产环境配置

const webpack = require('webpack');
const path = require('path');
const HtmlWebpackTagsPlugin = require('html-webpack-tags-plugin');const prodConfig = {mode: 'production',plugins: [  new webpack.DllReferencePlugin({manifest: path.resolve(__dirname,'../dll/vendor-manifest.json') // manifest文件路径}),new HtmlWebpackTagsPlugin({append: false, // 在生成资源后插入publicPath: '/', // 使用公共路径tags: ['dll/vendor.dll.js'] // 资源路径}),],......
}

Tree shaking

Tree shaking 的作用:消除无用的 JS 代码,减少代码体积

export const targetType = (target) => {return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
}export const deepClone = (obj) => { if(typeof obj !== 'object' || obj === null) {return obj}const result = Array.isArray(obj) ? [] : {}for (let key in obj) {if (obj.hasOwnProperty(key)) {result[key] = deepClone(obj[key])}}return result;
}

项目中只使用了 targetType 方法,但未使用 deepClone 方法,项目打包后,deepClone 方法不会被打包到项目里

tree-shaking 原理

依赖于 ES6 的模块特性,ES6 模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是 tree-shaking 的基础

静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。ES6 之前的模块化,比如 CommonJS 是动态加载,只有执行后才知道引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 tree-shaking 成为可能

合理的使用 Tree shaking

并不是说所有无用的代码都可以被消除,还是上面的代码,换个写法 tree-shaking 就失效了

// util.js
export default {targetType(target) {return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();},deepClone(target) {return JSON.parse(JSON.stringify(target));}
};// 引入并使用
import util from '../util';
util.targetType(null);

export default 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree-shaking 只对使用 export 导出的变量生效

比如,我们在项目中引入lodashlodash-es,同样都是引入debounce函数的情况下,结果是完全不一样的。

pnpm add lodash lodash-es
pnpm add @types/lodash @types/lodash-es -D
// import {debounce} from 'lodash';
import {debounce} from 'lodash-es';
debounce(() => {console.log('debounce')
}, 1000)();

lodash是使用commonjs规范编写的,并且打包的规范是umd,简单来说,无论怎么样,如果我们引入了lodash,那么就会引入lodash中的全部内容。而lodash-es本身就是ES module模块规范编写的,这样就会很方便的帮助我们Tree shaking

图片的优化

平常大部分性能优化工作都集中在 JS 方面,但图片也是页面上非常重要的部分

特别是对于移动端来说,完全没有必要去加载原图,浪费带宽。如何去压缩图片,让图片更快的展示出来,有很多优化工作可以做

图片的动态裁剪

很多云服务,比如阿里云或七牛云,都提供了图片的动态裁剪功能,效果很棒,使用也非常简单,只需在图片的url地址上动态添加参数即可。唯一的确点就是要花钱。虽然有免费的次数,看着有好几百次免费,不过在现实的线上环境就是瞬间的事情...

图片瘦身前后对比:

瘦身前:310KB

http://image.yanhongzhi.com/record/2.jpg

瘦身后:16.6KB

http://image.yanhongzhi.com/record/2.jpg?imageMogr2/thumbnail/300x300

第三方图片处理网站

image-minimizer-webpack-plugin

如果希望自己在项目中自动优化图片大小,可以使用这个插件,由于需要压缩不同的图片,因此插件还需要依赖不同图片的压缩算法

pnpm add image-minimizer-webpack-plugin -D
pnpm add imagemin imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");......
optimization: {minimize: true,minimizer: [......new ImageMinimizerPlugin({minimizer: {implementation: ImageMinimizerPlugin.imageminGenerate,options: {plugins: [["gifsicle", { interlaced: true }],["jpegtran", { progressive: true }],["optipng", { optimizationLevel: 5 }],["svgo",{plugins: ["preset-default","prefixIds",{name: "sortAttrs",params: {xmlnsOrder: "alphabetical",},},],},],],},},}),],
}

Gzip 压缩

线上的项目,一般都会结合构建工具 webpack 插件和服务端配置 Nginx,来实现 http 传输的 gzip 压缩,目的就是把服务端响应文件的体积尽量减小,优化返回速度。

Gzip的设置,Nginx服务器就能独立完成,这对于Nginx服务器就是几行配置的问题,就能自动的将适合类型和大小的文件转换成Gzip。这就是所谓的Nginx动态压缩:都是普通文件,请求来了再压缩,然后返回给前端

当然,也可以前端或者后端人员,将文件提前压缩好,就保存在服务端,需要用的时候直接返回,这就避免了每次请求响应的时候都要压缩的问题,这个其实就是Nginx静态压缩:提前把文件压缩成 .gz 格式,请求来了,直接返回即可

对于前端来说,可以使用压缩插件:compression-webpack-plugin

pnpm add compression-webpack-plugin -D
const CompressionPlugin = require('compression-webpack-plugin');......
plugins: [......new CompressionPlugin({algorithm: "gzip", // 压缩算法,默认gzip,也可以是brotliCompresstest: /\.(js|css)(\?.*)?$/i, //需要压缩的文件正则threshold: 1024, //文件大小大于这个值时启用压缩deleteOriginalAssets: true //压缩后是否删除原文件})
]

注意:但使用 gzip 压缩的最大错误之一,就是用 gzip 压缩页面中的每个资源文件。这里定义threshold比较小仅仅是为了测试需要

当然,压缩成gz文件之后,我们不能再通过之前那样,使用简单的server服务,需要服务器对gz资源进行处理。这里封装了一个简单的服务器,为了方便处理gz静态资源,使用了sirv插件

pnpm add sirv -D

server/app.cjs

const http = require('http')
const path = require('path')
const fs = require('fs')
// 提供静态文件服务
const sirv = require('sirv')const defaultWD = process.cwd()const publicPath = path.join(defaultWD, 'dist')const assets = sirv(publicPath, { gzip: true, brotli: true })function createServer() {const server = http.createServer()server.on('request', (req, res) => {assets(req, res, () => {res.statusCode = 404res.end('File not found')})})server.listen(8080, () => {const { port } = server.address()console.log(`server run on http://localhost:${port}`)})
}function main() {if (!fs.existsSync(publicPath)) throw new Error('Please check your\'re already run \'npm run build\'')createServer()
}main()

package.json

"scripts": {......"server": "node ./server/app.cjs"
},

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

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

相关文章

坐标转换软件 | CGCS2000 经纬度坐标 与 高斯克吕格3度投影平面坐标的互相转换

软件下载地址: 链接: https://pan.baidu.com/s/1ySwut1CpFLTSkGTVy792Fw?pwd=dyzj 提取码: dyzj 关于软件软件介绍:Fast坐标转换软件 是一款专为地理信息系统(GIS)和测绘领域设计的实用工具,旨在实现CGCS2000经纬度(地理坐标)与高斯-克吕格3度投影平面坐标之间的快速、精确转…

可持久化线段树(主席树)学习笔记

可持久化线段树(主席树)学习笔记主席树求解的问题一般是区间历史求值的问题,即对一些点进行操作后,求其中一个版本的答案 最朴素的方法就是每一次操作都将原线段树复制一遍,但是这样很费空间,而且产生了大量无用的点 但是可以发现,每次修改时所走的路径其实只经过了根到对…

FBI树

FBI树 题目描述 我们可以把由“0”和“1”组成的字符串分为三类:全“0”串称为B串,全“1”串称为I串,既含“0”又含“1”的串则称为F串。 FBI树是一种二叉树,它的结点类型也包括F结点,B结点和I结点三种。由一个长度为2N的“01”串S可以构造出一棵FBI树T,递归的构造方法如…

学嵌入式C语言,看这一篇就够了(4)

C语言的输入输出 C语言标准在发布的同时,ANSI组织同时也一起发布了和C语言相关的函数库,也就是标准C库,标准C库集成了很多的API函数接口,比如常用的输入和输出函数就是标准C库提供的 用户如果打算使用标准C库的函数,就必须要包含函数库对应的头文件,比如输入输出函数对应…

数字三角形最大路径和

1 #include <iostream>2 #include <vector>3 #include <algorithm>4 using namespace std;5 6 // 自顶向下的方式7 pair<int, vector<int>> maximumTotal(vector<vector<int>>& triangle) {8 int n = triangle.size();9 …

查询实战

统计员工性别并返回数据: select if(gender=1,man,women) 性别,count(*) from tb_emp group by gender;统计员工职位并返回数据: selectcase job when 1 then 班主任when 2 then 讲师when 3 then 学工主管when 4 then 教研主管else 未分配 end 职位,count(*) from tb_emp group…

grpc使用postman测试-问题集合

问题1:postman中proto文件import问题 Unresolved "import" directives We could not find some of the files imported by the .proto file. Specify import paths to those unresolved files using the options below.解决方法: 项目结构如下 - code |- pbentity…

Qt利用QScrollArea和QLabel实现滚轮完整显示OpenCV图片大小

解决三个常见问题,满足大部分UI界面需求。1、加载图像尺寸过大(大于窗口尺寸),在窗口自适应缩放显示大小,不改变宽高比例。2、加载图像尺寸小于窗口尺寸,使图像对齐居中显示在窗口。3、加载图像尺寸过大(大于窗口尺寸),不对图像进行缩放显示,而是在窗口产生滚轮来显示…

MYSQL-DQL操作

基本查询:查询特定列: select name,entrydate from tb_emp; 查询所有: select * from tb_emp; 查询并起别名: select name as bbb,entrydate as aaa from tb_emp;(as可省略) 去除重复记录: select distinct job from tb_emp; 条件查询:点击查看代码 select * from tb_emp where…

第二章练习题

2.1 点击查看代码 TempStr = input("请输入带有符号的温度值:") if isinstance(TempStr, str) and TempStr[-1] in [F, f]:C = int((float(TempStr[0:-1]) - 32) / 1.8)print(f"转换后的温度是{C}C") elif isinstance(TempStr, str) and TempStr[-1] in […

DBeaver Ultimate Edtion 25.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具

DBeaver Ultimate Edtion 25.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具DBeaver Ultimate Edtion 25.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具 One tool for all data sources 请访问原文链接:https://sysin.org/blog/dbeaver/ 查看最新版。原…

book_77_78作业

第2章程序练习题 2.1改造温度转换的输入(不会) 2.2汇率转换 2.3重量转换 2.4彩色蟒蛇 2.5等边三角形 2.6叠加等边三角形 2.7六角形 2.8正方形螺旋