自打 06 年接触 Ruby 开始直到现在,一直从事基于 Rails 的 Web 开发,不知不觉九年过去了……从最开始的惊艳,到喜欢,再到热爱,随着开发经验的不断积累,自己对 Rails 这个框架的看法也有了很大的变化(干了十年的开发,或许是成熟了?),正所谓爱之深,恨之切,大概也就是这个意思吧。今天在这里开个头,让我们抛开 Rails 的优点,只说不好的地方,当然其中本不是框架自身的问题,但是既然遇到了,就写出来,欢迎大家继续补充或拍砖:)
Rails 一向以开发效率高而著称,这从当初那个“十分钟构建 blog”的操作视频就能看出来,这也是吸引我关注它的一大原因(那会儿还不知道 Ruby 是啥,更不要提语法优美了)。MVC、ActiveRecord、脚手架、惯例优于配置、DRY、Database migration……这些新东西组合起来,在当初那个时代算得上是超前了,但是就如 DHH 的固执一般,基于 Rails 来做 Web 开发也意味着你必须遵守他的这些潜规则才能用得舒服:Autoload?恩,你不必显式的 require 依赖,直接在代码里面用就好了,但前提是目录结构、文件命名都要按照约定的来;RESTful 默认定好了七个 action,想要改别的名字可以,自己改 route 定义去吧……
上面这些都还好,其实最大的问题来自于 Rails 对 MVC 的理解,模型层是 Active Record 模式的实现,控制器获取请求数据,执行一系列的操作,并最终决定渲染什么样的视图返回给客户端,看上去简单直观,不是么?Ruby 社区一向对 Java 这种大怪物不屑一顾,JavaEE 那些层次划分与设计模式从来都不被待见,但为什么会发展到那种地步,肯定是有其合理性的,就拿“服务层”来说,Java Web 框架在集成 ORM 的时候,一般都不会采取 Active Record 这种充血模型,而是将 Service Object 与 DAO 分为两层,专门使用服务层来封装复杂的业务逻辑,ORM 只负责数据的存取而已。为什么会这样设计?其实一开始我也觉得是把简单的问题复杂化了,ActiveRecord 多好用,模型类就是表,属性就是字段,再加上业务算法,多省事儿!恩,简单的场景下这种方式确实简洁明了,但是随着系统的业务需求不断增加和变化,还采用这种模式的话,就要求你不断的扩充模型类,往里面加代码,”Duang,Duang,在 PM 要求加功能的时候,其实我是拒绝的……”,最后的事情大家都知道的,恩,说的就是你,抓个典型 User.rb,打开看看我敢保证你一眼是看不到底的,更不要说理解里面的逻辑都是干嘛的了。什么,你把逻辑都写控制器,模型一点都不复杂?好吧,你牛你先走。
其实 Rails 也不是不知道这个问题,违反 SRP 的事情谁都不好意思当优点讲,所以 4.0 开始加入了 Concerns,寄希望于给开发者多一种组织代码的方式,但是别忘了,这仅仅是利用了 mixin,将代码物理上分开而已!就好比你嫌屋子脏,拿起扫把将垃圾清扫到四个墙角,然后拍拍手认为打扫干净了。Jim Gay 在他的《Clean Ruby》中说,你不要看表象,最好打开一个 rails c,然后看看 puts user.methods,真相就是这样……所以 Rails 社区就服务层,或者更准确的说,如何避免过渡使用充血模型这个主题也是争论不断,有人赞成 Java 社区的做法,也有人另辟蹊径,想采用装饰器、DCI 等更新颖的方式来实现瘦模型,但问题的根源在于,这些方案都不是官方的,可不符合 Rails 的“哲学”,不是么?
总之,就架构层面的问题,我个人主观、固执、不负责任的认为,Rails 的架构就是: 基本没有架构 ……
前面关于架构的牢骚看起来既空洞又无趣对吧,恩,那接下来写的内容应该会更贴近实际,你们拍砖轻点,哈哈。
Turbolinks
这货也算是 DHH 得意之作了吧,丫就喜欢在某个版本里面突然就给你搞出个新概念来。来看看 Rails guide 官方是怎么说的:
Turbolinks 为页面中所有的 a 元素添加了一个点击事件处理程序。如果浏览器支持 PushState,Turbolinks 会发起 Ajax 请求,处理响应,然后使用响应主体替换原始页面的整个 body 元素。最后,使用 PushState 技术更改页面的 URL,让新页面可刷新和收藏,拥有美观的 URL。
瞧见没,好处大大的对吧,但是……实际开发中这货不是那么美好,首先,因为页面加载流程被改变了,你不能再使用之前的 dom ready 来监听页面加载完毕与否了,必须使用 page:change、page:load 等自定义事件才行,这都不是什么问题,改改遗留代码就好(其实也挺费事的,不是么);最坑爹的是,Turbolinks 会缓存加载过的页面,默认是 10 个(具体可以参见其 github 项目 readme 页面,或者查阅 the rails way,官方指南可不会告诉你!),这就很蛋疼了,要求你的 js 脚本对 dom 的操作必须是“幂等”的,什么意思,就是说同一段代码会在同样的 dom 节点上执行多次,你,作为开发者,code monkey,必须自己来保证不会出问题!(我擦)没明白什么意思?恩,手把手来个例子好了,比方说,你监听了 page:change 事件,并在其中给某个按钮添加了点击事件,弹出个 bootstrap modal,比如电商网站经常放导航条上的购物车按钮,很常见对吧?访问商品列表第一次加载没问题,用户点击某个商品查看详情页面,也没什么问题,但是,如果这会儿他点击了浏览器的“返回”按钮,那么问题就来了,因为 Turbolinks 是会缓存之前访问的页面的,默认十个哟,第一次访问列表页的时候点击事件已经监听好了,这下又执行了一次,然后用户点击购物车按钮,Duang,Duang……弹出两个 Modal,真酸爽。
怪不得别人,只能怪自己不去翻项目的 readme,不好好看《the rails way》,解决办法其实也挺简单的,把这句代码加入你的 js 文件吧:Turbolinks.pagesCached(0)
,关掉缓存页面的功能就好。另外如果你要基于 Backbone 之类的前端 JS 框架来开发界面的话,最好直接去掉 Trubolinks,要不然,有你好受的,真的。
其实 Trubolinks 也是个替罪羊,那罪魁祸首是谁呢?恩,说的就是你——Assets Pipeline!鉴于这货的罪行,加上现在已经夜深了,我还是先不讨伐它,留作后续吧,先把能长话短说的坑写了。
redirect_to
恩,如果说前面的是框架的原罪,那么这个确实不应该算在 Rails 的头上。具体说来就是,某些客户端(包括 Chrome 的 Ajax 实现、Rested、Paw 等)在发起的 delete、put 等非 get 请求时,如果被 redirect 了,后续的请求依然会采取同样的方法而非 HTTP 规范里要求的 get 方法,这会造成死循环,或者导致服务器返回错误信息(很显然嘛,请求方法都不对,除非你正好有路由规则能碰上)。解决方法说起来也很简单——设置 303 status code 响应码:
redirect_to posts_url, status: :see_other redirect_to action: 'index', status: 303
更多信息可以参考 redirect_to 方法的 API 文档:
If you are using XHR requests other than GET or POST and redirecting after the request then some browsers will follow the redirect using the original request method. This may lead to undesirable behavior such as a double DELETE. To work around this you can return a 303 See Other status code which will be followed using a GET request.
这不是 Rails 的错,真不是,但是总有一天你会遇到的,比方说我就很喜欢在异步删除某个资源之后,直接 redirect 到 index 列表,如果不注意,那么就会执行delete /resources
……
继续填坑……
先回复下前几条评论的大大:
@blacktulip 不到万不得已,还是别 disable 这个特性,毕竟是 Rails 的新功能,好处还是有的。其实最主要的原因是 Assets Pipeline 的坑需要用 Turbolinks 来弥补,可以说就是帮它擦屁股的!因为采用 Assets Pipeline 的方式来 serve 静态资源的话,会把 javascript、stypesheet 都合并成单个文件,以减少浏览器需要发起的请求数量,但是这样一来就会导致文件本身比较大,每次全页刷新的时候加载速度会有一定的影响,所以 DHH 才会用 Trubolinks 来做异步抓取并判断资源文件是否有改动,没有的话仅仅替换 body 的内容,这样就不会白屏了。如果你采用了 Assets Pipeline(我想绝大多数 Rails 应用都是这样,主动去关闭的应该很少吧),最好还是留着 Turbolinks,以便减轻前者带来的性能问题。
@kgen 这篇文章的主题是吐槽,Rails 的好处自然不必多说啦,抛砖引玉就是想让大家都来发牢骚:)当然我知道如果仅仅是抛出问题又不给解决方案,那肯定会被大家认为是脑残黑,所以这里就第一点再扩充讨论下:
首先,我们要承认 Rails 是一个非常固执的框架,DHH 的性格不可能让 Rails 变得迎合大众的口味,还记得他那篇有名的 blog 么? Rails is omakase Rails 最大的特点也就是为开发者提供了一套简单的解决方案,惯例优于配置(COC)、DRY 和最小惊讶都是大家耳熟能详的原则,初学者通过简单的学习就能掌握构成 Rails MVC 的几大组件:ActiveRecord,ActionPack,Route,然后就可以快速开发 web 程序了。在社区里最常见的一个词就是 rails way(这里不是说的同名书 The Rails Way,你懂的),写代码、思考问题的时候都会尽量的靠着这个目标来,但是,the rails way 并不一定等同于 the right way,我想这点没人能反驳吧,类似哲学的问题,嘿嘿。要不然为啥那本同名的 rails 圣经(The Rails Way)最新版在编写的时候,宁愿冒着被读者骂的风险抛弃默认栈的组件,改为使用 haml 编写 view,rspec 做 tdd,以及使用decent exposure做控制器 - 视图的数据共享?
初学者在接触 Rails 的时候,肯定首先考虑官方的默认设计,并严格贯彻这一套思想去指导开发,久而久之,也就养成了习惯,姑且不说这些习惯好与不好,对今后的影响肯定是比较大的。我觉得 Rails 最大的问题就是,默认给你个简单的,第一眼看起来很不错的东西,但是又不是完备,非得等开发者自己去摸爬打滚,通过长时间的填坑慢慢积累经验。默认的 MVC 实现很好的分离了模型、视图、控制器(其实后两者还是纠缠不清,都放到 ActionPack),简单的 CRUD 只要遵循原则写起来确实快速方便,但问题是业务复杂之后,很多逻辑明显不能放 Controller 对吧,于是按照 Active Record 充血模型的原则,很自然就让人觉得该放到 model 层,thin controller,fat model 应该是首先被社区认可的手法,但这样真的是最佳实践吗?反正基于 Rails 潜移默化的引导,随着开发经验的增长以及业务逻辑的日趋复杂化,羁绊的问题会逐渐凸显出来。这里举个简单的例子,很多 Rails 开发者都认为,一个类如果不继承 ActiveRecord::Base 的话,就不算是 Model(比方说电商网站的购物车 ShoppingCart),那这些代码放哪里呢?框架指导性的目录结构只有 models/controllers/views……恩,有个 lib,放这里吧!但其实它真真切切就是个 domain object 啊,放 models 很合适不是么?
缺少服务层的抽象,提倡充血模型,将业务逻辑放到 model 层,这是 Rails 最大的问题,这样的潜移默化会让开发者在后期尝到苦果……
解决方案?我们先来看看国外的开发者们都是如何应对的吧,相比他们的槽点和做法,我这种小白真心只是稍有微词,呵呵:
首先有请 Avdi 大神隆重登场,人物信息我就不用废话了,大家 google 一番自然就能知道谁谁谁。我们来看看他的这部《Object on Rails》,在线版是免费的,推荐各位感兴趣的 Rubyist 有时间都看看。在 Avdi 看来,Rails 风(下文均以此指代 Rails Way)根本就算不上面向对象!他觉得 AR 层实在是肩负了太多的责任,应该减负,所以自己写了个叫做 FigLeaf 的 gem 来限制模型类的接口,只将 ActiveRecord 作为 DAO 工具来使用,除了简单的 CRDU,其方法一律被自动设置为 private 的,够疯狂吧?通过这种方式强制让你思考,业务逻辑到底应该放在那里(Rails 君含泪飘过,我可不造,你造吗?)。另一方面,Avdi 还提出了一个新名词:Exhibits,说白了其实就是 Presenter 的一种变体(虽然他自己觉得区别很大),只不过加上了 chainable 功能的 decorator,通过层层装饰,让模型对象在不同的上下文环境具备不同的功能,比方说在 view 里面可以附加上展现相关的逻辑,这些逻辑原本是不应该放到模型对象的类定义里面的。
然后,Andrzej Krzywda 在他的《Rails refactoring》里面,也含泪控诉了多条 Rails 的不是,焦点都集中在 Controller 上,他认为 callback(比如 before_action 这些)、cv 通过实例变量来传递数据等等一系列 Rails 开发者习以为常的做法都是错误(或者说,是不好)的,建议引入 service object 来处理复杂的业务逻辑,让 controller 只处理跟 http 有关的事情(解析请求,发送响应)。
再然后,更激进一点的,Jim Gay 写了本《Clean Ruby》,想从源头上解决复杂逻辑的组织问题,这次可不仅仅是黑 Rails,连 Ruby 都黑了,他认为,我们之前写代码的方式,都称不上 Object-Oriented,只能算作 Class-Oriented,正因为不是真的 OO,才会导致违反 SRP,增加开发人员对代码的理解难度。他的方案通过将 User Story 映射到场景,然后把代码按照“是什么”以及“做什么”的类别分开,基于 Data(数据)、Context(上下文)、Interaction(交互)来编写代码,以便更适合心智模型。
吃完饭了,继续填坑。看了下评论,可能某些大大有点误会了,我发这贴的初衷,并不是为了说 Rails 有多烂,而是想开个头,让大家能互相吸取经验,避免在同一个坑里面摔倒。我用过很多 web 框架,包括 java 系的 ssh、grails,以及 python 社的 django,还有 node 的 express,直到现在还是觉得 Rails 比他们先进、好用,但还谈不上完美,真的是爱之深恨之切:)
接着说老外的解决方案。
上面那些都是比较偏门的,而且不成体系,那么好戏来了,请看这个:
就像它的名字,很新奇,在一堆 leanpub 的书里面,你绝对看不出是讲 Rails 的,我敢肯定,当然副标题 A New Architecture For Rails 深深的出卖了它:)作者的信息我也不在这里废话了,请自行 google。在这本书里面,Nick Sutterer 提出了一套完整的架构风格,从前到后,展示了完整的、他认为比较合理的全栈模式,当然我觉得最好的一点是,他遵循了一个标准,那就是尽量不或者很少的打破 rails 风,这点很好,为什么这么说,等待会儿讲另一种方案的时候对比一下就知道了。Nick 给出了实际可用的东西,不得不令人钦佩,具体内容我还是不透露了,大家都去买一本支持他吧,虽然还没写完,这里转帖下他自己的大纲说明:
Logicless models solely focusing on persistence. Skinny controllers working as endpoints, only. Domain objects for your business logic, known as operations. Form objects for deserialisation and validation. View models to introduce a widget architecture. Representers that parse and render documents for APIs. Twin objects for decorating models.
是不是很眼熟?没错,这些手法都是为了解决正统 rails 风没解决的问题,也就是我之所以主观认为的,rails 没有架构,需要更高层面的方法来指导,呵呵。
还有一种从不同视角去组织业务逻辑的方式,那就是 Modular,也叫做 Component Based 架构,这在我之前那篇《Spree 源码导读》中有过介绍。具体说来,就是采用 Rails engins 的方式,将整个系统的功能点划分为不同的模块,打包成 gem,然后在需要的时候可以随意组合,这在很大程度上解决了上面都没提到的 Rails 的一个问题:倾向于促使开发者实现“孤岛”应用,也就是无法很好的拆分组合、巨无霸的应用。这种方式目前在国外 Rails 社区也是比较热门的话题,相关研究也比较多了,比如:
《Component-Based Rails Applications》 Stephan Hagemann 的新书,目前也还在编写阶段,但是非常值得期待,他在 RailsConf 2013 上有过讲座,视频就在书籍介绍页面,不容错过。
《Modular Rails》 作者 Thibault Denizet,现居曼谷,这本书现在已经完成了,也是我目前主要的学习资料。
看起来很美好的架构,但是的但是,经过我个人的实操发现,其中最蛋疼的是,完全打破传统的 Rails 风,这从 Modular Rails 的前面章节就能看得出来,要改多少东西,包括目录结构、文件名、类名……得出的结论是,初学勿入,在复杂性没到一定程度的阶段,折腾大于实际带来的好处。当然,或许是因为个人天资不行,摸爬打滚了九年还是这模样,你,大概可以哟。
好了,问题抛出来,解决方案也有了,但现在我又有了更大的疑惑:
问题的问题是,这么多条路,都不是官方扶持的,我应该选哪个?真是个问题,你觉得呢?
关于前后端代码重复的问题,这里还想解释下,这里的“重复”是怎么定义的。我的想法是,如果后端 view 可以简单实现,那就大可不必非要把构建逻辑移到前端 javascript 身上,除非你的团队有明确的分工,前后端可以有清晰的职责分离,要不然对于一个前后台都要写的程序员来说,其实挺精分的。还以 Ajax 购物车为例吧,你有一个按钮,点击之后异步请求服务器,成功之后在当前页面上弹出一个 bootstrap modal,因为这个功能全站都在用,也不会提供其他的展现方式,那么你完全可以就用全局的 CartItemsController 来提供接口就好:
class CartItemsController < ApplicationController
layout false #我只需要返回html片段,无需layout
def index
@cart_items = shopping_cart.cart_items
#这里我选择render默认的view,一个table之类的,因为不想把逻辑交给js,毕竟整个网站就一个购物车弹出,无需响应json
end
def create
shopping_cart.add cart_item_params
#新加商品到购物车,之后,怎么来得简单?我懒,所以干脆直接重定向到index呗,都不需要关心总价怎么更新
redirect_to :index, status: 303 #恩恩,你不加303?好吧……而且这真不是什么炫耀,只是个善意的提醒
end
#其他的修改购物车商品数量啊,删除购物车项啊,清空购物车啊啥的,都可以同样的逻辑,操作完直接重定向好了
private
def shopping_cart
# 这里抽象的购物车是一个model,但是不必是AR,传self你可以获得用户是否登录来执行不同的操作,比如游客的话就操作cookies……
ShoppingCart.for self
end
end
为什么说这样的偷懒做法是可行的?因为简单。你可以继续用 Rails 的 view helper,比如 link_to、form_for、url_for、xx_path…… 当然你也可以设计成返回 JSON 数据,但是那样的话,你就把构建前端展现的责任推给前端开发人员了,在明确分工的团队,这是合理的,但是对于一个从头干到尾的苦逼程序员来说,为了组织代码,你得做前端 MVC,比如用 backbone,然后用 eco 或者 ejs 来做 template,将服务器返回的 JSON 数据 eval 到 template 中,最后再更新到 dom 树,这又是何苦呢?其实 Ajax 的那个 x,一开始强调的是 xml,html 作为一种通用表述,直接返回片段标记也是非常可行的,装逼点说就是 Ajah,《the rails way》在讲解 Ajax 的章节也有比较具体的实例场景,以告诉读者,某些时候这种方案会比 JSON 纯数据接口更加合适。如果你真喜欢严格分离的方式,有些问题必须记住:
至于响应*.js.erb 的做法,我个人是非常反感的,这个方案会导致前后端的耦合度大大增加,你的服务器端代码、前端 js 都得了解具体的 html 结构,这是很蛋疼的,ajah 的方式还好,嘿嘿……
然后,那个改成桌面 GUI 的场景,我换个方式好了,BOSS 说:Rails 好难用,讨厌得很,明天给我改成 sinatra 或者 ooxx 框架!什么,服务层是基于 rails 特定的 controller,没法直接用?我不管,你加班……
继续挖坑,闲着在控诉 Assets Pipeline 的罪行,DHH 挖的一个大坑,然后从旁边挖了点土来填,于是 Turbolinks 就粗线鸟,哈哈哈。剧透下,接某位大大那句话,IE 6 的兼容性问题要不要算到 Rails 头上?这个我表示中立,但是 Assets Pipeline 的问题确实有一部分跟 IE 有关,而且正是 Rails 导致了这个问题发生的几率大大增加,可以说,项目大了必然会遇到!
来最后补一刀,在我看来,Assets Pipeline 不说有七宗罪,至少也有两宗罪:
第一宗罪:增加集成客户端组件的复杂度
大家都知道,要做出美观漂亮简洁大方的用户界面,其实是挺麻烦的一件事,特别是对我这种不懂设计、前后端都得一个人来扛的野生程序员来说,更是难上加难。只有借助诸如 bootstrap 之类的 css 框架,以及国外设计师设计好的商业模板,才能在最短的时间完成项目,尽早上线。比方说,http://themeforest.net/就是一个巨大的宝藏,不会设计也没关系,好好选个适合手头项目的 theme 其实不是难事,花费也不会太高(比专门请设计师做划算多了),这里随便选个来做例子,就 Flatlab 吧,蛮漂亮的扁平化风格 Admin Template:
介绍页: FlatLab - Bootstrap 3 Responsive Admin Template 在线演示: http://thevectorlab.net/theme/?theme=Flat%20Lab
一般来说,这些模板在购买后,你会得到一个源码包,里面包含了完整的 html、css、javascriot 以及相关图片,类似这样的目录结构:
在早期的 Rails 项目中使用这些设计模板是非常简单的事情,你只需要把必须的资源放到 public 目录下就可以了。但是现在,由于 Assets Pipeline 的机制,要正确集成却成了一件非常蛋疼的事情,这是因为:
Rails 项目以 production 模式启动的话,默认是不会动态实时编译 assets 文件的。这就要求你在启动之前先自行编译,Rails 会按照 Manifile 声明好的依赖关系和顺序,去压缩串接资源文件,比方说那个 application.js 以及 application.css,让然如果你在配置文件(/config/initializes/assets.rb)设定了其他的预编译文件,他们也会被处理好,最后放到 public/assets 文件夹下(具体的细节请参见官方指南 Assets Pipeline 的相关章节)。要成功编译,并保证运行的时候所有需要的依赖都正确,其实是件很蛋疼的事儿,因为这些 css 或者 js 代码中,如果涉及到字体,或者图片的话,作者原来都是基于相对路径来编写的,一点问题都没有对吧?这里节选一段(你可以随便打开模板源码中的某个.css 文件,搜索下“url(”这个字符串,一般都是用来设置背景图片的):
/* vendor/assets/themes/flatlab/css/style.css 的片段代码 */
.active .dcjq-icon {
background: url(../img/nav-expand.png) no-repeat bottom; /*注意这里的图片路径,真实地址是vendor/assets/themes/flatlab/img/nav-expand.png */
border-radius:3px;
-moz-border-radius:3px;
-webkit-border-radius:3px;
}
这样写在本地文件系统直接打开相关页面是没有问题的,你放到服务器上按理说也是没问题的,但是如果启用了 Assets Pipeline,那么问题就来了:Rails 认为编译之后的资源都放在 public/assets 文件夹下,url 访问路径就是/assets/,如果不做修改,直接把这些 Theme 的源码 require 到 manifile,rails 可不会智能到帮你自动修复相对路径,比方说上面的代码,最终会导致浏览器访问:
http://demo.com/img/nav-expand.png
结果你懂的,404,找不到对象……恩,谁都不希望找不到对象是不是,嘿嘿……
解决办法是有的,那就是你只能采用 Rails 提供的 Assets helper 来动态生成这些资源路径,但是前提是,只能在 erb 或者 scss 中使用,比方说采用 erb,然后我把整个 theme 源码放到 rails 工程的 vendor/assets/themes/flatlab 下(为什么会是这个目录,下文会解释),接下来需要将原始的 css 文件改名添加.erb 后缀(这样你才能享受 Assets Pipeline 的便利哟),最后去把 所有的 路径改为类似这样的:
background: url(<%= asset_url flatlab/img/nav-expand.png %>) no-repeat bottom;
……
……
……
处理完所有的 css 文件
……
……
……
加油少年,继续处理所有的 js 文件
……
……
……
然后的然后,这还不算完,因为,你还得去/config/initializes/assets.rb 中把所有非 css 和 js 的资源文件(不仅是各种图片,还有各种字体哟)加入预编译路径,类似这样的:
Rails.application.config.assets.precompile += %w(
flatlab/fonts/*.*
flatlab/img/**/*.*
)
因为 theme 作者经常会使用熬很多三方开源的东西,我们看看 flatlab 的目录结构就知道了,它把所有的三方 plugin 都放到 assets 子目录,如下图:
看到了没,这带来的后果是什么你造吗?意味着,你还得递归去处理每一个三方 plugin 的资源路径!改文件名,改源码,改改改……
然后为啥我建议将整个 theme 放到"vendor/assets/themes/模板名/"这个目录?其实也是为了减轻集成的工作量,因为 Rails 在搜索资源的时候,其实是不会关注目录名的,只要在 app/assets、lib/assets、vendor/assets 下的任意一个子文件夹下都可以,我们这里搞个 themes 来存放所有的模板,以后也好做切换皮肤的功能,另一个原因就是,可以利用资源 index 的功能,也就是在某个目录下如果有 index.js 和 index.css 这两个 manifile 的话,你在主 manifile 中就可以简单的引用到整个目录,就好像一个独立的文件一样(同样的原理可以应用到子目录上,比方说其他三方插件),然后在 application.css 中:
/*
*= require flatlab
*/
恩,让我歇口气,解释步骤都用了半天,我敢说作为看官的你,都觉得太折腾的对不对?完全同意!但有什么办法,要用 Assets Pipeline 就得这样……你不用 Theme 的?那我想问,你用不用三方 UI plugin,比如 js tree 之类的?如果用的话,正巧那个 plugin 又没有人提供现成的 assets gem 包,那么恭喜你,还是难逃一劫,嘿嘿。我在想如果 bootstrap 不提供 gem 包的话,这个问题会有更多的人感同身受吧,那些 icon font……所以真的要感谢下那些做 assets gem 的人,挺不容易的。
这还不算完,某一天某个 plugin 告诉你升级了,加入了多少多少新功能,解决了 N 多的 bug,请问你打算怎么办?唯一能做的,就是下载源码包,然后再来一遍上面的功夫,爽不爽?如果想要亲自尝试下,我强烈推荐各位集成下这个:
绝逼是个典型,为啥到现在都么有 ruby gem?恩,我来告诉你,因为源码里面到处充斥了 hard code 的资源地址,包括 js 里面,有你好受的,嘿嘿嘿……
这是就是 Assets Pipeline 的第一宗罪: 徒增集成前端组件的难度,以及后期维护的工作量
回来了,继续最后的控诉。
第二宗罪:诱发 IE 解析 css 的 bug
实在无力吐槽了,码子挺累的,各位先自己 google 下这个关键字:IE css 4096,或者直接点这里:
Internet Explorer 9 and below have a max of 4,096 selectors per stylesheet.
光看标题就知道了吧,非要翻译的话,那就是:IE10 以下的所有版本,针对同一个 stylesheet link 的样式文件,最多只能解析 4096 个选择符(selector)。其实真相是:
A sheet may contain up to 4095 rules A sheet may @import up to 31 sheets @import nesting supports up to 4 levels deep
兄弟姐妹们,知道为啥我想骂街了吧?不知道?你好懒啊,偶继续翻译:
单个样式文件最多只能包含 4095 个规则 单个样式文件中,采用@import声明最多只能导入 31 个子文件 @import的嵌套最多支持到 4 层
你非要问如果超过了限制会怎么样,那我来告诉你,多出来的样式声明将会不起作用……
还是不明白这意味着什么?I 服了 U,奉上白话版——
在 Rails 没有推 Assets Pipeline 之前,如果要诱发 IE 的样式 limit bug,其实挺不容易的对吧?要么单个样式文件超级长,超过了 4096 条 rule,要么你在一个样式里面@import了超多的子样式,最后一条貌似很难发生吧,谁蛋疼到嵌套 N 层的@import?
现在好了,Assets Pipeline 横空出世,大声告诉世人,我们提供了一套非常完美的解决方案,能帮您实现静态资源的源码结构化管理,以及自动压缩、合并……
然后的然后,你们懂的,application.css 作为集大成者,串联了 bootstrap、font-awesome、ooxx-awesome 之后,何止 4095,40950 都是小菜一碟,不是么?
那么问题来了,您的最终用户,全都是 geek 吗?他们不用 IE 来着?或许都是有钱人,统统升级到了 IE10?好,当我什么也没说,关掉浏览器享受生活去吧。不是的话,你就惨了,跟我之前一样,面对这个神奇的 bug,只能毫无头绪,为啥?因为别的浏览器都是好的哇,还有哇,我的开发机上跑起来也是完全正常哇,还有哇,IE 我要如何调试哇???DHH,我能讲脏话吗?
其实如果知道症结所在,只要 google 下上面的关键字就能找到解决方案,毕竟已经有人掉过坑了,不过很不解的是,为啥都是老外?各位国内的同胞们,可都是见过大世面做过大项目的,为啥从来没有发现过?难道就因为个人天资愚昧,花了九年才有缘碰到吗?真是缘分呐。
如何解决?请参考这个 gem:
说多了都是泪,这事儿完全应该交给 sprockets-rails 这个核心 gem 来完成,在编译的时候统计下 rule 的数量,自动切分 css 文件,然后在 link 到页面上的时候自动处理 IE 的问题,这样的话,整个世界就清净了。
(完)