Skip to content

打包构建踩坑

Vite / Webpack 项目构建过程中常见的配置错误、性能问题和解决方案。


预防措施

  1. 锁定构建工具版本:package.json 中明确指定 Vite/Webpack 版本,避免升级导致构建行为变化
  2. 配置 CI 构建检查:每次提交自动运行构建,及早发现问题
  3. 使用构建分析工具:定期检查打包体积,防止依赖膨胀
  4. 环境变量统一管理:通过 .env 文件集中管理,避免硬编码
  5. 建立兼容性基线:明确需要支持的浏览器版本,据此配置 Babel 和 Polyfill

1. 构建后静态资源 404

js
// 原因:publicPath / base 配置错误
// publicPath(Webpack)或 base(Vite)决定了静态资源的基础路径

// ✅ 根部署(部署在域名根目录)
// vite.config.ts
export default defineConfig({
  base: '/'  // 资源路径从根目录开始,如 https://example.com/assets/index.js
})

// ✅ 子路径部署(部署在域名的子路径下)
export default defineConfig({
  base: '/my-app/'  // 资源路径为 https://example.com/my-app/assets/index.js
})

// ✅ CDN 部署(静态资源放在 CDN 上)
export default defineConfig({
  base: 'https://cdn.example.com/my-app/'  // 资源直接从 CDN 加载
})

2. 路由懒加载构建后白屏

js
// 原因:动态 import 的 chunk 路径错误

// ❌ 相对路径在某些部署场景下出错
const Home = () => import('./views/Home.vue')

// ✅ 使用别名(@ 指向 src 目录,路径更稳定)
const Home = () => import('@/views/Home.vue')

// ✅ webpackChunkName 注释(方便调试)
/* webpackChunkName 是 Webpack 的"魔法注释",用于给动态导入的 chunk 命名 */
/* 打包后会生成 home.js 而非随机哈希名,便于调试和排查加载问题 */
const Home = () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')

3. 第三方库打包体积过大

为什么要优化打包体积?

打包体积直接影响首屏加载速度。体积越大,用户等待时间越长,尤其是在移动端或网络较差的环境下。

两种优化方式:

  • 按需引入:UI 库(如 Element Plus)通常有很多组件,但你可能只用了一部分。按需引入只打包用到的组件,体积可以减少 80% 以上
  • CDN 外部化:Vue、Axios 等稳定的基础库,用 CDN 引入不打包到项目中,减小构建产物体积,还能利用 CDN 缓存
bash
# 分析包大小
# Vite 使用 rollup-plugin-visualizer(需先安装)
npm install -D rollup-plugin-visualizer
# 在 vite.config.ts 中配置插件后运行 build,会自动生成分析报告

# 或使用 source-map-explorer(无需额外配置)
npx source-map-explorer dist/assets/*.js

# Webpack(Vue CLI)
npm run build -- --report    # 生成 report.html,展示模块依赖关系和大小
js
// ✅ 按需引入(以 Element Plus 为例)
// ❌ 全量引入(打包所有组件,体积大)
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// ✅ 按需引入(自动按需导入组件和样式)
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'         // 自动导入 API
import Components from 'unplugin-vue-components/vite'       // 自动注册组件
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'  // Element Plus 解析器

export default defineConfig({
  plugins: [
    AutoImport({ resolvers: [ElementPlusResolver()] }),     // 自动导入 Element Plus 的 API
    Components({ resolvers: [ElementPlusResolver()] })      // 自动注册使用的组件
  ]
})
js
// ✅ 外部化(CDN 引入)
// 将 Vue、Vue Router、axios 等基础库排除在打包之外
// 运行时从 CDN 加载,减小构建产物体积
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      external: ['vue', 'vue-router', 'axios'],  // 告诉打包工具这些库不打包
      output: {
        globals: {
          vue: 'Vue',              // CDN 加载后挂载到 window.Vue
          'vue-router': 'VueRouter',  // 挂载到 window.VueRouter
          axios: 'axios'           // 挂载到 window.axios
        }
      }
    }
  }
})

4. Vite 构建后图片路径错误

js
// 原因:CSS 中使用了相对路径,但构建后路径变化

// ❌ CSS 中
background: url('./assets/bg.png')

// ✅ 使用 @ 别名(需要配置 resolve.alias)
background: url('@/assets/bg.png')

// ✅ 或放在 public/ 目录,用绝对路径
background: url('/images/bg.png')

5. 环境变量未生效

bash
# Vite 环境变量必须以 VITE_ 开头
VITE_API_URL=https://api.example.com    # ✅ 可用
API_URL=https://api.example.com          # ❌ 不可用

# Vue CLI 环境变量必须以 VUE_APP_ 开头
VUE_APP_API_URL=https://api.example.com  # ✅ 可用
API_URL=https://api.example.com          # ❌ 不可用
bash
# 文件名错误也会导致不加载
# ✅ .env.production
# ✅ .env.development
# ❌ .env.prod(不会被自动加载)

6. Vite 开发环境正常,构建报错

js
// 原因:Vite 开发用 esbuild,生产用 Rollup,语法支持不同

// ❌ Rollup 不支持某些语法
const modules = import.meta.glob('./modules/*.js')  // ✅ Vite 特有

// ✅ 检查 Rollup 兼容性
// vite.config.ts
export default defineConfig({
  build: {
    target: 'es2015',  // 指定构建目标
    rollupOptions: {
      onwarn(warning, warn) {
        if (warning.code === 'MODULE_LEVEL_DIRECTIVE') return
        warn(warning)
      }
    }
  }
})

7. Webpack 构建速度慢

js
// ✅ 排除不需要转译的依赖
// vue.config.js
module.exports = {
  transpileDependencies: []  // 只添加必须转译的依赖
}

// ✅ 开启持久化缓存(Webpack 5)
module.exports = {
  configureWebpack: {
    cache: {
      type: 'filesystem'
    }
  }
}

// ✅ 使用 thread-loader 多线程编译
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('js')
      .use('thread-loader')
      .loader('thread-loader')
      .options({ workers: 4 })  // 建议设为 CPU 核心数减 1,过多反而降低性能
      .before('babel-loader')
  }
}

8. Vite HMR(热更新)失效

js
// 原因一:文件监听失败(Docker/WSL)
// vite.config.ts
export default defineConfig({
  server: {
    watch: {
      usePolling: true,   // 轮询模式
      interval: 1000
    }
  }
})

// 原因二:循环依赖
// 检查是否有 A import B, B import A 的情况

// 原因三:文件在 node_modules 中
// node_modules 中的文件修改不会触发 HMR

9. CSS Modules 类名冲突

什么是 CSS Modules?为什么要用?

多人协作时,不同开发者可能写了相同的类名(如 .container.title),导致样式互相覆盖。

CSS Modules 会自动给每个类名加上唯一的哈希值后缀,实现样式隔离:

  • .container.container_abc123
  • 不同组件的 .container 会生成不同的哈希值,不会冲突

什么时候用 CSS Modules?

  • 多人协作的大型项目
  • 组件样式需要严格隔离
  • 不想用 BEM 命名规范(.block__element--modifier
vue
<!-- Vue 文件中使用 CSS Modules -->
<style module>
.container {
  color: red;
}
</style>

<script setup>
import { useCssModule } from 'vue'
const style = useCssModule()
console.log(style.container)  // 生成的唯一类名
</script>
js
// ✅ Vite 配置 CSS Modules 命名规则
// vite.config.ts
export default defineConfig({
  css: {
    modules: {
      localsConvention: 'camelCase',          // kebab-case 转 camelCase
      generateScopedName: '[name]__[local]___[hash:base64:5]'
    }
  }
})

10. Polyfill 缺失导致旧浏览器报错

bash
# Vite 默认不注入 polyfill
npm install -D @vitejs/plugin-legacy
js
// vite.config.ts
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11']  // 兼容目标
    })
  ]
})
js
// Webpack(Vue CLI)默认会注入
// babel.config.js
module.exports = {
  presets: [
    ['@vue/cli-plugin-babel/preset', {
      useBuiltIns: 'usage',   // 按需注入 polyfill
      corejs: 3               // core-js 版本
    }]
  ]
}

11. 打包后出现 eval(安全审计不通过)

js
// 原因:Source Map 使用了 eval 类型

// ✅ 关闭 Source Map
// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: false
  }
})

// ✅ 使用 hidden-source-map(不暴露源码但可调试)
export default defineConfig({
  build: {
    sourcemap: 'hidden'
  }
})

12. 微前端 / 子应用资源加载失败

js
// 原因:子应用的资源路径是相对的,被主应用拦截

// ✅ 设置绝对路径
// vite.config.ts
export default defineConfig({
  base: '//cdn.example.com/sub-app/'  // CDN 绝对路径
})

// ✅ 或使用 publicPath 动态设置
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

常用调试命令

bash
# Vite 查看构建产物分析
# 使用 rollup-plugin-visualizer 插件(需在 vite.config.ts 中配置)
npx vite build  # 构建后自动生成分析报告

# Vite 构建时打印详细日志
npx vite build --debug

# Webpack 查看最终配置
vue inspect > webpack.config.js

# Webpack 分析依赖
npx webpack-bundle-analyzer stats.json

真实场景

场景 1:部署到子路径后页面白屏

  • 问题:项目部署到 https://example.com/admin/ 后,页面白屏,控制台报 404
  • 原因:Vite 的 base 配置为默认值 '/',资源请求发到了 https://example.com/assets/
  • 解决:设置 base: '/admin/',资源路径变为 https://example.com/admin/assets/

场景 2:首次加载超过 10 秒

  • 问题:项目打包后 vendor.js 有 2MB,首次加载耗时 10 秒以上
  • 原因:全量引入了 Element Plus 和 ECharts,未做按需引入
  • 解决:使用 unplugin-auto-import 按需引入 Element Plus,ECharts 按需加载模块,体积降至 400KB

场景 3:构建后路由懒加载白屏

  • 问题:本地开发正常,部署后点击路由跳转白屏
  • 原因:使用了相对路径 import('./views/Home.vue'),部署到子路径后 chunk 路径错误
  • 解决:改用别名路径 import('@/views/Home.vue')

参考

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准