在资产管道中使用Rails静态HTML模板文件以及开发模式下的缓存

10
我正在使用AngularJS和Rails构建网站。我用于模板的HTML文件存储在/app/assets/templates下,每次更新路由或更改模板中嵌套部分内的内容时,我需要“touch” /app/assets/templates目录中与我要更改的html文件相关的最高级文件。
因此,如果我有一个名为“edit.html”的页面,它加载一个名为“_form.html”的部分,则每当我更新路由或更改_form.html中的内容时,我需要确保edit.html被修改过。
这很烦人而且非常棘手。是否有任何方法通知asset pipeline/sprockets避免缓存app/assets/templates目录?
6个回答

21

我发现解决这个问题的最佳方案是不要使用资产管道(asset pipeline)来处理HTML模板文件。

相反,创建一个名为TemplatesController的控制器,并仅创建一个动作。然后使用类似下面的路由将所有模板URL映射到该控制器:

get /templates/:path.html => 'templates#page', :constraints => { :path => /.+/  }

将所有模板文件移动到app/views/templates中。

然后在控制器内进行以下设置:

caches_page :page

def page
  @path = params[:path]
  render :template => 'templates/' + @path, :layout => nil
end

这种方式可以让你的所有模板文件都从控制器中提供并缓存到public/templates中。为了避免缓存问题,你可以在模板路由中创建一个时间戳路径,以便你的缓存文件带有版本信息:

get '/templates/:timestamp/:path.html' => 'templates#page', :constraints => { :path => /.+/ }

通过这种方式,每次上传网站时您都可以获得一个新的时间戳,并且可以将模板文件夹存储在任何地方。您甚至可以将模板文件夹存储在S3上,并为其设置资产URL。然后无论您在哪里寻址模板文件,都可以使用自定义资产方法:

templateUrl : <%= custom_asset_template_url('some/file.html') %>

具体位置:

def custom_asset_template_url(path)
  "http://custom-asset-server.website.com/templates/#{$some_global_timestamp}/#{path}"
end

如果资源文件不存在,只需将其重定向到Rails服务器,它就会被生成。或者可以在上传后预先生成所有模板文件。


3
这个解决方案还可以,因为它能够发挥作用。然而,与其他资产相比,它的表现不够好——缺乏带指纹的缓存。 - mateusz.fiolka
1
主要的权衡是,如果您继续使用资产目录来传递模板文件,则无法访问“partials”、“routes”、“url helpers”以及“任何其他共享Rails代码”。 每次更改它们时,您还必须手动“touch”HTML文件。虽然在您的资产中拥有指纹文件很好,但问题随之而来:每次部署时都必须上传这些资产文件。由于您的模板可能由CDN提供,因此您还需要为您的资产文件创建一个CORS策略。您可以看到我为什么选择这种方式。 - matsko
这个解决方案非常棒!不过有一个错误:你在定义动作文件的路由,但是在控制器中你使用了动作页面。 - yagooar
很好的建议!我会使用的!谢谢! - bbonamin
真是个很好的方法,这个。它真的为我节省了很多时间,让我不必再折腾Rails资源管道和CDN了。 - yalestar
显示剩余3条评论

6

有一个更好的方式来处理这个问题。

<%= path_to_asset("template_name.html") %>

这将从资产管道返回一个完整工作的文件,可以使用ERB等。虽然这是未记录的,但它是sprockets/asset pipeline的一部分。


不需要,我以前从来没有用过。 - RandallB
1
问题就在这里。AWS不支持CloudFront或S3上的SSL和CORS,因此您需要支付专用CDN(如Limelight)或像@matsko建议的那样提供自己的HTML。如果您想避免缓存问题的话。 - smothers
S3 管理器内确实提供了 CORS 标头支持。但这是否完全可用呢? - matsko
Cloudfront确实支持SSL,但价格高达每月300美元。关于S3,请参考:http://docs.aws.amazon.com/AmazonS3/latest/dev/cors-troubleshooting.html。 - RandallB

3
依我看,这里需要做几件事情:
  1. 给模板命名空间,使人们的模板位于app/views/people/templates目录下
  2. 由于模板完全是静态的,因此不应调用before filters。
  3. 应缓存模板以加快速度。
以下是使用Rails concern可能的解决方案:
# Allows static content to be served from the templates
# directory of a controller
module HasTemplates

  extend ActiveSupport::Concern

  included do
    # Prepend the filter
    prepend_before_filter :template_filter, only: [:templates]
    # Let's cache the action
    caches_action :templates, :cache_path => Proc.new {|c| c.request.url }
  end

  # required to prevent route from baulking
  def templates;end

  # Catch all template requests and handle before any filters
  def template_filter
    render "/#{params[:controller]}/templates/#{params[:template]}", layout: 'blank'
    rescue ActionView::MissingTemplate
      not_found layout: 'blank'
    false
  end
end

请注意,我们在一个预处理过滤器中返回模板。这使我们能够返回静态内容而不触发其他过滤器。

然后,您可以创建一个路由,类似于以下内容:

resources :people do
  collection do
    get 'templates/:template' => 'people#templates', as: :templates
  end
end

您的控制器变得简单起来:
class PeopleController < ApplicationController
  include HasTemplates
end

现在,任何放置在/app/views/people/templates目录下的文件都可以通过URL高速访问。

这是一个很好的方法,你能描述一下JS前端框架如何访问这些信息吗?我不喜欢为每个JS视图异步加载模板文件的想法。或者有没有一种方法可以设置一个带有周围脚本标签的布局,并将它们包含在主布局页面的底部? - aviemet

1
你可以尝试使用 gem js_assets (https://github.com/kavkaz/js_assets)。
这个 gem 允许你在 JavaScript 代码中使用 asset_path 函数,它模拟了 sprockets 的类似方法。

1

对RandallB的回答进行了扩展; 这在资源管道的文档中明确提到:http://guides.rubyonrails.org/asset_pipeline.html

请注意,您必须将扩展名.erb附加到.coffee文件以使其起作用。 (例如,application.js.coffee.erb)


0

最干净的解决方案是使用ejs预编译您的html资源,并将它们作为其他javascript文件提供。

  • 没有http查询
  • 缩小文件大小
  • 可以将js数据传递给模板,使其具有动态性(ejs是从underscore模板移植过来的,基本上像javascript的erb一样)

它的工作原理基本上就是这样:

    #in you gemfile
    gem 'ejs'

    #in app/assets/javascripts/templates/my_template.jst.ejs
    <p>my name is <%= name %> !<p>

    #in your application.coffee
    #= require_tree ./templates

    JST['templates/my_template'](name: 'itkin')
    => '<p>my name is itkin !<p>'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接