还记得去年在百姓网组织的一次meetup上,听到过sofish分享了饿了么前端的一些架构,当时印象最深的是proxy_pass解决跨域问题,只听过词汇却不知道是什么意思。后来大量依赖NGINX,proxy_pass已经可以说是用得最多的一个配置了。还记得当时他们提到了请求合并、JS本地化,觉得实为厉害,但一直都没有机会实践。前不久突然想到我们的前端项目,尤其是通过APP加载的H5工程,应该很适合做本地化,于是也实践了一下。

基本原理

通过将JS首次访问时缓存在localstorage中,后续的再次访问都从localstorage中读取js文件,也就不用再下载这个JS了(一般也是整个工程中最大的文件)。同时,当我们服务器端的js有改动更新时,我们也需要更新该缓存中的JS文件。

由于每次文件有变动时,文件名都会改变,我想找到一个支持通过token来更新本地JS的库,这样和目前的项目结构最匹配,我发现basket.js 恰好满足这个要求。

basket.js 不能单独使用,它依赖于rsvp.js来异步加载JS,其本身只是localstorage的操作逻辑。

改造

我直接在首页文件中加入改JS代码,而没有再创建其他文件来做这件事。 因为只需要在生产环境做本地化缓存,默认将这段代码注释。
同时,项目的JS文件是动态生成的,其文件名也会跟随着JS内容变化而变化,故文件名是需要在编译器做静态替换的。

此处没有注释,为了markdown语法高亮的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* COMMENT_BEGIN_PLACEHOLDER */
function loadJs (path) {
var mobileScript = document.createElement('script')
mobileScript.setAttribute('src', path)
document.body.appendChild(mobileScript)
}

function basketload () {
basket.require(
{url:'MAIN-JS-PLACEHOLDER', key: 'main-js', unique:'MAIN_JS_UNIQUE_HOLDER', expire: 168}
)
.then(function (success) {

}, function (error) {
console.error(error)
})
}

try{
if (window.localStorage) {
basketload()
} else {
console.info('不支持localStorage')
loadJs('MAIN-JS-PLACEHOLDER')
}

} catch (e) {
console.warn('尝试读取local storage 失败')
loadJs('MAIN-JS-PLACEHOLDER')
}
/* COMMENT_END_PLACEHOLDER */

在此,我使用了 MAIN-JS-PLACEHOLDER 来作为生成JS文件名的placeholder, 使用 MAIN_JS_UNIQUE_HOLDER来当做该JS是否变化的token标示符。也可以直接使用文件名做标识符,不过我其实取出了文件名中的MD5作为标识符。同时,我设置了过期时间为168小时,即一星期。

通过编译工具,将上面代码中的placeholder替换完成,并将注释符号删除,就可以使其工作了。 在如上代码中,我也针对一些不支持localstorage的场景,比如隐身模式,做了异常处理,针对这些场景我会直接请求原始JS代码。

后话

有的同学可能发现,我上面的代码有一个前提,那就是已经提前加载了 baseket.js (basket.js+rsvp.js可以手工合并)。既然都已经判断是否支持localstorage,那么为何不在判断结束后再插入basket.js呢,这样在不支持localstorage的地方,就可以节省一个不必要的请求呀?

确实我一开始也这么打算,因为真的很不喜欢请求多余的资源,而且既然这样可以实现何必不呢?
因此一开始的版本其实是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try{
if (window.localStorage) {
loadJs('/pathofjs/basket.min.js')
// 通过onload事件,在JSbasket.min.js加载结束后,加载我们真正需要的JS文件
window.onload = basketload
} else {
console.info('不支持localStorage')
loadJs('MAIN-JS-PLACEHOLDER')
}

} catch (e) {
console.warn('尝试读取local storage 失败')
loadJs('MAIN-JS-PLACEHOLDER')
}

看起来也没什么问题,但是问题还是来了,来自中国移动!由于该网站还没有切换到HTTPS,中国移动在里面劫入了自己的JS文件,域名赤裸裸地绑定在10086.cn下。我们在测试的时候,发现移动页面很久才能打开。经调试,中国移动的一个js文件(base.js, 才几KB),有时候要经过50多秒才能下载完,window.onload也会等待这个下载完才执行,因此页面内容也不会被初始化。项目正在迁移HTTPS,但毕竟还有个过程,因此忍痛放弃了这种方式,直接加载baseket.js了。

basket.js 在我禁用缓存进行测试时,会抛一个读取localstorage的异常, 还是毕竟烦人,故而我在里面加入了一段主动判断localstorage的地方,并catch这个异常,虽然没什么用,但是毕竟没有红色警告了。。

以上,JS本地化的迁移就算是告一段落了。

1
2
3
4
5
6
7
8
9
10
11
<html lang="en">
<head>
<title>标题</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="/">
</head>
<body>
<div id="root" style="height: 100%"></div>
</body>
</html>

sss