Skip to content

Commit

Permalink
sc-server can now load any asset from the index_root as well as the u…
Browse files Browse the repository at this point in the history
…rl_root. Assets loaded from the index_root will return with a header set to expire immediately (so each request will go back to the browser), while any asset loaded from url_root is returned with a 10-years expires header. This means you can super-fast cache based loading even in developer mode. Yay!
  • Loading branch information
Charles Jolley committed Jan 29, 2009
1 parent 1e912a9 commit c3ae463
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 66 deletions.
13 changes: 9 additions & 4 deletions Buildfile
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,16 @@ mode :debug do
# debug settings for sc-server
:serve_exceptions => true,
:reload_project => true,

# In debug mode, we want to simply compute the build number each time
# to ensure the latest version is always loaded.
:build_number => nil,

# set default build number in debug mode since caching is not used
# anyway.
:build_number => 'current',
:use_query_cache_control => true # add query strings to control caching
# the fast build number method uses file mtimes. It will work correctly
# on a single machine, but it may produce different build numbers from
# one machine to the next, so it should not be used in production mode
# builds.
:compute_fast_builder_numbers => true


end
Expand Down
3 changes: 2 additions & 1 deletion buildtasks/manifest.rake
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ namespace :manifest do

# Generate composite entries for each directory...
entries_by_dirname.each do |dirname, entries|
MANIFEST.add_composite "#{dirname}.html",
filename = "#{dirname}.html"
MANIFEST.add_composite filename,
:build_task => "build:test",
:entry_type => :test,
:ext => :html,
Expand Down
23 changes: 19 additions & 4 deletions lib/sproutcore/models/target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,25 @@ def compute_build_number(seen=nil)
require 'digest/md5'

# No predefined build number was found, instead let's compute it!
digests = Dir.glob(File.join(source_root, '**', '*')).map do |path|
allowed = File.exists?(path) && !File.directory?(path)
allowed = allowed && !target_directory?(path)
allowed ? Digest::SHA1.hexdigest(File.read(path)) : nil
if config.compute_fast_build_numbers
# Note: this method is not good for production builds because it
# depends on the file mtime, which can change from one machine to
# the next. It is faster though, which is better for use in
# development mode when you need to quickly calculate the build
# number for an asset.
digests = Dir.glob(File.join(source_root, '**', '*')).map do |path|
File.mtime(path)
end
else
# This method computes the build number based on the contents of the
# files. It is not as fast as using an mtime, but it will remain
# constant from one machine to the next so it can be used when
# deploying across multiple build machines, etc.
digests = Dir.glob(File.join(source_root, '**', '*')).map do |path|
allowed = File.exists?(path) && !File.directory?(path)
allowed = allowed && !target_directory?(path)
allowed ? Digest::SHA1.hexdigest(File.read(path)) : nil
end
end
digests.compact!

Expand Down
53 changes: 42 additions & 11 deletions lib/sproutcore/rack/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ module Rack
#
class Builder

# used to set expires header.
TEN_YEARS = 10 * 364 * 24 * 60 * 60

# When you create a new builder, pass in one or more projects you want
# the builder to monitor for changes.
def initialize(project)
Expand All @@ -85,7 +88,7 @@ def _call(env)
return not_found("No matching target") if target.nil?

# normalize url to resolve to entry & extract the language
url, language = normalize_url(url, target)
url, language, cacheable = normalize_url(url, target)
return not_found("Target requires language") if language.nil?

# lookup manifest
Expand All @@ -112,9 +115,10 @@ def _call(env)
file_size = File.size(build_path)
headers = {
"Last-Modified" => File.mtime(build_path).httpdate,
"Etag" => File.mtime(build_path).to_i.to_s,
"Content-Type" => mime_type(build_path),
"Content-Length" => file_size.to_s,
"Expires" => Time.now.httpdate
"Expires" => (cacheable ? (Time.now + TEN_YEARS) : Time.now).httpdate
}
[200, headers, File.open(build_path, 'rb')]
end
Expand Down Expand Up @@ -175,25 +179,52 @@ def target_for(url)
# for all the different ways you can request an index.html file and
# convert it to a canonical form
#
# ==== Params
# url<String>:: The URL
# Returns the normalized url, the language extracted from the url and
# a boolean indicating whether the url is considered cacheable or not.
# any url beginning with the target's url_root is considered cacheable
# and will therefore be returned with an expires <10years> header set.
#
# === Params
# url:: the url to normalize
# target:: the suspected target url
#
# === Returns
# [normalized url, matched language, cacheable]
#
def normalize_url(url, target)

# Parse the URL
matched = url.match(/^#{Regexp.escape target.index_root}(\/([^\/\.]+))?(\/([^\/\.]+))?(\/|(\/index\.html))?$/)
cacheable = true

# match
# /foo - /foo/index.html
# /foo/en - /foo/en/index.html
# /foo/en/build_number - /foo/en/build_number/index.html
# /foo/en/CURRENT/resource-name
matched = url.match(/^#{Regexp.escape target.index_root}(\/([^\/\.]+))?(\/([^\/\.]+))?(\/(.*))?$/)
unless matched.nil?
matched_language = matched[2] || target.config.preferred_language
matched_build_number = matched[4] || target.build_number || 'current'
url = [target.url_root,
matched_language, matched_build_number,
'index.html'] * '/'

matched_build_number = matched[4]
if matched_build_number.blank? || matched_build_number == 'current'
matched_build_number = target.build_number
end

resource_name = matched[6]
resource_name = 'index.html' if resource_name.blank?

# convert to url root based
url = [target.url_root, matched_language, matched_build_number,
resource_name] * '/'
cacheable = false # index_root based urls are not cacheable

# otherwise, just get the language -- url_root-based urls must be
# fully qualified
else
matched = url.match(/^#{Regexp.escape target.url_root}\/([^\/\.]+)/)
matched_language = matched ? matched[1] : nil
end

return [url, matched_language]
return [url, matched_language, cacheable]
end

# Returns the mime type. Basically this is the Rack mime mapper with
Expand Down
36 changes: 18 additions & 18 deletions spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -243,23 +243,23 @@ def run_task
end
end

describe "Generates javascript-packed.js" do

before do
@entry = entry_for('javascript-packed.js')
end

it "should generate a javascript-packed.js entry" do
@entry.should_not be_nil
end

it "should include javascript.js entries from other targets" do
@entry.source_entries.size.should > 0
@entry.source_entires.each do |entry|
entry.filename.should == 'javascript.js'
end
end

end
# describe "Generates javascript-packed.js" do
#
# before do
# @entry = entry_for('javascript-packed.js')
# end
#
# it "should generate a javascript-packed.js entry" do
# @entry.should_not be_nil
# end
#
# it "should include javascript.js entries from other targets" do
# @entry.source_entries.size.should > 0
# @entry.source_entires.each do |entry|
# entry.filename.should == 'javascript.js'
# end
# end
#
# end

end
2 changes: 1 addition & 1 deletion spec/buildtasks/manifest/prepare_build_tasks/tests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def run_task(load_tests=true)
entry.build_task.to_s.should == "build:test"
end
end

end

it "should create a composite entry to generate a -index.json with test entries as source (excluding composite summary entries)" do
Expand Down
94 changes: 67 additions & 27 deletions spec/lib/models/target/compute_build_number_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,68 +47,108 @@ def add_dummyfile(project)
end


it "generates a unique build number based on content if nothing is explicitly set" do
target = @project.target_for(:sproutcore)
target.config.build_numbers = nil #precondition
target.config.build_number = nil #precondition
describe "accurate method to compute build number" do

target.compute_build_number.should_not be_nil
end
before do
@target = @project.target_for(:sproutcore)
@target.config.build_numbers = nil #precondition
@target.config.build_number = nil #precondition
@target.config.compute_fast_build_numbers = false
end

it "generates a unique build number based on content if nothing is explicitly set" do
@target.compute_build_number.should_not be_nil
end

it "changes its generated build number if contents of source files change" do
target = @project.target_for(:sproutcore)
target.config.build_numbers = nil #precondition
target.config.build_number = nil #precondition
it "changes its generated build number if contents of source files change" do
old_build_number = @target.compute_build_number

# write an extra file into target for testing
add_dummyfile(@target)

# get new build number
new_build_number = @target.compute_build_number
new_build_number.should_not eql(old_build_number)
end
end

old_build_number = target.compute_build_number
describe "fast method to compute build number" do

# write an extra file into target for testing
add_dummyfile(target)
before do
@target = @project.target_for(:sproutcore)
@target.config.build_numbers = nil #precondition
@target.config.build_number = nil #precondition
@target.config.compute_fast_build_numbers = true
end

# get new build number
new_build_number = target.compute_build_number
new_build_number.should_not eql(old_build_number)
it "uses faster method to compute build numbers" do
start = Time.now
20.times { @target.compute_build_number }
fast_time = Time.now - start

@target.config.compute_fast_build_numbers = false
start = Time.now
20.times { @target.compute_build_number }
accurate_time = Time.now - start

fast_time.should < accurate_time
end

it "generates a unique build number based on content if nothing is explicitly set" do
@target.compute_build_number.should_not be_nil
end

it "changes its generated build number if contents of source files change" do
old_build_number = @target.compute_build_number

# write an extra file into target for testing
add_dummyfile(@target)

# get new build number
new_build_number = @target.compute_build_number
new_build_number.should_not eql(old_build_number)
end
end

it "changes generated build number if build number for a required target changes" do
target = @project.target_for(:sproutcore)
target.should_not be_nil

required = target.target_for(:desktop)
required.should_not be_nil
required.config.build_numbers = nil #precondition
required.config.build_number = nil #precondition

target.config.build_numbers = nil #precondition
target.config.build_number = nil #precondition
target.required_targets.should include(required) #precondition

old_build_number = target.compute_build_number

# write an extra file into required target for testing -- changes number
add_dummyfile(required)

# get new build number
new_build_number = target.compute_build_number
new_build_number.should_not eql(old_build_number)
end

it "does not change generated build number if a nested target that is not required by target changes" do
target = @project.target_for(:sproutcore)
target.should_not be_nil

not_required = target.target_for(:mobile)
not_required.should_not be_nil

#precondition
target.expand_required_targets.should_not include(not_required)

target = @project.target_for(:sproutcore)
old_build_number = target.compute_build_number

# write an extra file into required target for testing -- changes number
add_dummyfile(not_required)

# get new build number
new_build_number = target.compute_build_number
new_build_number.should eql(old_build_number)
Expand Down

0 comments on commit c3ae463

Please sign in to comment.