cd ..

Vue-cli 中使用 Unocss 热更新缓慢

改一行代码,等三分钟才看到效果——这大概是前端开发者最崩溃的体验之一了。偏偏我在给一个 Vue2 老项目引入 UnoCSS 后,就遇上了这个问题。

从”真香”到”真慢”

事情的起因很简单:团队有个用 Vue CLI 搭建的老项目,样式写得比较混乱,我想引入 UnoCSS 来统一原子化 CSS 的使用,提升开发效率。装好依赖、跑起来,效果确实不错——直到我发现热更新变得慢到离谱。

改个文字,浏览器要转好几分钟的圈才刷新出来。在大型项目里,这种等待简直是生产力杀手。

罪魁祸首:缓存被禁用了

翻了一下官方示例,在 Vue CLI 中使用 UnoCSS 需要在 vue.config.js 里加上这段配置:

// vue.config.js
module.exports = {
  chainWebpack: (config) => {
    config.module.rule('vue').uses.delete('cache-loader');
    config.merge({
      cache: false,
    });
  },
  css: {
    extract: {
      filename: '[name].[hash:9].css',
    },
  },
};

问题就藏在这两行里:

config.module.rule('vue').uses.delete('cache-loader');
config.merge({
  cache: false,
});

看到了吗?cache: false 加上删除 cache-loader,等于把 webpack 的缓存机制全部砍掉了。这样做确实能让 UnoCSS 正常工作(因为它需要实时扫描和生成样式),但代价是每次热更新都要从零开始编译整个项目,而不是只处理变更的部分。

项目小的时候可能感觉不明显,但一旦项目规模上来,编译时间就会呈指数级增长。

折中方案:用文件系统缓存替代

经过一番摸索,我找到了一个折中方案——不是完全禁用缓存,而是换一种缓存策略:

// vue.config.js
module.exports = {
  configureWebpack: {
    cache: {
      type: 'filesystem',
      cacheDirectory: path.join(__dirname, '.cache/'),
    },
    plugins: [
      UnoCSS({}),
    ],
  }
};

同时,在 package.json 中添加 --watch 参数:

// package.json
{
  "scripts": {
    "serve": "vue-cli-service serve --watch"
  }
}

核心思路是用 webpack 的文件系统缓存(type: 'filesystem')来代替完全禁用缓存。文件系统缓存会把编译结果持久化到磁盘上,下次编译时只需要处理真正发生变化的文件,而 --watch 参数则增强了 webpack 对文件变动的监听能力。

效果如何?

改完配置之后,热更新速度基本恢复到了正常水平。不过坦白说,这不是一个完美方案:

  • 普通代码修改能快速反映在浏览器中,开发体验回来了
  • 但修改 UnoCSS 相关的样式类名时,需要手动刷新页面才能看到效果

也就是说,UnoCSS 的实时样式热更新能力在这个方案下是缺失的。但和之前每次改代码都要等几分钟相比,偶尔手动刷新一下页面,我觉得完全可以接受。

回顾与思考

这个问题本质上是 UnoCSS 的工作机制和 webpack 缓存之间的冲突。UnoCSS 需要扫描源码来生成对应的 CSS,而 webpack 的缓存会跳过未变更的文件,导致 UnoCSS 无法感知到新增的工具类。

官方的解决方式是简单粗暴地禁用缓存,保证 UnoCSS 每次都能完整扫描。而我们的方案是在缓存和 UnoCSS 功能之间找了一个平衡点——牺牲了 UnoCSS 样式的实时热更新,换回了整体的开发效率。

如果你也在 Vue CLI 项目中遇到了类似的问题,不妨试试这个方案。当然,如果条件允许,迁移到 Vite 才是更彻底的解决之道——Vite 对 UnoCSS 的支持要好得多,完全没有这类缓存冲突的烦恼。