hyper-react 0.99.6 → 1.0.0.lap21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.codeclimate.yml +27 -0
- data/.gitignore +30 -37
- data/.rubocop.yml +1159 -0
- data/.travis.yml +32 -0
- data/Appraisals +31 -0
- data/CHANGELOG.md +143 -0
- data/DOCS.md +1515 -0
- data/Gemfile +2 -5
- data/LICENSE +19 -0
- data/README.md +5 -33
- data/Rakefile +25 -6
- data/UPGRADING.md +24 -0
- data/component-name-lookup.md +145 -0
- data/dciy.toml +3 -0
- data/dciy_prepare.sh +8 -0
- data/dciy_run.sh +10 -0
- data/hyper-react.gemspec +24 -18
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +117 -0
- data/lib/hyper-react.rb +66 -4
- data/lib/rails-helpers/top_level_rails_component.rb +75 -0
- data/lib/react/api.rb +203 -0
- data/lib/react/callbacks.rb +41 -0
- data/lib/react/children.rb +30 -0
- data/lib/react/component.rb +177 -0
- data/lib/react/component/api.rb +69 -0
- data/lib/react/component/base.rb +13 -0
- data/lib/react/component/class_methods.rb +181 -0
- data/lib/react/component/dsl_instance_methods.rb +23 -0
- data/lib/react/component/params.rb +6 -0
- data/lib/react/component/props_wrapper.rb +78 -0
- data/lib/react/component/should_component_update.rb +99 -0
- data/lib/react/component/tags.rb +108 -0
- data/lib/react/config.rb +5 -0
- data/lib/react/config/client.rb.erb +19 -0
- data/lib/react/config/server.rb +23 -0
- data/lib/react/element.rb +150 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/opal-jquery/element.rb +26 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/hash.rb +13 -0
- data/lib/react/native_library.rb +87 -0
- data/lib/react/object.rb +15 -0
- data/lib/react/react-source-browser.rb +3 -0
- data/lib/react/react-source-server.rb +3 -0
- data/lib/react/react-source.rb +16 -0
- data/lib/react/ref_callback.rb +31 -0
- data/lib/react/rendering_context.rb +146 -0
- data/lib/react/server.rb +19 -0
- data/lib/react/state_wrapper.rb +23 -0
- data/lib/react/test.rb +16 -0
- data/lib/react/test/dsl.rb +17 -0
- data/lib/react/test/matchers/render_html_matcher.rb +56 -0
- data/lib/react/test/rspec.rb +15 -0
- data/lib/react/test/session.rb +37 -0
- data/lib/react/test/utils.rb +71 -0
- data/lib/react/top_level.rb +110 -0
- data/lib/react/top_level_render.rb +28 -0
- data/lib/react/validator.rb +136 -0
- data/lib/reactive-ruby/component_loader.rb +43 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +235 -0
- data/lib/reactive-ruby/rails.rb +8 -0
- data/lib/reactive-ruby/rails/component_mount.rb +48 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
- data/lib/reactive-ruby/rails/railtie.rb +20 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +41 -0
- data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/lib/reactrb/auto-import.rb +27 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- data/spec/controller_helper_spec.rb +35 -0
- data/spec/index.html.erb +11 -0
- data/spec/react/callbacks_spec.rb +142 -0
- data/spec/react/children_spec.rb +132 -0
- data/spec/react/component/base_spec.rb +36 -0
- data/spec/react/component_spec.rb +1073 -0
- data/spec/react/dsl_spec.rb +323 -0
- data/spec/react/element_spec.rb +132 -0
- data/spec/react/event_spec.rb +39 -0
- data/spec/react/native_library_spec.rb +387 -0
- data/spec/react/observable_spec.rb +31 -0
- data/spec/react/opal_jquery_extensions_spec.rb +68 -0
- data/spec/react/param_declaration_spec.rb +253 -0
- data/spec/react/react_spec.rb +278 -0
- data/spec/react/refs_callback_spec.rb +65 -0
- data/spec/react/server_spec.rb +25 -0
- data/spec/react/state_spec.rb +52 -0
- data/spec/react/test/dsl_spec.rb +43 -0
- data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
- data/spec/react/test/rspec_spec.rb +62 -0
- data/spec/react/test/session_spec.rb +88 -0
- data/spec/react/test/utils_spec.rb +28 -0
- data/spec/react/top_level_component_spec.rb +103 -0
- data/spec/react/tutorial/tutorial_spec.rb +42 -0
- data/spec/react/validator_spec.rb +134 -0
- data/spec/reactive-ruby/component_loader_spec.rb +74 -0
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +157 -0
- data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +17 -0
- data/spec/reactive-ruby/rails/component_mount_spec.rb +64 -0
- data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +39 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/test_app/README.md +24 -0
- data/spec/test_app/Rakefile +6 -0
- data/spec/test_app/app/assets/config/manifest.js +3 -0
- data/spec/test_app/app/assets/images/.keep +0 -0
- data/spec/test_app/app/assets/javascripts/application.rb +7 -0
- data/spec/test_app/app/assets/javascripts/cable.js +13 -0
- data/spec/test_app/app/assets/javascripts/channels/.keep +0 -0
- data/spec/test_app/app/assets/javascripts/server_rendering.js +5 -0
- data/spec/test_app/app/assets/stylesheets/application.css +15 -0
- data/spec/test_app/app/channels/application_cable/channel.rb +4 -0
- data/spec/test_app/app/channels/application_cable/connection.rb +4 -0
- data/spec/test_app/app/controllers/application_controller.rb +3 -0
- data/spec/test_app/app/controllers/concerns/.keep +0 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/jobs/application_job.rb +2 -0
- data/spec/test_app/app/mailers/application_mailer.rb +4 -0
- data/spec/test_app/app/models/application_record.rb +3 -0
- data/spec/test_app/app/models/concerns/.keep +0 -0
- data/spec/test_app/app/views/components.rb +11 -0
- data/spec/test_app/app/views/components/hello_world.rb +11 -0
- data/spec/test_app/app/views/components/todo.rb +14 -0
- data/spec/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/test_app/app/views/layouts/explicit_layout.html.erb +0 -0
- data/spec/test_app/app/views/layouts/mailer.html.erb +13 -0
- data/spec/test_app/app/views/layouts/mailer.text.erb +1 -0
- data/spec/test_app/app/views/layouts/test_layout.html.erb +0 -0
- data/spec/test_app/bin/bundle +3 -0
- data/spec/test_app/bin/rails +4 -0
- data/spec/test_app/bin/rake +4 -0
- data/spec/test_app/bin/setup +38 -0
- data/spec/test_app/bin/update +29 -0
- data/spec/test_app/bin/yarn +11 -0
- data/spec/test_app/config.ru +5 -0
- data/spec/test_app/config/application.rb +45 -0
- data/spec/test_app/config/boot.rb +6 -0
- data/spec/test_app/config/cable.yml +10 -0
- data/spec/test_app/config/database.yml +25 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +54 -0
- data/spec/test_app/config/environments/production.rb +91 -0
- data/spec/test_app/config/environments/test.rb +42 -0
- data/spec/test_app/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/test_app/config/initializers/assets.rb +14 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/cookies_serializer.rb +5 -0
- data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/test_app/config/initializers/inflections.rb +16 -0
- data/spec/test_app/config/initializers/mime_types.rb +4 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +33 -0
- data/spec/test_app/config/puma.rb +56 -0
- data/spec/test_app/config/routes.rb +3 -0
- data/spec/test_app/config/secrets.yml +32 -0
- data/spec/test_app/config/spring.rb +6 -0
- data/spec/test_app/db/development.sqlite3 +0 -0
- data/spec/test_app/db/schema.rb +15 -0
- data/spec/test_app/db/seeds.rb +7 -0
- data/spec/test_app/db/test.sqlite3 +0 -0
- data/spec/test_app/lib/assets/.keep +0 -0
- data/spec/test_app/log/.keep +0 -0
- data/spec/test_app/package.json +5 -0
- data/spec/test_app/public/404.html +67 -0
- data/spec/test_app/public/422.html +67 -0
- data/spec/test_app/public/500.html +66 -0
- data/spec/test_app/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/test_app/public/apple-touch-icon.png +0 -0
- data/spec/test_app/public/favicon.ico +0 -0
- data/spec/vendor/es5-shim.min.js +7 -0
- data/spec/vendor/jquery-2.2.4.min.js +4 -0
- metadata +401 -61
- data/CODE_OF_CONDUCT.md +0 -49
- data/lib/react/version.rb +0 -3
@@ -0,0 +1,43 @@
|
|
1
|
+
module ReactiveRuby
|
2
|
+
class ComponentLoader
|
3
|
+
attr_reader :v8_context
|
4
|
+
private :v8_context
|
5
|
+
|
6
|
+
def initialize(v8_context)
|
7
|
+
unless v8_context
|
8
|
+
raise ArgumentError.new('Could not obtain ExecJS runtime context')
|
9
|
+
end
|
10
|
+
@v8_context = v8_context
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(file = components)
|
14
|
+
return true if loaded?
|
15
|
+
!!v8_context.eval(opal(file))
|
16
|
+
end
|
17
|
+
|
18
|
+
def load!(file = components)
|
19
|
+
return true if loaded?
|
20
|
+
self.load(file)
|
21
|
+
ensure
|
22
|
+
raise "No HyperReact components found in #{components}" unless loaded?
|
23
|
+
end
|
24
|
+
|
25
|
+
def loaded?
|
26
|
+
!!v8_context.eval('Opal.React !== undefined')
|
27
|
+
rescue ::ExecJS::Error
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def components
|
34
|
+
opts = ::Rails.configuration.react.server_renderer_options
|
35
|
+
return opts[:files].first.gsub(/.js$/,'') if opts && opts[:files]
|
36
|
+
'components'
|
37
|
+
end
|
38
|
+
|
39
|
+
def opal(file)
|
40
|
+
Opal::Sprockets.load_asset(file)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require "react/config"
|
2
|
+
|
3
|
+
module React
|
4
|
+
module IsomorphicHelpers
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
if RUBY_ENGINE != 'opal'
|
10
|
+
def self.load_context(ctx, controller, name = nil)
|
11
|
+
@context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller, name)
|
12
|
+
@context.load_opal_context
|
13
|
+
::Rails.logger.debug "************************** React Server Context Initialized #{name} #{Time.now.to_f} *********************************************"
|
14
|
+
@context
|
15
|
+
end
|
16
|
+
else
|
17
|
+
def self.load_context(unique_id = nil, name = nil)
|
18
|
+
# can be called on the client to force re-initialization for testing purposes
|
19
|
+
if !unique_id || !@context || @context.unique_id != unique_id
|
20
|
+
if on_opal_server?
|
21
|
+
`console.history = []` rescue nil
|
22
|
+
message = "************************ React Prerendering Context Initialized #{name} ***********************"
|
23
|
+
else
|
24
|
+
message = "************************ React Browser Context Initialized ****************************"
|
25
|
+
end
|
26
|
+
log(message)
|
27
|
+
@context = Context.new(unique_id)
|
28
|
+
end
|
29
|
+
@context
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.context
|
34
|
+
@context
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.log(message, message_type = :info)
|
38
|
+
message = [message] unless message.is_a? Array
|
39
|
+
|
40
|
+
is_production = React::Config.config[:environment] == 'production'
|
41
|
+
|
42
|
+
if (message_type == :info || message_type == :warning) && is_production
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
if message_type == :info
|
47
|
+
if on_opal_server?
|
48
|
+
style = 'background: #00FFFF; color: red'
|
49
|
+
else
|
50
|
+
style = 'background: #222; color: #bada55'
|
51
|
+
end
|
52
|
+
message = ["%c" + message[0], style]+message[1..-1]
|
53
|
+
`console.log.apply(console, message)`
|
54
|
+
elsif message_type == :warning
|
55
|
+
`console.warn.apply(console, message)`
|
56
|
+
else
|
57
|
+
`console.error.apply(console, message)`
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if RUBY_ENGINE != 'opal'
|
62
|
+
def self.on_opal_server?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.on_opal_client?
|
67
|
+
false
|
68
|
+
end
|
69
|
+
else
|
70
|
+
def self.on_opal_server?
|
71
|
+
`typeof Opal.global.document === 'undefined'`
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.on_opal_client?
|
75
|
+
!on_opal_server?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def log(*args)
|
80
|
+
IsomorphicHelpers.log(*args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def on_opal_server?
|
84
|
+
self.class.on_opal_server?
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_opal_client?
|
88
|
+
self.class.on_opal_client?
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.prerender_footers(controller = nil)
|
92
|
+
footer = Context.prerender_footer_blocks.collect { |block| block.call controller }.join("\n")
|
93
|
+
if RUBY_ENGINE != 'opal'
|
94
|
+
footer = (footer + @context.send_to_opal(:prerender_footers).to_s) if @context
|
95
|
+
footer = footer.html_safe
|
96
|
+
end
|
97
|
+
footer
|
98
|
+
end
|
99
|
+
|
100
|
+
class Context
|
101
|
+
attr_reader :controller
|
102
|
+
attr_reader :unique_id
|
103
|
+
|
104
|
+
def self.define_isomorphic_method(method_name, &block)
|
105
|
+
@@ctx_methods ||= {}
|
106
|
+
@@ctx_methods[method_name] = block
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.before_first_mount_blocks
|
110
|
+
@before_first_mount_blocks ||= []
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.prerender_footer_blocks
|
114
|
+
@prerender_footer_blocks ||= []
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize(unique_id, ctx = nil, controller = nil, cname = nil)
|
118
|
+
@unique_id = unique_id
|
119
|
+
@cname = cname
|
120
|
+
if RUBY_ENGINE != 'opal'
|
121
|
+
@controller = controller
|
122
|
+
@ctx = ctx
|
123
|
+
if defined? @@ctx_methods
|
124
|
+
@@ctx_methods.each do |method_name, block|
|
125
|
+
@ctx.attach("ServerSideIsomorphicMethod.#{method_name}", proc{|args| block.call(args.to_json)})
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
Hyperloop::Application::Boot.run(context: self)
|
130
|
+
self.class.before_first_mount_blocks.each { |block| block.call(self) }
|
131
|
+
end
|
132
|
+
|
133
|
+
def load_opal_context
|
134
|
+
send_to_opal(:load_context, @unique_id, @cname)
|
135
|
+
end
|
136
|
+
|
137
|
+
def eval(js)
|
138
|
+
@ctx.eval(js) if @ctx
|
139
|
+
end
|
140
|
+
|
141
|
+
def send_to_opal(method_name, *args)
|
142
|
+
return unless @ctx
|
143
|
+
args = [1] if args.length == 0
|
144
|
+
::ReactiveRuby::ComponentLoader.new(@ctx).load!
|
145
|
+
method_args = args.collect do |arg|
|
146
|
+
quarg = "#{arg}".tr('"', "'")
|
147
|
+
"\"#{quarg}\""
|
148
|
+
end.join(', ')
|
149
|
+
@ctx.eval("Opal.React.$const_get('IsomorphicHelpers').$#{method_name}(#{method_args})")
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.register_before_first_mount_block(&block)
|
153
|
+
before_first_mount_blocks << block
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.register_prerender_footer_block(&block)
|
157
|
+
prerender_footer_blocks << block
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class IsomorphicProcCall
|
162
|
+
|
163
|
+
attr_reader :context
|
164
|
+
|
165
|
+
def result
|
166
|
+
@result.first if @result
|
167
|
+
end
|
168
|
+
|
169
|
+
def initialize(name, block, context, *args)
|
170
|
+
@name = name
|
171
|
+
@context = context
|
172
|
+
block.call(self, *args)
|
173
|
+
@result ||= send_to_server(*args)
|
174
|
+
end
|
175
|
+
|
176
|
+
def when_on_client(&block)
|
177
|
+
@result = [block.call] if IsomorphicHelpers.on_opal_client?
|
178
|
+
end
|
179
|
+
|
180
|
+
def send_to_server(*args)
|
181
|
+
if IsomorphicHelpers.on_opal_server?
|
182
|
+
method_string = "ServerSideIsomorphicMethod." + @name + "(" + args.to_json + ")"
|
183
|
+
@result = [JSON.parse(`eval(method_string)`)]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def when_on_server(&block)
|
188
|
+
@result = [block.call.to_json] unless IsomorphicHelpers.on_opal_client? || IsomorphicHelpers.on_opal_server?
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
module ClassMethods
|
193
|
+
def on_opal_server?
|
194
|
+
IsomorphicHelpers.on_opal_server?
|
195
|
+
end
|
196
|
+
|
197
|
+
def on_opal_client?
|
198
|
+
IsomorphicHelpers.on_opal_client?
|
199
|
+
end
|
200
|
+
|
201
|
+
def log(*args)
|
202
|
+
IsomorphicHelpers.log(*args)
|
203
|
+
end
|
204
|
+
|
205
|
+
def controller
|
206
|
+
IsomorphicHelpers.context.controller
|
207
|
+
end
|
208
|
+
|
209
|
+
def before_first_mount(&block)
|
210
|
+
React::IsomorphicHelpers::Context.register_before_first_mount_block(&block)
|
211
|
+
end
|
212
|
+
|
213
|
+
def prerender_footer(&block)
|
214
|
+
React::IsomorphicHelpers::Context.register_prerender_footer_block(&block)
|
215
|
+
end
|
216
|
+
|
217
|
+
if RUBY_ENGINE != 'opal'
|
218
|
+
def isomorphic_method(name, &block)
|
219
|
+
React::IsomorphicHelpers::Context.send(:define_isomorphic_method, name) do |args_as_json|
|
220
|
+
React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, self, *JSON.parse(args_as_json)).result
|
221
|
+
end
|
222
|
+
end
|
223
|
+
else
|
224
|
+
require 'json'
|
225
|
+
|
226
|
+
def isomorphic_method(name, &block)
|
227
|
+
self.class.send(:define_method, name) do | *args |
|
228
|
+
React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, self, *args).result
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
require 'react-rails'
|
3
|
+
require 'reactive-ruby/server_rendering/hyper_asset_container'
|
4
|
+
require 'reactive-ruby/server_rendering/contextual_renderer'
|
5
|
+
require 'reactive-ruby/rails/component_mount'
|
6
|
+
require 'reactive-ruby/rails/railtie'
|
7
|
+
require 'reactive-ruby/rails/controller_helper'
|
8
|
+
require 'reactive-ruby/component_loader'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ReactiveRuby
|
2
|
+
module Rails
|
3
|
+
class ComponentMount < React::Rails::ComponentMount
|
4
|
+
attr_accessor :controller
|
5
|
+
|
6
|
+
def setup(controller)
|
7
|
+
self.controller = controller
|
8
|
+
end
|
9
|
+
|
10
|
+
def react_component(name, props = {}, options = {}, &block)
|
11
|
+
if options[:prerender] || [:on, 'on', true].include?(Hyperloop.prerendering)
|
12
|
+
options = context_initializer_options(options, name)
|
13
|
+
end
|
14
|
+
props = serialized_props(props, name, controller)
|
15
|
+
super(top_level_name, props, options, &block).gsub("\n","")
|
16
|
+
.gsub(/(<script>.*<\/script>)<\/div>$/,'</div>\1').html_safe +
|
17
|
+
footers
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def context_initializer_options(options, name)
|
23
|
+
options[:prerender] = {options[:prerender] => true} unless options[:prerender].is_a? Hash
|
24
|
+
existing_context_initializer = options[:prerender][:context_initializer]
|
25
|
+
|
26
|
+
options[:prerender][:context_initializer] = lambda do |ctx|
|
27
|
+
React::IsomorphicHelpers.load_context(ctx, controller, name)
|
28
|
+
existing_context_initializer.call(ctx) if existing_context_initializer
|
29
|
+
end
|
30
|
+
|
31
|
+
options
|
32
|
+
end
|
33
|
+
|
34
|
+
def serialized_props(props, name, controller)
|
35
|
+
{ render_params: props, component_name: name,
|
36
|
+
controller: controller.class.name.gsub(/Controller$/,"") }.react_serializer
|
37
|
+
end
|
38
|
+
|
39
|
+
def top_level_name
|
40
|
+
'React.TopLevelRailsComponent'
|
41
|
+
end
|
42
|
+
|
43
|
+
def footers
|
44
|
+
React::IsomorphicHelpers.prerender_footers(controller)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
# adds render_component helper to ActionControllers
|
5
|
+
class Base
|
6
|
+
def render_component(*args)
|
7
|
+
@component_name = (args[0].is_a? Hash) || args.empty? ? params[:action].camelize : args.shift
|
8
|
+
@render_params = args.shift || {}
|
9
|
+
options = args[0] || {}
|
10
|
+
render inline: '<%= react_component @component_name, @render_params %>',
|
11
|
+
layout: options.key?(:layout) ? options[:layout].to_s : :default
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ReactiveRuby
|
2
|
+
module Rails
|
3
|
+
class Railtie < ::Rails::Railtie
|
4
|
+
config.before_configuration do |app|
|
5
|
+
app.config.assets.enabled = true
|
6
|
+
app.config.assets.paths << ::Rails.root.join('app', 'views').to_s
|
7
|
+
app.config.react.server_renderer = ReactiveRuby::ServerRendering::ContextualRenderer
|
8
|
+
app.config.react.view_helper_implementation = ReactiveRuby::Rails::ComponentMount
|
9
|
+
ReactiveRuby::ServerRendering::ContextualRenderer.asset_container_class = ReactiveRuby::ServerRendering::HyperAssetContainer
|
10
|
+
end
|
11
|
+
config.after_initialize do
|
12
|
+
class ::HyperloopController < ::ApplicationController
|
13
|
+
def action_missing(name)
|
14
|
+
render_component
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
[Bignum, FalseClass, Fixnum, Float, Integer, NilClass, String, Symbol, Time, TrueClass].each do |klass|
|
2
|
+
klass.send(:define_method, :react_serializer) do
|
3
|
+
as_json
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
BigDecimal.send(:define_method, :react_serializer) { as_json } rescue nil
|
8
|
+
|
9
|
+
Array.send(:define_method, :react_serializer) do
|
10
|
+
self.collect { |e| e.react_serializer }.as_json
|
11
|
+
end
|
12
|
+
|
13
|
+
Hash.send(:define_method, :react_serializer) do
|
14
|
+
Hash[*self.collect { |key, value| [key, value.react_serializer] }.flatten(1)].as_json
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ReactiveRuby
|
2
|
+
module ServerRendering
|
3
|
+
def self.context_instance_name
|
4
|
+
'@context'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.context_instance_for(context)
|
8
|
+
context.instance_variable_get(context_instance_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
class ContextualRenderer < React::ServerRendering::BundleRenderer
|
12
|
+
def initialize(options = {})
|
13
|
+
super(options)
|
14
|
+
ComponentLoader.new(v8_context).load
|
15
|
+
end
|
16
|
+
|
17
|
+
def render(component_name, props, prerender_options)
|
18
|
+
if prerender_options.is_a?(Hash)
|
19
|
+
if !v8_runtime? && prerender_options[:context_initializer]
|
20
|
+
raise React::ServerRendering::PrerenderError.new(component_name, props, "you must use 'mini_racer' with the prerender[:context] option") unless v8_runtime?
|
21
|
+
else
|
22
|
+
prerender_options[:context_initializer].call v8_context
|
23
|
+
prerender_options = prerender_options[:static] ? :static : true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
super(component_name, props, prerender_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def v8_runtime?
|
33
|
+
ExecJS.runtime.name == 'mini_racer (V8)'
|
34
|
+
end
|
35
|
+
|
36
|
+
def v8_context
|
37
|
+
@v8_context ||= ReactiveRuby::ServerRendering.context_instance_for(@context)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'react/server_rendering/environment_container'
|
2
|
+
require 'react/server_rendering/manifest_container'
|
3
|
+
require 'react/server_rendering/webpacker_manifest_container'
|
4
|
+
|
5
|
+
module ReactiveRuby
|
6
|
+
module ServerRendering
|
7
|
+
class HyperTestAssetContainer
|
8
|
+
def find_asset(logical_path)
|
9
|
+
::Rails.cache.read(logical_path)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class HyperAssetContainer
|
14
|
+
def initialize
|
15
|
+
@ass_containers = []
|
16
|
+
if assets_precompiled?
|
17
|
+
@ass_containers << React::ServerRendering::ManifestContainer.new if React::ServerRendering::ManifestContainer.compatible?
|
18
|
+
else
|
19
|
+
@ass_containers << React::ServerRendering::EnvironmentContainer.new if ::Rails.application.assets
|
20
|
+
end
|
21
|
+
if React::ServerRendering::WebpackerManifestContainer.compatible?
|
22
|
+
@ass_containers << React::ServerRendering::WebpackerManifestContainer.new
|
23
|
+
end
|
24
|
+
@ass_containers << HyperTestAssetContainer.new if ::Rails.env.test?
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_asset(logical_path)
|
28
|
+
@ass_containers.each do |ass|
|
29
|
+
begin
|
30
|
+
asset = ass.find_asset(logical_path)
|
31
|
+
return asset if asset && asset != ''
|
32
|
+
rescue
|
33
|
+
next # no asset found, try the next container
|
34
|
+
end
|
35
|
+
end
|
36
|
+
raise "No asset found for #{logical_path}, tried: #{@ass_containers.map { |c| c.class.name }.join(', ')}"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def assets_precompiled?
|
42
|
+
!::Rails.application.config.assets.compile
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|