Code前端首页关于Code前端联系我们

Vue3项目Build打包上线踩过无数坑?这里有从配置到优化的完整避坑指南

terry 38分钟前 阅读数 16 #Vue
文章标签 Vue3打包避坑指南

刚上手Vue3不久的同事小王上周又找我吐槽:“昨天熬了大夜改的功能,本地跑起来顺得飞起,结果一Build传到测试服务器,白屏了!”这种场景是不是很熟悉?我做前端四年多,经手过不下30个Vue3(从Beta版到正式版都有)项目的打包上线,踩过的坑从环境变量失效到CDN加载错乱,从性能不达公司要求到样式兼容性拉胯,大大小小攒了一箩筐经验,今天就把这些内容整理成问答形式,覆盖从准备Build、基础配置排查、核心问题解决到进阶性能优化的全流程,不管你是刚摸完Vue3 API就想上线的新手,还是做过几个项目但总在细节上卡壳的老手,都能找到能用的东西。

Vue3项目Build前有哪些必须做的“预检查”?

很多人Build完出问题,第一反应是去改配置或者代码,但其实大部分小问题在Build前就能排查出来,省得白等打包时间(大项目打包慢的话,等一次可能十几分钟甚至更久),我总结了5个Build前必做的预检查项,每一个都是踩过坑后总结出来的:

第一个预检查是“跑一遍生产环境预演”,很多人可能知道,Vue3的Vite和Webpack脚手架都提供了生产环境预演功能——Vite是npm run preview,Webpack版是在vue.config.js里配置devServer的historyApiFallback这些后,用--mode production启动开发服务器(或者某些脚手架直接带npm run serve:prod),为什么这个很重要?因为本地开发环境和生产环境有本质区别:开发环境会用热更新插件、不会做Tree Shaking、不会压缩混淆代码、会加载完整的Vue DevTools支持、还会开启sourcemap(如果没配置生产关闭的话),这些都可能掩盖问题,比如同事小王之前那次白屏,就是预检查没做——他在router/index.js里写路由的时候用了绝对路径“/about”,但服务器上部署的是子目录“/project/vue3-demo”,预演的时候他如果切到子目录就能发现,但直接Build传上去就白了。

第二个预检查是“核对环境变量配置”,很多团队会用.env.development、.env.production、.env.staging这些文件来区分不同环境的接口地址、CDN前缀、日志开关等参数,但踩坑率最高的两个点:一是变量名的格式——不管是Vite还是Webpack,环境变量名必须以VITE_(Vite)或者VUEAPP(Webpack Vue CLI)开头,不然代码里import.meta.env或者process.env根本读不到;二是变量值的引号——Webpack里如果变量值带空格、特殊符号(amp;),最好加双引号,但Vite里如果不加引号直接写字符串没问题,加了反而可能把引号也读进去;三是环境变量的生效优先级——.env.production.local的优先级最高,其次是.env.production,然后是.env,很多人本地加了.local覆盖了测试或生产的CDN,上传代码的时候没删掉,导致测试服务器加载了本地不存在的静态资源,我之前有个项目,测试环境一直报某个图片404,查了一天才发现,前端负责人写了个.env.production.local放了本地的mock图路径,gitignore里没加.env.local后缀的文件,直接push到测试分支了。

第三个预检查是“检查路由模式和服务器部署配置是否匹配”,Vue3的路由还是hash和history两种模式,hash模式兼容性最好,服务器不用额外配置,但URL里会带#,不够美观;history模式URL干净,但部署到Nginx、Apache这类静态服务器的时候,必须配置重定向规则,不然刷新页面或者直接输入非根路径的URL,就会报404(因为服务器会去硬盘上找这个路径对应的文件,但实际上Vue3的路由是前端控制的,硬盘上根本没有),Nginx的重定向规则很简单,就是在location / { ... }里加一行try_files $uri $uri/ /index.html;;Apache的话要在根目录下建.htaccess文件,写RewriteEngine On、RewriteCond %{REQUEST_FILENAME} !-f、RewriteCond %{REQUEST_FILENAME} !-d、RewriteRule . /index.html [L]这些内容;如果部署到GitHub Pages或者Gitee Pages这类托管平台,只能用hash模式,或者用gh-pages这类插件配合配置base路径才能勉强用history,但刷新还是会有问题,建议直接选hash。

第四个预检查是“清理浏览器缓存后测试关键功能”,虽然开发的时候会开Chrome DevTools的Disable cache,但浏览器有时候还是会偷偷缓存一些静态资源,比如旧的CSS、JS文件,导致Build前的最后一次修改没生效,清理缓存可以用Ctrl+Shift+Delete(Windows/Linux)或者Cmd+Shift+Delete(Mac),只勾选“缓存的图片和文件”就行,不用清理其他数据,测试的时候最好覆盖PC端、移动端的主流浏览器(Chrome、Firefox、Safari、Edge),尤其是iOS的Safari,兼容性问题最多,比如不支持某些CSS新特性、ES6+的某些语法(如果没配置Babel转译完全的话)。

第五个预检查是“检查git提交状态,确保所有修改都已提交”,很多人Build的时候会改一些临时配置,比如开启sourcemap方便调试,或者改一下CDN前缀测一下本地加载速度,改完之后忘了撤回,直接push到上线分支,导致线上出现问题,虽然大部分脚手架会把配置写在gitignore里的文件,但还是建议每次Build前用git status看一下,有没有不该提交的修改,如果有的话,要么提交(如果是正常配置),要么用git checkout HEAD -- filename撤回。

Vue3项目Build完最常见的白屏问题怎么排查?

说完了Build前的预检查,接下来就是Build完最让人头疼的问题——白屏,白屏的原因五花八门,但排查的时候可以按照从易到难的顺序,我总结了一个“白屏五步排查法”:

第一步是“打开Chrome DevTools的Console和Network面板,看有没有报错和404/500的请求”,这是最直接的排查方法,80%的白屏问题都能在这里找到线索,比如Console里报“Uncaught SyntaxError: Unexpected token '<'”,大概率是静态资源加载错了——服务器返回了index.html而不是对应的JS/CSS文件,这时候看Network面板就能发现,某个JS/CSS的请求URL不对,比如CDN前缀没加、路径是绝对路径而服务器部署的是子目录;如果Console里报“Cannot read properties of undefined (reading 'xxx')”或者“ReferenceError: xxx is not defined”,可能是环境变量没读出来、某个依赖包没正确Tree Shaking或者压缩混淆出了问题;如果Network面板里所有静态资源都是200,但页面还是白屏,那可能是HTML的根节点挂载失败,比如HTML里的

被删掉了或者改了id名,或者main.js里的createApp(App).mount('#app')的id名不对。

第二步是“检查vite.config.js或vue.config.js里的base路径配置”,如果服务器部署的是子目录,https://www.example.com/project/vue3-demo”,那base路径必须设置成“/project/vue3-demo/”(注意末尾要加斜杠,不然静态资源的拼接路径会有问题),Vite的base路径配置是在vite.config.js的export default defineConfig({ ... })里加base: '/project/vue3-demo/';Webpack Vue CLI的是在vue.config.js的module.exports = { ... }里加publicPath: '/project/vue3-demo/';如果部署在根目录,base路径就设置成'/'或者'./'(相对路径,但相对路径有时候在某些场景下会有问题,比如路由跳转,建议用绝对路径),我之前有个电商项目,部署在阿里云OSS的子目录,base路径忘了加末尾的斜杠,结果CSS里的背景图URL拼接成了“https://oss.example.com/project/vue3-demobg.jpg”,直接404,导致页面样式全丢,看起来像白屏。

第三步是“检查路由模式和服务器重定向规则是否匹配”,如果白屏只在刷新页面或者直接输入非根路径URL的时候出现,大概率是history模式但服务器没配置重定向规则,这时候可以先把路由模式改成hash,Build后再传上去试试,如果没问题,就说明是服务器的问题,赶紧去改重定向规则,还要注意一下Vue3的路由配置里有没有设置base参数——比如Webpack Vue CLI里publicPath和router/index.js里的base参数最好保持一致,不然也会出现路径问题。

第四步是“检查是否开启了sourcemap导致文件过大加载慢,或者压缩混淆出了问题”,如果Network面板里的JS文件加载时间特别长(比如超过5秒),可能是开启了生产环境的sourcemap,或者没正确配置Tree Shaking和代码压缩,生产环境一定要关闭sourcemap,不然代码不仅体积大,还会暴露源码——Vite的默认配置是生产环境不开启sourcemap,但如果手动设置了build.sourcemap: true,要改成false或者'hidden'('hidden'会生成sourcemap,但不会在JS文件末尾加注释,只有需要的时候用map文件调试);Webpack Vue CLI的默认配置也是生产环境不开启sourcemap,但如果手动设置了productionSourceMap: true,要改成false,如果压缩混淆出了问题(比如把某个全局变量名压缩错了),可以先关闭压缩,Build后传上去试试,如果没问题,就去调整压缩配置——Vite的压缩插件默认是esbuild,如果esbuild有问题,可以换成terser;Webpack Vue CLI的压缩插件默认是terser,可以在chainWebpack里调整terser的配置,比如保留某些全局变量名。

第五步是“检查是否是CSS/JS加载顺序的问题”,Vue3的Vite脚手架默认会把CSS插入到JS里,动态加载,但如果是SSR项目或者某些特殊场景(比如有内联样式依赖外部CSS),可能需要把CSS提取出来单独加载;Webpack Vue CLI的默认配置是生产环境把CSS提取出来单独加载,还要注意一下有没有在index.html里手动引入了某个JS/CSS文件,导致加载顺序不对——比如先引入了依赖Vue的插件,后引入了Vue的核心文件。

Vue3项目Build完体积太大怎么优化?

现在很多公司对前端项目的性能有要求,比如首屏加载时间不能超过3秒,LCP(最大内容绘制)不能超过2.5秒,而项目体积太大是影响性能的主要因素之一,我经手过一个Vue3的电商项目,最开始Build出来的主包JS文件有2.1MB,首屏加载时间超过8秒,后来经过优化,主包JS文件降到了350KB,首屏加载时间降到了1.8秒,完全符合公司的要求,下面就分享几个我常用的、效果明显的优化方法:

配置合理的Tree Shaking

Tree Shaking的作用是把代码里没用到的部分删掉,比如引入了一个完整的UI组件库,但只用到了几个组件,Tree Shaking就能把没用到的组件删掉,Vue3的Vite脚手架默认支持ES6+的Tree Shaking,但前提是代码必须用ES6+的模块化语法(import/export),不能用CommonJS的(require/module.exports);如果依赖包是用CommonJS写的,Vite会把整个包都打包进去,这时候可以用rollup-plugin-commonjs插件转换成ES6+的模块化语法,或者换一个用ES6+模块化语法写的依赖包,还要注意一下某些依赖包的配置,比如Lodash,最好不要直接引入整个包(import _ from 'lodash'),而是按需引入(import { debounce, throttle } from 'lodash-es'),或者用babel-plugin-lodash(Webpack)/unplugin-lodash(Vite)插件自动转换成按需引入。

配置代码分割

代码分割的作用是把大的主包拆分成多个小的chunk,按需加载,比如路由对应的组件,只有用户跳转到这个路由的时候才会加载对应的chunk,这样首屏加载的时候只需要加载主包和首屏路由对应的chunk,就能大大减少首屏加载时间,Vue3的Vite和Webpack脚手架都支持自动路由代码分割——Vite只要在router/index.js里用import()的方式引入路由组件就行;Webpack Vue CLI只要用Vue CLI的路由生成工具或者手动用import()的方式引入路由组件就行,还可以手动配置代码分割,把大的第三方依赖包(比如Vue、Vue Router、Pinia、Element Plus)拆分成单独的chunk——Vite的配置是在build.rollupOptions.output.manualChunks里配置,比如把vue、vue-router、pinia拆分成vendor-vue chunk,把element-plus拆分成vendor-element chunk;Webpack Vue CLI的配置是在chainWebpack里配置optimization.splitChunks,比如设置cacheGroups,把第三方依赖包和公共组件拆分成单独的chunk。

使用CDN加载大的第三方依赖包

CDN的全称是Content Delivery Network,内容分发网络,它的作用是把静态资源缓存到离用户最近的服务器上,用户访问的时候直接从离他最近的服务器加载,大大减少加载时间,对于Vue、Vue Router、Pinia、Element Plus这类大的、更新频率不高的第三方依赖包,用CDN加载是非常合适的,Vite的配置是在index.html的里手动引入CDN链接,然后在vite.config.js的build.rollupOptions.external里配置这些依赖包,告诉Rollup不要把它们打包进去,最后在main.js里还是正常import就行;Webpack Vue CLI的配置是在vue.config.js的chainWebpack里配置externals,然后在index.html的里手动引入CDN链接,需要注意的是,CDN链接最好选国内的,比如jsdelivr(虽然有时候会抽风,但免费且稳定)、unpkg、BootCDN、七牛云CDN等;还要注意一下CDN链接的版本号,最好和package.json里的版本号一致,不然可能会出现兼容性问题。

压缩图片和静态资源

图片和静态资源(比如字体文件、视频文件)是项目体积的重要组成部分,尤其是电商项目,图片占比可能超过80%,压缩图片可以用很多工具,比如在线工具TinyPNG、Squoosh,本地工具ImageOptim(Mac)、FileOptimizer(Windows),或者脚手架插件unplugin-imagemin(Vite/Webpack),unplugin-imagemin插件非常好用,可以在Build的时候自动压缩图片,支持多种图片格式(JPG、PNG、GIF、SVG、WebP、AVIF等),压缩率很高,而且配置简单,还可以把图片转换成WebP或者AVIF格式,这两种格式的压缩率比JPG和PNG高很多,但兼容性稍微差一点(现在主流浏览器都支持WebP了,AVIF的支持率也在慢慢提高),可以用标签来做兼容性处理——比如先放AVIF格式的图片,再放WebP格式的,最后放JPG/PNG格式的,浏览器会自动加载它支持的格式,对于字体文件,可以用fontmin这类工具,只保留项目里用到的字符,大大减少字体文件的体积;对于视频文件,可以用ffmpeg这类工具,压缩成MP4或者WebM格式,并且设置合适的码率和分辨率。

开启Gzip或Brotli压缩

Gzip和Brotli是两种常用的HTTP压缩算法,Brotli的压缩率比Gzip高,但需要服务器和浏览器都支持(现在主流浏览器和服务器都支持了),开启Gzip或Brotli压缩可以把JS/CSS/HTML这类文本文件的体积减少60%-80%,效果非常明显,Vite和Webpack脚手架都支持在Build的时候预生成Gzip或Brotli压缩文件——Vite可以用vite-plugin-compression插件;Webpack Vue CLI可以用compression-webpack-plugin插件,预生成压缩文件的好处是服务器不需要实时压缩,直接返回预生成的文件就行,减少服务器的压力,还要在服务器上开启Gzip或Brotli压缩的支持,比如Nginx需要在http块或者server块里加gzip on、gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml这些内容,开启Brotli的话需要安装ngx_brotli模块。

Vue3项目Build完还有哪些细节需要注意?

除了上面提到的预检查、白屏排查和体积优化,还有几个细节需要注意,不然也可能会影响项目的正常运行:

第一个细节是“配置合适的缓存策略”,静态资源的缓存策略非常重要,可以减少用户的重复加载时间,提升用户体验,HTML文件的缓存时间设置成0或者很短(比如1分钟),因为HTML文件是入口文件,必须让用户每次都加载最新的;JS/CSS/图片/字体这类静态资源的缓存时间设置成很长(比如1年),因为它们的文件名里会带哈希值,只要内容变了,哈希值就会变,文件名也就变了,浏览器就会自动加载最新的,服务器的缓存策略可以在响应头里设置,比如Nginx可以在location块里用expires和add_header Cache-Control来设置——HTML文件设置expires -1或者add_header Cache-Control "no-cache, no-store, must-revalidate";其他静态资源设置expires 1y或者add_header Cache-Control "public, max-age=31536000, immutable"。

第二个细节是“设置合适的meta标签”,meta标签可以告诉浏览器和搜索引擎一些关于页面的信息,比如viewport(移动端适配)、charset(字符编码)、description(页面描述,有利于SEO)、keywords(页面关键词,虽然现在搜索引擎不太重视,但还是可以加一下)等,移动端适配的viewport标签必须加,不然页面在移动端会显示得非常小——viewport标签的常用配置是;字符编码的meta标签最好加在的最前面,不然可能会出现乱码——字符编码的配置是。

第三个细节是“做好SEO优化”,如果是普通的单页应用(SPA),搜索引擎的爬虫只能抓取到index.html的内容,不能抓取到路由跳转后的内容,SEO效果会很差,如果需要SEO优化,可以用服务端渲染(SSR)、静态站点生成(SSG)或者预渲染(Prerendering),Vue3的SSR框架有Nuxt.js 3;SSG框架也有Nuxt.js 3(可以配置成SSG模式);预渲染可以用vite-plugin-prerender(Vite)或者prerender-spa-plugin(Webpack)插件,预渲染的作用是在Build的时候把指定的路由对应的页面渲染成静态HTML文件,这样搜索引擎的爬虫就能抓取到这些页面的内容了,预渲染适合路由数量不多、内容变化不频繁的项目。

第四个细节是“配置错误监控和性能监控”,项目上线后,不能不管不问,必须配置错误监控和性能监控,及时发现和解决问题,错误监控可以用Sentry、Fundebug、阿里云ARMS前端监控等工具;性能监控可以用Lighthouse、Web Vitals、阿里云ARMS前端监控等工具,Web Vitals是Google推出的一套核心性能指标,包括LCP(最大内容绘制)、FID(首次输入延迟)、CLS(累积布局偏移),现在主流的性能监控工具都支持Web Vitals的监控。

Vue3项目的Build打包上线看起来简单,但其实有很多细节需要注意,稍不注意就会踩坑,今天分享的内容,从Build前的预检查、Build后的白屏排查,到进阶的体积优化、细节注意,都是我四年多来踩过坑后总结出来的,非常实用,最后想说的是,不管用什么工具和方法,Build打包上线的核心目的都是让项目在服务器上正常运行,并且性能好、用户体验好,希望今天的内容能对大家有所帮助,如果还有其他Vue3 Build的问题,欢迎在评论区留言讨论。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门