如何将 grunt-contrib-copy 替换为 vite-static-copy
解读
在国内前端工程化面试中,“老项目从 Grunt 迁移到 Vite” 是高频考点。面试官真正想考察的是:
- 你是否能准确识别两套工具链的插件映射关系;
- 能否无痛迁移存量配置,保证上线节奏不被打断;
- 是否理解拷贝行为差异(Grunt 的 IO 同步 vs Vite 的内存+Rollup 插件钩子),避免上线后出现 404、缓存穿透等事故;
- 对Monorepo、微前端、CDN 路径等国内典型场景的适配经验。
一句话:不是简单“换个插件”,而是在持续交付压力下完成构建层换血。
知识点
-
grunt-contrib-copy 核心配置项
- src / dest 支持 minimatch 通配
- expand:true 开启动态文件对象
- flatten、rename、filter 函数可二次加工路径
-
vite-static-copy 运行原理
- 基于 Rollup 的 generateBundle 钩子,在 Vite 生产打包阶段把文件写进 dist
- dev 阶段默认不拷贝,除非额外写 server.middlewares.use 做代理
- targets 数组每一项等价于 Grunt 的一个文件对象,但不支持函数式 filter/ rename,只能用 transformPath、transform 做字符串级处理
-
路径差异
- Grunt 以 Gruntfile.js 所在目录为 CWD;Vite 以 root(默认项目根) 为基准
- 国内项目常把静态资源放 public,但public 目录会被 Vite 整体伺服,vite-static-copy 的目标文件若与 public 重名会覆盖且不会告警
-
性能与缓存
- Grunt 每次全量 IO;Vite 仅在 rollup 生成阶段写盘,dev 时无 IO 收益
- 若拷贝体积 > 50 MB(常见政务、可视化大屏项目),需用 vite-plugin-static-copy 的 parallel 选项 开启多线程,否则 CI 时长翻倍
-
灰度与回滚
- 国内上线常走 “蓝绿 + CDN 刷新”;务必保证拷贝后文件名带 contenthash,并在 vite.config 里配置 rollupOptions.assetFileNames,否则回滚时 CDN 边缘节点缓存无法及时失效
答案
步骤一:卸载旧插件
npm remove grunt-contrib-copy
npm remove grunt --save-dev
步骤二:安装新插件
npm i -D vite-plugin-static-copy
步骤三:把 Gruntfile 中的 copy 子任务映射成 vite.config.js 配置
原 Grunt 片段:
copy: {
libs: {
expand: true,
cwd: 'node_modules/@company/lib/dist/',
src: ['**/*'],
dest: 'dist/assets/lib/'
},
locales: {
expand: true,
cwd: 'src/locales',
src: ['**/*.json'],
dest: 'dist/locales',
rename: function(dest, src) {
return dest + '/' + src.replace('zh-CN', 'zh');
}
}
}
Vite 等效配置:
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';
export default defineConfig({
plugins: [
viteStaticCopy({
targets: [
{
src: 'node_modules/@company/lib/dist/**/*',
dest: 'assets/lib'
},
{
src: 'src/locales/**/*.json',
dest: 'locales',
transformPath: (path) => path.replace('zh-CN', 'zh')
}
],
// 国内服务器 IO 慢,开启并行
parallel: true
})
],
// 保证回滚安全
build: {
rollupOptions: {
output: {
assetFileNames: 'assets/[name]-[hash][extname]'
}
}
}
});
步骤四:验证
- npm run build 后检查 dist 目录结构是否与 Grunt 时代完全一致
- 跑一遍 Jest 快照测试 或 Playwright 视觉回归,防止路径 404
- 在 GitLab CI 里对比产物体积,若增长 > 5%,排查是否重复拷贝了 node_modules 源码图
步骤五:清理
删除 Gruntfile.js、grunt 目录及所有 grunt-* 插件,更新 README 中的 “构建命令” 章节,避免新成员误用 grunt dev
拓展思考
-
混合迁移场景
若公司仍保留 Grunt 做遗留 JSP 项目,而新模块用 Vite,可通过 npm-run-all 把 grunt & vite 串行跑,但需统一 dist 输出目录,防止运维脚本找不到包。此时建议把 vite-static-copy 的 dest 设成绝对路径,指向 Grunt 的 dist,实现**“双构建、单产物”**。 -
微前端基座隔离
在 qiankun 基座里,子应用静态资源需带 “micro_app_name” 前缀。可在 transformPath 里注入子应用名,或写自定义插件在 generateBundle 阶段统一加前缀,避免手动维护。 -
CDN 上传闭环
国内云厂商(阿里云 OSS、腾讯云 COS)插件只认本地磁盘文件。vite-static-copy 执行时机在 generateBundle,之后立即走 upload-cdn-plugin,可保证拷贝文件被一并上传;若用 dev 阶段的代理方案,则 CDN 不会命中,导致首次 404。务必关闭 dev 代理,强制走 build。 -
未来可替换为 import.meta.globEager
若拷贝的是本地化 JSON 配置,其实可以直接改写成 ES Module,通过 globEager 动态导入,利用 Vite 的 HTTP 304 缓存,节省一次拷贝。面试时可主动提及,体现**“不仅迁移,而且优化”** 的思维。