Webpack配置优化实战:提升构建速度与打包效率的实用技巧
又踩坑了,Vite 构建后静态资源路径全乱套
上周上线一个新项目,本地跑得好好的,一构建部署到测试环境,页面直接白屏。打开控制台一看,所有 CSS 和 JS 资源 404 —— 路径全变成根目录下的绝对路径了,比如 /assets/index.xxxx.js,但我们的部署路径其实是 /my-app/ 子目录。这问题我其实以前遇到过,但每次都要重新折腾一遍,今天干脆记录下来,省得下次再抓狂。
一开始我以为是 Nginx 配错了
第一反应:是不是服务器配置漏了?赶紧去翻 Nginx 配置,确认了 location /my-app/ 的 alias 指向正确,也加了 try_files。但刷新还是 404。再看 network 面板,请求的 URL 确实是 https://xxx.com/assets/xxx.js,而不是 https://xxx.com/my-app/assets/xxx.js。显然,问题出在前端构建输出的 HTML 里引用的路径不对。
这时候我才意识到:Vite 默认把 publicPath 设成 / 了。而我们部署在子路径下,必须手动指定 base 路径。
改 base 配置,结果图片又挂了
打开 vite.config.js,加上:
export default defineConfig({
base: '/my-app/',
// ...
})
重新 build,部署。HTML 里的 script 和 link 标签路径确实变成了 /my-app/assets/xxx.js,JS/CSS 加载正常了。但页面上的图片又裂了!
原来,我在 Vue 组件里用了这样的写法:
<template>
<img src="@/assets/logo.png" />
</template>
本地开发没问题,但构建后,这个路径被 Vite 处理成了相对路径(比如 ./logo.xxxx.png),而 HTML 文件在 /my-app/index.html,图片实际在 /my-app/assets/logo.xxxx.png,所以相对路径是对的……等等,那为什么裂了?
折腾了半天发现:不是路径错,而是我用了一个动态拼接的图片路径:
const imgUrl = /assets/icons/${type}.png;
这种写法在 Vite 里不会被当作模块处理,而是当成普通字符串,构建时不会 hash 也不会调整路径。结果它试图加载 /my-app/assets/icons/xxx.png,但实际文件在 /my-app/my-app/assets/icons/xxx.png?不对,等等……
哦!因为 base 是 /my-app/,Vite 把所有静态资源都输出到 dist/my-app/ 目录下了?不,不是这样。Vite 的 base 只影响 HTML 中引用的路径前缀,实际文件还是输出在 dist/assets/ 下,只是 HTML 里写的是 /my-app/assets/...。所以我的硬编码路径 /assets/icons/... 就少了一层 /my-app。
核心代码就这几行:统一处理静态资源引用
解决办法其实很简单:不要硬编码以 / 开头的静态资源路径。要么用相对路径,要么通过 Vite 的 import 方式引入。
对于组件内的静态资源,推荐用 import:
import logo from '@/assets/logo.png'
export default {
data() {
return {
logo
}
}
}
<template>
<img :src="logo" />
</template>
这样 Vite 会把它当作模块处理,自动加上正确的 hash 和路径。
但如果像我一样,有大量动态路径(比如根据变量加载不同图标),import 就不太方便。这时候可以借助 new URL() 语法(Vite 原生支持):
function getIconPath(type) {
return new URL(../assets/icons/${type}.png, import.meta.url).href
}
这个写法会在构建时被 Vite 静态分析,生成正确的带 hash 的绝对路径(包含 base 前缀)。亲测有效!
不过要注意:路径必须是确定的字符串模板,不能是运行时拼接的任意字符串,否则 Vite 无法分析。
还有一种偷懒方案:把 base 设为空
如果你的部署路径不确定,或者想让产物完全用相对路径(比如直接拖进文件夹双击 index.html 就能跑),可以把 base 设成 ./:
export default defineConfig({
base: './',
})
这样所有资源引用都会变成相对路径,比如 ./assets/xxx.js。好处是部署灵活,坏处是如果页面路由用了 history 模式,跳转到深层路由(如 /my-app/user/profile)时,相对路径会基于当前 URL 解析,导致资源 404。
所以这个方案只适合纯静态、无路由跳转的简单页面。我们项目有 Vue Router,果断放弃。
踩坑提醒:这三点一定注意
- base 配置只影响构建输出的 HTML 中的资源路径,不影响你代码里硬编码的字符串路径。很多人以为改了 base 就万事大吉,结果动态拼接的图片、字体还是 404。
- public 目录下的文件不会被 hash,也不会受 base 影响路径。如果你把图片放
public/icons/,那在代码里就得自己拼完整的路径:/my-app/icons/xxx.png。但这样又和 base 耦合了,不推荐。建议尽量用src/assets+ import 或 new URL。 - 开发环境(dev)和生产环境(build)行为不一致。dev 时 Vite dev server 会代理所有请求,所以即使你写
/assets/xxx也能加载;但 build 后就是纯静态文件,路径必须精确匹配。所以一定要在 build 后本地预览一下(可以用npx serve -s dist),别光看 dev 没问题就以为 OK。
最终配置长这样
我们的 vite.config.js 关键部分:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
base: '/my-app/',
plugins: [vue()],
build: {
outDir: 'dist',
assetsDir: 'assets',
rollupOptions: {
// 如果有特殊需求可以在这里配置
}
}
})
然后所有动态资源路径都改成 new URL(..., import.meta.url).href 方式。改完之后,build 出来的 HTML 里资源路径带 /my-app/ 前缀,图片、字体、JS、CSS 全部加载正常。
虽然还有一点小瑕疵:比如某些第三方库内部可能硬编码了资源路径(这种情况很少见),但目前没遇到。整体来说,这套方案稳定上线了,暂时没回滚。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如有没有办法全局重写所有字符串里的 /assets/ 前缀?理论上可以用 rollup 插件处理,但感觉太 hack 了,不如规范写法来得稳妥。

暂无评论