react-rails 1.9.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +345 -9
- data/README.md +137 -389
- data/lib/assets/javascripts/JSXTransformer.js +6 -6
- data/lib/assets/javascripts/react_ujs.js +1 -7
- data/lib/assets/react-source/development/react-server.js +423 -21528
- data/lib/assets/react-source/development/react.js +191 -21409
- data/lib/assets/react-source/production/react-server.js +2 -19
- data/lib/assets/react-source/production/react.js +2 -19
- data/lib/generators/react/component_generator.rb +203 -73
- data/lib/generators/react/install_generator.rb +98 -25
- data/lib/generators/templates/component.es6.jsx +9 -12
- data/lib/generators/templates/component.es6.tsx +24 -0
- data/lib/generators/templates/component.js.jsx +15 -18
- data/lib/generators/templates/component.js.jsx.coffee +5 -8
- data/lib/generators/templates/component.js.jsx.tsx +36 -0
- data/lib/generators/templates/react_server_rendering.rb +4 -0
- data/lib/generators/templates/server_rendering.js +6 -0
- data/lib/generators/templates/server_rendering_pack.js +5 -0
- data/lib/react/jsx/babel_transformer.rb +12 -6
- data/lib/react/jsx/jsx_transformer.rb +7 -6
- data/lib/react/jsx/processor.rb +3 -1
- data/lib/react/jsx/sprockets_strategy.rb +12 -6
- data/lib/react/jsx/template.rb +7 -6
- data/lib/react/jsx.rb +11 -7
- data/lib/react/rails/asset_variant.rb +7 -8
- data/lib/react/rails/component_mount.rb +48 -14
- data/lib/react/rails/controller_lifecycle.rb +36 -7
- data/lib/react/rails/controller_renderer.rb +13 -4
- data/lib/react/rails/railtie.rb +34 -29
- data/lib/react/rails/test_helper.rb +25 -0
- data/lib/react/rails/version.rb +4 -2
- data/lib/react/rails/view_helper.rb +3 -1
- data/lib/react/rails.rb +9 -7
- data/lib/react/server_rendering/{sprockets_renderer → bundle_renderer}/console_replay.js +1 -1
- data/lib/react/server_rendering/bundle_renderer/console_reset.js +3 -0
- data/lib/react/server_rendering/bundle_renderer/timeout_polyfill.js +26 -0
- data/lib/react/server_rendering/bundle_renderer.rb +117 -0
- data/lib/react/server_rendering/environment_container.rb +2 -0
- data/lib/react/server_rendering/exec_js_renderer.rb +43 -16
- data/lib/react/server_rendering/manifest_container.rb +5 -1
- data/lib/react/server_rendering/separate_server_bundle_container.rb +19 -0
- data/lib/react/server_rendering/yaml_manifest_container.rb +12 -4
- data/lib/react/server_rendering.rb +26 -12
- data/lib/react-rails.rb +12 -4
- data/lib/react.rb +8 -4
- metadata +106 -41
- data/lib/assets/javascripts/react_ujs_event_setup.js +0 -29
- data/lib/assets/javascripts/react_ujs_mount.js +0 -104
- data/lib/assets/javascripts/react_ujs_native.js +0 -18
- data/lib/assets/javascripts/react_ujs_pjax.js +0 -10
- data/lib/assets/javascripts/react_ujs_turbolinks.js +0 -9
- data/lib/assets/javascripts/react_ujs_turbolinks_classic.js +0 -10
- data/lib/assets/javascripts/react_ujs_turbolinks_classic_deprecated.js +0 -13
- data/lib/assets/react-source/development-with-addons/react-server.js +0 -24053
- data/lib/assets/react-source/development-with-addons/react.js +0 -23890
- data/lib/assets/react-source/production-with-addons/react-server.js +0 -19
- data/lib/assets/react-source/production-with-addons/react.js +0 -19
- data/lib/generators/react/ujs_generator.rb +0 -44
- data/lib/react/server_rendering/sprockets_renderer.rb +0 -79
- /data/lib/react/server_rendering/{sprockets_renderer → bundle_renderer}/console_polyfill.js +0 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
function getStackTrace() {
|
2
|
+
var stack;
|
3
|
+
try {
|
4
|
+
throw new Error('');
|
5
|
+
}
|
6
|
+
catch (error) {
|
7
|
+
stack = error.stack || '';
|
8
|
+
}
|
9
|
+
stack = stack.split('\\n').map(function (line) {
|
10
|
+
return line.trim();
|
11
|
+
});
|
12
|
+
return stack.splice(stack[0] == 'Error' ? 2 : 1);
|
13
|
+
};
|
14
|
+
|
15
|
+
function printError(functionName){
|
16
|
+
console.error(functionName + ' is not defined for execJS. See https://github.com/sstephenson/execjs#faq. Note babel-polyfill may call this.');
|
17
|
+
console.error(getStackTrace().join('\\n'));
|
18
|
+
};
|
19
|
+
|
20
|
+
function setTimeout() {
|
21
|
+
printError('setTimeout');
|
22
|
+
};
|
23
|
+
|
24
|
+
function clearTimeout() {
|
25
|
+
printError('clearTimeout');
|
26
|
+
};
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "react/server_rendering/environment_container"
|
4
|
+
require "react/server_rendering/manifest_container"
|
5
|
+
require "react/server_rendering/yaml_manifest_container"
|
6
|
+
require "react/server_rendering/separate_server_bundle_container"
|
7
|
+
|
8
|
+
module React
|
9
|
+
module ServerRendering
|
10
|
+
# Extends ExecJSRenderer for the Rails environment
|
11
|
+
# - fetches JS code from the Rails app (Shakapacker or Sprockets)
|
12
|
+
# - stringifies props
|
13
|
+
# - implements console replay
|
14
|
+
class BundleRenderer < ExecJSRenderer
|
15
|
+
# Reimplement console methods for replaying on the client
|
16
|
+
CONSOLE_POLYFILL = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/console_polyfill.js"))
|
17
|
+
CONSOLE_REPLAY = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/console_replay.js"))
|
18
|
+
CONSOLE_RESET = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/console_reset.js"))
|
19
|
+
TIMEOUT_POLYFILL = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/timeout_polyfill.js"))
|
20
|
+
|
21
|
+
def initialize(options = {})
|
22
|
+
@replay_console = options.fetch(:replay_console, true)
|
23
|
+
filenames = options.fetch(:files, ["server_rendering.js"])
|
24
|
+
js_code = CONSOLE_POLYFILL.dup
|
25
|
+
js_code << TIMEOUT_POLYFILL.dup
|
26
|
+
js_code << options.fetch(:code, "")
|
27
|
+
|
28
|
+
filenames.each do |filename|
|
29
|
+
js_code << asset_container.find_asset(filename)
|
30
|
+
end
|
31
|
+
|
32
|
+
super(options.merge(code: js_code))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Prerender options are expected to be a Hash however might also be a symbol.
|
36
|
+
# pass prerender: :static to use renderToStaticMarkup
|
37
|
+
# pass prerender: true to enable default prerender
|
38
|
+
# pass prerender: {} to proxy some custom options
|
39
|
+
def render(component_name, props, prerender_options)
|
40
|
+
t_options = prepare_options(prerender_options)
|
41
|
+
t_props = prepare_props(props)
|
42
|
+
super(component_name, t_props, t_options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def before_render(_component_name, _props, _prerender_options)
|
46
|
+
@replay_console ? CONSOLE_RESET : ""
|
47
|
+
end
|
48
|
+
|
49
|
+
def after_render(_component_name, _props, _prerender_options)
|
50
|
+
@replay_console ? CONSOLE_REPLAY : ""
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
attr_accessor :asset_container_class
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get an object which exposes assets by their logical path.
|
58
|
+
#
|
59
|
+
# Out of the box, it supports a Sprockets::Environment (application.assets)
|
60
|
+
# and a Sprockets::Manifest (application.assets_manifest), which covers the
|
61
|
+
# default Rails setups.
|
62
|
+
#
|
63
|
+
# You can provide a custom asset container
|
64
|
+
# with `React::ServerRendering::BundleRenderer.asset_container_class = MyAssetContainer`.
|
65
|
+
#
|
66
|
+
# @return [#find_asset(logical_path)] An object that returns asset contents by logical path
|
67
|
+
def asset_container
|
68
|
+
@asset_container ||= asset_container_class.new
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def prepare_options(options)
|
74
|
+
r_func = render_function(options)
|
75
|
+
opts = case options
|
76
|
+
when Hash then options
|
77
|
+
else
|
78
|
+
{}
|
79
|
+
end
|
80
|
+
# This seems redundant to pass
|
81
|
+
opts.merge(render_function: r_func)
|
82
|
+
end
|
83
|
+
|
84
|
+
def render_function(opts)
|
85
|
+
if opts == :static
|
86
|
+
"renderToStaticMarkup"
|
87
|
+
else
|
88
|
+
"renderToString"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def prepare_props(props)
|
93
|
+
props.is_a?(String) ? props : props.to_json
|
94
|
+
end
|
95
|
+
|
96
|
+
def assets_precompiled?
|
97
|
+
!::Rails.application.config.assets.compile
|
98
|
+
end
|
99
|
+
|
100
|
+
# Detect what kind of asset system is in use and choose that container.
|
101
|
+
# Or, if the user has provided {.asset_container_class}, use that.
|
102
|
+
# @return [Class] suitable for {#asset_container}
|
103
|
+
def asset_container_class
|
104
|
+
return self.class.asset_container_class if self.class.asset_container_class.present?
|
105
|
+
return SeparateServerBundleContainer if SeparateServerBundleContainer.compatible?
|
106
|
+
|
107
|
+
return EnvironmentContainer unless assets_precompiled?
|
108
|
+
|
109
|
+
return ManifestContainer if ManifestContainer.compatible?
|
110
|
+
return YamlManifestContainer if YamlManifestContainer.compatible?
|
111
|
+
|
112
|
+
# Even though they are precompiled, we can't find them :S
|
113
|
+
EnvironmentContainer
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,41 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module React
|
2
4
|
module ServerRendering
|
3
5
|
# A bare-bones renderer for React.js + Exec.js
|
4
|
-
# - Depends on global
|
6
|
+
# - Depends on global ReactRailsUJS in the ExecJS context
|
5
7
|
# - No Rails dependency
|
6
8
|
# - No browser concerns
|
7
9
|
class ExecJSRenderer
|
8
|
-
|
10
|
+
# @return [ExecJS::Runtime::Context] The JS context for this renderer
|
11
|
+
attr_reader :context
|
12
|
+
|
13
|
+
def initialize(options = {})
|
9
14
|
js_code = options[:code] || raise("Pass `code:` option to instantiate a JS context!")
|
10
|
-
|
15
|
+
full_code = GLOBAL_WRAPPER + js_code
|
16
|
+
# File.write("./test/dummy/tmp/latest_js_context.js", full_code)
|
17
|
+
@context = ExecJS.compile(full_code)
|
11
18
|
end
|
12
19
|
|
13
20
|
def render(component_name, props, prerender_options)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
var result = ReactDOMServer.#{render_function}(React.createElement(#{component_name}, #{props}));
|
19
|
-
#{after_render(component_name, props, prerender_options)}
|
20
|
-
return result;
|
21
|
-
})()
|
22
|
-
JS
|
23
|
-
@context.eval(js_code).html_safe
|
21
|
+
js_executed_before = before_render(component_name, props, prerender_options)
|
22
|
+
js_executed_after = after_render(component_name, props, prerender_options)
|
23
|
+
js_main_section = main_render(component_name, props, prerender_options)
|
24
|
+
render_from_parts(js_executed_before, js_main_section, js_executed_after)
|
24
25
|
rescue ExecJS::ProgramError => err
|
25
26
|
raise React::ServerRendering::PrerenderError.new(component_name, props, err)
|
26
27
|
end
|
27
28
|
|
28
29
|
# Hooks for inserting JS before/after rendering
|
29
|
-
def before_render(
|
30
|
-
|
30
|
+
def before_render(_component_name, _props, _prerender_options)
|
31
|
+
""
|
32
|
+
end
|
33
|
+
|
34
|
+
def after_render(_component_name, _props, _prerender_options)
|
35
|
+
""
|
36
|
+
end
|
31
37
|
|
32
38
|
# Handle Node.js & other ExecJS contexts
|
33
39
|
GLOBAL_WRAPPER = <<-JS
|
34
40
|
var global = global || this;
|
35
41
|
var self = self || this;
|
36
|
-
var window = window || this;
|
37
42
|
JS
|
38
43
|
|
44
|
+
private
|
45
|
+
|
46
|
+
def render_from_parts(before, main, after)
|
47
|
+
js_code = compose_js(before, main, after)
|
48
|
+
@context.eval(js_code).html_safe
|
49
|
+
end
|
50
|
+
|
51
|
+
def main_render(component_name, props, prerender_options)
|
52
|
+
render_function = prerender_options.fetch(:render_function, "renderToString")
|
53
|
+
"this.ReactRailsUJS.serverRender('#{render_function}', '#{component_name}', #{props})"
|
54
|
+
end
|
55
|
+
|
56
|
+
def compose_js(before, main, after)
|
57
|
+
<<-JS
|
58
|
+
(function () {
|
59
|
+
#{before}
|
60
|
+
var result = #{main};
|
61
|
+
#{after}
|
62
|
+
return result;
|
63
|
+
})()
|
64
|
+
JS
|
65
|
+
end
|
39
66
|
end
|
40
67
|
end
|
41
68
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module React
|
2
4
|
module ServerRendering
|
3
5
|
# Get asset content by reading the compiled file from disk using a Sprockets::Manifest.
|
@@ -10,7 +12,9 @@ module React
|
|
10
12
|
end
|
11
13
|
|
12
14
|
def find_asset(logical_path)
|
13
|
-
asset_path = @manifest.assets[logical_path] || raise(
|
15
|
+
asset_path = @manifest.assets[logical_path] || raise(
|
16
|
+
"No compiled asset for #{logical_path}, was it precompiled?"
|
17
|
+
)
|
14
18
|
asset_full_path = ::Rails.root.join("public", @manifest.dir, asset_path)
|
15
19
|
File.read(asset_full_path)
|
16
20
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open-uri"
|
4
|
+
|
5
|
+
module React
|
6
|
+
module ServerRendering
|
7
|
+
# Get a compiled file from Shakapacker's output path
|
8
|
+
class SeparateServerBundleContainer
|
9
|
+
def self.compatible?
|
10
|
+
!!defined?(Shakapacker)
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_asset(filename)
|
14
|
+
asset_path = Shakapacker.config.public_output_path.join(filename).to_s
|
15
|
+
File.read(asset_path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,23 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module React
|
2
4
|
module ServerRendering
|
3
|
-
# Get asset content by reading the compiled file from disk using the generated
|
5
|
+
# Get asset content by reading the compiled file from disk using the generated manifest.yml file
|
4
6
|
#
|
5
7
|
# This is good for Rails production when assets are compiled to public/assets
|
6
8
|
# but sometimes, they're compiled to other directories (or other servers)
|
7
9
|
class YamlManifestContainer
|
8
10
|
def initialize
|
9
|
-
@assets = YAML.load_file(
|
11
|
+
@assets = YAML.load_file(public_asset_path("manifest.yml"))
|
10
12
|
end
|
11
13
|
|
12
14
|
def find_asset(logical_path)
|
13
15
|
asset_path = @assets[logical_path] || raise("No compiled asset for #{logical_path}, was it precompiled?")
|
14
|
-
|
15
|
-
File.read(asset_full_path)
|
16
|
+
File.read(public_asset_path(asset_path))
|
16
17
|
end
|
17
18
|
|
18
19
|
def self.compatible?
|
19
20
|
::Rails::VERSION::MAJOR == 3
|
20
21
|
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def public_asset_path(asset_name)
|
26
|
+
asset_path = File.join("public", ::Rails.application.config.assets.prefix, asset_name)
|
27
|
+
::Rails.root.join(asset_path)
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
23
31
|
end
|
@@ -1,33 +1,47 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "connection_pool"
|
4
|
+
require "react/server_rendering/exec_js_renderer"
|
5
|
+
require "react/server_rendering/bundle_renderer"
|
4
6
|
|
5
7
|
module React
|
6
8
|
module ServerRendering
|
7
9
|
mattr_accessor :renderer, :renderer_options,
|
8
|
-
|
10
|
+
:pool_size, :pool_timeout
|
11
|
+
|
12
|
+
self.renderer_options = {}
|
9
13
|
|
14
|
+
# Discard the old ConnectionPool & create a new one.
|
15
|
+
# This will clear all state such as loaded code, JS VM state, or options.
|
16
|
+
# @return [void]
|
10
17
|
def self.reset_pool
|
11
|
-
options = {size: pool_size, timeout: pool_timeout}
|
12
|
-
|
18
|
+
options = { size: pool_size, timeout: pool_timeout }
|
19
|
+
@pool = ConnectionPool.new(options) { renderer.new(renderer_options) }
|
13
20
|
end
|
14
21
|
|
22
|
+
# Check a renderer out of the pool and use it to render the component.
|
23
|
+
# @param component_name [String] Component identifier, looked up by UJS
|
24
|
+
# @param props [String, Hash] Props for this component
|
25
|
+
# @param prerender_options [Hash] Renderer-specific options
|
26
|
+
# @return [String] Prerendered HTML from `component_name`
|
15
27
|
def self.render(component_name, props, prerender_options)
|
16
|
-
|
28
|
+
@pool.with do |renderer|
|
17
29
|
renderer.render(component_name, props, prerender_options)
|
18
30
|
end
|
19
31
|
end
|
20
32
|
|
21
|
-
|
22
|
-
|
33
|
+
# Yield a renderer for an arbitrary block
|
34
|
+
def self.with_renderer(&block)
|
35
|
+
@pool.with(&block)
|
23
36
|
end
|
24
37
|
|
38
|
+
# Raised when something went wrong during server rendering.
|
25
39
|
class PrerenderError < RuntimeError
|
26
40
|
def initialize(component_name, props, js_message)
|
27
|
-
message = ["Encountered error \"#{js_message}\" when prerendering #{component_name} with #{props}",
|
28
|
-
|
41
|
+
message = ["Encountered error \"#{js_message.inspect}\" when prerendering #{component_name} with #{props}",
|
42
|
+
js_message.backtrace.join("\n")].join("\n")
|
29
43
|
super(message)
|
30
44
|
end
|
31
45
|
end
|
32
46
|
end
|
33
|
-
end
|
47
|
+
end
|
data/lib/react-rails.rb
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "react"
|
4
|
+
require "react/jsx"
|
5
|
+
require "react/rails"
|
6
|
+
require "react/server_rendering"
|
7
|
+
|
8
|
+
module React
|
9
|
+
module Rails
|
10
|
+
autoload :TestHelper, "react/rails/test_helper"
|
11
|
+
end
|
12
|
+
end
|
data/lib/react.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module React
|
2
4
|
# Recursively camelize `props`, returning a new Hash
|
3
5
|
# @param props [Object] If it's a Hash or Array, it will be recursed. Otherwise it will be returned.
|
4
6
|
# @return [Hash] a new hash whose keys are camelized strings
|
5
7
|
def self.camelize_props(props)
|
6
|
-
|
8
|
+
props_as_json = props.as_json
|
9
|
+
|
10
|
+
case props_as_json
|
7
11
|
when Hash
|
8
|
-
|
12
|
+
props_as_json.each_with_object({}) do |(key, value), new_props|
|
9
13
|
new_key = key.to_s.camelize(:lower)
|
10
14
|
new_value = camelize_props(value)
|
11
15
|
new_props[new_key] = new_value
|
12
16
|
end
|
13
17
|
when Array
|
14
|
-
|
18
|
+
props_as_json.map { |item| camelize_props(item) }
|
15
19
|
else
|
16
|
-
|
20
|
+
props_as_json
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|