使用Top-level await新特性,按需加载国际化资源文件,提高首屏效率
我正在参加「掘金·启航计划」
前言
现在很多项目都需要支持国际化,多语言,即页面会默认显示浏览器的语言词条,也可以自行在页面上切换语言。
有很多封装好的第三方国际化库,这里举一个简单例子:通过读取各语言的json格式国际化资源文件,然后封装common get方法获取词条value。
// i18n/en/a.en.json
{
"I18N_A_Key1": "value1",
"I18N_A_Key2": "value2",
"I18N_A_Key3": "value3"
}
// i18n/en/index.js
import a from './a.en.json';
import b from './b.en.json';
export default { ...a, ...b };
// i18n/index.js
import en from './en';
import zh from './zh';
const lang = localStorage.getItem('lang') || 'en';
let json = {};
switch (lang) {
case 'zh':
json = zh;
break;
case 'en':
default:
json = en;
break;
}
export default {
get: (key) => {
return json[key];
}
};
// page.js
import i18n from './i18n';
const text = i18n.get('I18N_A_Key1');
但是这样会有个问题,如果国际化词条过多,资源文件过大,会每次进页面时,都需要加载所有语言的资源,而页面上只需要一种语言,那其它语言的资源,都是unused resource,可能会很影响网站首屏加载速度。
所以需求是拆分国际化资源文件,并按需加载。
本文只研究多语言国际化资源文件的拆分及按需加载,是指国际化词条已经翻译之后,已经交付给开发后,开发这边需要做的工作。打包用的是webpack
import 动态加载
封装成aync方法,然后顶层调用。
let json = {};
async function load() {
const lang = localStorage.getItem('lang') || 'en';
switch (lang) {
case 'zh':
const zh = await import('./zh');
json = zh.default;
break;
case 'en':
default:
const en = await import('./en');
json = en.default;
break;
}
}
load();
export default {
get: (key) => {
return json[key];
}
};
用webpack-bundle-analyzer
分析下,资源文件拆分了,满足需求。
但是这种毕竟是异步,会有延迟,如果跟页面组件同时加载,那肯定会有get不到value的情况。 如果想做到执行先后顺序,那就得在async结束后,再加载页面组件,不仅会影响首屏速度,也增加了逻辑复杂度。
Top-level await
developer.mozilla.org/zh-CN/docs/…
是一个ECMAScript
提案 Top-level await,允许开发者在 async
函数外使用 await
字段,目前已进入Stage 4阶段。
之前:
// utils.js
let result = '';
async function asyncGet() {
await new Promise((resolve) => setTimeout(resolve, 1000));
result = 'result';
}
export { asyncGet, result };
// page.js
import { asyncGet, result } from './utils';
alert(result); // => ''
asyncGet().then(() => {
alert(result); // => 'result'
});
现在:
// utils.js
...
await asyncGet();
export { asyncGet, result };
// page.js
import { result } from './utils';
alert(result); // => 'result'
之前需要执行下异步方法,然后callback回来后才能取到结果;现在用了Top-level await
,支持顶层调用await,也就是说会等待await执行结束后,才会继续执行page.js下面的代码,这样就不用自己控制执行顺序了。
用在上例国际化里:
let json = {};
async function load() {
... // 同上例
}
await load();
export default {
get: (key) => {
return json[key];
}
};
用webpack-bundle-analyzer
分析下,资源文件拆分了,满足需求。
这样就可以保证在国际化资源文件加载时候,json都已经写在全局变量里了之后,才会加载页面组件,这样就能保证每次get都能取到值。
上例更简便写法,webpack打包时会按路径自动检索文件夹内的文件,然后进行拆分打包,用了魔法注释给拆分的文件起名。
const lang = localStorage.getItem('lang') || 'en';
const result = await import(/* webpackChunkName: "i18n" */ `./${lang}/index`);
const json = result.default;
export default {
get: (key) => {
return json[key];
}
};
这样就实现了资源文件的打包拆分,并且按需加载,比如当前语言是en
刷新页面进来就只会加载英语资源文件,语言是zh
就只加载中文。会有效提升首屏加载速度。
Top-level await
虽然是提案阶段,但是大部分主流浏览器都已经支持了:caniuse.com/mdn-javascr…
另外,webpack默认的不支持 Top-level await
,如果想支持需要在config文件里打开设置:
// webpack.config.js
experiments: { topLevelAwait: true }
总结
本文只是探讨一种资源文件按需加载的两种方式,也不一定是最优解,比如官方对Top-level await
的支持还不算完美,还存在很多问题及风险。如果有更好的解决方案,欢迎分享。
转载自:https://juejin.cn/post/7231457691679211557