From 10894b7777a80063f25f77619de21a190ef45282 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Tue, 24 Nov 2015 11:50:01 -0500 Subject: [PATCH] Merge EmberCLI-generated Manifest with Sprockets' Closes [#298] Expose Ember's fingerprinted assets to Sprockets. [#298]: https://github.com/thoughtbot/ember-cli-rails/issues/298 --- CHANGELOG.md | 2 + app/helpers/ember_rails_helper.rb | 4 +- lib/ember_cli/app.rb | 2 + lib/ember_cli/assets.rb | 57 +++++++++++++++ lib/ember_cli/manifest.rb | 14 ++++ lib/ember_cli/path_set.rb | 8 +++ lib/ember_cli/sprockets.rb | 30 +++++++- spec/lib/ember_cli/assets_spec.rb | 101 +++++++++++++++++++++++++++ spec/lib/ember_cli/manifest_spec.rb | 31 ++++++++ spec/lib/ember_cli/path_set_spec.rb | 21 ++++++ spec/lib/ember_cli/sprockets_spec.rb | 42 +++++++++++ 11 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 lib/ember_cli/assets.rb create mode 100644 lib/ember_cli/manifest.rb create mode 100644 spec/lib/ember_cli/assets_spec.rb create mode 100644 spec/lib/ember_cli/manifest_spec.rb create mode 100644 spec/lib/ember_cli/sprockets_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index bd871dd2..0e6424f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ master ------ +* Merge EmberCLI-generate manifest into Sprockets'. [#316] * Delete previous build output on application boot instead of on process exit. [#308] +[#316]: https://github.com/thoughtbot/ember-cli-rails/pull/316 [#308]: https://github.com/thoughtbot/ember-cli-rails/pull/308 0.5.6 diff --git a/app/helpers/ember_rails_helper.rb b/app/helpers/ember_rails_helper.rb index 8f610a3d..42a8c24a 100644 --- a/app/helpers/ember_rails_helper.rb +++ b/app/helpers/ember_rails_helper.rb @@ -20,10 +20,10 @@ def render_ember_app(name, &block) end def include_ember_script_tags(name, **options) - javascript_include_tag(*EmberCli[name].sprockets.assets, options) + javascript_include_tag(*EmberCli[name].sprockets.javascript_assets, options) end def include_ember_stylesheet_tags(name, **options) - stylesheet_link_tag(*EmberCli[name].sprockets.assets, options) + stylesheet_link_tag(*EmberCli[name].sprockets.stylesheet_assets, options) end end diff --git a/lib/ember_cli/app.rb b/lib/ember_cli/app.rb index 6ad2129b..6ef86376 100644 --- a/lib/ember_cli/app.rb +++ b/lib/ember_cli/app.rb @@ -34,6 +34,7 @@ def compile @shell.compile @build.check! copy_index_html_file + sprockets.update_manifest! true end end @@ -82,6 +83,7 @@ def build_and_watch prepare @shell.build_and_watch copy_index_html_file + sprockets.update_manifest! end def prepare diff --git a/lib/ember_cli/assets.rb b/lib/ember_cli/assets.rb new file mode 100644 index 00000000..d1aa62ad --- /dev/null +++ b/lib/ember_cli/assets.rb @@ -0,0 +1,57 @@ +module EmberCli + class Assets + def initialize(app_name:, ember_app_name:, manifest:) + @app_name = app_name + @ember_app_name = ember_app_name + @manifest = manifest + end + + def javascripts + if empty_manifest? + fallback_assets + else + [ + latest_matching(%r{#{app_name}/assets/vendor(.*)\.js\z}), + latest_matching(%r{#{app_name}/assets/#{ember_app_name}(.*)\.js\z}), + ] + end + end + + def stylesheets + if empty_manifest? + fallback_assets + else + [ + latest_matching(%r{#{app_name}/assets/vendor(.*)\.css\z}), + latest_matching(%r{#{app_name}/assets/#{ember_app_name}(.*)\.css\z}), + ] + end + end + + private + + attr_reader :app_name, :ember_app_name, :manifest + + def fallback_assets + ["#{app_name}/assets/vendor", "#{app_name}/assets/#{ember_app_name}"] + end + + def latest_matching(regex) + file, = files. + select { |key| key =~ regex }. + sort_by { |_, data| data["mtime"] }. + reverse. + first + + file + end + + def files + manifest.files + end + + def empty_manifest? + files.empty? + end + end +end diff --git a/lib/ember_cli/manifest.rb b/lib/ember_cli/manifest.rb new file mode 100644 index 00000000..451d5276 --- /dev/null +++ b/lib/ember_cli/manifest.rb @@ -0,0 +1,14 @@ +require "sprockets" + +module EmberCli + class Manifest + def initialize(environment, path) + @manifest = ::Sprockets::Manifest.new(environment, path) + end + + def merge_into!(other) + other.assets.merge!(@manifest.assets) + other.files.merge!(@manifest.files) + end + end +end diff --git a/lib/ember_cli/path_set.rb b/lib/ember_cli/path_set.rb index 8597d5e3..fc68ceca 100644 --- a/lib/ember_cli/path_set.rb +++ b/lib/ember_cli/path_set.rb @@ -75,6 +75,10 @@ def build_error_file @build_error_file ||= tmp.join("error.txt") end + def manifest + manifests.first + end + def bower @bower ||= begin bower_path = app_options.fetch(:bower_path) { configuration.bower_path } @@ -109,6 +113,10 @@ def bundler delegate :name, :options, to: :app, prefix: true + def manifests + Pathname.glob(app_assets.join("assets", "manifest*.json")) + end + def default_root rails_root.join(app_name) end diff --git a/lib/ember_cli/sprockets.rb b/lib/ember_cli/sprockets.rb index ed8f20ad..8057062a 100644 --- a/lib/ember_cli/sprockets.rb +++ b/lib/ember_cli/sprockets.rb @@ -1,6 +1,8 @@ require "ember_cli/errors" require "non-stupid-digest-assets" require "ember_cli/html_page" +require "ember_cli/manifest" +require "ember_cli/assets" module EmberCli class Sprockets @@ -14,6 +16,10 @@ def register! register_or_raise!(NonStupidDigestAssets.whitelist) end + def update_manifest! + ember_manifest.merge_into!(rails_manifest) + end + def index_html(head:, body:) html_page = HtmlPage.new( content: app.index_file.read, @@ -24,14 +30,26 @@ def index_html(head:, body:) html_page.render end - def assets - ["#{app.name}/assets/vendor", "#{app.name}/assets/#{ember_app_name}"] + def javascript_assets + assets.javascripts + end + + def stylesheet_assets + assets.stylesheets end private attr_reader :app + def assets + Assets.new( + app_name: app.name, + ember_app_name: ember_app_name, + manifest: rails_manifest, + ) + end + def ember_app_name @ember_app_name ||= app.options.fetch(:name) { package_json.fetch(:name) } end @@ -41,6 +59,14 @@ def package_json JSON.parse(app.paths.package_json_file.read).with_indifferent_access end + def ember_manifest + @ember_manifest ||= Manifest.new(Rails.env, app.paths.manifest) + end + + def rails_manifest + Rails.application.assets_manifest + end + def asset_matcher %r{\A#{app.name}/} end diff --git a/spec/lib/ember_cli/assets_spec.rb b/spec/lib/ember_cli/assets_spec.rb new file mode 100644 index 00000000..9d3c97c0 --- /dev/null +++ b/spec/lib/ember_cli/assets_spec.rb @@ -0,0 +1,101 @@ +require "ember_cli/assets" + +describe EmberCli::Assets do + describe "#javascripts" do + it "includes the most recent javascript build artifacts" do + manifest = build_manifest( + files: { + "not-a-match" => {}, + "foo/assets/bar-abc123.js" => { "mtime" => 1.day.ago.iso8601 }, + "foo/assets/bar-def456.js" => { "mtime" => 2.days.ago.iso8601 }, + "foo/assets/vendor-abc123.js" => { "mtime" => 1.day.ago.iso8601 }, + "foo/assets/vendor-def456.js" => { "mtime" => 2.days.ago.iso8601 }, + }, + ) + assets = build_assets( + app_name: "foo", + ember_app_name: "bar", + manifest: manifest, + ) + + javascripts = assets.javascripts + + expect(javascripts).to match_array([ + "foo/assets/bar-abc123.js", + "foo/assets/vendor-abc123.js", + ]) + end + + context "when the manifest is empty" do + it "falls back to the default assets" do + assets = build_assets( + manifest: build_empty_manifest, + app_name: "foo", + ember_app_name: "bar", + ) + + javascripts = assets.javascripts + + expect(javascripts).to match_array([ + "foo/assets/vendor", + "foo/assets/bar", + ]) + end + end + end + + describe "#stylesheets" do + it "includes the most recent stylesheet build artifacts" do + manifest = build_manifest( + files: { + "not-a-match" => {}, + "foo/assets/bar-abc123.css" => { "mtime" => 1.day.ago.iso8601 }, + "foo/assets/bar-def456.css" => { "mtime" => 2.days.ago.iso8601 }, + "foo/assets/vendor-abc123.css" => { "mtime" => 1.day.ago.iso8601 }, + "foo/assets/vendor-def456.css" => { "mtime" => 2.days.ago.iso8601 }, + }, + ) + assets = build_assets( + app_name: "foo", + ember_app_name: "bar", + manifest: manifest, + ) + + stylesheets = assets.stylesheets + + expect(stylesheets).to match_array([ + "foo/assets/bar-abc123.css", + "foo/assets/vendor-abc123.css", + ]) + end + + context "when the manifest is empty" do + it "falls back to the default assets" do + assets = build_assets( + manifest: build_empty_manifest, + app_name: "foo", + ember_app_name: "bar", + ) + + stylesheets = assets.stylesheets + + expect(stylesheets).to match_array([ + "foo/assets/vendor", + "foo/assets/bar", + ]) + end + end + end +end + +def build_assets(manifest: build_manifest, **options) + EmberCli::Assets.new(options.merge(manifest: manifest)) +end + +def build_manifest(files: {}) + double(files: files) +end + +def build_empty_manifest + build_manifest +end diff --git a/spec/lib/ember_cli/manifest_spec.rb b/spec/lib/ember_cli/manifest_spec.rb new file mode 100644 index 00000000..5998edaf --- /dev/null +++ b/spec/lib/ember_cli/manifest_spec.rb @@ -0,0 +1,31 @@ +require "ember_cli/manifest" + +describe EmberCli::Manifest do + describe "#merge_into!" do + it "merges the manifest into the provided manifest" do + manifest_file = create_json_file( + "assets" => { + "asset" => "path/to/asset.js", + }, + "files" => { + "file" => "path/to/file.js", + }, + ) + manifest = EmberCli::Manifest.new("test", manifest_file.path) + rails_manifest = OpenStruct.new(assets: {}, files: {}) + + manifest.merge_into!(rails_manifest) + + expect(rails_manifest.assets).to eq("asset" => "path/to/asset.js") + expect(rails_manifest.files).to eq("file" => "path/to/file.js") + end + end + + def create_json_file(json) + tempfile = Tempfile.new("json") + tempfile.write(JSON.dump(json)) + tempfile.close + + tempfile + end +end diff --git a/spec/lib/ember_cli/path_set_spec.rb b/spec/lib/ember_cli/path_set_spec.rb index 1a74fc09..7414d474 100644 --- a/spec/lib/ember_cli/path_set_spec.rb +++ b/spec/lib/ember_cli/path_set_spec.rb @@ -61,6 +61,21 @@ end end + describe "#manifest" do + it "is a hashed child of #app_assets" do + manifest_path = ember_cli_root.join( + "assets", + "bar", + "assets", + "manifest-abc123.json", + ) + create_file(manifest_path) + path_set = build_path_set(app: double(name: "bar")) + + expect(path_set.manifest).to eq(manifest_path) + end + end + describe "#app_assets" do it "is a child of #assets" do app = double(name: "bar") @@ -197,6 +212,12 @@ def create_executable(path) path end + def create_file(path) + path.parent.mkpath + FileUtils.touch(path) + path + end + def build_app(**options) double( options.reverse_merge( diff --git a/spec/lib/ember_cli/sprockets_spec.rb b/spec/lib/ember_cli/sprockets_spec.rb new file mode 100644 index 00000000..d241e519 --- /dev/null +++ b/spec/lib/ember_cli/sprockets_spec.rb @@ -0,0 +1,42 @@ +require "ember_cli/sprockets" + +describe EmberCli::Sprockets do + describe "#update_manifest!" do + it "merges the EmberCLI-generated manifest with Sprockets' manifest" do + ember_manifest = create_manifest( + "assets" => { + "foo.js" => "foo.js", + }, + "files" => { + "foo.js" => "foo.js", + }, + ) + path_set = double(manifest: ember_manifest) + app = double(paths: path_set) + sprockets = EmberCli::Sprockets.new(app) + rails_manifest_path = create_manifest("assets" => {}, "files" => {}) + assets_manifest = load_manifest(rails_manifest_path) + application = double(assets_manifest: assets_manifest) + allow(Rails).to receive(:application).and_return(application) + + sprockets.update_manifest! + + expect(assets_manifest).to have_attributes( + assets: { "foo.js" => "foo.js" }, + files: { "foo.js" => "foo.js" }, + ) + end + end + + def load_manifest(manifest_path) + Sprockets::Manifest.new("production", manifest_path) + end + + def create_manifest(json) + tempfile = Tempfile.new("json") + tempfile.write(JSON.dump(json)) + tempfile.close + + Pathname(tempfile.path) + end +end