前端构建工具:Webpack不够用了之后

但这次把一个跑了三年的 Webpack 项目换成 Vite,不是因为 Vite 新,也不是因为大家都说它快,纯粹是因为每次 都要等上 5 分钟,而每次改完代码后刷新浏览器也要等上十几秒。

项目不算大,大约 20 个页面,Vue 2 + TypeScript,用了 Webpack 4 配置。

为什么动手

项目不算大,大约 20 个页面,Vue 2 + TypeScript,用了 Webpack 4 配置。最开始的时候项目还小,构建慢也能忍。但随着业务堆上去,问题就慢慢暴露了:

  • 本地开发改一行代码,HMR 要等 8-15 秒
  • 打包命令跑完要 5 分钟,CI/CD 流水线经常超时
  • Webpack 配置文件堆到了 800 行,每次改点东西都要小心翼翼
  • 新人入职光是理解构建配置就要花半天

不是没想过优化,试过多进程、cache-loader、硬 source-map,效果有但有限。Webpcak 4 本身的限制就在那儿,再怎么调也跳不出它的框。

决定换 Vite 的那一刻,更多是无奈:要么继续忍受,要么赌一把迁移成本。


迁移前的准备

换工具不是拍脑袋就干,先做技术调研。主要确认了几件事:

  1. 项目里有没有 Webpack 特有的 loader 和 plugin
  2. 第三方库有没有 ESM 版本或者兼容性问题
  3. 团队成员是否接受新工具的学习成本
  4. CI/CD 环境是否需要调整

翻了一遍 package.jsonwebpack.config.js,发现用到的 loader 主要是:

  • vue-loader 处理 .vue 文件
  • ts-loader 处理 TypeScript
  • file-loader 处理静态资源
  • css-loaderstyle-loader 处理样式

plugin 相对简单,主要是 HtmlWebpackPluginCopyWebpackPlugin。看起来都能在 Vite 生态里找到对应方案。

第三方库这块稍微麻烦,有些老旧依赖还是 CommonJS 格式。但 Vite 底层用了 Rollup,天生支持 CJS 转 ESM,问题不大。

唯一纠结的是团队学习成本。但想想每天 wasted 在等构建上的时间,还是值得。


第一步:安装和基础配置

先装依赖:

npm install vite @vitejs/plugin-vue2 --save-dev
npm remove webpack webpack-cli webpack-dev-server

建一个新的 vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue2'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  },
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'vuex'],
          'utils': ['lodash-es', 'axios']
        }
      }
    }
  }
})

改一下 package.json 里的 scripts:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

跑一下 npm run dev,本想着能直接起飞,结果直接报错。


踩坑与解决

坑一:入口文件找不到

Webpack 项目通常入口是 src/main.js,但 Vite 默认找 index.html。改了半天才发现,Vite 的 HTML 要手动引入脚本:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>My App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>

之前 Webpack 用 HtmlWebpackPlugin 自动注入,现在要手动写。虽然麻烦一点,但透明度更高。

坑二:环境变量不兼容

Webpack 项目用了 process.env.NODE_ENV,但在 Vite 里取不到。Vite 用的是 import.meta.env,而且要显式声明才能用:

// vite.config.ts
export default defineConfig({
  define: {
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
  }
})

或者统一改代码,把 process.env 换成 import.meta.env。最后选了后者,毕竟 Vite 的方式更标准。

坑三:动态 import 路径问题

项目里有这样的代码:

const module = require(`./modules/${moduleName}.js`)

Webpack 能处理这种动态路径,但 Vite 会直接报错。改成显式的 import:

const modules = import.meta.glob('./modules/*.js')
const module = await modules[`./modules/${moduleName}.js`]()

这算是 Vite 的一个限制,但换个角度想,显式总比隐式好。

坑四:样式文件处理

Webpack 项目里用了 @import 引入全局样式,但在 Vite 里有些路径解析不对。最后在 vite.config.ts 里加了 css.preprocessorOptions.scss.additionalData

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  }
})

坑五:老旧依赖的 ESM 兼容

有个第三方库还是纯 CommonJS,动态 import 时出错。用了 Vite 的 optimizeDeps 配置:

export default defineConfig({
  optimizeDeps: {
    include: ['old-commonjs-lib']
  }
})

这样 Vite 会在启动时预构建这些依赖,转成 ESM 格式。


构建优化

开发环境跑起来后,生产构建又遇到问题。初始打包体积比 Webpack 时大了 30%,主要是没有做代码分割。

调整了 rollupOptions.output.manualChunks,把 Vue 核心库和第三方工具包分开:

rollupOptions: {
  output: {
    manualChunks(id) {
      if (id.includes('node_modules')) {
        if (id.includes('vue')) {
          return 'vue-vendor'
        }
        if (id.includes('axios') || id.includes('lodash')) {
          return 'utils'
        }
        return 'vendor'
      }
    }
  }
}

同时启用了 build.chunkSizeWarningLimitbuild.cssCodeSplit,把 CSS 也按 chunk 分开。

最终构建产物和 Webpack 版本相当,但构建时间从 5 分钟降到了 30 秒,压缩率还提高了 5%。


开发体验对比

迁移完成后,团队反馈最明显的是 HMR 速度。之前改个组件要等 8-15 秒,现在是 100-300ms,几乎感觉不到延迟。

Vite 的另一个优势是原生 ESM 支持。Webpack 在开发环境会把所有模块打包成 bundle,而 Vite 直接用浏览器的原生 ESM 加载,按需编译。这意味着修改的模块才会重新编译,而不是整个项目都跑一遍。

还有一个细节是错误提示。Vite 的错误信息更清晰,直接定位到源码位置,不像 Webpack 有时要猜。


遗留问题

不是所有问题都完美解决了。比如:

  • SSR 渲染还没完全适配,生产环境暂时还是用 Webpack
  • 有些 Webpack 特有的 plugin 还没找到等价方案,绕过去了
  • CI/CD 环境需要调整 Node 版本和缓存策略

这些问题不算致命,但说明迁移不是一劳永逸的。


技术选型的一点思考

这次迁移让我重新思考了技术选型的问题。工具本身没有绝对的好坏,关键看场景。

Webpcak 成熟稳定,生态完善,适合大型复杂项目。Vite 新颖快速,开发体验好,适合中小项目和新项目。

但技术选型不能只看当下,还要考虑未来。Vite 背后的 Rollup 生态在成长,Vue 官方也在推,长远来看可能更值得投资。


迁移不是目的,解决问题才是。如果 Webpack 能满足需求,没必要硬换。但当你每天被构建速度折磨,而且有明确的优化路径时,就该动手了。

这次折腾花了一个月,中间想过放弃,但现在回头看,值。

毕竟,省下来的时间,可以用来做更有意思的事情。

版权声明: 本文首发于 指尖魔法屋-前端构建工具:Webpack不够用了之后https://blog.thinkmoon.cn/post/109-frontend-build-tools-webpack-vite-evolution/) 转载或引用必须申明原指尖魔法屋来源及源地址!