Skip to content
Browse files

Added support for generated packed versions of the javascript.js file…

…s and auto-selecting said packed javascript versions to minimize the total number of resources loaded.
  • Loading branch information...
1 parent 05deb9c commit 555f70bbfa5d0b724c55cd82e3a9498f0dc62e43 Charles Jolley committed Jan 28, 2009
View
8 Buildfile
@@ -57,7 +57,9 @@ mode :all do
# name a framework to use as the theme. will be included in required
# frameworks automatically if found.
- :theme => 'sproutcore/standard_theme'
+ :theme => 'sproutcore/standard_theme',
+
+ :use_packed => true
end
@@ -91,7 +93,9 @@ mode :debug do
# 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
+ :compute_fast_builder_numbers => true,
+
+ :use_packed => false
end
View
52 buildtasks/manifest.rake
@@ -128,7 +128,7 @@ namespace :manifest do
namespace :prepare_build_tasks do
desc "main entrypoint for preparing all build tasks. This should invoke all needed tasks"
- task :all => %w(css javascript sass combine minify html strings tests) #%w(image)
+ task :all => %w(css javascript sass combine minify html strings tests packed)
desc "executes prerequisites needed before one of the subtasks can be invoked. All subtasks that have this as a prereq"
task :setup => %w(manifest:catalog manifest:hide_buildfiles manifest:localize)
@@ -270,20 +270,40 @@ namespace :manifest do
:combined => true
end
- # # Build packed JavaScript entry
- # targets = TARGET.expand_required_targets + [TARGET]
- # entries = targets.map do |target|
- # target.manifest_for(MANIFEST.varation).entry_for('javascript.js')
- # end
- # entries.compact!
- # MANIFEST.add_composite 'javascript-packed.js',
- # :build_task => 'build:combine',
- # :source_entries => entries,
- # :hide_entries => false,
- # :entry_type => :javascript,
- # :combined => true
+ end
+
+ desc "adds a packed entry including javascript.js from required targets"
+ task :packed => %w(setup combine) do
+
+ # don't add packed entries for apps.
+ if TARGET.target_type != :app
+ # Handle JavaScript version. get all required targets and find their
+ # javascript.js. Build packed js from that.
+ targets = TARGET.expand_required_targets + [TARGET]
+ entries = targets.map do |target|
+ m = target.manifest_for(MANIFEST.variation).build!
+
+ # need to find the version that is not minified
+ entry = m.entry_for('javascript.js')
+ entry = entry.source_entry while entry && entry.minified?
+ entry
+ end
+
+ entries.compact!
+ MANIFEST.add_composite 'javascript-packed.js',
+ :build_task => 'build:combine',
+ :source_entries => entries,
+ :hide_entries => false,
+ :entry_type => :javascript,
+ :combined => true,
+ :ordered_entries => entries, # orderd by load order
+ :targets => targets,
+ :packed => true
+ # TODO: Do the same for stylesheets here.
+ end
end
+ task :minify => :packed # IMPORTANT: don't want minified version
desc "create a builder task for all sass files to create css files"
task :sass => :setup do
@@ -372,15 +392,17 @@ namespace :manifest do
MANIFEST.add_transform entry,
:build_task => 'build:minify:css',
:entry_type => :css,
- :minified => true
+ :minified => true,
+ :packed => entry.packed? # carry forward
end
when :javascript
if minify_javascript
MANIFEST.add_transform entry,
:build_task => 'build:minify:javascript',
:entry_type => :javascript,
- :minified => true
+ :minified => true,
+ :packed => entry.packed? # carry forward
end
end
end
View
1 lib/sproutcore/builders/combine.rb
@@ -13,6 +13,7 @@ class Builder::Combine < Builder::Base
def build(dst_path)
lines = []
entries = entry.ordered_entries || entry.source_entries
+
entries.each do |entry|
src_path = entry.stage!.staging_path
next unless File.exist?(src_path)
View
95 lib/sproutcore/helpers/packed_optimizer.rb
@@ -0,0 +1,95 @@
+module SC
+
+ # Given a set of targets, this can determine the optimial mix of loading
+ # packed targets vs individual targets to yield the smaller number of
+ # assets. To use this optimizer, just call the optimize() method.
+ class PackedOptimizer
+
+ # Returns two arrays: the first array are targets that should be loaded
+ # packed. The array of targets that should be loaded, but not packed.
+ # And optional third array is also returned that includes targets which
+ # were passed in but are no longer needed because they are included in
+ # a packed target.
+ def self.optimize(targets)
+ packed = []
+ unpacked = targets
+ cnt = packed.size + unpacked.size
+
+ # for each target, try to use the packed version and see how many
+ # total items we come back with. If the total number of targets is
+ # less than the current best option, use that instead.
+ targets.each do |target|
+ cur_packed, cur_unpacked = self.new.process(target, targets)
+ cur_cnt = cur_packed.size + cur_unpacked.size
+ if cur_cnt < cnt
+ packed = cur_packed
+ unpacked = cur_unpacked
+ cnt = cur_cnt
+ end
+ end
+
+ # return best!
+ return [packed, unpacked]
+ end
+
+ attr_reader :packed
+ attr_reader :unpacked
+
+ def initialize
+ @seen = []
+ @packed = []
+ @unpacked = []
+ end
+
+ # Sorts the passed array of targets into packed and unpacked, starting
+ # with the passed target as the packed target.
+ def process(packed_target, targets)
+ # manually add in packed target...
+ @seen << packed_target
+ @packed << packed_target
+ packed_target.expand_required_targets.each { |t| @seen << t }
+
+ # then handle the rest of the targets
+ targets.each { |t| process_target(t) }
+
+ return [packed, unpacked]
+ end
+
+ # Sorts a single target into the correct bucket based on whether it is
+ # seen or not. If you also pass true to the second param, the required
+ # targets will also be sorted.
+ def process_target(target)
+ # if target was seen already, nothing to do
+ return if @seen.include?(target)
+
+ # add to seen
+ @seen << target
+
+ # have we already seen any of the required targets?
+ req_targets = target.expand_required_targets
+ if req_targets.size > 0
+ already_seen = req_targets.find do |t|
+ @seen.include?(t)
+ end
+ else
+ already_seen = true
+ end
+
+
+ # if we have seen one, then we can't use the packed version so put it
+ # in the unpacked set.
+ if already_seen
+ req_targets.each { |t| process_target(t) }
+ @unpacked << target # add last to keep order intact
+
+ # if we have not seen any required, then we can mark this as packed.
+ # yeah! mark the required targets as seen also
+ else
+ req_targets.each { |t| @seen << t }
+ @packed << target
+ end
+ end
+
+ end
+
+end
View
32 lib/sproutcore/helpers/static_helper.rb
@@ -83,7 +83,7 @@ def javascripts_for_client(target_name = nil, opts = {})
# collect urls from entries
urls = []
combine_javascript = t.config.combine_javascript
- combined_entries(t, opts, 'javascript.js') do |cur_target, cur_entry|
+ combined_entries(t, opts, 'javascript.js', 'javascript-packed.js') do |cur_target, cur_entry|
# include either the entry URL or URL of ordered entries
# depending on setup
@@ -213,14 +213,36 @@ def strings_hash(for_language)
return ret # done!
end
- def combined_entries(t, opts, entry_name, &block)
+ # Find all of the combined entries.
+ def combined_entries(t, opts, entry_name, packed_entry_name=nil, &block)
# choose manifest variant. default to current manifest variant
# if no explicit language was passed.
v = opts[:language] ? { :language => opts[:language] } : manifest.variation
- targets = (expand_required_targets(t) + [t])
- targets.each do |t|
+ # choose which targets to include packed and unpacked
+ targets = expand_required_targets(t)
+ if t.config.use_packed && packed_entry_name # must pass to activate
+ packed, unpacked = SC::PackedOptimizer.optimize(targets)
+ unpacked << t # always use unpacked for main target
+ else
+ packed = []
+ unpacked = targets + [t] # always use unpacked
+ end
+
+ # deal with packed targets...
+ packed.each do |t|
+ # get the manifest for the target
+ cur_manifest = t.manifest_for(v).build!
+
+ # get the stylesheet or js entry for it...
+ entry = cur_manifest.entry_for packed_entry_name
+ next if entry.nil? || !entry.composite? # no stylesheet or js
+
+ yield(t, entry)
+ end
+
+ unpacked.each do |t|
# get the manifest for the target
cur_manifest = t.manifest_for(v).build!
@@ -231,7 +253,7 @@ def combined_entries(t, opts, entry_name, &block)
yield(t, entry)
end
end
-
+
end
View
7 lib/sproutcore/tools.rb
@@ -159,7 +159,12 @@ def find_targets(*targets)
# If include required was specified, merge in all required bundles as
# well.
if options['include-required']
- targets.each { |target| targets += target.expand_required_targets }
+ targets.each do |target|
+ targets += target.expand_required_targets :theme => true,
+ :debug => target.config.load_debug,
+ :tests => target.config.load_tests
+ end
+
targets = targets.flatten.uniq.compact
end
View
26 lib/sproutcore/tools/build.rb
@@ -24,6 +24,21 @@ def build(*targets)
# Get the manifests to build
manifests = build_manifests(targets)
+ # First clean all manifests
+ # Do this before building so we don't accidentally erase already build
+ # nested targets.
+ if SC.env.clean
+ manifests.each do |manifest|
+ build_root = manifest.target.build_root
+ info "Cleaning #{build_root}"
+ FileUtils.rm_r(build_root) if File.directory?(build_root)
+
+ staging_root = manifest.target.staging_root
+ info "Cleaning #{staging_root}"
+ FileUtils.rm_r(staging_root) if File.directory?(staging_root)
+ end
+ end
+
# Now build entries for each manifest...
manifests.each do |manifest|
@@ -45,17 +60,6 @@ def build(*targets)
if entries.size > 0
info "Building entries for #{manifest.target.target_name}:#{manifest.language}..."
- # if clean is enabled, first delete the build_root & staging_root
- # dir for the target.
- if SC.env.clean
- build_root = manifest.target.build_root
- info "Cleaning #{build_root}"
- FileUtils.rm_r(build_root) if File.directory?(build_root)
-
- staging_root = manifest.target.staging_root
- info "Cleaning #{staging_root}"
- end
-
entries.each do |entry|
info " #{entry.filename} -> #{entry.build_path}"
entry.build!
View
19 spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb
@@ -243,23 +243,4 @@ 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
-
end
View
101 spec/buildtasks/manifest/prepare_build_tasks/packed_spec.rb
@@ -0,0 +1,101 @@
+require File.join(File.dirname(__FILE__), %w(.. spec_helper))
+
+# Creates packed combined entries for javascript & css
+describe "manifest:prepare_build_tasks:packed" do
+
+ include SC::SpecHelpers
+ include SC::ManifestSpecHelpers
+
+ before do
+ std_before
+ end
+
+ def run_task
+ # capture any log warnings...
+ @msg = capture('stderr') {
+ @manifest.prepare!
+ super('manifest:prepare_build_tasks:packed')
+ }
+ end
+
+ it "should run setup, combine as prereq" do
+ %w(setup combine).each do |task_name|
+ should_run("manifest:prepare_build_tasks:#{task_name}") { run_task }
+ end
+ end
+
+ it "should not add packed entries for apps" do
+ @target = @project.target_for(:contacts)
+ @target.target_type.should == :app # precondition
+
+ @manifest = @target.manifest_for(:language => :en)
+ run_task
+
+ @manifest.entry_for('javascript-packed.js').should be_nil
+ @manifest.entry_for('stylesheet-packed.css').should be_nil
+ end
+
+ it "should add packed entries for frameworks" do
+ # verify default target is a framework - precondition
+ @target.target_type.should == :framework #precondition
+ run_task
+
+ @manifest.entry_for('javascript-packed.js').should_not be_nil
+ end
+
+ #######################################
+ # javascript-packed.js support
+ #
+ describe "javascript-packed.js" do
+
+ before do
+ run_task
+ @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 all required targets" do
+ @entry.source_entries.size.should > 0
+ @entry.source_entries.each do |entry|
+ entry.filename.should == 'javascript.js'
+ end
+ end
+
+ it "should include ordered_entries ordered by required target order" do
+
+ # find targets, sorted by order. remove any that don't have a
+ # javascript.js entry.
+ targets = @target.expand_required_targets + [@target]
+ variation = @entry.manifest.variation
+ targets.reject! do |t|
+ t.manifest_for(variation).build!.entry_for('javascript.js').nil?
+ end
+
+ @entry.ordered_entries.each do |entry|
+ entry.target.should == targets.shift
+ end
+ end
+
+ it "should include the actual targets this packed version covers in the targets property (even those w no javascript.js)" do
+ targets = @target.expand_required_targets + [@target]
+ @entry.targets.should == targets
+ end
+
+ it "should NOT include minified source entries" do
+ @entry.source_entries.each do |entry|
+ entry.should_not be_minified
+ end
+ end
+
+ it "should be marked as packed" do
+ @entry.should be_packed
+ end
+
+ end
+
+ # TODO: Same thing for Stylesheets
+
+end

0 comments on commit 555f70b

Please sign in to comment.
Something went wrong with that request. Please try again.