分享 Rails Assets Pipeline 的价值

Rei · 2013年03月22日 · 最后由 liuyang_1991 回复于 2017年07月29日 · 12387 次阅读
本帖已被管理员设置为精华贴

Rails Assets Pipeline 的价值

http://chloerei.com/2013/03/10/rails-assets-pipeline-s-value/

综合了之前看到的对 Assets Pipeline 的问题,整理了一篇文章。

说的不一定都对,欢迎指正。

刚刚掉进一个 assets pipeline 的坑里面搞了半宿,早看到这篇文章就好了!

感谢分享。Asset Pipeline 还有在 caching 上的好处。

把 controller 和 action 直接写到 js 里面,这样维护起来会很麻烦把

打包在一起有做 cache 的好处,开发也挺省事。但浏览器解析 js 的时间还是存在,所以 Rails 又有了 turbolink。不过这样会不会有点为解决问题而解决问题?

一直在琢磨着要写一篇《为什么我不喜欢 Assets Pipeline》,看到你这篇文章,看来我得抓紧了 先整理一些观点列举一下:

  1. 依赖管理、合并、精简之外,应该还有个冲突隔离(命名空间),这是 Assets Pipeline 做不到(也不可能做到)的而 RequireJS 能提供的
  2. 但是这也是 RequireJS 的缺点,因为这样需要改变 JS 的书写方式,而 Assets Pipeline 不需要,反而对新手更加友好,尤其是纯 Rails 开发者
  3. RequireJS 系的方案属于纯前端的解决方案,对纯前端人员来说更友好,而且要做到自动化的合并精简也不难,当然在 Rails 范畴内相比 Assets Pipeline 肯定不那么方便,但是放广义一点来说,纯前端的解决方案适用性广阔的多
  4. 使用 RequireJS 会强迫你用面向对象、模块化的方式去解决问题,悄然提高了代码的可维护性,但是 Assets Pipeline 不会,现在前端交互越来越复杂,没有这些东西支撑很难想象代码会成什么样
  5. 可以关注一下 SeaJS,相比 RequireJS 我更喜欢它,国内搞前端应该都知道它的吧,我写过一篇介绍文 http://chaoskeh.com/blog/why-seajs.html 上面的部分观点在里面

几年前公司的项目里发布的时候就会自己压缩 js 不习惯的是啥都是 /assets/xxx.png /assets/xxx.css

我习惯的是/images/xxx.png

谢谢分享。小项目挺方便,省了页面去引用。

4.0 有新变化么,什么单页应用。

#6 楼 @chenge turbolink 之后整站成了个单页应用,意味 js 逻辑不会自动释放,有内存泄漏的可能。

所以写页面逻辑除了要考虑什么时候执行,还要考虑离开的时候释放。所以会有类似代码:

$(document).on 'page:load', ->
  if page is scrollPage   # pseudo code 
    $(window).bind 'scroll', customScrollFunction
    $(document).one 'page:change', ->
      $(window).unbind 'scroll', customScrollFunction

https://github.com/rails/turbolinks/issues/154#issuecomment-11916707

这值得再写一篇文章了,我也是摸索中。

#4 楼 @edokeh 期待文章。

因为我是先接触 pipeline,然后接触 require.js,所以觉得前端方案挺复杂的,为 js 代码做了很多模块化约定,最终还是要借助服务器预编译。相信如果是纯前端出身,看法会反过来,都会先入为主。

pipeline 的写法,就好像 Ruby 代码,需要什么就先在顶部 require,命名空间不强制。如果用 CoffeeScript 的话,默认所有代码会被包在匿名空间里。模块化就交给开发人员了,pipeline 起一个支撑但不强制的作用。

pipeline 提供了工具但是没有多少文章说明怎么去做,新手掉了不少坑,所以我打算接着写这方面的看法。

#2 楼 @knwang 要一些技巧,相比不用 pipeline 是麻烦点,但是要用 turbolink 就需要尽量打包成一个 js。

有了 pipeline 可以拆成 app/assets/articles/show.js 这么小块的 js 文件,也不用怕文件太多太琐碎,最后都打包成一个。

#3 楼 @darkbaby123 这两个相互依赖,本来我不喜欢全部 js 打包成一个,看了 Turbolink 的原理之后发现需要全部打包成一个才能发挥作用,然后才 require_tree 了。

总结得很好,至于“区分不同页面的特定代码”,我习惯这样做:

$('body#topics-show').each(function(){
  var scope = $(this);
  var foo;
});

这样能即能按元素存在执行相应的代码,也能减少全局变量的产生。

#11 楼 @rainchen 我对 js 了解不深入,.length 会导致全局变量?

#12 楼 @Rei if 只是个语法,里面的变量会影响上下文,如:

if($('body#topics-show').length){
  var scope = $('body#topics-show');
  var foo;
});

console.info(scope);

#13 楼 @rainchen 奥,明白了。实际用时我放在 $(function() {}) 里面,应该间接避开了

$(function() {
  if( ... ) {
  }
});

#8 楼 @Rei 那个打包成一个 JS 文件的默认做法我最初觉得实在太奇葩,当时还特地去看了 basecamp,发现人家居然还真就是这么做的,有一个 gzip 之后 200k+ 的 JS。。。 仔细想想,这个策略某种程度上还是不错的,平衡了连接数与缓存两个方面,不过最大缺点是发新版的时候就。。。

#15 楼 @edokeh 我也是特意看了 basecamp,发现他们依然在用 .js.erb,顿时晴空万里。

新版也就一次性下载,而且有个 hash 文件名,避免了用户端缓存不过期——这才是影响可用性的问题。

#16 楼 @Rei 恩,不过这种方式还是适合小网站,大网站真要合并估计都能上 M,那就不太合适了 ——当然现在我的想法变了,为什么我们非要做大网站?小而美也很好嘛!

#4 楼 @edokeh 我觉得你这是把两个不同的概念放在一起来对比了;require.js 解决的问题是模块化和依赖调用,assets pipeline 的主要目的是减少 http request,这两者本来就不冲突。require.js 是从语言和代码结构的角度来提升开发,而 assets pipeline 则是从非代码本身的角度来提升开发,完全可以把二者的优势结合在一起,为什么非要隔离开来,还要比个高下呢?

说到纯前端的解决方案,assets pipeline + gems 就相当于 grunt + bower(yeoman 则是一个使用了两者的整体解决方案)无论是 require.js 还是 sea.js 都有 grunt 下的 task plugin,利用 grunt 就可以达到上述两个方面的优势和好处同时获得。

那么在 rails 这个领域里,现在的状况即 assets pipeline 就是 grunt,gems 就是 bower,而且都要更加强大的多(有 ruby 作为脚本语言支撑,当然 grunt 和 bower 也有 node.js 作为支撑,不过前者发展的更为成熟一些),我们完全可以在 rails 框架里使用 require.js 或者 sea.js,在满足了依赖管理等要求之后,还能让 assets pipeline 完成合并、压缩、cache、revision 等功能,何乐而不为?

#18 楼 @nightire 你说的对,两者实际上确实不是一个层面的东西,但是他们解决的问题有些部分是有重合的(或者说很大部分?) 不过现在问题是,我还没找到非常好的办法,让两者并存,其实最主要问题就是最终的合并部分,你提到的利用 grunt 让两者并存具体是怎么做呢?能否分享一下?

#19 楼 @edokeh 我对 SeaJS 比较熟,拿 SeaJS 举例,SeaJS 的模块合并并不是简单将文件拼合就完了,还得去转换 模块的格式(其实是通过静态语法分析给 JS 加一些源信息),这个工作 assets pipeline 是完成不了的,那么如果我既想要 SeaJS 的这种合并工作、又想要 assets pipeline 的 md5 重命名该怎么办呢?

@Rei 所以我才觉得 Rails 现在有点为解决问题而解决问题。一些新东西的推出是为了解决上一个方案留下的问题。

  1. 手动打包麻烦,用 assets pipeline 统一打包
  2. 统一打包文件太大,虽然有缓存,但每次刷新页面还是要解释并执行那么多 js 代码,于是有了 turbolink。页面一次加载,用类似 pjax 的技术替代页面跳转。
  3. 不刷新页面了,一些在 domReady 中处理的逻辑不能工作了。自定义 page:load 和 page:unload,照着规矩写。嗯,这次还要注意 destroy 对象释放内存了。

不知道下一次会出现什么。

#19 楼 @edokeh 很遗憾,我说了 grunt 是一个纯前端的工具,它依赖的是 node.js 的环境,所以不能和 rails 一起用。

不过你不要忘记,ruby 本身就是一个很强大很强大的脚本语言,脚本语言能做的事情多了去了,并非一定要全部交给 assets pipeline。

举例说明,require.js 里有一个工具叫 r,js,它完成的工作就是将一个项目里的所有依赖关系文件打包,并且在内部做一些额外的工作(类似于你说的 SeaJS 的静态语法分析这一类的工作)。OK,那么 r.js 是怎么运行的呢?node r.js -o you_file.js

See? 它的工作是通过 node 执行的,那么我们完全可以在 rails 框架里,在 assets pipeline 执行前写个脚本调用这个命令,这样我们就可以使用 require.js 自带的打包工具了。

至于 SeaJS,因为我没用过所以不多做评价,不过我知道 SeaJS 的打包工具是和 Require.js 类似的,好像叫 spm.js?所以理论上也可以用一样的方法来完成这件事情。

明白这一点,我们也可以理解为什么 assets pipeline 没有这个功能,因为 assets pipeline 提供的是一种 common way,能够满足多数 general javascript 的打包、压缩等要求。但是对于特定的库,比如 require.js 或 sea.js 它又怎么去分别实现两者对于自己的库进行的特殊处理呢?就算它能实现(比如通过配置文件),那么你觉得有这个必要吗?你用 A,就要求 assets pipeline 支持 A 的特性,如果我用 B 呢?他用 C 呢?Rails 得多复杂才能满足所有人的胃口?

你只是看到了 Sea.JS 在 js 这一块比 assets pipeline 更多的功能,却没有看到 rails 做一个 full stack framework 又远比 Sea.JS 复杂了多少?正所谓:术业有专攻,作为开发者,我们当有能力在 Rails 以及 Ruby 给我们提供的基础上自力更生,而不是等着别人把我们七零八碎的要求一一去实现。

至于 grunt,那是纯前端领域的类似 assets pipeline 的工具,而且它不从属于任何一个框架,所以可定制性极强,且插件都是用 node.js 写的,对前端工程师天然友好。有兴趣的话直接去看看它的文档吧。

#22 楼 @nightire 唉,说白了两者还是没法很好的并存,我现在的用法就是 spm 处理 js,assets pipeline 处理 scss+img,分离开来做,同样是静态资源,这样很奇怪对不? 你说的我都赞同,我只是觉得,Rails 现在管的太多了(侵入到了前端领域),虽然目前看来还挺好,但是这未必是件好事 想想看以前 Rails 还支持 ejs(好像是叫这个名字?)呢,那时候是不是也觉得挺方便的?

#21 楼 @darkbaby123 你的看法,又是被 require_tree . 陷进去了。

我未了解 Turbolink 之前,是这样用 pipeline 的。

application.js

//= require jquery
//= reuqire rails_ujs
//= require 其他基础库

topics.js

// topic 页面的代码

编译的时候都独立编译。

pipeline 是一个打包工具,可以按自己想法去用。我是看到了 Turbolink 的好处,才接受把所有 js 打包在一起。

这些工具的目的,都是为了让页面加载更快,Rails 工具链提供了一套方案。相比客户端 MVC,我觉得简单多了。

#23 楼 @edokeh 两者无法并存,其原因并不在 rails,而是太多的前端工具所拥有的各种特性,你没法指望 rails 全部去支持;如果你认为 rails 管前端的事情管多了,那你有没有考虑过那些不用 require.js 或者 sea.js 的开发者又该怎样处理合并、压缩、cache 等等这些事情呢?是不是还要让他们去重新学习前端的处理工具?

作为一个最终用户我们当然习惯于从自己的角度来看待问题,但是作为一个框架的作者或团队,他们要考虑的问题面就要更加广泛了,它没法做得太简单而让很多开发者觉得不爽,也不能太复杂为了照顾一些高端用户而引入了太多的功能和复杂的处理流程。如何取得一个平衡点,同时又能提供一个扩展性和定制性强的工具框架,这毕竟不是一件简单的事情。

在我看来,Rails 在努力消弭前后端的界限,让更多专们从事后台开发的开发者也可以尽可能多和轻松的使用一些前端技术,反之亦然。这种做法本身是很积极的,但是肯定会遇到众口难调的问题。不管怎么说,理应获得更多的支持和鼓励,才有可能让事情变得越来越好。

另外,技术永远是在不断进步和发展的,node.js 的诞生不也一样可以被视作“前端侵入后端”的经典案例?如果其作者事先也想:“这样做会不会不好?”,那我们今天还能看到 node.js 吗?没有什么东西是完美无缺的,但这一点恰恰是证明我们作为“开发者”的价值所在,我们不就是那一群“让不可能变成可能,让可能变成更好”的人吗?

#23 楼 @edokeh nodejs 的出现也是想模糊前后端的界限。(哎呀,发现楼上说了)

我目前还是觉得,assets pipeline 已经干了 require.js 这些前端方案干的事,如果在文件头加注释就可以解决编译问题,那么为什么要在代码里面 require('...') 或者 define('...'),然后再用语法分析器将依赖抽出来呢?

所以我也没怎么考虑要它们融合。实际开发中二选一吧。

#26 楼 @Rei nodejs 是因为异步方式,号称能增加并发水平吧。还有就是一种语言开发。不过 js 本身语法不是太吸引人,所以还不流行。如果前端支持 ruby,就可以一统天下了。

#28 楼 @chenge 我看过一些 nodejs 的 web 项目之后,觉得自己是不会想写这样的代码了,回调太多了,可读性低。

有并发要求的,我会找 go 这样的编译语言。

#25 楼 @nightire #27 楼 @Rei 哈哈,25 楼这个回复好,点醒了我 是我对 SeaJS 的喜爱让我执着了 另外 rei,这两个还是有很大差别的,我说的不是表面,是内在,有空把代码全改为用 SeaJS 感受下?你的那个笔记开源嘛?

#30 楼 @edokeh wirtings 最多 js 的地方是编辑器,其他都是小块的页面效果。编辑器除了依赖 jQuery 和 mousetrap,别的都是 dom 操作。还没打算开源。

我看看 seajs 的文档吧。

#30 楼 @edokeh 无论是 sea.js 还是 require.js, define 都实在是太丑了..

纯的 CommonJS 解决方案也有一些是不用写 define 的,但是会多一个 build 的过程,自动加 define 给你,这样就可以跟 node 一样只需要 require 就可以了。( component && browerify

sea.js 跟 require.js 的 sync load 基本属于伪命题,大家都还是需要用 r.js 与 spm.js 事先压缩,或者通过 nginx 等 hack 方式加载所必须的文件 ( 是不是值得,是值得商榷的,因为损失了 cache 特性。

压缩合并为整个文件/或几个小文件 ( 权衡 cache 与 性能 ) 是现阶段唯一好使的做法。

同时,这类产品都有一个问题。就是 module 缺乏,需要有额外的代码来支持才能使用。shim 是一个方案,但是不够平滑.. .. 问题多多..

#32 楼 @Saito 纯 CommonJS 的方案肯定要后端支持,所以很难获得纯前端的喜爱,后端人员也会觉得麻烦 sync load 确实是个伪命题,所以 SeaJS 说自己主要是模块加载器,其次才是脚本加载器

module 确实是缺乏,但是就我这一年来使用 SeaJS 的经验,基本上不是问题,一来著名的模块官方基本都有提供封装好的版本,二来熟悉了 SeaJS 原理以后,遇到不提供的,自己封装一下也非常简单,除非你想用某些 jQuery 插件,那确实麻烦一些。。。 不过我想 jQuery 插件不是一种好的前端代码组织形式已经是前端界的共识了吧?

#33 楼 @edokeh 我今天才被科普:jQuery 本身就是个 Anti-Pattern.. ..orz

#33 楼 @edokeh 另外插件如果都是 Fork + Patch 的模式的话,会跟 Rails 现在 gem 加载 javascript 陷入同一个困境,更新跟不上..

模块化方案大家貌似还是在等 ES6. 但现阶段来看,require.js 貌似已经领先了,很多应用默认支持 require.js.

另一个较为成功的库 analytics.js 默认支持 component.

还是希望标准早日落地吧..

#35 楼 @Saito RequireJS 领先,我觉得一方面是因为人家行动的早所以影响大,另一方面是他到处跑到人家的 repo 去提 issue 让人家支持 AMD 规范。。。 SeaJS 嘛本来就不打算让人家原生支持,提倡自己封装,所以感觉上会弱一点,不过其实不要紧啦,一个支持 AMD 的模块,基本上不要怎么修改,就能变成 SeaJS 的 CMD 规范

扯跑偏了……

前端 mvc 比 rails pjax 好

assets pipeline 本来就只是用来解决服务器管理静态文件的。和 Javascript 前端库管理是两回事

asset pipeline 不能和 grunt、require.js、spm 共存?因为我还没有开始做单页面的应用,所以还没有尝试。但我觉得应该可以共存的。

  1. asset pipeline 也是调用 js 解析器的,其中一个常见的就是 nodejs。理论上扩展一下没什么难度。
  2. 以 grunt 为例,你可以修改 application.js,保存的瞬间立刻帮你生成编译好的。rails 接着要做的很简单,就是 copy 一下,md5 一下,done!
  3. 我试过 less 用 asset pipeline 的 require 语法,混用 less 自身的 @import 语法,完全没有问题

不管怎么说,我觉得前端方案肯定是能在 rails 里面 work 的,等我做单页应用的时候玩玩

我感觉 asset pipeline 让 javascript mvc 的框架很难集成到 rails 中,不知道这个问题各路大神和怎么解决的

#41 楼 @zhlwish js 的事情还是让 js 自己来解决好,我的做法是禁掉 asset pipeline

对于我这种新手来说,pipeline 可以为我料理一切,方便我的入门

而对于大牛们来说,也提供了一定的按需定制

我觉得并不矛盾啊

学习观摩

刚学习 rails,发现 lz 说的我都看不懂,泪崩。。。

刚学完 rails 初级,发现 lz 说的我还是看不懂,更泪崩。。。 玩笑管玩笑,现在 yeoman 出 1.0 后,yo 就是 rails,bower 就是 gem,grunt 就是 rake 或者 bundle,感觉 node.js 现在的水比 rails 深多了,抄了不少 rails 东西,又有 N 多自己的东西……

#4 楼 @edokeh 感觉和前端模块化相关的话题,到哪都有你啊! 刚在考虑如何在项目引入 Assets Pipeline,看了你们的讨论,感觉这方案并不适合当前项目,继续折腾 Spm 去

链接失效了~ 作为一个才接触 Rails 的小白 我看直接把 Assets Pipeline 禁用先 虽然它压缩合并 JS、CSS 文件 减少 http request 的大小和数量的出发点非常好 但一股脑打包可能会打乱了渲染的顺序和执行的逻辑 本来给一个简单网站中各个 HTML 页面来添加正确的 JS、CSS 是件再简单不过的事了 小白中的小白都能干好的一件事 就因为在 ROR 中想用 Assets Pipeline 的压缩功能 反而要花一些 时间检查不同的 JS 是否有冲突、CSS 之间是否有覆盖 还有再花些时间 写几句代码 把这些分离出来 单独引用。。。 用 ROR 不就是为了敏捷开发么 但被这些引用的小事搞得抓耳挠腮实在没必要 所以直接禁用

#48 楼 @sunnyleo 就因为这 JS 和 CSS 的引用问题 今天在坛子已经泡了很长时间了。。。现在还是木找到方法。。。 对于网页间共用的文件 肯定是压缩好 但对于有特需的 肯定单独引用好 也看到坛子里有处理 但是主要看到有几个帖子都是同样的问题 但方法都不一样 https://ruby-china.org/topics/14374 https://ruby-china.org/topics/5324 https://ruby-china.org/topics/11455

小白我已经被搞晕了 请大侠详赐简单明了、暴力有效的方法 谢谢~

#49 楼 @sunnyleo 找到了@Rei 链接失效的博文 http://chloerei.com/2013/03/10/rails-assets-pipeline-s-value/ 写的很详实 思路清晰了不少 非常感谢!

楼主的博客挂了么,打不开

@Rei 连接失效了,老大

需要 登录 后方可回复, 如果你还没有账号请 注册新账号