Grunt自动化构建实战:从配置到优化的完整指南

怡彤 Dev 前端 阅读 2,790
赞 15 收藏
二维码
手机扫码查看
反馈

Grunt 打包时图片路径全乱了,折腾一晚上才搞定

昨天改一个老项目,用的是 Grunt 构建的,本来只是想加个新功能,结果打包完发现所有图片都 404 了。页面上全是裂图,控制台一堆 Failed to load resource。我寻思着又没动图片路径相关配置,怎么就崩了?

Grunt自动化构建实战:从配置到优化的完整指南

一开始我以为是 CDN 配置问题,查了下 Gruntfile.js 里的 cdnify 任务,发现压根没开。那问题肯定出在本地路径处理上。翻了翻 dist 目录,发现图片确实被复制过去了,但 HTML 里引用的路径却是错的 —— 比如原本是 img/logo.png,打包后变成了 dist/img/logo.png,但实际图片在 dist/assets/img/logo.png,路径层级完全对不上。

这里我踩了个坑:我以为是 copy 任务的问题,于是花了一个小时调整 copy:distcwddest,结果越调越乱。后来试了下发现,根本不是复制的问题,而是 usemin 在处理 HTML 里的资源引用时,路径映射没对上。

原来 useminPrepare 和 usemin 是两套逻辑

这个项目用的是 grunt-usemin 插件,它分两步走:先用 useminPrepare 分析 HTML,生成临时的 Grunt 配置(比如 cssmin、uglify 的任务),再用 usemin 替换 HTML 中的引用路径。

问题就出在第一步。我看了下 HTML 里的写法:

<!-- build:img /assets/img/logo.png -->
<img src="img/logo.png" alt="logo">
<!-- endbuild -->

这种写法其实是错的。grunt-usemin 并不支持 build:img 这种 block。它只认 cssjs,以及默认的 concat。对于图片、字体这些静态资源,它不会自动处理路径。

那为什么以前能跑?我翻了 Git 历史,发现之前项目里所有图片都是直接写绝对路径,比如 /img/logo.png,而构建时通过 copysrc/img 复制到 dist/img,所以路径刚好对得上。但这次我为了模块化,把图片挪到了 src/assets/img,复制目标也改成了 dist/assets/img,但 HTML 里还是写的相对路径 img/logo.png,自然就断链了。

三种方案对比,我选了最简单的

我试了三种方式:

  • 方案一:给所有图片加 build:img 支持。但 grunt-usemin 官方不支持,得自己写自定义 block,太麻烦。
  • 方案二:用 grunt-string-replace 批量替换 HTML 中的图片路径。但要维护正则,容易误伤,而且新增图片还得改配置。
  • 方案三:统一用绝对路径,并让 copy 任务保持目录结构一致。这个最省事。

我果断选了方案三。反正老项目也没用啥 fancy 的模块化,绝对路径反而更稳定。

但光改 HTML 还不够。因为 usemin 在替换 CSS/JS 路径时,会基于 HTML 文件所在位置计算相对路径。如果 HTML 在 dist/ 根目录,而图片在 dist/assets/img/,那引用就得写成 /assets/img/logo.png —— 注意开头的斜杠,表示从根目录开始。

关键来了:usemin 默认不会处理以 / 开头的路径,它只处理相对路径。所以如果你写 src="/assets/img/logo.png",它会原样保留,不会做任何替换或校验。这其实反而是好事,只要你的服务器配置正确,这种路径永远是对的。

核心代码就这几行

最终我的 Gruntfile.js 里做了两处改动:

第一,确保 copy 任务把所有静态资源(包括图片)按原结构复制到 dist

copy: {
  dist: {
    files: [{
      expand: true,
      cwd: 'src',
      src: [
        'assets/**/*', // 包含 img, fonts, icons 等
        'index.html'
      ],
      dest: 'dist'
    }]
  }
}

第二,HTML 里所有图片、字体等资源,全部使用/ 开头的绝对路径:

<!-- 正确写法 -->
<img src="/assets/img/logo.png" alt="logo">
<link rel="icon" href="/assets/favicon.ico">

注意:不要用 build 注释包裹这些静态资源,因为 usemin 不处理它们,强行包裹反而可能导致解析错误。

然后,确保 usemin 任务只处理 CSS 和 JS:

useminPrepare: {
  html: 'dist/index.html',
  options: {
    dest: 'dist'
  }
},
usemin: {
  html: ['dist/index.html'],
  css: ['dist/styles/*.css'],
  options: {
    assetsDirs: ['dist', 'dist/assets']
  }
}

这里有个细节:assetsDirs 必须包含所有可能存放静态资源的目录,否则 usemin 在替换 CSS 中的背景图路径时可能会找不到文件。比如 CSS 里有 background: url('../assets/img/bg.jpg'),如果 assetsDirs 没包含 dist/assets,它就无法正确解析这个路径。

踩坑提醒:这三点一定注意

1. 别乱用 build:xxx:除了 cssjs,其他 block 类型基本不被支持,写了也白写,还可能干扰解析。

2. 绝对路径比相对路径更可靠:在构建工具里,相对路径依赖于文件之间的相对位置,一旦目录结构调整,全盘皆输。而以 / 开头的路径,只要服务器根目录对,就永远正确。

3. assetsDirs 别漏配:这个配置项决定了 usemin 在哪些目录里找资源文件。如果 CSS 引用了图片,但图片所在目录没列在 assetsDirs 里,构建时就会报错说找不到文件。

改完之后重新构建,图片终于正常显示了。虽然还有个小问题:开发时用的本地服务器(比如 grunt serve)可能不支持根路径 /,这时候需要配个 base href 或者用代理。不过这个问题只影响开发环境,生产环境没问题就行,我暂时忍了。

以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。说实话,现在新项目我都用 Vite 或 Webpack 了,Grunt 这种老古董真是一碰就掉渣。但没办法,有些遗留项目还得维护,只能硬着头皮修。希望这篇记录能帮到同样在和 Grunt 搏斗的你。

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

暂无评论