项目复盘:通过动态脚本,实现按需加载语言包

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了项目复盘:通过动态脚本,实现按需加载语言包相关的知识,希望对你有一定的参考价值。

参考技术A

大家好,我是前端西瓜哥,是一名前端开发。

最近做了一个将按需加载语言包的需求,有不少收获,这里记录一下。

原来项目是将所有的语言包合并在一起,放到一个 JSON 文件里然后被引入。

打包后的脚本里,有完整的语言包的代码,导致打包文件非常大。理论上用户只会使用一种语言,其他的语言没有加载的必要。

目前来说项目只支持两种语言,每个语言有文案 4000 多条。如果还是使用全量加载的话,以后支持的语言每多一个,打包后的文件就要膨胀一圈。

做语言包的拆分还是很有必要的。它可以减少加载资源的大小,减少首次页面加载时间,提高用户体验。

实现按需加载语言包的方式很多,我了解到的有三种:

请求 HTML 时,后端做渲染工作,给 HTML 加上语言包的内容。

前端没有什么改造的工作量,但问题是不能利用缓存。但这个问题其实也可以解决,就是后端生成好语言包 js 文件,将嵌入语言包内容的方式改为 cdn 引入的方式,可以利用好缓存。

但这让模板引擎的逻辑变得很重,cdn 上传到哪里,如何维护也是个问题。

使用 React 等框架打包出来单页面应用的文件通常很大,下载需要不少时间。

动态 import 必须在脚本整个下载完后,再执行,所以这是一个串行下载的逻辑。

如果可以的话,我们希望语言包可以和业务代码同时下载。此外,更重要的一点是,在 动态 import 前,我们不能调用获取文案的方法 getText

我在改造项目代码时,发现在我动态 import 语言包并 ReactDOM.render() 之前,有些模块文件调用了getText 方法,因为它们作为枚举指直接暴露出来,没有用函数封装,被 import 时就直接执行了。

语言包都没加载,你执行 getText 是拿不到文案的,这个方案我果断放弃。

这种方案利用了脚本里创建脚本的方式。能在更前面的位置加载语言包脚本。

优点是我们可以不需要做后端渲染的工作,让选择语言包的逻辑交给前端。但涉及到前端工程化,需要写插件改变原来的加载脚本形式。

我们的项目使用了 webpack,如果用这个方案,就需要写一个 webpack 插件去改造 HtmlWebpackPlugin 的构建流程。

目前来说,方案 1 和 方案 3 都是不错的。

但考虑到我们公司的前后端是分离的,后端的代码实现对我来说其实是黑盒,我没有权限也没有能力去写后端代码。而项目是前端项目,最好还是让前端来掌控维护。所以我最终选择了方案 3。

方案1 和方案 2 的更具体介绍,可以看我的这篇文章:前端国际化,该如何实现按需加载语言包?

原来项目打包后的 html 文件大致如下。

app.js 里有全量语言包的内容。

改造后的 html 文件如下:

我们语言包将 app.js 从中提取出来,并且分为多个语言包放到 js 文件,如 zh-CN.js、en-US.js,在 app.js 之前执行。

我们先确认用户使用的语言是什么。

如果我们不支持持久化设置,可以通过 navigator.language 或前端的其他地方获取。

但通常用户可以设置语言,这个语言标识就要后端给,再请求一次用户信息可太离谱了,所以这里还是需要后端给我们往 html 里嵌入用户选择的语言。然后我们从语言 cdn 列表里选我们需要的语言。

script 元素默认会将 async 设置为 true,效果是脚本下载完立即执行。需要将其改为 false,保证多个动态脚本顺序执行。

文件名使用了哈希,是为了解决浏览器缓存问题。

执行后,就会将语言包文案暴露在全局变量中。

业务代码 app.js 也得改成动态加载形式,如果原来的非动态写法,执行时机就会早于语言包脚本

这里涉及到了 script 的执行时机,具体规则可以看我的这篇文章:script 的三种加载模式:默认加载、defer、async

这样我们就能保证先执行语言包脚本,再执行 app.js。

app.js 中的业务代码执行时,使用 getText 方法就能正常通过 key 获取到对应的文案。

这里 app.js 改为动态的写法后,需要脚本解析执行后才下载脚本,可以考虑加个 link preload 提前下载脚本。

link 的 preload 作用可以看我的这篇文章。

思路并不复杂,但改造过程中做了很多工作,遇到了不少问题。这里简单列举一下,不展开讲了,到时候会考虑另写文章讨论。

行文有点仓促,想到什么写什么,希望对你做按需加载语言方案有一定的帮助。

我是啥都写写的前端西瓜哥,欢迎关注我。

AngularJS项目中如何实现按需加载js文件?

参考技术A 使用angularjs
+
requirejs就可以实现js文件的按需加载。
实现代码如下:
define(['app','navData'],
function
(app)

app.config(function($stateProvider,
$urlRouterProvider,
$controllerProvider,
navData)
app.registerController
=
$controllerProvider.register;
app.loadJs
=
function(js)
return
function($rootScope,
$q)
var
def
=
$q.defer(),
deps=[];
angular.isArray(js)
?
(deps
=
js)
:
deps.push(js);
require(deps,function()
$rootScope.$apply(function()
def.resolve();
);
);
return
def.promise;
;

$urlRouterProvider.otherwise('/memory');
angular.forEach(navData,
function(it)
var
st
=
it.state.split(/\./gi),
ctrlPath
=
'controllers/'
+
st[0],
ctrlName
=
'ctrl.'
+
st[0]
;
$stateProvider.state(st[0],
url
:
'/'
+
st[0],
templateUrl
:
'tpls/'
+
st[0]
+
'.html',
controller
:
ctrlName,
resolve:
deps:app.loadJs(ctrlPath)

);
)
);
);

以上是关于项目复盘:通过动态脚本,实现按需加载语言包的主要内容,如果未能解决你的问题,请参考以下文章

javascript按需加载

javascript按需加载

利用 React/Redux/React-Router 4/webpack 开发大型 web 项目时如何按需加载

Angular cli按需加载脚本

多个按需动态加载的 CoreML 模型

按序加载