最近做了一个vue项目,打包后部署到nginx,页面访问时一直停留在加载页面,浏览器调试观察到chunk开头的多个js文件加载时间超过了30s还没有加载完,js文件大小有20多Mb
执行如下命令
npm install --save-dev webpack-bundle-analyzer
npm install babel-plugin-component -D
先引入依赖
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
新增这两个插件,webpack-bundle-analyzer用于优化打包体积和依赖关系,compression-webpack-plugin静态资源进行 Gzip/Brotli 压缩
在configureWebpack配置项的plugins新增这两个插件配置
plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionWebpackPlugin({
cache: false, // 不启用文件缓存
test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: '[path].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8 // 压缩率小于1才会压缩
}),
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
})
],
执行项目打包命令,在dist目录下会生成一个bundle-report.html页面,里面可以看到项目依赖和打包的文件大小
点击左上角小箭头,可以看到js和压缩后的gz文件大小
nginx新增gzip配置
# Gzip 全局配置
gzip on;
gzip_min_length 1k;
gzip_comp_level 6; # 1-9,6 是性能与压缩率的平衡点
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_vary on;
gzip_disable "MSIE [1-6]\."; # 禁用旧版 IE 的压缩
gzip_proxied any; # 允许代理请求的压缩
配置完成重启nginx在访问就正常了,F12控制台可以看到各个文件大小和具体加载时间,比原来几十秒一分多钟好了很多
在响应中也可以看到content-encoding选项
'use strict'
const path = require('path')
//
const packageName = require('./package.json').name
function resolve(dir) {
return path.join(__dirname, dir)
}
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '高碳排放行业电碳核算系统' // 网页标题
const port = process.env.port || process.env.npm_config_port || 80 // 端口
console.log("NODE_ENV =", process.env.NODE_ENV);
// vue.config.js 配置说明
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
// 这里只列一部分,具体配置参考文档
module.exports = {
// 部署生产环境和开发环境下的URL。
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === "production" || process.env.NODE_ENV === "test" ? "/ecap/" : "/",
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
outputDir: 'dist',
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: 'static',
// 是否开启eslint保存检测,有效值:ture | false | 'error'
lintOnSave: process.env.NODE_ENV === 'development',
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: port,
open: true,
historyApiFallback: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: `http://localhost:8081`,
// target: `http://192.168.2.52:30800`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
},
[process.env.VUE_APP_GRAPH_API]: {
target: `http://localhost:9999`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_GRAPH_API]: ''
}
},
},
disableHostCheck: true,
headers: {
'Access-Control-Allow-Origin': '*' // 允许跨域
}
},
css: {
loaderOptions: {
sass: {
additionalData: `@import "@/assets/styles/util.scss";`, // 全局配置 util.scss
sassOptions: {outputStyle: "expanded"},
}
}
},
configureWebpack: {
name: name,
resolve: {
alias: {
'@': resolve('src')
}
},
plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionWebpackPlugin({
cache: false, // 不启用文件缓存
test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: '[path].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8 // 压缩率小于1才会压缩
}),
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
})
],
output: {
library: `${packageName}`,
libraryTarget: 'umd',
jsonpFunction: `wabpackJsonp_${packageName}`
},
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/assets/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
config.when(process.env.NODE_ENV !== 'development', config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
vueBase: {
name: 'chunk-vueBase',
test: /[\\/]node_modules[\\/](vue|vue-router|vuex)/,
priority: 40,
chunks: 'initial'
},
elementUI: {
name: 'chunk-elementUI',
test: /[\\/]node_modules[\\/]_?element-ui(.*)/,
priority: 30,
chunks: 'initial'
},
echarts: {
name: 'chunk-echarts',
test: /[\\/]node_modules[\\/](echarts|echarts-gl|zrender)/,
priority: 25,
chunks: 'async'
},
three: {
name: 'chunk-three',
test: /[\\/]node_modules[\\/]three/,
priority: 20,
chunks: 'async'
},
moment: {
name: 'chunk-moment',
test: /[\\/]node_modules[\\/]moment/,
priority: 15,
chunks: 'async'
},
vendors: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
return `npm.${packageName.replace('@', '')}`
},
priority: 10,
chunks: 'all'
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'),
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single')
})
}
}
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
server_tokens off;
# Gzip 全局配置
gzip on;
gzip_min_length 1k;
gzip_comp_level 6; # 1-9,6 是性能与压缩率的平衡点
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_vary on;
gzip_disable "MSIE [1-6]\."; # 禁用旧版 IE 的压缩
gzip_proxied any; # 允许代理请求的压缩
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 静态资源(JS/CSS/图片)配置
location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|svg)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
# 优先使用预压缩文件(需提前生成 .gz 文件)
gzip_static on;
try_files $uri.gz $uri =404;
# 动态压缩(如果预压缩文件不存在)
gzip on;
add_header Vary Accept-Encoding;
}
# 前端项目路由:/ecap/
location /ecap/ {
try_files $uri $uri/ /ecap/index.html;
}
# 前端项目路由:/electric/
location /electric/ {
try_files $uri $uri/ /electric/index.html;
}
# 接口代理:/test-api/
location /test-api/ {
proxy_pass http://169.254.240.105:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
}
# 接口代理:/test-graph/
location /test-graph/ {
proxy_pass http://169.254.240.105:9999/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
}
# 404 处理
error_page 404 /404.html;
location = /404.html {
internal;
}
# 50x 错误处理
error_page 500 502 503 504 /50x.html;
location = /50x.html {
internal;
}
}
}