Skip to content

Commit 6d44757

Browse files
author
Robert Mosolgo
committed
Merge pull request #430 from reactjs/support-asset-manifest
Fix(SprocketsRenderer) support Sprockets::Manifest if assets were precompiled
2 parents 253cbda + a2ef5ca commit 6d44757

File tree

7 files changed

+114
-14
lines changed

7 files changed

+114
-14
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module React
2+
module ServerRendering
3+
# Return asset contents by getting them from a Sprockets::Environment instance.
4+
#
5+
# This is good for Rails development but bad for production because:
6+
# - It compiles the asset lazily, not ahead-of-time
7+
# - Rails 5 / Sprockets 3 doesn't expose a Sprockets::Environment in production.
8+
class EnvironmentContainer
9+
def initialize
10+
@environment = ::Rails.application.assets
11+
end
12+
13+
def find_asset(logical_path)
14+
@environment[logical_path].to_s
15+
end
16+
end
17+
end
18+
end

lib/react/server_rendering/exec_js_renderer.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# A bare-bones renderer for React.js + Exec.js
2-
# - Depends on global ReactDOMServer in the ExecJS context
3-
# - No Rails dependency
4-
# - No browser concerns
51
module React
62
module ServerRendering
3+
# A bare-bones renderer for React.js + Exec.js
4+
# - Depends on global ReactDOMServer in the ExecJS context
5+
# - No Rails dependency
6+
# - No browser concerns
77
class ExecJSRenderer
88
def initialize(options={})
99
js_code = options[:code] || raise("Pass `code:` option to instantiate a JS context!")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module React
2+
module ServerRendering
3+
# Get asset content by reading the compiled file from disk using a Sprockets::Manifest.
4+
#
5+
# This is good for Rails production when assets are compiled to public/assets
6+
# but sometimes, they're compiled to other directories (or other servers)
7+
class ManifestContainer
8+
def initialize
9+
@manifest = ::Rails.application.assets_manifest
10+
end
11+
12+
def find_asset(logical_path)
13+
asset_path = @manifest.assets[logical_path] || raise("No compiled asset for #{logical_path}, was it precompiled?")
14+
asset_full_path = ::Rails.root.join("public", @manifest.directory, asset_path)
15+
File.read(asset_full_path)
16+
end
17+
end
18+
end
19+
end

lib/react/server_rendering/sprockets_renderer.rb

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# Extends ExecJSRenderer for the Rails environment
2-
# - builds JS code out of the asset pipeline
3-
# - stringifies props
4-
# - implements console replay
1+
require "react/server_rendering/environment_container"
2+
require "react/server_rendering/manifest_container"
53
module React
64
module ServerRendering
5+
# Extends ExecJSRenderer for the Rails environment
6+
# - builds JS code out of the asset pipeline
7+
# - stringifies props
8+
# - implements console replay
79
class SprocketsRenderer < ExecJSRenderer
810
# Reimplement console methods for replaying on the client
911
CONSOLE_POLYFILL = File.read(File.join(File.dirname(__FILE__), "sprockets_renderer/console_polyfill.js"))
@@ -15,7 +17,7 @@ def initialize(options={})
1517
js_code = CONSOLE_POLYFILL.dup
1618

1719
filenames.each do |filename|
18-
js_code << ::Rails.application.assets[filename].to_s
20+
js_code << asset_container.find_asset(filename)
1921
end
2022

2123
super(options.merge(code: js_code))
@@ -39,6 +41,30 @@ def render(component_name, props, prerender_options)
3941
def after_render(component_name, props, prerender_options)
4042
@replay_console ? CONSOLE_REPLAY : ""
4143
end
44+
45+
class << self
46+
attr_accessor :asset_container_class
47+
end
48+
49+
# Get an object which exposes assets by their logical path.
50+
#
51+
# Out of the box, it supports a Sprockets::Environment (application.assets)
52+
# and a Sprockets::Manifest (application.assets_manifest), which covers the
53+
# default Rails setups.
54+
#
55+
# You can provide a custom asset container
56+
# with `React::ServerRendering::SprocketsRender.asset_container_class = MyAssetContainer`.
57+
#
58+
# @return [#find_asset(logical_path)] An object that returns asset contents by logical path
59+
def asset_container
60+
@asset_container ||= if self.class.asset_container_class.present?
61+
self.class.asset_container_class.new
62+
elsif ::Rails.application.config.assets.compile
63+
EnvironmentContainer.new
64+
else
65+
ManifestContainer.new
66+
end
67+
end
4268
end
4369
end
4470
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require "test_helper"
2+
3+
# Rails 3.x doesn't have `application.assets_manifest=`
4+
# I don't think we have to support Rails 3 + Sprockets 3 anyways!
5+
if Rails::VERSION::MAJOR > 3
6+
class ManifestContainerTest < ActiveSupport::TestCase
7+
def setup
8+
precompile_assets
9+
10+
# Make a new manifest since assets weren't compiled before
11+
config = Rails.application.config
12+
path = File.join(config.paths['public'].first, config.assets.prefix)
13+
new_manifest = Sprockets::Manifest.new(Rails.application.assets, path)
14+
Rails.application.assets_manifest = new_manifest
15+
16+
@manifest_container = React::ServerRendering::ManifestContainer.new
17+
end
18+
19+
def teardown
20+
clear_precompiled_assets
21+
end
22+
23+
def test_find_asset_gets_asset_contents
24+
application_js_content = @manifest_container.find_asset("application.js")
25+
assert(application_js_content.length > 50000, "It's the compiled file")
26+
end
27+
end
28+
end

test/react_test.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,9 @@ class ReactTest < ActionDispatch::IntegrationTest
2626

2727
test 'precompiling assets works' do
2828
begin
29-
ENV['RAILS_GROUPS'] = 'assets' # required for Rails 3.2
30-
Dummy::Application.load_tasks
31-
Rake::Task['assets:precompile'].invoke
32-
FileUtils.rm_r(File.expand_path("../dummy/public/assets", __FILE__))
29+
precompile_assets
3330
ensure
34-
ENV.delete('RAILS_GROUPS')
31+
clear_precompiled_assets
3532
end
3633
end
3734

test/test_helper.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ def manually_expire_asset(asset_name)
3939
def asset.fresh?(env); false; end
4040
end
4141

42+
def precompile_assets
43+
ENV['RAILS_GROUPS'] = 'assets' # required for Rails 3.2
44+
Dummy::Application.load_tasks
45+
Rake::Task['assets:precompile'].reenable
46+
Rake::Task['assets:precompile'].invoke
47+
end
48+
49+
def clear_precompiled_assets
50+
FileUtils.rm_r(File.expand_path("../dummy/public/assets", __FILE__))
51+
ENV.delete('RAILS_GROUPS')
52+
end
53+
4254
# Load support files
4355
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
4456

0 commit comments

Comments
 (0)