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