react-rails 1.11.0 → 2.0.0
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 +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +294 -214
- data/lib/assets/javascripts/react_ujs.js +429 -7
- data/lib/generators/react/component_generator.rb +24 -12
- data/lib/generators/react/install_generator.rb +76 -18
- data/lib/generators/templates/react_server_rendering.rb +2 -0
- data/lib/generators/templates/server_rendering.js +6 -0
- data/lib/generators/templates/server_rendering_pack.js +5 -0
- data/lib/react/jsx.rb +2 -0
- data/lib/react/rails/component_mount.rb +23 -5
- data/lib/react/rails/controller_lifecycle.rb +35 -7
- data/lib/react/rails/railtie.rb +17 -11
- data/lib/react/rails/version.rb +1 -1
- data/lib/react/server_rendering.rb +16 -4
- data/lib/react/server_rendering/{sprockets_renderer.rb → bundle_renderer.rb} +40 -20
- data/lib/react/server_rendering/{sprockets_renderer → bundle_renderer}/console_polyfill.js +0 -0
- 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/{sprockets_renderer → bundle_renderer}/timeout_polyfill.js +0 -0
- data/lib/react/server_rendering/exec_js_renderer.rb +4 -1
- data/lib/react/server_rendering/webpacker_manifest_container.rb +34 -0
- data/lib/react/server_rendering/yaml_manifest_container.rb +1 -1
- metadata +16 -16
- 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/generators/react/ujs_generator.rb +0 -44
@@ -11,13 +11,70 @@ module React
|
|
11
11
|
default: false,
|
12
12
|
desc: 'Skip Git keeps'
|
13
13
|
|
14
|
+
class_option :skip_server_rendering,
|
15
|
+
type: :boolean,
|
16
|
+
default: false,
|
17
|
+
desc: "Don't generate server_rendering.js or config/initializers/react_server_rendering.rb"
|
18
|
+
|
19
|
+
# Make an empty `components/` directory in the right place:
|
14
20
|
def create_directory
|
15
|
-
|
16
|
-
|
21
|
+
components_dir = if webpacker?
|
22
|
+
Pathname.new(javascript_dir).parent.to_s
|
23
|
+
else
|
24
|
+
javascript_dir
|
25
|
+
end
|
26
|
+
empty_directory File.join(components_dir, 'components')
|
27
|
+
if !options[:skip_git]
|
28
|
+
create_file File.join(components_dir, 'components/.gitkeep')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add requires, setup UJS
|
33
|
+
def setup_react
|
34
|
+
if webpacker?
|
35
|
+
setup_react_webpacker
|
36
|
+
else
|
37
|
+
setup_react_sprockets
|
38
|
+
end
|
17
39
|
end
|
18
40
|
|
19
|
-
def
|
20
|
-
|
41
|
+
def create_server_rendering
|
42
|
+
if options[:skip_server_rendering]
|
43
|
+
return
|
44
|
+
elsif webpacker?
|
45
|
+
ssr_manifest_path = File.join(javascript_dir, "server_rendering.js")
|
46
|
+
template("server_rendering_pack.js", ssr_manifest_path)
|
47
|
+
else
|
48
|
+
ssr_manifest_path = File.join(javascript_dir, "server_rendering.js")
|
49
|
+
template("server_rendering.js", ssr_manifest_path)
|
50
|
+
initializer_path = "config/initializers/react_server_rendering.rb"
|
51
|
+
template("react_server_rendering.rb", initializer_path)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def webpacker?
|
58
|
+
!!defined?(Webpacker)
|
59
|
+
end
|
60
|
+
|
61
|
+
def javascript_dir
|
62
|
+
if webpacker?
|
63
|
+
Webpacker::Configuration.source_path
|
64
|
+
.join(Webpacker::Configuration.entry_path)
|
65
|
+
.relative_path_from(::Rails.root)
|
66
|
+
.to_s
|
67
|
+
else
|
68
|
+
'app/assets/javascripts'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def manifest
|
73
|
+
Pathname.new(destination_root).join(javascript_dir, 'application.js')
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup_react_sprockets
|
77
|
+
require_react = "//= require react\n//= require react_ujs\n//= require components\n"
|
21
78
|
|
22
79
|
if manifest.exist?
|
23
80
|
manifest_contents = File.read(manifest)
|
@@ -32,26 +89,27 @@ module React
|
|
32
89
|
else
|
33
90
|
create_file manifest, require_react
|
34
91
|
end
|
35
|
-
end
|
36
92
|
|
37
|
-
def inject_components
|
38
|
-
inject_into_file manifest, "//= require components\n", {after: "//= require react\n"}
|
39
|
-
end
|
40
|
-
|
41
|
-
def inject_react_ujs
|
42
|
-
inject_into_file manifest, "//= require react_ujs\n", {after: "//= require react\n"}
|
43
|
-
end
|
44
|
-
|
45
|
-
def create_components
|
46
93
|
components_js = "//= require_tree ./components\n"
|
47
|
-
components_file = File.join(
|
94
|
+
components_file = File.join(javascript_dir, "components.js")
|
48
95
|
create_file components_file, components_js
|
49
96
|
end
|
50
97
|
|
51
|
-
|
98
|
+
WEBPACKER_SETUP_UJS = <<-JS
|
99
|
+
// Support component names relative to this directory:
|
100
|
+
var componentRequireContext = require.context("components", true)
|
101
|
+
var ReactRailsUJS = require("react_ujs")
|
102
|
+
ReactRailsUJS.useContext(componentRequireContext)
|
103
|
+
JS
|
52
104
|
|
53
|
-
def
|
54
|
-
|
105
|
+
def setup_react_webpacker
|
106
|
+
yarn_binstub = File.expand_path("./bin/yarn", ::Rails.root)
|
107
|
+
`#{yarn_binstub} add react_ujs`
|
108
|
+
if manifest.exist?
|
109
|
+
append_file(manifest, WEBPACKER_SETUP_UJS)
|
110
|
+
else
|
111
|
+
create_file(manifest, WEBPACKER_SETUP_UJS)
|
112
|
+
end
|
55
113
|
end
|
56
114
|
end
|
57
115
|
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
// By default, this pack is loaded for server-side rendering.
|
2
|
+
// It must expose react_ujs as `ReactRailsUJS` and prepare a require context.
|
3
|
+
var componentRequireContext = require.context("components", true)
|
4
|
+
var ReactRailsUJS = require("react_ujs")
|
5
|
+
ReactRailsUJS.useContext(componentRequireContext)
|
data/lib/react/jsx.rb
CHANGED
@@ -17,6 +17,8 @@ module React
|
|
17
17
|
# - #transform(code) => new code
|
18
18
|
self.transformer_class = DEFAULT_TRANSFORMER
|
19
19
|
|
20
|
+
# @param code [String] JSX code to transform into JavaScript
|
21
|
+
# @return [String] plain, browser-ready JavaScript code
|
20
22
|
def self.transform(code)
|
21
23
|
self.transformer ||= transformer_class.new(transform_options)
|
22
24
|
self.transformer.transform(code)
|
@@ -11,12 +11,13 @@ module React
|
|
11
11
|
attr_accessor :output_buffer
|
12
12
|
mattr_accessor :camelize_props_switch
|
13
13
|
|
14
|
-
# ControllerLifecycle calls these hooks
|
14
|
+
# {ControllerLifecycle} calls these hooks
|
15
15
|
# You can use them in custom helper implementations
|
16
|
-
def setup(
|
16
|
+
def setup(controller)
|
17
|
+
@controller = controller
|
17
18
|
end
|
18
19
|
|
19
|
-
def teardown(
|
20
|
+
def teardown(controller)
|
20
21
|
end
|
21
22
|
|
22
23
|
# Render a UJS-type HTML tag annotated with data attributes, which
|
@@ -30,7 +31,7 @@ module React
|
|
30
31
|
|
31
32
|
prerender_options = options[:prerender]
|
32
33
|
if prerender_options
|
33
|
-
block = Proc.new{ concat
|
34
|
+
block = Proc.new{ concat(prerender_component(name, props, prerender_options)) }
|
34
35
|
end
|
35
36
|
|
36
37
|
html_options = options.reverse_merge(:data => {})
|
@@ -45,7 +46,24 @@ module React
|
|
45
46
|
# remove internally used properties so they aren't rendered to DOM
|
46
47
|
html_options.except!(:tag, :prerender, :camelize_props)
|
47
48
|
|
48
|
-
content_tag(html_tag, '', html_options, &block)
|
49
|
+
rendered_tag = content_tag(html_tag, '', html_options, &block)
|
50
|
+
if React::ServerRendering.renderer_options[:replay_console]
|
51
|
+
# Grab the server-rendered console replay script
|
52
|
+
# and move it _outside_ the container div
|
53
|
+
rendered_tag.sub!(/\n(<script class="react-rails-console-replay">.*<\/script>)<\/(\w+)>$/m,'</\2>\1')
|
54
|
+
rendered_tag.html_safe
|
55
|
+
else
|
56
|
+
rendered_tag
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# If this controller has checked out a renderer, use that one.
|
63
|
+
# Otherwise, use {React::ServerRendering} directly (which will check one out for this rendering).
|
64
|
+
def prerender_component(component_name, props, prerender_options)
|
65
|
+
renderer = @controller.try(:react_rails_prerenderer) || React::ServerRendering
|
66
|
+
renderer.render(component_name, props, prerender_options)
|
49
67
|
end
|
50
68
|
end
|
51
69
|
end
|
@@ -1,25 +1,53 @@
|
|
1
1
|
module React
|
2
2
|
module Rails
|
3
|
+
# This module is included into ActionController so that
|
4
|
+
# per-request hooks can be called in the view helper.
|
3
5
|
module ControllerLifecycle
|
4
6
|
extend ActiveSupport::Concern
|
5
7
|
|
6
8
|
included do
|
7
9
|
# use both names to support Rails 3..5
|
8
|
-
|
9
|
-
|
10
|
-
public_send(before_action_with_fallback, :setup_react_component_helper)
|
11
|
-
public_send(after_action_with_fallback, :teardown_react_component_helper)
|
10
|
+
around_action_with_fallback = respond_to?(:around_action) ? :around_action : :around_filter
|
11
|
+
public_send(around_action_with_fallback, :use_react_component_helper)
|
12
12
|
attr_reader :__react_component_helper
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
module ClassMethods
|
16
|
+
# Call this in the controller to check out a prerender for the whole request.
|
17
|
+
# You can access the renderer with {#react_rails_prerenderer}.
|
18
|
+
def per_request_react_rails_prerenderer
|
19
|
+
around_action_with_fallback = respond_to?(:around_action) ? :around_action : :around_filter
|
20
|
+
public_send(around_action_with_fallback, :per_request_react_rails_prerenderer)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Instantiate the ViewHelper implementation and call its #setup method
|
25
|
+
# then let the controller action run,
|
26
|
+
# then call the ViewHelper implementation's #teardown method
|
27
|
+
def use_react_component_helper
|
16
28
|
new_helper = React::Rails::ViewHelper.helper_implementation_class.new
|
17
29
|
new_helper.setup(self)
|
18
30
|
@__react_component_helper = new_helper
|
31
|
+
yield
|
32
|
+
@__react_component_helper.teardown(self)
|
19
33
|
end
|
20
34
|
|
21
|
-
|
22
|
-
|
35
|
+
# If you want a per-request renderer, add this method as an around-action
|
36
|
+
#
|
37
|
+
# (`.per_request_react_rails_prerenderer` does this for you)
|
38
|
+
# @example Having one renderer instance for each controller action
|
39
|
+
# around_action :per_request_react_rails_prerenderer
|
40
|
+
def per_request_react_rails_prerenderer
|
41
|
+
React::ServerRendering.with_renderer do |renderer|
|
42
|
+
@__react_rails_prerenderer = renderer
|
43
|
+
yield
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# An instance of a server renderer, for use during this request
|
49
|
+
def react_rails_prerenderer
|
50
|
+
@__react_rails_prerenderer
|
23
51
|
end
|
24
52
|
end
|
25
53
|
end
|
data/lib/react/rails/railtie.rb
CHANGED
@@ -15,14 +15,27 @@ module React
|
|
15
15
|
# Server rendering:
|
16
16
|
config.react.server_renderer_pool_size = 1 # increase if you're on JRuby
|
17
17
|
config.react.server_renderer_timeout = 20 # seconds
|
18
|
-
config.react.server_renderer = nil # defaults to
|
19
|
-
config.react.server_renderer_options = {} #
|
18
|
+
config.react.server_renderer = nil # defaults to BundleRenderer
|
19
|
+
config.react.server_renderer_options = {} # BundleRenderer provides defaults
|
20
|
+
# Changing files with these extensions in these directories will cause the server renderer to reload:
|
21
|
+
config.react.server_renderer_directories = ["/app/assets/javascripts/", "app/javascript"]
|
22
|
+
config.react.server_renderer_extensions = ["jsx", "js"]
|
20
23
|
# View helper implementation:
|
21
24
|
config.react.view_helper_implementation = nil # Defaults to ComponentMount
|
22
25
|
|
23
26
|
# Watch .jsx files for changes in dev, so we can reload the JS VMs with the new JS code.
|
24
27
|
initializer "react_rails.add_watchable_files", group: :all do |app|
|
25
|
-
|
28
|
+
# Watch files ending in `server_renderer_extensions` in each of `server_renderer_directories`
|
29
|
+
reload_paths = config.react.server_renderer_directories.reduce({}) do |memo, dir|
|
30
|
+
app_dir = File.join(app.root, dir)
|
31
|
+
memo[app_dir] = config.react.server_renderer_extensions
|
32
|
+
memo
|
33
|
+
end
|
34
|
+
|
35
|
+
# Rails checks these objects for changes:
|
36
|
+
app.reloaders << app.config.file_watcher.new([], reload_paths)
|
37
|
+
# Reload renderers in dev when files change
|
38
|
+
config.to_prepare { React::ServerRendering.reset_pool }
|
26
39
|
end
|
27
40
|
|
28
41
|
# Include the react-rails view helper lazily
|
@@ -81,20 +94,13 @@ module React
|
|
81
94
|
|
82
95
|
config.after_initialize do |app|
|
83
96
|
# The class isn't accessible in the configure block, so assign it here if it wasn't overridden:
|
84
|
-
app.config.react.server_renderer ||= React::ServerRendering::
|
97
|
+
app.config.react.server_renderer ||= React::ServerRendering::BundleRenderer
|
85
98
|
|
86
99
|
React::ServerRendering.pool_size = app.config.react.server_renderer_pool_size
|
87
100
|
React::ServerRendering.pool_timeout = app.config.react.server_renderer_timeout
|
88
101
|
React::ServerRendering.renderer_options = app.config.react.server_renderer_options
|
89
102
|
React::ServerRendering.renderer = app.config.react.server_renderer
|
90
|
-
|
91
103
|
React::ServerRendering.reset_pool
|
92
|
-
# Reload renderers in dev when files change
|
93
|
-
if Gem::Version.new(::Rails::VERSION::STRING) >= Gem::Version.new("5.x")
|
94
|
-
ActiveSupport::Reloader.to_prepare { React::ServerRendering.reset_pool }
|
95
|
-
else
|
96
|
-
ActionDispatch::Reloader.to_prepare { React::ServerRendering.reset_pool }
|
97
|
-
end
|
98
104
|
end
|
99
105
|
|
100
106
|
initializer "react_rails.setup_engine", :group => :all do |app|
|
data/lib/react/rails/version.rb
CHANGED
@@ -1,27 +1,39 @@
|
|
1
1
|
require 'connection_pool'
|
2
2
|
require 'react/server_rendering/exec_js_renderer'
|
3
|
-
require 'react/server_rendering/
|
3
|
+
require 'react/server_rendering/bundle_renderer'
|
4
4
|
|
5
5
|
module React
|
6
6
|
module ServerRendering
|
7
7
|
mattr_accessor :renderer, :renderer_options,
|
8
8
|
:pool_size, :pool_timeout
|
9
9
|
|
10
|
+
self.renderer_options = {}
|
11
|
+
|
12
|
+
# Discard the old ConnectionPool & create a new one.
|
13
|
+
# This will clear all state such as loaded code, JS VM state, or options.
|
14
|
+
# @return [void]
|
10
15
|
def self.reset_pool
|
11
16
|
options = {size: pool_size, timeout: pool_timeout}
|
12
|
-
@@pool = ConnectionPool.new(options) {
|
17
|
+
@@pool = ConnectionPool.new(options) { self.renderer.new(self.renderer_options) }
|
13
18
|
end
|
14
19
|
|
20
|
+
# Check a renderer out of the pool and use it to render the component.
|
21
|
+
# @param component_name [String] Component identifier, looked up by UJS
|
22
|
+
# @param props [String, Hash] Props for this component
|
23
|
+
# @param prerender_options [Hash] Renderer-specific options
|
24
|
+
# @return [String] Prerendered HTML from `component_name`
|
15
25
|
def self.render(component_name, props, prerender_options)
|
16
26
|
@@pool.with do |renderer|
|
17
27
|
renderer.render(component_name, props, prerender_options)
|
18
28
|
end
|
19
29
|
end
|
20
30
|
|
21
|
-
|
22
|
-
|
31
|
+
# Yield a renderer for an arbitrary block
|
32
|
+
def self.with_renderer
|
33
|
+
@@pool.with { |renderer| yield(renderer) }
|
23
34
|
end
|
24
35
|
|
36
|
+
# Raised when something went wrong during server rendering.
|
25
37
|
class PrerenderError < RuntimeError
|
26
38
|
def initialize(component_name, props, js_message)
|
27
39
|
message = ["Encountered error \"#{js_message.inspect}\" when prerendering #{component_name} with #{props}",
|
@@ -1,22 +1,24 @@
|
|
1
1
|
require "react/server_rendering/environment_container"
|
2
2
|
require "react/server_rendering/manifest_container"
|
3
|
+
require "react/server_rendering/webpacker_manifest_container"
|
3
4
|
require "react/server_rendering/yaml_manifest_container"
|
4
5
|
|
5
6
|
module React
|
6
7
|
module ServerRendering
|
7
8
|
# Extends ExecJSRenderer for the Rails environment
|
8
|
-
# -
|
9
|
+
# - fetches JS code from the Rails app (webpacker or sprockets)
|
9
10
|
# - stringifies props
|
10
11
|
# - implements console replay
|
11
|
-
class
|
12
|
+
class BundleRenderer < ExecJSRenderer
|
12
13
|
# Reimplement console methods for replaying on the client
|
13
|
-
CONSOLE_POLYFILL = File.read(File.join(File.dirname(__FILE__), "
|
14
|
-
CONSOLE_REPLAY = File.read(File.join(File.dirname(__FILE__), "
|
15
|
-
|
14
|
+
CONSOLE_POLYFILL = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/console_polyfill.js"))
|
15
|
+
CONSOLE_REPLAY = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/console_replay.js"))
|
16
|
+
CONSOLE_RESET = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/console_reset.js"))
|
17
|
+
TIMEOUT_POLYFILL = File.read(File.join(File.dirname(__FILE__), "bundle_renderer/timeout_polyfill.js"))
|
16
18
|
|
17
19
|
def initialize(options={})
|
18
20
|
@replay_console = options.fetch(:replay_console, true)
|
19
|
-
filenames = options.fetch(:files, ["
|
21
|
+
filenames = options.fetch(:files, ["server_rendering.js"])
|
20
22
|
js_code = CONSOLE_POLYFILL.dup
|
21
23
|
js_code << TIMEOUT_POLYFILL.dup
|
22
24
|
js_code << options.fetch(:code, '')
|
@@ -38,6 +40,10 @@ module React
|
|
38
40
|
super(component_name, t_props, t_options)
|
39
41
|
end
|
40
42
|
|
43
|
+
def before_render(component_name, props, prerender_options)
|
44
|
+
@replay_console ? CONSOLE_RESET : ""
|
45
|
+
end
|
46
|
+
|
41
47
|
def after_render(component_name, props, prerender_options)
|
42
48
|
@replay_console ? CONSOLE_REPLAY : ""
|
43
49
|
end
|
@@ -53,23 +59,11 @@ module React
|
|
53
59
|
# default Rails setups.
|
54
60
|
#
|
55
61
|
# You can provide a custom asset container
|
56
|
-
# with `React::ServerRendering::
|
62
|
+
# with `React::ServerRendering::BundleRenderer.asset_container_class = MyAssetContainer`.
|
57
63
|
#
|
58
64
|
# @return [#find_asset(logical_path)] An object that returns asset contents by logical path
|
59
65
|
def asset_container
|
60
|
-
@asset_container ||=
|
61
|
-
self.class.asset_container_class.new
|
62
|
-
elsif assets_precompiled? && ManifestContainer.compatible?
|
63
|
-
ManifestContainer.new
|
64
|
-
elsif assets_precompiled? && YamlManifestContainer.compatible?
|
65
|
-
YamlManifestContainer.new
|
66
|
-
else
|
67
|
-
EnvironmentContainer.new
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def assets_precompiled?
|
72
|
-
!::Rails.application.config.assets.compile
|
66
|
+
@asset_container ||= asset_container_class.new
|
73
67
|
end
|
74
68
|
|
75
69
|
private
|
@@ -97,6 +91,32 @@ module React
|
|
97
91
|
def prepare_props(props)
|
98
92
|
props.is_a?(String) ? props : props.to_json
|
99
93
|
end
|
94
|
+
|
95
|
+
def assets_precompiled?
|
96
|
+
!::Rails.application.config.assets.compile
|
97
|
+
end
|
98
|
+
|
99
|
+
# Detect what kind of asset system is in use and choose that container.
|
100
|
+
# Or, if the user has provided {.asset_container_class}, use that.
|
101
|
+
# @return [Class] suitable for {#asset_container}
|
102
|
+
def asset_container_class
|
103
|
+
if self.class.asset_container_class.present?
|
104
|
+
self.class.asset_container_class
|
105
|
+
elsif WebpackerManifestContainer.compatible?
|
106
|
+
WebpackerManifestContainer
|
107
|
+
elsif assets_precompiled?
|
108
|
+
if ManifestContainer.compatible?
|
109
|
+
ManifestContainer
|
110
|
+
elsif YamlManifestContainer.compatible?
|
111
|
+
YamlManifestContainer
|
112
|
+
else
|
113
|
+
# Even though they are precompiled, we can't find them :S
|
114
|
+
EnvironmentContainer
|
115
|
+
end
|
116
|
+
else
|
117
|
+
EnvironmentContainer
|
118
|
+
end
|
119
|
+
end
|
100
120
|
end
|
101
121
|
end
|
102
122
|
end
|