fis就是这样的设计思路——基于静态资源表的资源管理框架。这件事说起来可能稍微长一些,希望你能耐心看完:
首先,这是一个 跨业务 资源引用问题。前端静态资源定位分为两种情况:
第一种是以字面量形式写在源代码中的,比如css中的背景图地址,js中的文件url字面量,还有html中的各种资源定位标识等。
第二种是可以通过编程接口获取的资源定位,比如当html是模板的时候,前面给出了解决方案,就是在模板中实现一个getURI接口。
第一种资源定位情况,需要经过构建工具处理,因为构建工具需要读取源码计算资源的指纹信息,因此这种方法仅仅适用于 一个业务模块内的资源定位静态替换 ,而第二种资源定位方式才是我们能跨业务模块加载带指纹信息的资源的唯一出路。
我想强调的是:
仅凭以上两条规则,完全可以组合出你所有的资源加载方案
是的,所有的。
大概要做这么几件事:
1. 每个业务模块会生成一张资源表
比如你的例子,假设有团队A维护了一个业务子系统叫 base,里面有有个core.js ,这个模块由A团队开发,他们团队的项目构建之后,会产生一个 base-map.json ,其中内容为:
{
"core" : {
"uri": "http://resource.com/base/core.t3a8V.js"
}
}
然后,你们还有一个B团队,负责维护业务子系统,假设维护的子系统叫 ui,里面有一个 calendar.js ,其代码为(如你上述):
// 注意,这里的依赖关系必须加上一个模块的命名空间,变成了base:core
define("ui:calendar", ["base:core"], function(c){
function init(){
var box = document.getElementById("box");
box.style.backgroundColor = c.color;
box.innerHTML = c.message + (new Date()).toDateString();
}
return {
createDate: init
}
});
好了,B团队的模块构建之后,也得到一张表,名为 ui-map.json ,其内容为:
{
"calendar" : {
"uri": "http://resource.com/ui/calendar.kI3lG.js",
"deps": [ "base:core" ]
}
}
上线部署的时候,所有业务模块的资源表是部署在一起的,于是就有了:
webserver
- map
- base-map.json
- ui-map.json
...
2. 接下来,要实现一个模板中的资源引用接口
假设它的名字叫require,比之前的 getURI 高级一些,这个接口只需支持多表查询,而且能在运行时分析依赖关系,它并不像getURI那样会输出资源的uri,而是仅负责收集,最后我们再多一个接口,比如叫 renderjs 吧,把收集到的资源都输出出来,这样,我们的页面就变成了:
...
...
...
模板引擎执行的时候,require函数运行,发现依赖 ui:calendar 知道要读取 ui-map.json 文件,查找其中的 calendar 资源,读表之后发现它还依赖了 base:core ,ok,又去读取 base-map.json 然后找到了资源路径。
全部收集起来之后,执行到 renderjs,检查资源收集情况,发现一共有两个:
[
"http://resource.com/base/core.t3a8V.js",
"http://resource.com/ui/calendar.kI3lG.js"
]
这个时候我们就可以渲染真正的资源加载代码了,可以将两个资源直接拼接成script标签输出在renderjs执行的位置:
...
...
...
也可以拼装一段js,把两个资源注册到前端模块化框架中:
...
...
requirejs.config({
paths: {
"base:core": "http://resource.com/base/core.t3a8V.js",
"ui:calendar": "http://resource.com/ui/calendar.kI3lG.js"
},
});
...
生成什么结构都是随心所欲的。(这里吐槽一下,我们最终生产生根本不会用requirejs这样框架,因为规范虽好,但很多冗余,如果我们自己定制,其实非常精简,资源表把依赖关系都记录好了,谁还需要前端框架运行时分析)
至此,我们利用后端的模块化框架实现了前端的资源管理,并且简单的多表查询逻辑就能实现跨模块资源引用。此外,我们还可以把css也入表,个别图片也入表,require接口可以查询样式及其依赖,getURI也支持多表查询,这样,一个页面就可以这样写了:
...
...
...
...
...
可能你会有一个疑问,这里仅解决了模板中的资源定位问题,其他情况怎么办?比如js代码中想跨模块资源定位、css图片中也想。
我想说,规则不需要很多,只要足够原子,能组合出全部应用场景即可。我们现在都有了什么呢:
规则1:模板中的 require 接口,解决跨业务资源引用问题
规则2:js中的模块化框架,解决js模块接口导入导出问题
规则3:业务内构建工具对字面量资源定位的替换(比如fis的__uri标识)
规则4:静态资源表,供前后端资源管理框架使用,可以存js、css以及任何你希望动态查找获取的资源
除了这些,其实我们还有一种隐蔽的资源引用方式:
规则5:css层叠样式
有了以上5中基本资源定位能力,回头看看我们的需求:
场景一:JS中想跨业务引用图片
比如ui中的js想引用base中的图片,我们首先在base中搞一个 icon 的js模块,其内容是:
// base/icon.js
define('base:icon', function(){
return {
logo: __uri('logo.png'),
file: __uri('file.png'),
folder: __uri('folder.png'),
foo: __uri('foo.png'),
}
});
这里使用了工具构建的资源定位替换,发生在base模块内部,构建之后得到:
// base/icon.js
define('base:icon', function(){
return {
logo: 'http://resource.com/base/logo.b33fc9.png',
file: 'http://resource.com/base/file.a5cf24.png',
folder: 'http://resource.com/base/folder.4234cb.png',
foo: 'http://resource.com/base/foo.2aabc3.png',
}
});
然后再在ui业务中的js依赖这个js模块即可,通过模块化接口导入导出来获取资源定位:
// 注意,这里的依赖关系必须加上一个模块的命名空间,变成了base:core
define("ui:calendar", ["base:icon"], function(c){
console.log(c.logo);
});
也就是说,我们将 JS中想跨业务引用图片 的需求转换成了:
js跨业务依赖js模块(规则1)
在跨业务的那个模块中静态编译资源定位标识(规则3)
通过模块化框架导出一个url列表对象(规则2)
场景二:CSS中想跨业务引用图片
由于css不支持运行时的编程逻辑,所以无法应用规则1-4,但我们有规则5,css可以有层叠啊,也就是说,你在css中使用图片,无非就是要引用为背景图嘛,那为何不考虑在跨业务的那里创建一个css单元,管理各种icon,并提供 class 定义呢?好像font-awsome那样。
所以,这个场景的处理就转换成了:
跨业务依赖另外一个css单元(规则1)
被依赖的css单元组织自己的图片,使用业务内资源定位(规则3)
之间在模板中使用class“接口”,如果需要复写,可以自己定义一个css来层叠样式(规则5)
比如我在base业务中搞了一个 fa 的css单元:
/**
* base/fa.css
*/
.fa-logo {
background: url(logo.png) no-repeat 0 0;
}
.fa-file {
background: url(file.png) no-repeat 0 0;
}
.fa-folder {
background: url(folder.png) no-repeat 0 0;
}
.fa-foo {
background: url(foo.png) no-repeat 0 0;
}
那么,我就可以在ui这个业务中直接使用这个样式API:
...
...
...
...
fis的设计精髓就在这里了。。。