Webpack配置优化实战:提升构建速度与打包效率的实用技巧

欧阳紫晨 前端 阅读 2,681
赞 16 收藏
二维码
手机扫码查看
反馈

又踩坑了,Vite 构建后静态资源路径全乱套

上周上线一个新项目,本地跑得好好的,一构建部署到测试环境,页面直接白屏。打开控制台一看,所有 CSS 和 JS 资源 404 —— 路径全变成根目录下的绝对路径了,比如 /assets/index.xxxx.js,但我们的部署路径其实是 /my-app/ 子目录。这问题我其实以前遇到过,但每次都要重新折腾一遍,今天干脆记录下来,省得下次再抓狂。

Webpack配置优化实战:提升构建速度与打包效率的实用技巧

一开始我以为是 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 了,不如规范写法来得稳妥。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论