-
Notifications
You must be signed in to change notification settings - Fork 2
静态资源管理方案
FIS在执行编译的过程中,会扫描这些编译标记,从而建立一张静态资源关系表,它在编译阶段最后会产出为一份map.json 文件,这份文件详细记录了项目内的静态资源id、发布后的线上路径、资源类型以及依赖关系 和 资源打包等信息。FIS-PC解决方法,在运行时根据组件使用情况来 按需加载资源或者资源所在的包,从而提升前端页面运行性能。
首先,我们将页面的每个小部件,当做一个组件,在fis中,我们叫它 widget 。接下来,一个页面将会是由多个组件进行组合,同时也会有页面自身需要的资源,对下面photo模块下的index.tpl进行举例说明:
{html framewrok="photo:static/mod.js"}
{head}
{/head}
{body}
{require name='photo:page/index.tpl'}
{widget name="photo:widget/A/A.tpl"}
{widget name="photo:widget/B/B.tpl"}
{widget name="photo:widget/C/C.tpl"}
{script}
require.async(['/static/index/index.js'],function(){
console.log('index');
});
{script}
{/body}
{/html}
这个网站的目录结构变成了:
根目录
├── photo
│ ├──page
│ │ └── index.tpl
│ ├──static
│ │ └── index
│ │ └── index.js
│ │ └── index.css
│ │ └── mod.js
│ ├──widget
│ │ └── A
│ │ └── A.tpl
│ │ └── A.css
│ │ └── B
│ │ └── B.tpl
│ │ └── B.css
│ │ └── C
│ │ └── C.tpl
│ │ └── C.css
大家还记得fis会产出的那个 map.json 么?使用fis,加入适当的配置,对这个项目进行编译会得到一个 map.json的文件,它的内容是:
{
"res" : {
"photo:page/index.tpl" : {
"uri" : "/template/photo/page/index.tpl",
"extras": {
"isPage": true,
"async": [
"photo:static/index/index.js"
]
}
},
"photo:static/index/index.js": {
"uri": "/static/photo/index/index_5deaf23.js",
"type": "js"
},
"photo:widget/A/A.tpl" : {
"uri" : "photo/widget/A/A.tpl",
"type": "tpl",
"deps": [
"photo:widget/A/A.css"
]
},
"photo:widget/A/A.css" : {
"uri" : "/static/photo/widget/A/A_7defa41.css",
"type" : "css"
},
"photo:widget/B/B.tpl" : {
"uri" : "photo/widget/B/B.tpl",
"type": "tpl",
"deps": [
"photo:widget/B/B.css"
]
},
"photo:widget/B/B.css" : {
"uri" : "/static/photo/widget/B/B_34derfb.css",
"type" : "css"
},
"photo:widget/C/C.tpl" : {
"uri" : "photo/widget/C/C.tpl",
"type": "tpl",
"deps": [
"photo:widget/C/C.css"
]
},
"photo:widget/C/C.css" : {
"uri" : "/static/photo/widget/C/C_23a1wfe.css",
"type" : "css"
}
}
}
到这里或许你已经猜到我们的 {widget name="resource_id"} 是如何工作的了:
- 准备三个数据结构:
- uris = [],数组,顺序存放要同步输出资源的uri
- asyncUris = {},异步资源表,顺序存放要异步输出的资源的信息
- has = {},hash表,存放已收集的静态资源,防止重复加载
- 加载资源表 photo-map.json
- 执行 {html framework="photo:static/mod.js"}
- 设置前端框架的JS文件,优先在全部静态资源之前加载
- 执行 {require name="photo:static/index/index.tpl"}
- 在表中查找id为 photo:static/index/index.tpl 的资源,查看 photo:static/index/index.tpl 资源的extras属性中的 async ,发现它依赖资源 photo:static/index/index.js
- 在表中查找id为 photo:static/index/index.js 的资源,将信息存入 asyncUris 数组中,并在 **has表 **里标记已加载 photo:static/index/index.tpl 资源,我们得到:
uris = []; asyncUris = { 'photo:static/index/index.js' : { 'url' : '_/static/photo/index/index_5deaf23.js_', 'pkg' : '', 'deps' : [] } }; has = { 'photo:static/index/index.tpl' : true };
- 执行 {widget name="photo:widget/A/A.tpl"}
- 在表中查找id为 photo:widget/A/A.tpl 的资源,取得它的资源路径 photo/widget/A/A.tpl ,记为 tpl_path
- 模板引擎加载并渲染 tpl_path 所指向的模板文件,即 photo/widget/A/A.tpl,并输出它的html内容
- 查看 photo/widget/A/A.tpl 资源的 deps 属性,发现它依赖资源 photo:widget/A/A.css
- 在表中查找id为 photo:widget/A/A.css 的资源,取得它的资源路径为 /static/photo/widget/A/A_7defa41.css,存入 uris数组 中,并在 has表 里标记已加载 photo:widget/A/A.css 资源,我们得到:
uris = [ '/static/photo/widget/A/A_7defa41.css' ]; asyncUris = { 'photo:static/index/index.js' : { 'url' : '/static/photo/index/index_5deaf23.js', 'pkg' : '', 'deps' : [] } }; has = { 'photo:static/index/index.tpl' : true 'photo:widget/A/A.css' : true };
- 执行 {widget name="B"},步骤与上述步骤3相同,我们得到:
uris = [ '/static/photo/widget/A/A_7defa41.css', '/static/photo/widget/B/B_34derfb.css' ]; asyncUris = { 'photo:static/index/index.js' : { 'url' : '/static/photo/index/index_5deaf23.js', 'pkg' : '', 'deps' : [] } }; has = { 'photo:static/index/index.js' : true, 'photo:widget/A/A.css' : true, 'photo:widget/B/B.css' : true };
- 执行 {widget name="C"},步骤与上述步骤3相同,我们得到:
uris = [ '/static/photo/widget/A/A_7defa41.css', '/static/photo/widget/B/B_34derfb.css', '/static/photo/widget/C/C_23a1wfe.css' ]; asyncUris = { 'photo:static/index/index.js' : { 'url' : '/static/photo/index/index_5deaf23.js', 'pkg' : '', 'deps' : [] } }; has = { 'photo:static/index/index.js' : true, 'photo:widget/A/A.css' : true, 'photo:widget/B/B.css' : true, 'photo:widget/C/C.css' : true };
- 在要输出的html前面,我们读取 uris数组 和 asyncUris的数据,生成静态资源外链,我们得到最终的html结果:
<html>
<head>
<link href="/static/photo/widget/A/A_7defa41.css">
<link href="/static/photo/widget/B/B_34derfb.css">
<link href="/static/photo/widget/C/C_23a1wfe.css">
</head>
<body>
<div>html of A</div>
<div>html of B</div>
<div>html of C</div>
<script type="text/javascript" src="/static/common/mod_d2a34f2.js"></script>
<script type="text/javascript">
require.async(['/static/index/index.js'],function(){
console.log('index');
});
</script>
<script type="text/javascript">
require.resourceMap({
"res":{
"photo:static/index/index.js": {
"url": "/static/photo/index/index_5deaf23.js",
"pkg": "",
"deps": []
}
}
});
</script>
</body>
</html>
看到了么!!!我们不但可以让资源按需加载,同时根据静态资源的类型在页面的不同位置进行加载,还能全部映射到正确的md5戳哦,这全依赖fis的表生成技术!那么,基于这项技术,我们是如何处理打包的呢:
现在,我们再来使用fis的 pack配置项,对网站的静态资源进行打包,配置文件大致为:
fis.config.merge({
pack : {
'pkg/aio.css' : '**.css'
}
});
执行fis的编译命令并使用 pack、md5 等功能:
fis release --pack --md5
再来查看我们的 map.json, 它的内容变为:
{
"res" : {
"photo:page/index.tpl" : {
"uri" : "/template/photo/page/index.tpl",
"extras": {
"isPage": true,
"async": [
"photo:static/index/index.js"
]
}
},
"photo:static/index/index.js": {
"uri": "/static/photo/index/index_5deaf23.js",
"type": "js"
},
"photo:widget/A/A.tpl" : {
"uri" : "photo/widget/A/A.tpl",
"type": "tpl",
"deps": [
"photo:widget/A/A.css"
]
},
"photo:widget/A/A.css" : {
"uri" : "/static/photo/widget/A/A_7defa41.css",
"type" : "css",
"pkg" : "p0"
},
"photo:widget/B/B.tpl" : {
"uri" : "photo/widget/B/B.tpl",
"type": "tpl",
"deps": [
"photo:widget/B/B.css"
]
},
"photo:widget/B/B.css" : {
"uri" : "/static/photo/widget/B/B_34derfb.css",
"type" : "css",
"pkg" : "p0"
},
"photo:widget/C/C.tpl" : {
"uri" : "photo/widget/C/C.tpl",
"type": "tpl",
"deps": [
"photo:widget/C/C.css"
]
},
"photo:widget/C/C.css" : {
"uri" : "/static/photo/widget/C/C_23a1wfe.css",
"type" : "css",
"pkg" : "p0"
}
},
"pkg" : {
"p0" : {
"uri" : "/static/pkg/aio_0cb4a19.css",
"has" : [ "photo:widget/A/A.css", "photo:widget/B/B.css", "photo:widget/C/C.css" ]
}
}
}
大家注意到了么,表里多了一张 pkg 表,所有被打包的资源会有一个 pkg属性 指向该表中的资源,而这个资源,正是我们配置的打包策略。好,让我们看看这种情况下,我们的 **{widget name="resource_id"}**是怎么工作的吧( 注意,这个过程工程师的代码从未改动过哦 ):
- 准备三个数据结构:
- uris = [],数组,顺序存放要同步输出资源的uri
- asyncUris = {},异步资源表,顺序存放要异步输出的资源的信息
- has = {},hash表,存放已收集的静态资源,防止重复加载
- 加载资源表 photo-map.json
- 执行 {html framework="photo:static/mod.js"}
- 设置前端框架的JS文件,优先在全部静态资源之前加载
- 执行 {require name="photo:static/index/index.tpl"}
- 在表中查找id为 photo:static/index/index.tpl 的资源,查看 photo:static/index/index.tpl 资源的extras属性中的 async ,发现它依赖资源 photo:static/index/index.js
- 在表中查找id为 photo:static/index/index.js 的资源,将信息存入 asyncUris 数组中,并在 **has表 **里标记已加载 photo:static/index/index.tpl 资源,我们得到:
uris = []; asyncUris = { 'photo:static/index/index.js' : { 'url' : '/static/photo/index/index_5deaf23.js', 'pkg' : '', 'deps' : [] } }; has = { 'photo:static/index/index.tpl' : true };
- 执行 {widget name="photo:widget/A/A.tpl"}
- 在表中查找id为 photo:widget/A/A.tpl 的资源,取得它的资源路径 photo/widget/A/A.tpl,记为 tpl_path
- 模板引擎加载并渲染 tpl_path 所指向的模板文件,即 photo/widget/A/A.tpl,并输出它的html内容
- 查看 photo:widget/A/A.tpl 资源的 deps 属性,发现它依赖资源 photo:widget/A/A.css
- 在表中查找id为 photo:widget/A/A.css 的资源,我们发现该资源有 pkg属性,表明它被 备份 在了一个打包文件中。
- 我们使用它的pkg属性值 p0 作为key,在pkg表里读取信息,取的这个包的资源路径为 /static/pkg/aio_0cb4a19.css 存入 uris数组 中
- 将p0包的 has 属性所声明的资源加入到 has表 里我们得到:
uris = [ '/static/pkg/aio_0cb4a19.css' ]; asyncUris = { 'photo:static/index/index.js' : { 'url' : '/static/photo/index/index_5deaf23.js', 'pkg' : '', 'deps' : [] } }; has = { 'photo:static/index/index.js' : true, 'photo:widget/A/A.css' : true, 'photo:widget/B/B.css' : true, 'photo:widget/C/C.css' : true };
- 执行 {widget name="photo:widget/B/B.tpl"},步骤与上述步骤3相同,但当我们要加载B/B.tpl的资源B/B.css时,发现它已经被has表标记为 已收集,因此跳过资源收集过程
- 执行 {widget name="photo:widget/C/C.tpl"},结果与步骤4相同
- 在要输出的html前面,我们读取 uris数组 的数据,生成静态资源外链,我们得到最终的html结果:
<html>
<head>
<link href="/static/pkg/aio_0cb4a19.css">
</head>
<body>
<div>html of A</div>
<div>html of B</div>
<div>html of C</div>
<script type="text/javascript" src="/static/common/mod.js"></script>
require.resourceMap({
"res":{
"photo:static/index/index.js": {
"url": "/static/photo/index/index_5deaf23.js",
"pkg": "",
"deps": []
}
}
});
</body>
</html>
出现打包了有木有啊!!!
抱歉,这货好处实在太多了。
- 我们可以统计 {widget} 插件的调用情况,然后自动生成最优的打包配置,让网站可以 自适应优化
- 工程师不用关心资源在哪,怎么来的,怎么没的,所有 资源定位 的事情,都交给fis好了。解决了前面说的 功能下线不敢删除相应资源 的问题
- 静态资源路径都带md5戳,这个值只跟内容有关,静态资源服务器从此可以放心开启强缓存了!还能实现静态资源的分级发布,回滚神马的超方便哦!
- 我们给 {widget name="resource_id"} 加一个小小的“后门”,我们可以利用cookie、url中的get参数来控制瞬间切换线上的页面输出结果为打包或者不打包、甚至是压缩或者不压缩的资源, 方便定位线上问题 有木有!
- 我们再给 {widget name="resource_id"} 加一个小小的“后门”,让它可以读取一个 domains.conf 配置文件,内容形如:
default=http://static.example.com
debug=http://localhost:8080
然后我们约定一个cookie或者url值,可以一键 把线上资源映射到本地 有木有!!!方便调试啊,魂淡!
- 我们还可以继续折腾,比如根据国际化、皮肤,终端等信息约定一种资源路径规范,当后端适配到特定地区、特定机型的访问时,静态资源管理系统帮你 送达不同的资源给不同的用户 啊,有木有!
- 更多好处,等你来挖掘,请鞭挞我吧,公瑾!