Hello World
Spiga

关于静态资源打包后的相对路径问题

2010-09-05 21:53 by 老赵, 3261 visits

将多个静态资源打包为单个资源以减少请求数目,是提高页面加载速度的常用手段。于是上个星期,我就在实现网站静态资源的自动打包功能,原以为是个比较简单的问题,实现起来也没有遇到什么障碍,不过在开发完毕投入使用的时候却让我跌了下眼镜。由于静态资源在打包以后,它们的访问路径势必会改变,这样其他一些依赖于原有路径的资源就访问不到了。这方面最常见的例子,便是CSS样式表中引用的图片路径是相对于CSS文件路径的。当意识到这个问题以后,还真是让人手忙脚乱了一把。

例如,有一个描述对话框组件的CSS文件,它的原有访问路径为/styles/dialog/core.css,其中有行样式表为url(header.png),这意味这幅图片的访问路径为/styles/dialog/header.png。同样,在另一个文件/styles/menu/core.css中也有url(bg.png)这样的代码。那么好,如果我们将这两个文件打包在一起,叫做/packed/styles/dialog-menu.css,那么浏览器就会去加载/packed/styles下的header.png和bg.png,为什么?因为url(…)这段代码,在打包后出现的位置就不同了。

解决这个问题似乎有多种办法,例如将资源文件复制到/packed/styles下面,但这就要处理文件重名的问题。如果使用组件化开发的方式,两个组件之间应该不去考虑资源的重名,否则就产生了依赖。另一种方式,是在CSS内部使用绝对路径,例如在/styles/menu/core.css中使用url(/styles/menu/bg.png)而不是url(bg.png)。这么做可以解决打包上的问题,但是却破坏了组件化的开发方式。例如此时dialog组件就不是独立的了,它的图片路径是死的。而在理想情况下,一个组件的CSS文件和图片等资源应该是统一的,并独立与其他条件,这也是CSS中url(…)含义的设计目的。

当然,我并不是说这种“组件化”的实践是必须遵守的,如果只是在开发一个独立的项目,便可以将“绝对路径”作为项目中的约定。但是在实际情况下,这种组件化及相对路径的使用是客观存在的。例如jQuery成百上千的插件,它们的CSS样式中一定不会使用绝对路径。那么我们又该如何对它进行打包呢?

其实路径问题也很容易解决,只要在“打包”的时候修改掉CSS文件内容里的路径即可。例如我们可以知道,如果浏览器是在/packed/styles/dialog-menu.css文件中,访问到原本在/styles/dialog/core.css里的内容,那么原本的url(bg.png)就必须改写成url(/styles/dialog/bg.png)这样的绝对路径,或者是url(../…/styles/dialog/bg.png)这样相对于新地址的“相对路径”。

这样的修改其实并不复杂,例如在CSS中,似乎需要替换的也只有url(…)这样的地址了,一个普通的正则表达式便可以解决,而比较麻烦的可能是JavaScript文件了。在某些组件化的JavaScript中,它需要的一些资源也是根据脚本文件的地址相对计算出来的。此时,其中的路径可能只是一些普通的字符串而已,没有规律保险的替换规则。如果要解决这个问题,我们可以在项目中形成某种约定,例如,计算相对路径时都不是简单的字符串拼接,而是使用一个函数调用。而这个函数调用,在进行“打包”时便可以看作是一个待替换的标志。

相对路径的问题似乎就这么解决了,我想前面这段描述也已经足够清楚,比写代码还要清楚,所以暂时就先不提供我在项目里使用的动态打包机制了。不过这里还有个小小的花絮可以一谈。

负责前端开发的同事很重视静态资源的打包问题,因此也就一直催促我实现这方面的功能。之前他说,他需要一个工具,输入一个配置文件,便可以将指定文件打包,然后在正式站点发布时修改页面上引用的地址就行了。我说这样不行,会死人的。例如,一个组件会在多个页面上使用,那么它的更新会导致多次打包,还要修改多个页面文件,这对于站点的快速升级更新也是一个灾难。此外,在页面上所使用的静态资源信息,还需要在打包工具的配置文件中重复,这也是一种违反DRY的情况

我理想中的打包机制要有以下几个条件:

  • 遵守DRY原则,例如,一张页面需要加载哪些脚本,此类信息只会出现在对应页面中。
  • 在开发系统和生产系统上部署同一套代码,而不会为了生产系统修改代码。
  • 自动打包,自动版本升级或回退。打包结果易于开发人员进行识别、分析及调试。
  • 简单,清晰,性能高。

上周实现的解决方案似乎基本符合这些条件,有机会我再来分享吧。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

17 条回复

  1. huacnlee
    119.4.252.*
    链接

    huacnlee 2010-09-06 07:43:23

    这么麻烦,为什么不这样?

    .face { background:url(/images/aa.jpg); }
    

    我在说什么你应该懂的

  2. huacnlee
    119.4.252.*
    链接

    huacnlee 2010-09-06 07:52:41

    此外,听你后面那段话意思,好象是说你们是手动打包 css 的?

    可以试着实现Rails 里面 stylesheets_include_tag 这样的功能,写html的时候,css 引入用它,然后在它里面做整合工作。

    下次更新css的时候直接清除整合的css文件,以便重新生成新的

  3. Bill lo
    183.33.106.*
    链接

    Bill lo 2010-09-06 08:51:18

    老趙去盛大忙了很多呀,半個月在發一篇技朮文章啊.

  4. 老赵
    admin
    链接

    老赵 2010-09-06 08:59:20

    @huacnlee

    我懂的,你的两条回复都说明你没怎么看我的文章内容……

  5. 老赵
    admin
    链接

    老赵 2010-09-06 08:59:42

    @Bill lo

    也没这么久,主要是懒。

  6. 链接

    twty1988 2010-09-06 09:59:45

    楼顶的@huacnlee有点傻。

    我在说什么你应该懂的。

  7. huacnlee
    125.69.76.*
    链接

    huacnlee 2010-09-06 12:16:11

    @老赵

    抱歉,我确实没有仔细看完。我说的那些其实不仅仅是对你的回复,更多的是给来看文章的人看到这么一种作法。

    @twty1988

    至于这样说话吗?

  8. 老赵
    admin
    链接

    老赵 2010-09-06 13:37:28

    @huacnlee

    呵呵,其实关键是在于,你说的做法等等,其实我在文章里已经详细解释了这么做(或不这么做)的原因了,所以你这样回复就显得很莫名其妙……

  9. winter
    61.172.247.*
    链接

    winter 2010-09-06 15:51:21

    我曾经踩到过两个雷:

    1. js/css访问自定义.net控件里的资源问题
    2. ASP.NET里dllimport的路径问题
  10. tshao
    58.41.196.*
    链接

    tshao 2010-09-06 22:31:51

    用绝对路径也未必一定是正确的方法,因为网站不一定会直接部署为website,也极可能部署为virtual directory,这种情况下/只会指向网站根目录结构。而且指定virtual directory也未必有用,因为名称也许会换的。

    个人看法,早期的Css语法在设计时,或者未考虑到当下的各种使用情况,因此少了变量的语法设计。现在很多公司都有自己的一套方案来解决此问题,一般都是在Css中模拟出变量的写法,然后在编译时动态替换,达到扩展语法的效果。

    至于在开发系统和生产系统上是否要部署完全相同的代码,我觉得可以讨论。因为开发系统要注重可读性,且便于各种浏览器工具进行调试跟踪,所以一般放的是原代码(特指Js, Css)。而生产系统上一般会放压缩过,甚至合并过的文件。所以两者有区别应该是可能的。可以考虑根据编译条件来改变文件的引用,这样保证了以上要求的同时,也无需release team手动修改,降低了出错的可能性。

  11. 老赵
    admin
    链接

    老赵 2010-09-06 23:28:12

    @tshao: 而生产系统上一般会放压缩过,甚至合并过的文件。所以两者有区别应该是可能的。可以考虑根据编译条件来改变文件的引用,这样保证了以上要求的同时,也无需release team手动修改,降低了出错的可能性。

    这点区别我是可以接受的。

    如果是大而泛的“倾向性”问题,我建议还是尽量用一致的代码,连编译条件都不要用比较好,配置也是越少越好,例如,资源可以在production环境中按需压缩,性能也就是第一次启动时可能有点损失。

    当然,对于这里特定的“压缩”场景,可能提前压缩是必要的,因为有些代码可以执行正常,但是会压缩出错,不压不知道,一压吓一跳。

  12. 链接

    小城故事 2010-09-07 15:54:43

    我们也常用静态文件Merge,不过同类静态文件都在同一目录下,没有子目录。 老赵说的情况,我觉得可以Server端上考虑解决,捕获并分析静态文件的请求,并将请求转到真正的路径,顺便防盗链,对于以后分设静态资源服务器也有帮助。

  13. zhangxuefei
    125.71.1.*
    链接

    zhangxuefei 2010-09-08 10:58:31

    老赵,你好!如果要学习在Mono上面的.net开发,应该从哪些方面入手开始学习啊!比如我用的是windows系统,选择安装哪个版本的mono比较容易点,官网上面有Virtual PC,VMware,openSUSE,windows等多个版本,对初学而言选哪个版本容易上手点。希望老赵能发多点关于mono上面开发的博文(从初级到高级那种阶梯似的),呵呵,要求有点过了吧,谢谢,祝老赵越来越好!

  14. 老赵
    admin
    链接

    老赵 2010-09-09 10:23:58

    @zhangxuefei

    装个虚拟机跑mono吧,比如Ubuntu什么的。

  15. ethan.zhu
    183.16.180.*
    链接

    ethan.zhu 2011-01-22 02:06:21

    是否可以在开发之前约定如何使用静态资源。例如使用二级域名或者是有CDN的方式,这样不管怎么折腾只要域名解析和指向没问题是不是都不会碰到路径问题。合理使用,合理合并,合理存放跨域问题也是可以完全避免的。

    在你打包机制中我不赞同的观点:开发和生产环境使用相同一套的代码。从web重构的角度来分析,本着为海量用户服务的精神,合理的压缩合并静态文件会减轻服务器负担,增加带宽利用率,同时能够给用户带来更加的用户体验。例如使用YUIcompressor压缩的js文件会将原来定义好的便于理解的函数名称修改为abcd这样的英文字母,这势必会造成开发和生产环境的文件不一致现象。

    以上个人观点。

    再向您请教一些问题:

    1. IIS是否有插件可以实现 Nginx 的扩展模块nginx_concat_module 可以实现的功能。
    2. 你这里的静态资源打包是不是在发布到生产环境时通过程序打包的?

    nginx_concat_module的介绍:http://www.gracecode.com/category/servers/

  16. ethan.zhu
    183.16.180.*
    链接

    ethan.zhu 2011-01-22 02:09:16

    另,你的这个编辑器在我输入code的时候不太直观。我的浏览器在编辑器中只是显示了缩进,没有任何标识正在输入code代码。当然下面的预览功能里面可以看到。

  17. 老赵
    admin
    链接

    老赵 2011-01-22 23:05:38

    @ethan.zhu: 在你打包机制中我不赞同的观点:开发和生产环境使用相同一套的代码。从web重构的角度来分析,本着为海量用户服务的精神,合理的压缩合并静态文件会减轻服务器负担,增加带宽利用率,同时能够给用户带来更加的用户体验。例如使用YUIcompressor压缩的js文件会将原来定义好的便于理解的函数名称修改为abcd这样的英文字母,这势必会造成开发和生产环境的文件不一致现象。

    估计你只看了我最后几句话没有仔细看我的文章内容吧,我说“使用相同代码”又没说“不压缩”。“使用相同代码”的意思是“只是压缩而不需要修改代码”。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我