前端打包性能优化及部署上线优化

279 阅读13分钟

打包优化策略

  • 移除未使用的模块:确保开发依赖(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 打包命令查看生成环境打包结果 图片.png

css.loaderOptions 属性

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 中配置命令

图片.png 运行打包分析命令npm run build:analyze 图片.png js/chunk-vendors.xxx.ja 第三方库打包生成的js js.app 蓝色模块代表自己编写的代码逻辑

根据图表进行优化

打包初始大小为:1.63M

图片.png

根据图表优化步骤

查看有没有重复的模块,或者没有用的模块被打包到最终代码中

  • 查看 package.json,对比以下是否有应该在 devDependencies 的模块,被错误的放置在 dependencies 中 图片.png
  • 检测是否有重复加载的模块,或者是功能大体相同的模块 图片.png 优先使用 es 版本的第三方库,es 版本支持 tree-shaking
  • 检测是否有没有用的模块是否打包到了最终文件

图片.png 移除 moment 中 locale 包

// 导入webpack模块
const webpack = require('webpack')
module.exports = {
    // 配置webpack,允许动态修改配置
    configureWebpack: config => {
        config.plugins.push(
            new webpack.IgnorePlugin({
                resourceRegExp:/^\.\/locale$/,
                //移除不需要的模块
                contextRegExp:/moment$/,
            })
        )
   }
}

此时的文件大小:1.4M

图片.png

  • 优化 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

图片.png

使用 SplitChunksPlugin 优化文件大小

分割第三方库的优点 图片.png 充分利用浏览器的缓存 浏览器支持平行加载多个文件

  • HTTP1 对同一个域名并行加载的个数限制 图片.png
  • 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'),
}

最终打包结果 图片.png 最终 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 与 页面部署

图片.png

图片.png 将本地打包好的站点压缩上传到目标服务器中

图片.png

图片.png 输入创建时添加的域名进行访问例如:test.com

HTTP 缓存优化

图片.png

使用 Nginx 配置以上字段

找到 nginx confi 配置文件

  • 宝塔面板位置 图片.png mac 本地测试配置文件位置

  • 图片.png

配置相应的指令

  • 在 nginx.conf 中先将 etag 与 Last-Modified 关闭,测试 expires 与 Cache-Control 字段

图片.png 重启 nginx 服务进行测试 图片.png

  • 在 nginx.conf 中先将 etag开启(默认开启),删除或注释 expires / add_header Last-Modified ""。 图片.png

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 打开网站查看压缩效果

图片.png

更高效的压缩算法 Bortli

Google 软件工程师在 2015年9月发布了包含通用无损数据压缩算法,特别侧重于 HTTP 压缩,其中的编码器部分被部分改写以提高压缩比,编码器和解码器都提高了速度,流式 API 已被改进,增加更多的压缩质量级别。

使用前提

要使用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 图片.png
  • brotil 图片.png

HTTP 协议传输优化

HTTP 是建立在 TCP 协议之上,所以 HTTP 协议的瓶颈及其优化技巧都是基于 TCP 协议本身的特性,例如 tcp 建立的 3 次握手和断开连接的 4 次挥手以及每次建立连接带来的延迟时间,所以减少这些重新握手和挥手至关重要

keepAlive 属性(默认开启)长连接

  • http1.0:不支持
  • keepalive_timeout 0;长链接时间
  • Keepalive 的优点:
    • TCP 链接更少节约 TCP 链接在建立与释放过程中,主机和路由的CPU内存开销
    • 减少网络拥塞,获取到响应的延时也减少了 图片.png

HTTP/2 性能提升

HTTP2 的兼容性:

图片.png

  • 需要 HTTPS 协议支持

二进制协议

  • 优化:HTTP/2 使用二进制协议,这意味着所有的消息都是以二进制帧(frames)的形式传输,而不是纯文本。这使得协议更加紧凑、高效,并且减少了解析文本所需的计算资源。
  • 特点:二进制协议能够更精确地定义消息的格式和结构,减少歧义和错误。同时,二进制数据可以直接在内存中操作,避免了字符串解析和编码的开销。

多路复用

HTTP/2 中引入了多路复用技术,多路复用很好的解决了 浏览器限制一个域名下请求数据量的问题,使链接更容易实现全速传输 同个域名只需要占用一个 TCP 链接,使用一个链接并行发送多个请求响应,消除了因多个 TCP 链接而带来的延时和内存消耗

Header 压缩

HTTP/2 在客户端和服务器端使用 首部表 来跟踪和存储之前发送的 键 - 值对,对相同的数据,不在通过请求和响应 在控制台查看开启HTTP/2和未开启的区别 图片.png