Ember应用如何按需加载Intl.Locale polyfill并保证优先执行
问题根因
你遇到的加载顺序问题本质是:所有包含异步逻辑(动态import()、async函数内await)的脚本,都不会阻塞后续普通script标签的解析执行。哪怕开启webpack顶层await特性,只要ember-auto-import的代码分割策略把动态导入的polyfill拆成独立chunk,polyfill chunk就会和后续vendor、app chunk并行加载,最终在iOS<14这类不支持Intl.Locale的环境下,ember-intl初始化时调用new Intl.Locale()的时机早于polyfill挂载完成,就会抛出你看到的构造函数不存在的错误。
可行解决方案
方案1:基于targets配置自动按需注入polyfill(最适配Ember生态,零顺序问题)
你已经在config/targets.js中配置了需要兼容的浏览器范围(包含iOS<14版本),可以直接通过babel插件自动判断目标环境是否需要polyfill,在编译期完成静态注入,完全规避异步加载顺序问题:
- 安装依赖:
npm install @formatjs/intl-locale babel-plugin-formatjs --save-dev - 修改
ember-cli-build.js,添加babel插件配置:let app = new EmberApp(defaults, { babel: { plugins: [ ['formatjs', { polyfill: { locale: true } }] ] }, // 保留原有autoImport配置即可 }); - 该方案会在编译阶段根据你配置的浏览器兼容列表自动判断:对原生支持Intl.Locale的环境,完全不打入polyfill代码;对需要兼容的低版本环境,自动把polyfill代码注入到vendor.js最顶部,不存在异步加载的顺序问题。
方案2:内联同步判断+阻塞式脚本加载(完全可控,无构建依赖)
不要把polyfill的判断逻辑交给webpack打包,直接在index.html所有业务脚本之前写内联同步脚本,判断当前环境是否需要polyfill,需要时插入同步阻塞的script标签,保证polyfill加载完成前后续脚本不会执行:
- 修改
index.html的body开头部分:<body> <!-- 内联脚本同步执行,无异步逻辑 --> <script> // 手动实现polyfill判断逻辑,和官方判断逻辑一致,体积可忽略 function shouldPolyfillIntlLocale() { try { new Intl.Locale('en-US'); return false; } catch (e) { return true; } } if (shouldPolyfillIntlLocale()) { // 必须用document.write插入脚本,该方式插入的script是同步阻塞的 // polyfill文件提前打包放到assets目录即可 document.write('<script src="{{rootURL}}assets/intl-locale-polyfill.min.js"><\/script>'); } </script> <!-- 原有脚本顺序保持不变 --> <auto-import-script entrypoint="polyfills"></auto-import-script> <script src="{{rootURL}}assets/vendor.js"></script> <auto-import-script entrypoint="app"></auto-import-script> <script src="{{rootURL}}assets/app.js"></script> </body> - 注意不要用
document.createElement('script')+appendChild的方式插入脚本,这种方式插入的脚本默认是异步非阻塞的,无法保证加载顺序。
方案3:修正webpack顶层await配置(适合需要保留动态导入逻辑的场景)
你之前配置顶层await不生效,核心原因是webpack默认的代码分割策略把polyfill拆成了独立chunk,没有和polyfills入口做执行顺序绑定,修改ember-cli-build.js的webpack配置即可:
autoImport: { insertScriptsAt: 'auto-import-script', webpack: { target: 'web', experiments: { topLevelAwait: true }, entry: { polyfills: './lib/polyfills.js', }, optimization: { splitChunks: { chunks: 'async', cacheGroups: { // 强制把所有@formatjs下的polyfill代码打进polyfills入口,不拆分独立chunk polyfills: { test: /[\\/]node_modules[\\/]@formatjs[\\/]/, name: 'polyfills', chunks: 'all', enforce: true } } } } }, }
- 同时修改
lib/polyfills.js的代码,使用顶层await:import {shouldPolyfill} from '@formatjs/intl-locale/should-polyfill'; if (shouldPolyfill()) { await import('@formatjs/intl-locale/polyfill'); // 其他需要的intl相关polyfill都可以在这里按顺序await加载 await import('@formatjs/intl-getcanonicallocales/polyfill'); } - 该方案配置相对复杂,不同版本的ember-auto-import对顶层await的支持程度有差异,优先选择前两个方案。
避坑说明
- 不要用异步立即执行函数(IIFE)、普通async函数调用的方式加载polyfill,这类写法只要存在
await,后续同步脚本就会立刻开始执行,完全无法保证加载顺序。 - ember-intl在应用初始化阶段就会同步调用
new Intl.Locale(),执行时机非常早,polyfill必须在vendor.js执行前完成全局挂载,没有延迟加载的空间。
内容的提问来源于stack exchange,提问作者st-h
相关产品推荐
相关产品推荐

