集成 Intl Polyfill 并在 grunt 中按地区打包
解读
面试官想考察三件事:
- 你是否理解 Intl API 的浏览器兼容性缺口 以及 polyfill 的体积代价;
- 能否在 Grunt 体系 里把「条件引入 + 地区分包」做成 可维护、可缓存、可增量构建 的工程化方案;
- 是否具备 国内落地意识:CDN 合规备案、ES5 语法向下兼容、Gzip/Brotli 双压缩、SourceMap 回源策略、小程序 WebView 白名单等。
知识点
- Intl 细分:
Intl.DateTimeFormat、Intl.NumberFormat、Intl.Collator、Intl.PluralRules、Intl.RelativeTimeFormat; - polyfill 选型:
intl,intl-locales-supported,@formatjs/intl-*系列; - locale-data 按需加载:
cldr-data与cldr-core的 npm 镜像源(淘宝源)同步; - Grunt 多任务范式:
grunt.registerMultiTask+this.files动态映射; - 条件注入模板:
grunt.template.process+lodash.template生成entry.js; - 文件指纹:
grunt-filerev配合grunt-assets-inline把 locale 包路径写进 HTML; - 差异化压缩:
grunt-contrib-uglify的compress.global_defs剔除DEBUG代码; - 国内部署:阿里云 OSS 分地区 Bucket、华为云 FunctionGraph 边缘 gzip、微信开发者工具 ES6 转 ES5 开关。
答案
-
安装依赖
npm i -D intl intl-locales-supported cldr-data npm i -D grunt-contrib-clean grunt-contrib-copy grunt-contrib-uglify grunt-contrib-concat grunt-filerev grunt-replace -
目录约定
src/ js/ entry.js intl-shim.js locale-data/ zh.json en.json dist/ js/ app.<hash>.js intl/<locale>/polyfill.<hash>.js -
Gruntfile.js 核心片段
module.exports = function(grunt) { const localeList = ['zh', 'en', 'ja']; // 国内业务常用 grunt.initConfig({ clean: { dist: 'dist' }, // 1. 拷贝并按 locale 拆包 copy: { intl: { files: localeList.map(l => ({ expand: true, cwd: 'node_modules/intl/locale-data/jsonp', src: `intl-${l}*.js`, dest: `dist/js/intl/${l}/`, rename: () => `polyfill.js` })) } }, // 2. 合并 shim + locale 数据 concat: { intl: { files: localeList.map(l => ({ src: ['src/js/intl-shim.js', `dist/js/intl/${l}/polyfill.js`], dest: `dist/js/intl/${l}/polyfill.js` })) } }, // 3. 压缩并加指纹 uglify: { intl: { files: localeList.map(l => ({ src: `dist/js/intl/${l}/polyfill.js`, dest: `dist/js/intl/${l}/polyfill.js` })) } }, filerev: { intl: { src: 'dist/js/intl/**/*.js' } }, // 4. 把带 hash 的路径写回 HTML replace: { html: { src: 'dist/*.html', overwrite: true, replacements: localeList.map(l => ({ from: `__INTL_${l.toUpperCase()}__`, to: grunt.filerev.summary[`dist/js/intl/${l}/polyfill.js`] })) } } }); grunt.registerTask('intl', ['copy:intl', 'concat:intl', 'uglify:intl', 'filerev:intl', 'replace:html']); grunt.registerTask('default', ['clean', 'intl']); }; -
运行时加载策略(entry.js)
const locale = window.LOCALE || navigator.language.slice(0, 2); if (!window.Intl || !Intl.DateTimeFormat.supportedLocalesOf(locale).length) { const s = document.createElement('script'); s.src = `__INTL_${locale.toUpperCase()}__`; // 被 grunt-replace 替换 s.async = false; document.head.appendChild(s); } -
国内优化细节
- 把
cldr-data镜像指向 淘宝 npm 镜像 加速 CI; - 产出文件同时生成
.br与.gz,OSS 配置 Brotli 优先级; - 针对 微信 WebView 强制 ES5 语法, uglify 输出
ie8: true; - 若走 小程序 web-view,域名需加入 业务域名白名单 并上 HTTPS;
- 为了 SEO 预渲染,在
grunt-phantom任务里把 locale 包预加载,避免首屏空白。
- 把
拓展思考
- 如果业务扩张到 50+ 地区,Grunt 的「枚举式」配置会爆炸,可改用 grunt-contrib-requirejs 的
include+exclude动态算子,或者把任务迁移到 Rollup 的代码分割 再回退到 Grunt 调用。 - 为了 SSR 同构,需要在 Node 端同样做 locale 分包,可用
intl-locales-supported在 Egg.js 中间件 里按需require(),避免把全部 cldr 数据打进 Docker 镜像。 - 国内 隐私合规 要求不能把用户语言偏好回传海外 CDN,可在 边缘函数(阿里云 EdgeRoutine) 里根据
Accept-Language直接 302 到最近的地区包,减少回源。