打包优化策略
- 移除未使用的模块:确保开发依赖(
devDependencies)不被误打包。 - 避免重复和无用模块:利用tree-shaking特性,优先选择ES模块格式的依赖。
- 移除特定模块部分:例如,通过
webpack.IgnorePlugin移除moment.js中的多余语言包。 - 按需加载:对于像 Ant Design Vue 这样的大型库,通过手动或自动化工具实现组件的按需导入。
- SplitChunksPlugin:拆分第三方库和公共代码,提升加载速度和利用浏览器缓存。
- 路由懒加载:通过动态导入(
import()语法)实现路由组件的按需加载,减少首屏加载时间。
部署上线优化
- 使用 Nginx 作为静态服务
- 使用 HTTP 缓存 Expires/Cache-Control/Etag/Last-Modified 指令进行缓存优化
- 使用 Gzip/Brotli 对静态文件压缩
- 使用 KeepAlive 保持长连接(HTTP/1同一域名下 TCP 链接限制 7-8个之后需要再开启一个 TCP)
- 使用 HTTP/2 提升传输速度(特点:二进制协议、多路复用、Header首部表压缩...)
vue.comfig.js 个性化构建
在基础配置上,自定义构建的结果-可以使用 vue.config.js 文档地址:cli.vuejs.org/zh/config/#…
简介两个字段 PublicPath 与 css.loaderOptions
PublicPath:部署应用包时的基本 URL,这个配置对应的是 webpack 的 publicPath 属性
- 默认值为:‘/’,Cue Cli 会假设应用被部署在一个域名的跟路径上:abcd.com
- 可以设置为子路径,如果应用被部署在 abcd.com/spa 那么就设置为 ‘/sap’
- 可以设置为 CDN 路径,在应用中,最后静态资源是要全部上传到 CDN 上,(脚手架自动完成),这里可以设置一个 CDN 域名 oss.abcd.com/home
- 可以设置为绝对路径(‘’或者‘/’),这样所有的资源都会被链接为相对路径
//vue.config.js
const isStaging = !!process.env.VUE_APP_STAGIN
const isProduction = process.env.NODE_ENV === 'production'
module.exports = {
//生成环境使用 OSS 地址
//其他环境使用绝对路径
publicPath:(isProduction && !isStaging) ? 'https://oos.abc.com':'/'
}
运行npm run build 打包命令查看生成环境打包结果
css.loaderOptions 属性
- 向 CSS 相关的 loader 传递选项
- Ant-design-vue 的样式变量:www.antdv.com/docs/vue/cu…
vue.config.js
css:{
//设置 ant 主题颜色
loaderOptions:{
vue cli 不支持 less 需要下载安装less/less-loader
less:{
lessOptions:{
modifyVars:{
'primary-color':'#3E7FFF',
},
javascriptEnanled:true
}
}
}
}
Bundle 打包分析工具
webpack-bundle-analyzer - www.npmjs.com/package/web…
- 可以作为 webpack plugins 使用
- 可以作为 cli 命令行工具使用
特点和分析过程:
- 分析 Bundle 由什么模块组成
- 分析什么模块占据了比较大的体积
- 分析是否有什么错误的模块被打包了
安装使用
安装
npm install --save-dev webpack-bundle-analyzer
yarn add -D webpack-bundle-analyzer
使用插件:在 vue.config.js 中引入使用插件
// 导入webpack-bundle-analyzer插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
// 检查环境变量ANALYZE_MODE是否存在,如果存在则isAnalyzeMode为true
const isAnalyzeMode = !!process.env.ANALYZE_MODE
module.exports = {
// 配置webpack,允许动态修改配置
configureWebpack: config => {
// 如果处于分析模式,向config.plugins中添加一个新的BundleAnalyzerPlugin插件,该插件用于静态分析打包后的资源大小
if (isAnalyzeMode) {
config.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static' // 设置分析模式为静态模式
})
)
}
}
}
在 package.json -> scripts 中配置命令
运行打包分析命令
npm run build:analyze
js/chunk-vendors.xxx.ja 第三方库打包生成的js
js.app 蓝色模块代表自己编写的代码逻辑
根据图表进行优化
打包初始大小为:1.63M
根据图表优化步骤
查看有没有重复的模块,或者没有用的模块被打包到最终代码中
- 查看 package.json,对比以下是否有应该在 devDependencies 的模块,被错误的放置在 dependencies 中
- 检测是否有重复加载的模块,或者是功能大体相同的模块
优先使用 es 版本的第三方库,es 版本支持 tree-shaking
- 检测是否有没有用的模块是否打包到了最终文件
移除 moment 中 locale 包
// 导入webpack模块
const webpack = require('webpack')
module.exports = {
// 配置webpack,允许动态修改配置
configureWebpack: config => {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp:/^\.\/locale$/,
//移除不需要的模块
contextRegExp:/moment$/,
})
)
}
}
此时的文件大小:1.4M
- 优化 ant-desian-vue 导入方式按需加载
- 手动加载需要的组件,创建一个configAntd.ts 导入项目中所需要的组件
//示例
import { Avatar, Button,Layout} from 'ant-design-vue'
import {App} from 'vue'
const component = {
Avatar,
Button,
Layout.Header
}
const install (app:App) => {
component.forEach(component => {
app.component(component.name,component)
})
}
export default {
install
}
- 在 main.ts 中引入
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store/index'
import Antd from './configAntd'
import "ant-design-vue/dist/antd.css"
const app = createApp(App)
app.use(Antd).use(router).use(store)
app.mount('#app')
通过优化过按需导入的方式,文件大小为:797.43k
使用 SplitChunksPlugin 优化文件大小
分割第三方库的优点
充分利用浏览器的缓存
浏览器支持平行加载多个文件
- HTTP1 对同一个域名并行加载的个数限制
- HTTP2 没有连接限制
分割第三方模块
在 vue.config.js 中设置
//手动分割
module.exports = {
config.optimization.splitChunks = {
maxInitialRequests:Infinity,
minSize:0,
chunks:'all',
cacheGroups:{
antVendor:{
name:'ant-design-vue',// 将 ant 单独打包
test: /[\\/]node_modules[\\/](ant-design-vue)[\\/]/,
},
vendor:{
name:'vendor',
test: /[\\/]node_modules[\\/](!ant-design-vue)[\\/]/,
}
}
}
}
//自动分割
module.exports = {
config.optimization.splitChunks = {
maxInitialRequests: Infinity,
minSize: 300 * 1024,
chunks: 'all',
cacheGroups: {
antVendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name.
// node_modules/packageName/sub/path
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
return `npm.${packageName.replace('@', '')}`
}
}
}
}
路由懒加载
{
path: "/abc",
name: "abc",
component: ()=> import(/*webpackChunkName:"abc"*/'../views/Abc.vue'),
}
最终打包结果
最终 vue.config.js
// 导入webpack-bundle-analyzer插件
// 该插件用于静态分析打包后的资源大小,有助于优化项目性能
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// 导入webpack模块
// webpack是一个前端资源构建工具,可以将多个模块打包成一个或多个文件
const webpack = require('webpack');
// 检查环境变量ANALYZE_MODE是否存在,如果存在则isAnalyzeMode为true
// 当ANALYZE_MODE环境变量存在时,会启用资源打包分析
const isAnalyzeMode = !!process.env.ANALYZE_MODE;
// 检查环境变量VUE_APP_STAGING是否存在,如果存在则isStaging为true,表示当前环境是预生产环境
// 预生产环境通常是用于在正式部署前进行功能和性能测试的环境
const isStaging = !!process.env.VUE_APP_STAGING;
// 检查环境变量NODE_ENV是否为"production",如果是,则isProduction为true,表示当前环境是生产环境
// 生产环境通常是最终部署给用户使用的环境
const isProduction = process.env.NODE_ENV === "production";
module.exports = {
// 生成环境使用 OSS 地址
// 其他环境使用绝对路径
// publicPath决定了打包后静态资源的引用路径
publicPath: (isProduction && !isStaging) ? 'https://oos.abc.com' : '/',
// 修改主题颜色
// 通过loaderOptions配置less-loader的lessOptions,可以修改主题颜色
css: {
loaderOptions: {
less: {
lessOptions: {
// 使用 modifyVars 替代 modiflyVars
// modifyVars用于覆盖less文件中的变量,此处用于修改主题颜色
modifyVars: {
'primary-color': '#3E7FFF' // 将主颜色设置为蓝色#3E7FFF
},
javascriptEnabled: true // 启用JavaScript解析,允许在less文件中使用JavaScript表达式
}
}
}
},
// 配置webpack,允许动态修改配置
configureWebpack: config => {
// 使用IgnorePlugin移除不需要的模块
// 此处用于移除moment库中的本地化文件,减少打包体积
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
})
);
// 如果处于分析模式,向config.plugins中添加一个新的BundleAnalyzerPlugin插件
// 该插件会在打包后生成一个可视化的资源大小报告
if (isAnalyzeMode) {
config.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static' // 设置分析模式为静态模式,即生成报告后需要手动打开
})
);
}
// 配置代码分割
// 通过splitChunks配置,可以将公共的依赖模块提取到已有的入口chunk中,或者提取到新生成的chunk
config.optimization.splitChunks = {
maxInitialRequests: Infinity, // 允许入口chunk并行加载的最大请求数,设置为Infinity表示不限制
minSize: 0, // 最小分割大小,设置为0表示不限制分割大小
chunks: 'all', // 表示哪些模块会被分割,'all'表示所有模块
cacheGroups: {
antVendor: {
name: 'ant-design-vue', // 抽取后的chunk名称
// ... 后续的配置代码被截断了,这里应该是用于抽取ant-design-vue相关依赖的代码
}
}
}
},
chainWebpack: config => {
config.plugin('html').tap(args => {
args[0].title = '王炸 '
args[0].desc = '哈哈哈哈哈'
return args
})
}
}
部署上线及服务端优化
前端部署:cli.vuejs.org/zh/guide/de…
原理就是:将构建生成的产物直接拷贝到任何的静态文件服务器中
后端部署:eggjs.org/zh-cn/core/…
- 方案一:直接将本地的资源代码打个压缩文件包拷贝到目标服务器,然后启动服务器
- 方案二:在服务器中直接 pull 源代码,install,然后重启服务器
Nginx 作为服务器软件的优点:
- 适合前后端分离的项目、保证安全、速度快、支持负载均衡
安装:Nginx
mac 本地安装
brew install nginx
brew uninstall nginx
// 查看版本号
nginx -V
// nginx 停止
nginx -s stop
// 重启
nginx -s reload
//访问配置文件
cat /opt/homebrew/etc/nginx/nginx.conf
sudo nano /opt/homebrew/etc/nginx/nginx.conf
使用 vscode 打开配置文件 code nginx.conf
网站目录
cd /opt/homebrew/var/www
vim index.html //查看文件内容
//拷贝文件
cd dist //当前目录
cp -r * 目标目录
安装成功后 可以访问 http://localhost:8080 访问服务
- 宝塔面板:安装 Nginx 与 页面部署
将本地打包好的站点压缩上传到目标服务器中
输入创建时添加的域名进行访问例如:test.com
HTTP 缓存优化
- Expires : 响应头包含日期/时间,响应过期时间
- Cache-Control:通用消息头字段,用于在 HTTP 请求响应中,通过指令来实现缓存机制
- Etag:响应头是资源的特定版本标识符,可以让缓存更高效,节省带宽,如果站点内容没有变化,Web 服务器不需要发送完整的响应(nginx 默认添加)
- Last-Modified : 响应首部,包含资源服务器认定的资源做出修改的日期及时间(nginx 默认添加)
使用 Nginx 配置以上字段
找到 nginx confi 配置文件
-
宝塔面板位置
mac 本地测试配置文件位置
-
配置相应的指令
- 在 nginx.conf 中先将 etag 与 Last-Modified 关闭,测试 expires 与 Cache-Control 字段
重启 nginx 服务进行测试
- 在 nginx.conf 中先将 etag开启(默认开启),删除或注释 expires / add_header Last-Modified ""。
nginx gzip 实现静态资源文件压缩
Gzip 是一种压缩文件格式并且也是一个在 unix 上的一种压缩的软件 文档地址:nginx.org/en/docs/htt…
# 开启 gzip
gzip on;
# 启用 gzip 压缩的最小文件,小于设置的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别 1~0,数字越大压缩越好,也越占用 CPU 时间
gzip_comp_level 1;
# 进行压缩的文件类型,javascript 有多种形式,其中的值可以在 mime.type 文件中找到
gzip_type text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
# 是否在 http header 中添加 Vary:Accept-Encoding,建议开启
gzip_vary on;
# 禁用 IE 6 gzip
gzip_disable "MSIE [1-6]\.";
# 设置压缩所需要的缓冲区大小
gzip_buffers 32 k4;
重启 nginx 打开网站查看压缩效果
更高效的压缩算法 Bortli
Google 软件工程师在 2015年9月发布了包含通用无损数据压缩算法,特别侧重于 HTTP 压缩,其中的编码器部分被部分改写以提高压缩比,编码器和解码器都提高了速度,流式 API 已被改进,增加更多的压缩质量级别。
使用前提
- 浏览器支持:camiuse.com/brotli
- 支持HTTPS 协议
- NGINX 需要安装对应的模块:github.com/google/ngx_…
要使用Brotli压缩在Nginx中,你需要确保你的Nginx版本支持Brotli模块。从Nginx 1.13.2版本开始,Brotli模块作为内置模块提供。如果你使用的是旧版本的Nginx,你可能需要升级到一个较新的版本,或者从源代码编译Nginx时包含Brotli模块。 以下是如何在Nginx中启用Brotli压缩的步骤:
- 确保Nginx支持Brotli:检查你的Nginx版本是否支持Brotli模块。你可以通过运行nginx -V命令来查看编译时包含的模块列表。如果列表中包含--with-http_brotli_module,那么你的Nginx支持Brotli。
- 编辑Nginx配置文件:打开你的Nginx配置文件(通常是nginx.conf或者在sites-available/目录下的特定配置文件)。
- 添加Brotli配置:在http块中添加以下配置来启用Brotli压缩:
http {
...
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/xml application/json image/svg+xml image/x-icon font/woff font/woff2 text/html;
...
}
这里的配置启用了Brotli压缩(brotli on;),并设置了压缩级别为6(brotli_comp_level 6;)。你可以根据需要调整压缩级别,范围从1(最快,最低压缩)到11(最慢,最高压缩)。brotli_types指令指定了哪些MIME类型应该被Brotli压缩。
(可选)配置Brotli静态文件: 如果你想为静态文件启用Brotli压缩,并在服务器上存储预压缩的Brotli文件,你可以使用brotli_static指令。这要求你预先压缩文件,并将它们与原始文件一起放在服务器上。Nginx将自动根据请求头中的Accept-Encoding字段来决定是提供原始文件还是Brotli压缩文件。
压缩对比表:quixdb.github.io/squash-benc…
- Gzip
- brotil
HTTP 协议传输优化
HTTP 是建立在 TCP 协议之上,所以 HTTP 协议的瓶颈及其优化技巧都是基于 TCP 协议本身的特性,例如 tcp 建立的 3 次握手和断开连接的 4 次挥手以及每次建立连接带来的延迟时间,所以减少这些重新握手和挥手至关重要
keepAlive 属性(默认开启)长连接
- http1.0:不支持
- keepalive_timeout 0;长链接时间
- Keepalive 的优点:
- TCP 链接更少节约 TCP 链接在建立与释放过程中,主机和路由的CPU内存开销
- 减少网络拥塞,获取到响应的延时也减少了
HTTP/2 性能提升
HTTP2 的兼容性:
- 需要浏览器支持:caniuse.com/http2
- 需要 HTTPS 协议支持
二进制协议
- 优化:HTTP/2 使用二进制协议,这意味着所有的消息都是以二进制帧(frames)的形式传输,而不是纯文本。这使得协议更加紧凑、高效,并且减少了解析文本所需的计算资源。
- 特点:二进制协议能够更精确地定义消息的格式和结构,减少歧义和错误。同时,二进制数据可以直接在内存中操作,避免了字符串解析和编码的开销。
多路复用
HTTP/2 中引入了多路复用技术,多路复用很好的解决了 浏览器限制一个域名下请求数据量的问题,使链接更容易实现全速传输
同个域名只需要占用一个 TCP 链接,使用一个链接并行发送多个请求响应,消除了因多个 TCP 链接而带来的延时和内存消耗
Header 压缩
HTTP/2 在客户端和服务器端使用 首部表 来跟踪和存储之前发送的 键 - 值对,对相同的数据,不在通过请求和响应
在控制台查看开启HTTP/2和未开启的区别