Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing the Rails Controller and refactor into middleware #251

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ gemspec

group :development, :test do
gem "rake", "0.9.2.2"
gem "rails", "2.3.14"
gem "cssmin", "1.0.3"
gem "jsmin", "1.0.1"
gem "yui-compressor", "0.9.6"
Expand All @@ -17,3 +16,9 @@ group :development do
gem "RedCloth", "4.2.9"
gem "redgreen", "1.2.2"
end

group :test do
gem 'rspec', '~>2'
gem 'nokogiri'
gem 'rack-test'
end
1 change: 1 addition & 0 deletions jammit.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Gem::Specification.new do |s|

s.add_dependency 'cssmin', ['>= 1.0.3']
s.add_dependency 'jsmin', ['>= 1.0.1']
s.add_dependency "rack", ">= 1.0"

s.files = Dir['lib/**/*', 'bin/*', 'rails/*', 'jammit.gemspec', 'LICENSE', 'README']
end
5 changes: 4 additions & 1 deletion lib/jammit.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
$LOAD_PATH.push File.expand_path(File.dirname(__FILE__))

require 'jammit/header_processor'
require 'jammit/controller'

# @Jammit@ is the central namespace for all Jammit classes, and provides access
# to all of the configuration options.
module Jammit
Expand Down Expand Up @@ -55,7 +58,7 @@ class << self
:embed_assets, :package_assets, :compress_assets, :gzip_assets,
:package_path, :mhtml_enabled, :include_jst_script, :config_path,
:javascript_compressor, :compressor_options, :css_compressor,
:css_compressor_options, :template_extension,
:css_compressor_options, # :template_extension, # <<--- TODO: activate later
:template_extension_matcher, :allow_debugging,
:rewrite_relative_paths, :public_root
attr_accessor :javascript_compressors, :css_compressors
Expand Down
157 changes: 121 additions & 36 deletions lib/jammit/controller.rb
Original file line number Diff line number Diff line change
@@ -1,47 +1,120 @@
require 'action_controller'
require 'rack'

module Jammit

# The JammitController is added to your Rails application when the Gem is
# loaded. It takes responsibility for /assets, and dynamically packages any
# missing or uncached asset packages.
class Controller < ActionController::Base
# defined somewhere else
# but for temporarily development
def self.public_root
'/public'
end

def self.template_extension
'js'
end


class Request < Rack::Request

VALID_FORMATS = [:css, :js]

SUFFIX_STRIPPER = /-(datauri|mhtml)\Z/

NOT_FOUND_PATH = "#{Jammit.public_root}/404.html"

def asset_path?
path_info =~ /^\/assets/
end

def asset_from_path
path_info =~ /^\/assets\/([\w\.]+)/
$1
end

def extension_from_path
path_info =~ /\.([\w\.]+)$/
$1
end

def path_info
@env['PATH_INFO']
end

# Extracts the package name, extension (:css, :js), and variant (:datauri,
# :mhtml) from the incoming URL.
def parse_request
pack = asset_from_path
@extension = extension_from_path.to_sym
puts @extension.inspect
raise PackageNotFound unless (VALID_FORMATS + [Jammit.template_extension.to_sym]).include?(@extension)
if Jammit.embed_assets
suffix_match = pack.match(SUFFIX_STRIPPER)
@variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
pack.sub!(SUFFIX_STRIPPER, '')
end
@package = pack.to_sym
end

# Tells the Jammit::Packager to cache and gzip an asset package. We can't
# just use the built-in "cache_page" because we need to ensure that
# the timestamp that ends up in the MHTML is also on the cached file.
def cache_package
dir = File.join(page_cache_directory, Jammit.package_path)
Jammit.packager.cache(@package, @extension, @contents, dir, @variant, @mtime)
end

# The "package" action receives all requests for asset packages that haven't
# yet been cached. The package will be built, cached, and gzipped.
def package
parse_request
template_ext = Jammit.template_extension.to_sym
case @extension
when :js
render :js => (@contents = Jammit.packager.pack_javascripts(@package))
puts @package.inspect
(@contents = Jammit.packager.pack_javascripts(@package))
when template_ext
render :js => (@contents = Jammit.packager.pack_templates(@package))
# (@contents = Jammit.packager.pack_templates(@package))
'foo_case2.jst'
when :css
render :text => generate_stylesheets, :content_type => 'text/css'
[generate_stylesheets, :content_type => 'text/css']
end
cache_package if perform_caching && (@extension != template_ext)
# cache_package if perform_caching && (@extension != template_ext)
rescue Jammit::PackageNotFound
package_not_found
end

def for_jammit?
get? && # GET on js resource in :hosted_at (fast, check first)
asset_path?
end
end

private

# Tells the Jammit::Packager to cache and gzip an asset package. We can't
# just use the built-in "cache_page" because we need to ensure that
# the timestamp that ends up in the MHTML is also on the cached file.
def cache_package
dir = File.join(page_cache_directory, Jammit.package_path)
Jammit.packager.cache(@package, @extension, @contents, dir, @variant, @mtime)
class Response < Rack::Response

# Rack response tuple accessors.
attr_accessor :status, :headers, :body

def initialize(env, asset)
@env = env
@body = asset
@status = 200 # OK
@headers = Rack::Utils::HeaderHash.new

headers["Content-Length"] = self.class.content_length(body).to_s
end

class << self

# Calculate appropriate content_length
def content_length(body)
if body.respond_to?(:bytesize)
body.bytesize
else
body.size
end
end

end

# Generate the complete, timestamped, MHTML url -- if we're rendering a
# dynamic MHTML package, we'll need to put one URL in the response, and a
# different one into the cached package.
Expand All @@ -63,35 +136,47 @@ def generate_stylesheets
css
end

# Extracts the package name, extension (:css, :js), and variant (:datauri,
# :mhtml) from the incoming URL.
def parse_request
pack = params[:package]
@extension = params[:extension].to_sym
raise PackageNotFound unless (VALID_FORMATS + [Jammit.template_extension.to_sym]).include?(@extension)
if Jammit.embed_assets
suffix_match = pack.match(SUFFIX_STRIPPER)
@variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
pack.sub!(SUFFIX_STRIPPER, '')
end
@package = pack.to_sym
end

# Render the 404 page, if one exists, for any packages that don't.
def package_not_found
return render(:file => NOT_FOUND_PATH, :status => 404) if File.exists?(NOT_FOUND_PATH)
render :text => "<h1>404: \"#{@package}\" asset package not found.</h1>", :status => 404
end

def to_rack
[status, headers.to_hash, [body]]
end
end

end
# The JammitController is added to your Rails application when the Gem is
# loaded. It takes responsibility for /assets, and dynamically packages any
# missing or uncached asset packages.
class Controller

# Make the Jammit::Controller available to Rails as a top-level controller.
::JammitController = Jammit::Controller
def initialize(app, options={})
@app = app
# yield self if block_given?
# validate_options
end

if defined?(Rails) && Rails.env.development?
ActionController::Base.class_eval do
append_before_filter { Jammit.reload! }
def call(env)
dup.call!(env)
end

def call!(env)
env['jammit'] = self

if (@request = Request.new(env.dup.freeze)).for_jammit?
Response.new(env.dup.freeze, @request.package).to_rack
else
status, headers, body = @app.call(env)

processor = HeaderProcessor.new(body)
processor.process!(env)

[ status, headers, processor.new_body ]
end
end
end

end
37 changes: 37 additions & 0 deletions lib/jammit/header_processor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Jammit

class HeaderProcessor

attr_reader :content_length, :new_body

HEAD_TAG_REGEX = /<\/head>|<\/head[^(er)][^<]*>/

def initialize(body)
@body = body
end

# Add the tags for script and stylesheets to the response
def process!(env)
@env = env

@new_body = @body.map(&:to_s)
@livereload_added = false

@new_body.each_with_index do |line, pos|
if line =~ HEAD_TAG_REGEX
@pos = pos
break
end
end
@new_body.insert(@pos, dependencies)
end

def dependencies
['<link rel="stylesheet" href="/assets/app.css" type="text/css">',
'<script src="/assets/app.js"></script>'].join
end


end

end
20 changes: 20 additions & 0 deletions spec/helpers/request_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# encoding: utf-8
module Jammit::Spec
module Helpers
def env_with_params(path = "/", params = {}, env = {})
method = params.delete(:method) || "GET"
env = { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => "#{method}" }.merge(env)
Rack::MockRequest.env_for("#{path}?#{Rack::Utils.build_query(params)}", env)
end

def setup_rack(app = basic_app, opts = {}, &block)
app ||= block if block_given?

Rack::Builder.new do
use Jammit::Controller, opts
run app
end
end

end
end
42 changes: 42 additions & 0 deletions spec/jammit/controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# encoding: utf-8
require 'spec_helper'
require 'nokogiri'

describe Jammit::Controller do

def test_html
'<html>
<head>
<title>test page</title>
</head>
<body><h1>FunkyBoss</h1></body>
</html>'
end

def index_app
lambda { |e| [200, {'Content-Type' => 'text/html'}, test_html.split("\n")] }
end

it "inserts Jammit into the rack env" do
env = env_with_params
setup_rack(index_app).call(env)
env["jammit"].should be_an_instance_of(Jammit::Controller)
end

describe "serving assets" do
it "responds with" do
env = env_with_params("/assets/app.js", {})
result = setup_rack(index_app).call(env)
result.last.should == ['var Foo = 1']
end
end

describe "serving non-assets" do
it "includes script tag" do
env = env_with_params("/index.html", {})
result = setup_rack(index_app).call(env)
test_html_head_script(result.last.join, '/assets/app.js')
end
end

end
35 changes: 35 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# encoding: utf-8
$TESTING=true

$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
$:.unshift File.expand_path(File.join(File.dirname(__FILE__)))
require 'jammit'

require 'rubygems'
require 'rack'

Dir[File.join(File.dirname(__FILE__), "helpers", "**/*.rb")].each do |f|
require f
end

RSpec.configure do |config|
config.include(Jammit::Spec::Helpers)
# config.include(Warden::Test::Helpers)

# def load_strategies
# Dir[File.join(File.dirname(__FILE__), "helpers", "strategies", "**/*.rb")].each do |f|
# load f
# end
# end
end

def test_html_head(body, tag, keys)
html = Nokogiri::HTML(body)
html.css("head #{tag}").text.should == keys
end

def test_html_head_script(body, str)
html = Nokogiri::HTML(body)
script = html.at_css("script")
script.attributes.first.last.text.should == str
end