解释在多语言 HTML 中保留特定 data 属性的白名单写法

解读

这道题考察的是“在国际化(i18n)场景下,如何用 Grunt 插件对 HTML 做压缩/清理,同时保留业务所需的 data- 属性*”。国内项目常见套路是:

  1. grunt-contrib-htmlmingrunt-processhtml 做压缩/预处理;
  2. data-i18ndata-lang 等自定义属性标记多语言字段;
  3. 如果直接开启 removeAttributeQuotesignoreCustomComments,极易误删这些关键属性,导致运行时国际化脚本取不到节点,页面白屏。
    面试官想看你是否理解“白名单机制”——即通过精确配置告诉 Grunt“哪些属性必须留下”,而不是一刀切地干掉所有未知属性。

知识点

  • grunt-contrib-htmlminoptions.customAttrCollapseoptions.customAttrSurround 仅控制折叠,不负责保留;真正决定保留的是 options.keepCustomAttributeSuffix(正则)与 options.ignoreCustomFragments(数组)。
  • 白名单写法本质是一段正则,放在 keepCustomAttributeSuffix 里,如 /^data-i18n|^data-lang/;也可在 ignoreCustomFragments 里写 /\sdata-i18n=".*?"/g,告诉压缩器“碰到这段就别动”。
  • 若项目同时跑 Vue/React 服务端渲染,还需把 data-v-*data-reactid 一并加进正则,避免 hydration 失败。
  • 国内 CI 常配合 grunt-usemin 做合并,htmlmin 只是其中一环,白名单需提前在 usemin 的 htmlmin 子配置里声明,否则后面再覆盖已来不及。
  • 测试方法:在本地跑 grunt htmlmin 后,用 grep -E 'data-i18n|data-lang' dist/*.html 验证属性是否还在,这是面试官最认可的验证脚本

答案

在 Gruntfile 里给 grunt-contrib-htmlmin 加一段白名单正则即可,核心配置如下

htmlmin: {
  dist: {
    options: {
      removeRedundantAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      // 关键行:保留以 data-i18n 或 data-lang 开头的属性
      keepCustomAttributeSuffix: /^data-i18n|^data-lang/
    },
    files: [{
      expand: true,
      cwd: 'src',
      src: '**/*.html',
      dest: 'dist'
    }]
  }
}

如果项目里还有 data-v-12345 这类服务端注入标记,把正则扩写为

keepCustomAttributeSuffix: /^data-(i18n|lang|v-)/

执行 grunt htmlmin 后,dist 目录下所有 HTML 的 data-i18n、data-lang 属性会被完整保留,其余无用属性则被安全移除,既减了体积又保证了多语言脚本正常运行

拓展思考

  1. 动态属性场景:当国际化 key 本身带版本号,如 data-i18n-v2025,可把正则可读性优先写成 /^data-i18n(-v\d+)?$/避免每次发版改配置
  2. 多插件联动:若还用 grunt-angular-gettext 提取字符串,需保证 extract 任务在 htmlmin 之前跑,否则白名单保留的属性可能已被提前删掉,导致提取不到 key。
  3. 安全红线:国内金融项目常要求 data-* 属性值做 XSS 过滤,白名单只能保“名”不能保“值”,仍需在 htmlmin 之后加一道 grunt-xss 扫描,这是面试官追问的加分点