一次vue项目的优化及部署之旅
Javascript 语言本身是不需要编译的,但是现代的前端项目使用的语言和或者的模块系统都无法在浏览器中使用,都需要使用特定的 bundler(webpack/rollup) 将源代码最终转换为浏览器支持的 Javascript 代码,这个过程就是构建
。
vue项目一般都是使用vue-cli脚手架搭建的,构建使用的工具是webpack,本文将详细介绍如何对构建后的文件大小进行优化,以及如何使用nginx如何部署前端项目。
环境及配置
不同的环境
前端项目一般都有三个环境:
- 生产环境 (production): juejin.com/
- 稳定是最重要的原则
- 速度是第一要务
- 开发环境 (development): http://localhost:8080/
- 会添加丰富的错误提示
- 以使用 mock server 或者本地后端环境
- 添加各种便利的功能 - 比如 hot reload,自动刷新
- 不太关心静态资源的大小,最好提供最丰富的调试信息 (sourcemap) 等。
- 测试环境(test 或者 staging): test.juejin.com/
- 高度相似
- 使用的后端服务不一样
在vue-cli中配置不同的环境
NODE_ENV
将决定您的应用运行的模式,是开发,生产还是测试,因此也决定了创建哪种 webpack 配置。
例如通过将 NODE_ENV 设置为 "test",Vue CLI 会创建一个优化过后的,并且旨在用于单元测试的 webpack 配置,它并不会处理图片以及一些对单元测试非必需的其他资源。
// package.json
"scripts": {
"test:unit": "vue-cli-service test:unit"
}
同理,NODE_ENV=development 创建一个 webpack 配置,该配置启用热更新,不会对资源进行 hash 也不会打出 vendor bundles,目的是为了在开发的时候能够快速重新构建。
// package.json
"scripts": {
"serve": "vue-cli-service serve"
}
当你运行 vue-cli-service build 命令时,无论你要部署到哪个环境,应该始终把 NODE_ENV 设置为 "production" 来获取可用于部署的应用程序。
// package.json
"scripts": {
"build": "vue-cli-service build",
}
staging预发布
staging预发布就是把项目发到跟线上环境一样的服务器中,方便测试人员进行测试,那如何设置staging环境呢?
在vue-cli中,提供了通过加载环境变量文件来配置不同的环境,首先需要在package.json中设置:
"scripts": {
"build:staging": "vue-cli-service build --mode staging",
}
根据mode的值来决定加载那些环境变量文件,比如上面的代码发现mode是staging,那么就去找.env.staging的文件:
staging是预发布环境,所以肯定是要运行 vue-cli-service build
的,所以NODE_ENV 肯定是production,不能是其他的,因为vue-cli是根据NODE_ENV来使用哪种webpack的配置文件的。
配置了.env.staging环境变量文件并加载之后,就能在代码文件中获取到文件中设置的变量,比如VUE-APP-STAGING。在staging的模式下,请求后端api应该是测试环境的api,不能是正式环境的api:
if (process.env.NODE_ENV === 'development' || process.env.VUE_APP_STAGINE) {
// 开发环境和staging环境的api接口
baseBackendUrl = 'http://182.192.108.192:8081'
} else {
baseBackendUrl = 'https://api.test.com'
}
bundle 打包分析工具
分析环境配置
项目打包之后,我们需要对打包后的文件进行分析,那么就需要一个分析环境,所以需要添加.env.analyze:
"scripts": {
"build:analyze": "vue-cli-service build --mode analyze"
}
然后在vue.config.js中获取到环境变量文件中的变量,根据变量就能判断是否是分析环境。
const isAnalyzeMode = !!process.env.ANALYZE_MODE
configureWebpack: config => {
if (isAnalyzeMode) {
config.plugins.push(
new BundleAnalyzerPlugin({
// server: 开启一个服务器 static:生成一个html文件
analyzerMode: 'static'
})
)
}
}
当运行npm run build:analyze的时候就会去打包文件,并且生成一个分析报告:
优化步骤
- 看看有没有什么重复的模块,或者没有用的模块被打包到了最终的代码中
- 按需加载组件库
- 使用动态 import 优化路由
按需加载组件库
以ant-design-vue
为例
// 按需注册使用的组件
// configAntd.js
// 把所有使用的ant-design-vue组件进行按需加载
import {
Avatar,
...
Select
} from 'ant-design-vue'
import { App } from 'vue'
const components = [
Avatar,
Button,
...
Select,
Select.Option
]
const install = (app: App) => {
components.forEach(component => {
app.component(component.name, component)
})
}
export default {
install
}
// main.js 进行使用
import Antd from './configAntd'
import 'ant-design-vue/dist/antd.less'
app.use(Antd)
注意:需要安装less less-loader,而且它们的版本不能太高,因为ant-design-vue使用的less及less-loader比较老旧,有些语法不兼容。"less": "^3.12.0","less-loader": "^7.1.0"。
SplitChunksPlugin
先来看一个场景:现在有一个主程序250k,两个依赖库vue:150k,jquery: 100k。如果全部打到一个包里,总的文件大小是500k,周一访问的时候是500k,周二访问的是访问的是缓存,周三修改下主程序后缓存失效,那么又访问500k,周四添加了一个库100k,总的访问就是1600,下图是分开打包后,总共的请求流量。
所以把一个大文件分割成小的文件,它有如下优点:
- 充分利用浏览器的缓存
- 浏览器支持平行加载多个文件
- HTTP1 对同一域名并行加载的个数限制
- HTTP2 完全突破这个限制
查看vue-cli的webpack的配置命令
npx vue-cli-service inspect | tee simple.webpack.config.js
执行命令后,配置信息写到了simple.webpack.config.js
中,vue-cli的默认SplitChunksPlugin 分割配置如下:
splitChunks: {
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
// 这是对于多入口文件同时复用同一段代码的提取并单独打包
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
}
现在我们需要把antd-design-vue和html2canvas两个比较大的库单独分割处理:
config.optimization.splitChunks = {
maxInitialRequests: Infinity,
// 表示不论多小都要分割,这里只是演示用的
minSize: 0,
chunks: 'all',
cacheGroups: {
antVendor: {
name: 'ant-design-vue',
test: /[\\/]node_modules[\\/](ant-design-vue)[\\/]/
},
canvasVendor: {
name: 'html2canvas',
test: /[\\/]node_modules[\\/](html2canvas)[\\/]/
},
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/](!html2canvas)(!ant-design-vue)[\\/]/
}
}
}
打包后的文件如下图:
在实际应用中,会使用很多第三方库,不可能像上面那样一个一个的配置,所以需要动态的进行分割:
config.optimization.splitChunks = {
maxInitialRequests: Infinity,
// 当包的大小超过300kb的时候,进行分割
minSize: 300 * 1024,
chunks: 'all',
cacheGroups: {
antVendor: {
test: /[\\/]node_modules[\\/]/,
// 这里的name就需要动态赋值
name(module) {
// 获取包名
// node_modules/packageName/sub/path or node_modules/packageName
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1]
return `npm.${packageName.replace('@', '')}`
}
}
}
}
细粒度配置 webpack 选项
通过chainWebpack来修改vue-cli原有配置,比如我们要修改html-template-plugin这个插件里面的配置选项。
chainWebpack: config => {
config.plugin('html').tap(args => {
// 修改html的title
args[0].title = '慕课乐高'
// 新增加一个属性desc,然后在public/index.html中就可以使用这个属性
args[0].desc = '一键生成 H5 海报'
return args
})
}
config.plugin('html')里面的插件名称html
就是来自于下图中的webpack原始配置:
在/public/index.html中就可以使用我们设置的title和desc:
<!DOCTYPE html>
<html lang="en">
<head>
...
<meta name="description" content="<%= htmlWebpackPlugin.options.desc %>" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
部署
利用nginx作为静态资源服务器
对于前端静态资源,我们通常使用nginx来进行部署,nginx基本配置如下:
server {
listen 8020;
server_name localhost;
// 文件资源路径
root /root/h5-editor/;
location / {
// 针对history路由
if ($uri ~ ^/((?!js)(?!img)(?!css)(?!fonts).)*$) {
rewrite .* /index.html break;
}
index index.html index.htm;
}
}
gzip压缩
gzip是一种格式,也是一种linux下的解压缩工具。我们使用gzip对app.js文件压缩后,原始文件就变为了以.gz结尾的文件,同时文件大小从42571减小到11862。
gzip使用方法
// 压缩
gzip app.js
// 解压
gzip -d app.js
nginx gzip设置
# 开启
gzip gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
gzip_comp_level 1;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types 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;
在本项目中gzip配置如下图:
设置gzip后,在响应头有个content-encoding: gzip
v同时可以看到上面的传输文件打下是14k,即通过gzip压缩后的,下面的42.6k就是浏览器解压后的文件原始大小。
除了gzip以外,还有一个更加高效的压缩算法Brotli,它的压缩比更大,这个也是现在大面积普及的压缩算法,它使用的前提是https协议。配置如下:
brotli on;
brotli_static on;
brotli_types *;
nginx 开启静态压缩提升效率
- 动态压缩: 服务器在返回任何的静态文件之前,由服务器对每个请求压缩在进行输出。
- 静态压缩:服务器直接使用现成的扩展名为 .gz 的预压缩文件,直接进行输出。
实现静态压缩有以下两个步骤:
- 生成gzip压缩文件
在利用webpack打包的时候,我们就把文件进行压缩,配置如下:
const isProduction = process.env.NODE_ENV === 'production'
if (isProduction) {
config.plugins.push(
new CompressionWebpackPlugin({
// 采用gzip进行压缩
algorithm: 'gzip',
test: /\.js$|\.html$|\.json$|\.css/,
threshold: 10240
})
)
}
就会看到压缩文件,以.gz结尾。然后把.gz后缀的文件上传到服务器中即可。
- 在 nginx 开启支持静态压缩的模块
在nginx配置中加上如下配置:
gzip_static on;
如果不加的话,访问的时候就会找不到,报404错误,因为服务器只有.gz的文件,没有原始文件。
使用 HTTP/2 提升性能
前提是需要HTTPS 协议支持,配置中只需要加上ss http2即可,在生产项目中尽量使用http2,因为它能大幅度的提升访问的速度。
转载自:https://juejin.cn/post/7096090694497861645