Refactored and simplified Sprockets asset caching #153

Open
marcusmateus wants to merge 5 commits from marcusmateus/simplify_sprockets_asset_caching into master
15 changed files with 57 additions and 279 deletions

View File

@ -23,7 +23,7 @@ gem 'mocha'
gem 'cucumber'
gem 'jquery-rails', '~> 1.0.0'
gem 'jquery-rails', '> 1.0'
gem 'ejs'
gem 'guard-jasmine-headless-webkit', :git => 'git://github.com/johnbintz/guard-jasmine-headless-webkit.git'

View File

@ -21,11 +21,6 @@ module Jasmine
autoload :TemplateWriter, 'jasmine/headless/template_writer'
autoload :FileChecker, 'jasmine/headless/file_checker'
autoload :CoffeeTemplate, 'jasmine/headless/coffee_template'
autoload :JSTemplate, 'jasmine/headless/js_template'
autoload :JSTTemplate, 'jasmine/headless/jst_template'
autoload :CSSTemplate, 'jasmine/headless/css_template'
autoload :NilTemplate, 'jasmine/headless/nil_template'
autoload :Report, 'jasmine/headless/report'

View File

@ -1,20 +0,0 @@
require 'coffee_script'
require 'digest/sha1'
require 'fileutils'
module Jasmine
module Headless
class CoffeeScriptCache < CacheableAction
class << self
def cache_type
"coffee_script"
end
end
def action
CoffeeScript.compile(File.read(file))
end
end
end
end

View File

@ -1,36 +0,0 @@
require 'tilt/template'
require 'rainbow'
module Jasmine::Headless
class CoffeeTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'application/javascript'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
begin
cache = Jasmine::Headless::CoffeeScriptCache.new(file)
source = cache.handle
if cache.cached?
%{<script type="text/javascript" src="#{cache.cache_file}"></script>
<script type="text/javascript">window.CSTF['#{File.split(cache.cache_file).last}'] = '#{file}';</script>}
else
%{<script type="text/javascript">#{source}</script>}
end
rescue CoffeeScript::CompilationError => ne
puts "[%s] %s: %s" % [ 'coffeescript'.color(:red), file.color(:yellow), "#{ne.message}".color(:white) ]
raise ne
rescue StandardError => e
puts "[%s] Error in compiling file: %s" % [ 'coffeescript'.color(:red), file.color(:yellow) ]
raise e
end
end
end
end

View File

@ -1,19 +0,0 @@
require 'tilt/template'
module Jasmine::Headless
class CSSTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'text/css'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
file ? %{<link rel="stylesheet" href="#{file}" type="text/css" />} : data
end
end
end

View File

@ -1,3 +1,5 @@
require 'rainbow'
module Jasmine::Headless::FileChecker
def excluded_formats
::Jasmine::Headless::EXCLUDED_FORMATS

View File

@ -39,6 +39,10 @@ module Jasmine::Headless
@sprockets_environment = nil
end
def sprockets_environment
@sprockets_environment ||= Sprockets::Environment.new
end
def registered_engines
@registered_engines ||= {}
end
@ -138,7 +142,7 @@ module Jasmine::Headless
def sprockets_environment
return @sprockets_environment if @sprockets_environment
@sprockets_environment = Sprockets::Environment.new
@sprockets_environment = self.class.sprockets_environment #|| Sprockets::Environment.new
search_paths.each { |path| @sprockets_environment.append_path(path) }
@sprockets_environment.unregister_postprocessor('application/javascript', Sprockets::SafetyColons)
@ -149,11 +153,10 @@ module Jasmine::Headless
register_engine ".#{extension}", Jasmine::Headless::NilTemplate
end
register_engine '.coffee', Jasmine::Headless::CoffeeTemplate
register_engine '.js', Jasmine::Headless::JSTemplate
register_engine '.css', Jasmine::Headless::CSSTemplate
register_engine '.jst', Jasmine::Headless::JSTTemplate
end
@sprockets_environment.logger = Logger.new(STDOUT)
@sprockets_environment.logger.level = Logger::WARN
@sprockets_environment
end
@ -207,7 +210,28 @@ module Jasmine::Headless
alert_time = nil
end
sprockets_environment.find_asset(file, :bundle => false).body
asset = sprockets_environment.find_asset(file, :bundle => false)
cache_file = File.join('.jhw-cache', 'code', asset.logical_path)
# Process & cache the asset only if needed
unless File.exist?(cache_file) && (File.mtime(file) < File.mtime(cache_file))
FileUtils.mkdir_p File.dirname(cache_file)
asset.write_to(cache_file)
end
html = ''
case asset.content_type
when 'application/javascript'
html = %{<script type="text/javascript" src="#{File.expand_path(cache_file)}"></script>}
# NOTE: In future sprockets versions below could be simplified w/ asset.extensions.include?('.coffee')
if asset.pathname.basename.to_s.scan(/\.[^.]+/).include?('.coffee')
html += %{\n<script type="text/javascript">window.CSTF['#{File.basename(cache_file)}'] = '#{file}';</script>}
end
when 'text/css'
html = %{<link rel="stylesheet" href="#{File.expand_path(cache_file)}" type="text/css" />}
else
html = asset.body
end
html
end.compact.reject(&:empty?)
end
@ -270,7 +294,12 @@ module Jasmine::Headless
end
def add_path(path, type = nil)
asset = sprockets_environment.find_asset(path)
begin
asset = sprockets_environment.find_asset(path)
rescue => e
sprockets_environment.logger.error "#{e.message} (#{e.class})"
raise e
end
@required_files << asset
@ -326,6 +355,10 @@ end
module Jasmine::Headless
extend self
def sprockets_environment
Jasmine::Headless::FilesList.sprockets_environment
end
def register_engine(file_extension, template_class)
Jasmine::Headless::FilesList.register_engine(file_extension, template_class)
end

View File

@ -1,23 +0,0 @@
require 'tilt/template'
module Jasmine::Headless
class JSTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'application/javascript'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
if data[%r{^<script type="text/javascript"}]
data
else
file ? %{<script type="text/javascript" src="#{file}"></script>} : data
end
end
end
end

View File

@ -1,15 +0,0 @@
require 'sprockets/jst_processor'
module Jasmine::Headless
class JSTTemplate < Sprockets::JstProcessor
include Jasmine::Headless::FileChecker
def evaluate(*args)
if bad_format?(file)
alert_bad_format(file)
return ''
end
%{<script type="text/javascript">#{super}</script>}
end
end
end

View File

@ -1,21 +0,0 @@
require 'spec_helper'
describe Jasmine::Headless::CoffeeScriptCache do
include FakeFS::SpecHelpers
describe '#action' do
let(:file) { 'file' }
let(:data) { 'data' }
let(:compiled) { 'compiled' }
before do
CoffeeScript.expects(:compile).with(data).returns(compiled)
File.open(file, 'wb') { |fh| fh.print(data) }
end
it 'should compile coffeescript' do
described_class.new(file).action.should == compiled
end
end
end

View File

@ -1,55 +0,0 @@
require 'spec_helper'
describe Jasmine::Headless::CoffeeTemplate do
let(:data) { 'data' }
let(:path) { 'path.coffee' }
let(:template) { described_class.new(path) { data } }
subject { template.render }
let(:handle_expectation) { Jasmine::Headless::CoffeeScriptCache.any_instance.stubs(:handle) }
context 'compilation error' do
let(:error) { CoffeeScript::CompilationError.new("fail") }
before do
handle_expectation.raises(error)
end
it 'should pass along the error' do
expect { subject }.to raise_error(CoffeeScript::CompilationError)
end
end
context 'compiles fine' do
let(:source) { 'source' }
before do
Jasmine::Headless::CoffeeScriptCache.any_instance.stubs(:cached?).returns(cache_return)
handle_expectation.returns(source)
end
context 'cached' do
let(:file_path) { 'dir/file.js' }
let(:cache_return) { true }
before do
Jasmine::Headless::CoffeeScriptCache.any_instance.stubs(:cache_file).returns(file_path)
end
it 'should return the cached file' do
subject.should include(%{<script type="text/javascript" src="#{file_path}"></script>})
end
end
context 'not cached' do
let(:cache_return) { false }
it 'should return the generated js' do
subject.should include(%{<script type="text/javascript">#{source}</script>})
end
end
end
end

View File

@ -1,25 +0,0 @@
require 'spec_helper'
describe Jasmine::Headless::CSSTemplate do
include FakeFS::SpecHelpers
let(:template) { described_class.new(file) { data } }
let(:file) { 'file' }
let(:data) { 'data' }
subject { template.render }
before do
File.open(file, 'wb') if file
end
context "no file'" do
let(:file) { nil }
it { should == data }
end
context 'file' do
it { should == %{<link rel="stylesheet" href="#{file}" type="text/css" />} }
end
end

View File

@ -189,6 +189,20 @@ describe Jasmine::Headless::FilesList do
end
end
describe "#sprockets_environment" do
before(:each) do
Jasmine::Headless::FilesList.reset!
end
it "should return shared class-level sprockets environment that will be used when tests are run" do
processor = Object.new
described_class.sprockets_environment.register_postprocessor "application/javascript", processor
described_class.new.sprockets_environment.postprocessors["application/javascript"].should include(processor)
end
end
end
end

View File

@ -1,31 +0,0 @@
require 'spec_helper'
describe Jasmine::Headless::JSTemplate do
include FakeFS::SpecHelpers
let(:template) { described_class.new(file) { data } }
let(:file) { 'file' }
let(:data) { 'data' }
subject { template.render }
before do
File.open(file, 'wb') if file
end
context "no file'" do
let(:file) { nil }
it { should == data }
end
context 'file' do
it { should == %{<script type="text/javascript" src="#{file}"></script>} }
end
context 'script as first thing' do
let(:data) { '<script type="text/javascript"' }
it { should == data }
end
end

View File

@ -1,21 +0,0 @@
require 'spec_helper'
describe Jasmine::Headless::JSTTemplate do
include FakeFS::SpecHelpers
let(:template) { described_class.new(file) }
let(:file) { 'file' }
let(:data) { 'data' }
let(:context) { stub(:logical_path => 'path') }
before do
File.open(file, 'wb') { |fh| fh.print data }
end
subject { template.render(context) }
it { should include(%{<script type="text/javascript">}) }
it { should include(data) }
end