论坛首页 编程语言技术论坛

慎用typo(theme_support)的换肤机制

浏览 8566 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-07-17  
  • 前言

  本文提到的typo版本是目前最新的5.0.3.98.1,theme_support版本是1.3.0。在typo中,我们看到了很好很花哨的换肤机制,而theme_support则是从typo中抽取出来的一个plugin,以供其他程序进行换肤操作。
  先简单介绍下typo换肤的使用。typogarden提供了typo十分丰富的皮肤,我们只需要下载喜欢的皮肤,解压,放在typo程序根目录的theme目录下即可,大致的结构图如下所示:

  然后,就可以在admin界面选择自己的皮肤。的确十分方便。但是,使用这种机制,会存在一个严重的性能问题,下面将详细分析问题的原理及其我目前所知的解决方案。

  • Typo的换肤原理

  通常,我们会将程序的图片,css等与皮肤相关的文件放在网站的public目录下,在view中直接引用即可。但是从上图我们可以看到,typo将新皮肤的所有相关文件都存放在theme目录下的各个子目录下。那么typo是如何引用这些文件的呢?下面,我们随便打开某一个皮肤下面的layout文件,例如theme/typographic/layouts下面的default.html.erb文件,可以看到如下代码:

<%= stylesheet_link_tag '/stylesheets/theme/style.css', :media => 'all' %>

  你仔细搜查一下程序,绝对发现不了/stylesheets/theme目录,乱引用?当然不是,我们可以在routes.rb中发现如下的route信息:

    get.with_options(:controller => 'theme', :filename => /.*/, :conditions => {:method => :get}) do |theme|
      theme.connect 'stylesheets/theme/:filename', :action => 'stylesheets'
      theme.connect 'javascripts/theme/:filename', :action => 'javascript'
      theme.connect 'images/theme/:filename',      :action => 'images'
    end
 

  很经典的route配置,原来在typo中,所有对css,javascript,image的引用,不是通过直接引用public目录下的文件,而是通过一个传统的controller:ThemeController来完成的。至于ThemeContorller具体代码,这里不详细谈,因为不是本文的重点,无非就是根据当前选择的皮肤(在admin界面选择的),到相应的皮肤目录(比如:theme/typographic)下,将各个文件找到,然后通过send_file的方式发送到客户端浏览器。那执行完一次action后,controller到底应该执行怎么样的render操作呢?(通过上图,我们看到不同的view文件,在各自的theme子目录下)
  在ApplicationController中,有如下代码:

class ApplicationController < ActionController::Base
......
  def setup_themer
    # Ick!
    self.view_paths = ::ActionController::Base.view_paths.dup.unshift("#{RAILS_ROOT}/themes/#{this_blog.theme}/views")
  end
end

   大致讲下,setup_themer方法的作用是根据目前皮肤配置(this_blog.theme),将controller的view_paths设置为某一个皮肤的目录(比如:theme/typographic/views),这样,在执行render操作的时候,将使用皮肤目录下的view,layout等等(还记得前面提到的layout中引用css的方法么?重要!)。并且,将setup_themer方法设置为需要换肤的contorller的before_filter。于是,当我们执行程序的时候,就可以达到动态换肤的目的。
  大致原理如是。。。

  • 性能问题的由来

  前面我讲了,这种机制会存在一个严重的性能问题,是怎么来的呢?在传统方式下,我们将css,images,js都放在public目录下,view进行引用的时候,在web服务器层面,就完成了文件的引用,发送操作。但是在typo这种机制下,每引用一个css,图片,js,web服务器会将请求route到rails,通过rails的ThemeController来处理请求,返回文件。这都还算小事,在一次request,response周期,这样一个额外操作往往占用不了多大的百分比。但是,我们知道ThemeController是rails中一个普通的Controller,它也继承自ApplicationController。通常,我们会将很多程序通用逻辑放在ApplicationController中来做,比如:验证用户合法性,处理本地化等等,而这些操作,大部分都是访问数据库。也就是说,我们通过ThemeController,仅仅想得到一个css或者图片文件,但是ApplicationController仍然会初始化,执行相应的操作(重复,无用的操作)。这就是性能问题的根源。口说无凭,下面的数据揭示了一切:
        
  上面两个性能测试是我随机访问一次typo首页得到的,我们可以看到,对css,image,js的请求,耗费了数十毫秒的操作。为什么请求一个css会耗费如此多时间呢?从下面的图中,我们可以看到原因:

  Oh~My god!我出来打个酱油而已,咋搞出个db访问占用了这么长的周期?原因就是我前面提到的,访问ThemeController的时候,ApplicationContorller偷偷摸摸的作祟着(执行了一些业务逻辑的操作,用户验证等)。具体执行的多余db操作如下图所示:

  什么show tables,select blogs,select triggers之类操作啊~我请求一个css,何必呢?
  性能问题的原因分析完毕!不要忘了,我举例的这些简单的皮肤中,只是简单的十几个css,js,images等。如果你真的使用这种机制在自己的程序中,我想,一个稍微复杂点的皮肤不止十个图片文件。加入我们要在皮肤中引用数十个此类文件,那么性能问题将是十分严重的。

  • 解决方案

  目前,我找到三个解决方案处理这个性能问题。

  1. 在ApplicationController中,执行某些业务逻辑的时候,判断一下controller,如果是ThemeController,则跳过。这样做的好处是不用变动typo中换肤方法的使用,仍然将皮肤放在theme目录下即可。但是,这样做似乎“侵入性”太强,仅仅为了一个皮肤,修改业务逻辑,实在有些得不偿失,因此,这种方案不是一个很好的方案。

  2. 要想不影响已有的业务逻辑解决这个性能问题,我们则需要手动做一些修改。将某一个皮肤放到theme目录下以后,我们收到将css,image,js拷贝到public的相应目录下。比如:将theme/typographic/stylesheets下的所有css文件拷贝到public/stylesheets/typographic目录下,然后,手动将皮肤中所有对css的引用修改为引用public下面相应的目录,比如:我将前面提到的default.html.erb中对css的引用修改为如下所示:

<%= stylesheet_link_tag 'typographic/style.css', :media => 'all' %>

  然后,将关于theme的roues信息全部删除,让web server为我们完成这个工作。这样,既能解决性能问题,还不影响业务逻辑。只不过,需要不少的手动工作(要将皮肤中所有关于css,js,images的引用修改到public目录下)。

  3. 一劳永逸,按照第二种方法重写theme_support plugin。。。
  这里,我按照第二种方法修改了typo的theme机制,主观上来讲,页面访问速度有不小的改进,不像原来,进度条象个蚂蚁一样一耸一耸的~最后,客观起见,还是来一张修改后的性能测试结果:

  整个世界清静了不少~不是吗?:)

 

  2008.7.17  23:30  星期四

   发表时间:2008-07-18  
晕,很简单的一点点东西竟然让你写出这么大一篇来
0 请登录后投票
   发表时间:2008-07-18  
没办法,人笨,只能把别人觉得简单的东西详细记录下来。
不过,你要把图片去掉,似乎也不怎么“大”,是不是应该放“贴图区”去阿?呵呵
0 请登录后投票
   发表时间:2008-07-18  
好像目前对于rails中的换肤,我所看到的只有theme_support插件,我用了它,感觉比没有是慢一些,不爽.

不过我想对于网站换肤,如果只有想改一下网站总体色调什么的,那么只需要引用不同的css样式文件即可啊,没有必要搞得这么麻烦.
0 请登录后投票
   发表时间:2008-07-18  
crazy!

<%= stylesheet_link_tag 'style.css', :theme =>:auto %>
 
or
<%= stylesheet_link_tag_with_theme 'style.css' %>  

enhance stylesheet_link_tag helper.

are they enough?
0 请登录后投票
   发表时间:2008-07-18  
liusong,我没搞懂你什么意思~能详细点么?
0 请登录后投票
   发表时间:2008-07-18  
woody_420420 told me:
Each theme has an individual layout file, and it operates views_path to switch between them.
so, no need to rewrite/enhance stylesheet_include_tag helper.

di, could you introduce views_path for us?

sorry, can't input Chinese.
0 请登录后投票
   发表时间:2008-07-18  
是的。typo这个机制其实还是不错的。关键问题就是css,images,js引用问题。
不过你说的“stylesheet_link_tag_with_theme”也不错,我们可以自己写一个,然后在每一个皮肤的layout中使用,就不用每次都"../../public/..."了。
enjoy ubuntu~~:)
0 请登录后投票
   发表时间:2008-07-18  
woody_420420 写道
liusong,我没搞懂你什么意思~能详细点么?


二大爷说话就素这个样子,你搞啊搞啊滴就明白鸟。
0 请登录后投票
   发表时间:2009-02-17  
不错,学习了,但好像typo-5.2仍然没有改变啊
看来typo团队没有看楼主的帖子
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics