-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
资源定位中md5戳的计算过程 #5
Comments
md5 的值主要依赖于文件的内容,而且当文件变化 md5 值也需要变化(包括依赖)。但是不一定需要替换后才能去 md5,首要关注的是文件的变化,所以我觉得只要将依赖文件计算出来,将他们的内容进行 md5 计算就可以了。 |
这样做不够严谨,以js、css为例,内容变化还可能是注释修改,并不会影响最终内容的改变 |
@fouber 但是文件确实变化了,压缩也不一定 100% 正确的,压缩工具修改也会造成输出变化。 |
而且必须是先替换引用资源的md5,才能再计算当前内容的md5,否则某次修改,我们只改了图片,其他js、css没有改动,只关注文件本身内容的md5算法就会认为资源没有修改,最终导致上线后没有更新这些文件,而最终修改的图片没有生效 |
同一份文件内容,压缩工具处理后的结果不会有变化的,这个已经证实过了 |
是不是md5其实无所谓,关键是计算出上一次部署文件和本次部署文件是否有差异。大概7、8年前就做过这样的方案——拿本次部署对应的资源文件(未加版本号的)比对上次部署对应的资源文件(未加版本号的),计算出差异,然后计算依赖,得到最终所有要变的资源文件集,所有变更的文件自增版本号,不变的用上次的版本号,更新所有依赖链接为最终的path。 |
压缩导致结果变化的情况没遇到过。不过某些大厂有用差异更新的,小差异导致压缩的短变量名大量变化从而增加了diff大小的情况,倒是会有的。 |
@fouber 压缩工具变更肯定会应该输出的,比如自己增加一些元信息,这个不影响压缩效果,也是可能的。 额,你们没有仔细看么,我没有说只是修改文件本身,是所有依赖文件的内容,比如图片改动,对应的 js 文件肯定会发生变化。我的分歧点是不需要坐资源定位标记的替换,其他我也是很认同,我也是这么做的。 md5 主要的作用是避免文件的覆盖,当文件变化所生成的文件变化必须不同,所有生成的 md5 只要考虑是否已经考虑到文件变化就可以了,至于是否必须为处理后的文件我就不做评价了。 |
用md5处理只是一种便捷方式而已,确实并不重要。md5无需关系版本diff,这是它的一个小优势,最终面向的原理是完全一致的。 |
替换资源定位标记之后再对文件本身求md5,这样可以自然引起当前资源的内容变更,便于形成递归处理逻辑,在工具设计上比较容易实现而已。 如果用别的方式先确认了内容变更的依据,最终再去替换定位标记也是一样的 |
注意,源码中,coffee里写的是 |
恩,如果coffee中写了baz.css是可以的,但这意味着要让工程师在编码过程中带上对构建工具处理的思考,资源定位不能以原始的工程路径为依据了,而是以构建的中间产物为依据,我觉得使用效果会大打折扣,本身并不是很完美的。 如果构建工具对每个文件对象的编译只有一个compile函数,在这个compile函数中,会经历coffee->js(没有临时文件,只是返回内容),压缩,包装等内容修改,那么这个过程就变得很简单了: var useHash = true;
var file = new File('a.coffee');
compile(file);
file.getContent().replace(/正则或者随便什么分析资源定位标记/, function(m, $1){
var f = new File($1);
compile(f);
return f.getUrl(useHash);
});
return file.getUrl(useHash); |
不好意思,之前的回复写的着急了一些,详细的是这样的: function compile(file, useHash){
var content = file.getContent();
content = parse(content, file.ext); // less2css, coffee2js
content = content.replace(/正则或者随便什么分析资源定位标记/, function(m, $1){
var f = new File($1);
compile(f); // 递归编译
return f.getUrl(useHash); // 计算带hash的路径引用
});
content = optimize(content, file.ext); // 压缩
file.setContent(content);
return file;
}
var file = new File('foo.coffee');
compile(file);
console.log(file.getContent()); |
/Workspace/git/static-resource-digest-project$ rsd release --md5 --dest ./output |
应该没有安装成功吧,或者安装的时候没有加 |
安装的时候提示了一个这个npm WARN optional dep failed, continuing fsevents@0.2.1 |
@shunyitian 这个可以忽略,你机器编译 fsevents 失败了 |
我在重装一次试试 |
不好意思,确实是一个bug,刚刚更新了,再安装一次就好了 |
@fouber 就当我帮忙了,哈哈 |
非常感谢 |
非常感谢你提供的工具,我用过之后非常的好用,非常适合小公司,小项目,之前在使用grunt时就遇到静态资源经过grunt处理过后还要去手动去改成处理后文件的路径实在是麻烦,但不知道grunt里有没有此类的解决,不过此工具已经解决,还有一个就是md5摘要形式发布了文件不会有缓存的问题了,以前我们修改后图片,在客户那儿没有反应,最后发现是文件缓存,特别是在手机上; 在使用的过程中我遇到了以下两个问题: |
|
@fouber 非常感谢,2.是文件路径的问题,我是以php中引入view路径为准的,此工具是以文件位置为依据: |
本地开发不用加 |
css 里面的资源定位还算容易。但 js 中的资源链接是通过字符串拼接生成的,那就无解了吧? |
@maplejan var url = __uri('a.png'); 构建之后变成: var url = '/static/img/a_0d4f22a.png 如果需要运行时变量控制多个资源的选取,可以这样做: var imgs = {
a: __uri('a.png'),
b: __uri('b.png'),
...
};
var name = 'a';
var url = img[name]; |
正如本blog描述的,md5是一个递归依赖的过程,如果你要解决这个问题,必须这样做:
也就是说A的md5是依赖B、C内容才能计算得到的,而不是分别计算ABC各自的内容在插入到引用的文件中。 MD5内容指纹的计算是要递归进行的! |
可能是我的问题没有描述清楚 我其实是另一个问题,如上述所属,A中require了B和C。已经递归算出A的md5,同时某页面index.html已经引用了A(md5 path).这时B更新了一个版本,(A和B是两个不同component),我如何通知到A修改呢,如何通知到index.html的A引用修改呢? 希望描述清楚了 |
如果实现了递归计算,这个问题不是自然而然的就解决了么。。。你确定理解我说的“递归计算”的真正过程么? 所谓的递归计算,不是说我们分别计算A.js,B.js,C.js的md5,然后插入到对应的引用的位置,这根本不是递归。我说的递归是——比如你这个例子,A依赖了B和C,你要:
只要你是
|
你试着举例说明index.html、A.js、B.js、C.js的原始内容,和构建后的内容 |
我先举例一下fis是怎么设计的吧,或许可以作为参考: 1. 源代码:index.html: <script src="a.js"></script> a.js: var url = __uri('b.js'); // fis的资源定位标识
load(url); b.js: alert(123); 2. 构建过程:
3. 构建工具设计
由于采用了文件内容作为指纹,所以如果所有文件没改动,构建的结果都还一样,如果b.js改了,那么热插入到a.js中的b的指纹会自动变化,a.js最终的内容也发生改变,a的指纹也变更了,最终index.html上引用的a.js的url也就变化了 |
嗯嗯 谢谢解释这么清楚,这个逻辑我也有看过代码。 不过若是:html/.net页面和js/css静态资源是分开独立发布的呢? 也就是说 不会因为改了a.js把html/.net页面重新build一次。 现在我们新建了一个.net方法,在.net编译时会去取到a.js的md5 name. 但是若a有引用b,b更新之后是无法通知到a的。(a和b是两个独立的component) 因为我电脑现在不在身边,如果我没解释清除,我回头再画一张图来 |
这里就是破坏了递归计算md5啊,你们的这个 |
如果你的问题仅仅是 fis每次构建,会扫描所有代码,计算标准的md5并且生成一张资源表,它是一份json数据,里面记录了所有资源的id和对应的带md5的url,形如: {
"res" : {
"a.js" : "/js/a.b33fc9.js",
"b.js" : "/js/b.0fab3c.js"
}
} 然后,你在.net模板中提供一个查询资源路径的接口,比如: <script src="<%# getURI("a.js") %>"></script> 这个getURI的后台方法就负责查上面的表来读取url,这样,每次前端构建就负责生成表,发布的时候把表部署到后台服务中,不用重新编译,只要重新读取数据就行了。线下构建的时候记得递归计算md5啊~~~ |
谢谢,抱歉过了好几天才来。 我把问题重新整理了一下,因为md5引发的会有两个问题。 问题一: 前提:这两个库是两个独立部门研发的。 core的代码如下
calendar的代码如下
其中__package()方法会在calendar build的时候做替换,类似于fis中的__uri()方法. 那么问题来了,某天更新了core.js源码,执行build core时,会将core的md5替换为core.t3a8V.js ->core.Dc3Al.js 如何通知到calendar去更新呢? |
关于.net页面中引用,现在的场景如下 .net源码
后端服务器维护一张md5的表
其中A和B是两个独立模块 a.js的代码如下:
遇到的问题和上面的类似,就是b.js更新的时候,如何通知到a。 你的意思是不是在b更新的时候,要把所有模块的md5值都要重新算一遍呢? 或者这样的场景有没有其他的解决方案? 另外,我看了一下FB的源码,发现他们是将页面需要的js的md5配置项都放在页面inline script中,猜测应该是在php运行的时候去做两件事情:
问题有点多,先谢谢了。 |
无法通知到 a ,必须是当任何代码有修改后重新对所有文件进行 md5 摘取计算。然后更新资源表。 如果硬要做成通知 a 也等于是对所有文件进行 md5 摘取计算,因为你无法直接知道哪些文件依赖了 b。需要对所有文件进行扫描,在扫描的过程中发现 a.js 依赖了 b.js 。 |
fis就是这样的设计思路——基于静态资源表的资源管理框架。这件事说起来可能稍微长一些,希望你能耐心看完: 首先,这是一个
第一种资源定位情况,需要经过构建工具处理,因为构建工具需要读取源码计算资源的指纹信息,因此这种方法仅仅适用于 我想强调的是: 仅凭以上两条规则,完全可以组合出你所有的资源加载方案是的,所有的。 大概要做这么几件事: 1. 每个业务模块会生成一张资源表比如你的例子,假设有团队A维护了一个业务子系统叫 {
"core" : {
"uri": "http://resource.com/base/core.t3a8V.js"
}
} 然后,你们还有一个B团队,负责维护业务子系统,假设维护的子系统叫 // 注意,这里的依赖关系必须加上一个模块的命名空间,变成了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团队的模块构建之后,也得到一张表,名为 {
"calendar" : {
"uri": "http://resource.com/ui/calendar.kI3lG.js",
"deps": [ "base:core" ]
}
} 上线部署的时候,所有业务模块的资源表是部署在一起的,于是就有了:
2. 接下来,要实现一个模板中的资源引用接口假设它的名字叫require,比之前的 <!doctype html>
<html>
...
<% require('ui:calendar'); %>
...
<%= renderjs() %>
...
</html> 模板引擎执行的时候,require函数运行,发现依赖 全部收集起来之后,执行到 [
"http://resource.com/base/core.t3a8V.js",
"http://resource.com/ui/calendar.kI3lG.js"
] 这个时候我们就可以渲染真正的资源加载代码了,可以将两个资源直接拼接成script标签输出在renderjs执行的位置: <!doctype html>
<html>
...
...
<script src="http://resource.com/base/core.t3a8V.js"></script>
<script src="http://resource.com/ui/calendar.kI3lG.js"></script>
...
</html> 也可以拼装一段js,把两个资源注册到前端模块化框架中: <!doctype html>
<html>
...
...
<script>
requirejs.config({
paths: {
"base:core": "http://resource.com/base/core.t3a8V.js",
"ui:calendar": "http://resource.com/ui/calendar.kI3lG.js"
},
});
</script>
...
</html> 生成什么结构都是随心所欲的。(这里吐槽一下,我们最终生产生根本不会用requirejs这样框架,因为规范虽好,但很多冗余,如果我们自己定制,其实非常精简,资源表把依赖关系都记录好了,谁还需要前端框架运行时分析) 至此,我们利用后端的模块化框架实现了前端的资源管理,并且简单的多表查询逻辑就能实现跨模块资源引用。此外,我们还可以把css也入表,个别图片也入表,require接口可以查询样式及其依赖,getURI也支持多表查询,这样,一个页面就可以这样写了: <!doctype html>
<html>
<head>
...
<% require('base:reset.css'); %>
<% require('base:grid.css'); %>
<% require('ui:calendar.css'); %>
...
<% renderCSS(); %>
</head>
<body>
<img src="<%= getURI('foo:logo.png') %>" >
...
<% require('ui:dialog'); %>
<% require('ui:calendar'); %>
...
<%= renderjs() %>
...
</body>
</html> 可能你会有一个疑问,这里仅解决了模板中的资源定位问题,其他情况怎么办?比如js代码中想跨模块资源定位、css图片中也想。 我想说,规则不需要很多,只要足够原子,能组合出全部应用场景即可。我们现在都有了什么呢:
除了这些,其实我们还有一种隐蔽的资源引用方式:
有了以上5中基本资源定位能力,回头看看我们的需求: 场景一:JS中想跨业务引用图片比如ui中的js想引用base中的图片,我们首先在base中搞一个 // 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);
}); 也就是说,我们将
场景二:CSS中想跨业务引用图片由于css不支持运行时的编程逻辑,所以无法应用规则1-4,但我们有规则5,css可以有层叠啊,也就是说,你在css中使用图片,无非就是要引用为背景图嘛,那为何不考虑在跨业务的那里创建一个css单元,管理各种icon,并提供 class 定义呢?好像font-awsome那样。 所以,这个场景的处理就转换成了:
比如我在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: <!doctype html>
<html>
<head>
...
<% require('base:fa.css'); %>
...
<% renderCSS(); %>
</head>
<body>
...
<div class="fa fa-logo">logo</div>
...
</body>
</html> fis的设计精髓就在这里了。。。 |
非常感谢解答,很有收获。仍然有两个小问题
按照场景一的思路是不是要这么调整代码为:这样是否正确? B:b的代码
A:a的代码
是不是每次run页面的时候,都要去读取一下md5的config list?这个会不会影响性能和稳定性? 后端是不是有一个service,比如get/restful_md5list 。这个service应该有一套后端缓存设计吧。另外,这个service必须高可用,如果一旦挂了,是否有备选方案呢? |
问题一:是的。跨业务引用资源的时候,要把依赖关系由静态的转成动态查表的形式。 问题二:是否每次运行页面都要读取map,这个要看你的框架实现。支持持久化的架构,每次都把表载入到内存中,我们可以在框架中判断map文件的修改时间,如map文件修改,就重新读入,否则直接使用内存中的数据,以提高性能。不过如果后端用的是php,就需要每次都读取了,这个时候,也可以做一些小优化,比如我们可以把map.json转成map.php,相比反序列化json文件,直接是php文件的话性能好很多。后端并不需要提供RESTFul的md5查询接口,所有资源md5值都是直接输出在模板中的 |
好的,谢谢。 问题二:你所指的map是一个维护了所有静态资源文件的一对一MD5表吗?类似于这样: map.json
index.php页面是
当用户访问index.php, 我理解下来的步骤是如下,请看下是否正确?
|
补充一下上述的内容 |
另外,如果用户访问的是静态页面index.html,就不会有类似编译php页面这样的流程。这样的话,读取json、md5这样的信息,是不是只能用ajax请求来玩了? |
@feifeipan var map = __inline('map.json') |
基本步骤理解的差不多,有些细节不太合理
步骤2中可以多做一些事情,直接遍历ui:calendar的依赖也push到数组中,而且push的值直接就是uri,这样等到了第3步就是直接输出数组的内容,不需要再多查表了。 此外,纯前端(只有静态页面index.html的时候)实现不了很极致的静态资源管理,只能把表内嵌到页面上(推荐),或者ajax请求map。不过ajax方案会导致多一个请求从而影响性能。 |
我又来了。 现在我们设计了给.net应用使用的方案,有两套。请大神帮忙看看哪种更合适些 假设页面需要jquery和calendar,以及main样式
方案2(Razer)
#CS#
最终代码是一致的
不知道是否描述清楚了?谢谢。 |
第一种更好一些,运行时收集资源,能做到资源的就近使用与按需。第二种相当于中心化的管理,时间长了资源的引用会出现“泄露”问题 |
谢谢。不好意思,你说的“泄漏”问题是指? |
就是你集中管理之后,资源的引用与功能分离,将来会不自觉的增大,因为你下线功能的时候总是不敢随意删除集中管理起来的这些资源,导致资源越加越多,性能优化反倒变成了性能恶化。 可以参考这个小故事:https://github.com/fouber/blog/blob/master/201405/01.md#静态资源管理与模块化框架 |
最近折腾了一下 gulp 文件 hash 资源定位。 html,css,js,img 的互相关联,思路大概是
虽然实现了资源定位,但是属于 非 现在要想办法加快watch时编译速度,参考本贴的讨论。 我接着踩坑,有新思路了本帖继续回复。 |
接着上面的 gulp hash 资源定位问题,多次尝试后发现只能跳过解决。
发布构建时做2次编译,1. 编译源码生成静态资源 hash表 2. 根据 hash表 替换所有路径引用。慢速操作都在发布时才操作 非要折腾 gulp 的原因是 gulp + webpack-stream 的编译速度非常快
2016年12月01日0:40:27 踩坑更新 主要配置如下 fis.match('*.js', {
release: false
})
var src_To_SrcAndUndo = {
preprocessor: function (content) {
return content.replace(/src=(['"].*?.js['"])/g, '_src=$1')
},
postprocessor: function (content, file) {
return content.replace(/_src=(['"].*?.js['"])/g, 'src=$1')
}
}
fis.media('dev').match('{*.html,*.md}', src_To_SrcAndUndo)
fis.media('online1').match('{*.html,*.md}', src_To_SrcAndUndo)
// static domain
fis.media('online2').match('**', {
domain: staticDomain
})
// 最终发布阶段需要编译 js,因为此js是 webpack 生成的
fis.media('online2').match('*.js', {
release: true
})
fis.media('online2').match('**', {
useHash: true
})
fis.media('online2').match('*.html', {
useHash: false
}) package.json scripts "dev": "fis3 release -w -d ./output",
"webpack": "webpack -w --progress --colors",
"online": "fis3 release online1 -d ./output && NODE_ENV=online webpack --progress --colors && fis3 release online2 -r ./output -d ./output", |
原来是这样 ,怪不得我文件内容不变的时候 ,打包加的md5戳不会变化。 |
md5戳只有8个字符是不是会产生重复? |
个人认为,这种根据状态改变触发的方式很适用于订阅发布机制来处理,要比文件内容一层层的递归查找要好得多,避免无效的递归操作。 |
要实现完整的md5计算,最终必须将task-based的流程转变成one-task形式。此处给出相关说明:
假设我们有三个文件,比如
foo.coffee
,foo.scss
和foo.png
,文本文件的内容为:foo.coffee
foo.scss
最终形成这样一种资源引用关系:
当我们要计算foo.coffee的md5戳的时候,其实是一个这样的过程:
整个计算过程是一个递归编译的过程,计算文件的摘要信息应该根据文件的
最终内容计算
,所以这个过程中要加入对sass、coffee、图片的编译和压缩处理,从而能得到真正的最终内容
,这就等同于要把所有文件的处理过程整合在一次流程中,所以引入md5计算,对整个构建系统的设计影响是非常大的。在task-based的构建机制中,task之间没有办法在处理一个文件的过程中暂停,然后去对另一个文件完成完整流程处理得到内容再继续当前流程。task-based之间仅仅是任务的调度,使得部分构建信息在调度的过程中失去了“上下文环境”,无法形成对同一个文件内容的管道式处理过程。假设上述过程我们用task-based的系统构建,会变得非常复杂,有兴趣的朋友可以尝试一下,把你们的想法写在下面。
用 F.I.S 包装了一个 小工具 ,完整实现整个资源部署方案,并提供了源码对照:
源码项目:fouber/static-resource-digest-project · GitHub
部署项目:fouber/static-resource-digest-project-release · GitHub
部署项目可以理解为线上发布的结果,可以在部署项目里查看所有资源引用的md5化处理。
The text was updated successfully, but these errors were encountered: