vue 项目 chunk.js 太大导致页面加载超时

一、问题

最近做了一个 vue 项目,打包后部署到 nginx,页面访问时一直停留在加载页面,浏览器调试观察到 chunk 开头的多个 js 文件加载时间超过了 30s 还没有加载完,js 文件大小有 20 多 Mb

二、解决方案

2.1 安装插件

执行如下命令

npm install --save-dev webpack-bundle-analyzer npm install babel-plugin-component -D

2.2 修改配置

先引入依赖

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' }) ],

2.3 打包

执行项目打包命令,在 dist 目录下会生成一个 bundle-report.html 页面,里面可以看到项目依赖和打包的文件大小

image.png

点击左上角小箭头,可以看到 js 和压缩后的 gz 文件大小

image.png

image.png

2.4 配置 nginx

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 控制台可以看到各个文件大小和具体加载时间,比原来几十秒一分多钟好了很多

image.png

在响应中也可以看到 content-encoding 选项

image.png

2.5 文件参考

2.5.1 vue 配置文件

'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') }) } }

2.5.2 nginx 配置文件

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; } } }
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    246 引用 • 1338 回帖 • 1 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    268 引用 • 666 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...