isomorfeus-react 16.13.11 → 16.13.12
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/README.md +3 -1
- data/lib/isomorfeus/react/config.rb +189 -189
- data/lib/isomorfeus/react/memcached_component_cache.rb +19 -19
- data/lib/isomorfeus/react/redis_component_cache.rb +19 -19
- data/lib/isomorfeus/react/thread_local_component_cache.rb +15 -15
- data/lib/isomorfeus/react_view_helper.rb +231 -231
- data/lib/isomorfeus/top_level.rb +103 -103
- data/lib/isomorfeus/top_level_ssr.rb +42 -42
- data/lib/isomorfeus-react-material-ui.rb +4 -4
- data/lib/isomorfeus-react-native.rb +5 -5
- data/lib/isomorfeus-react-paper.rb +4 -4
- data/lib/isomorfeus-react.rb +120 -120
- data/lib/isomorfeus_react/lucid_app/api.rb +26 -26
- data/lib/isomorfeus_react/lucid_app/base.rb +7 -7
- data/lib/isomorfeus_react/lucid_app/mixin.rb +23 -23
- data/lib/isomorfeus_react/lucid_app/native_component_constructor.rb +48 -48
- data/lib/isomorfeus_react/lucid_app/native_lucid_component_constructor.rb +94 -95
- data/lib/isomorfeus_react/lucid_component/api.rb +75 -75
- data/lib/isomorfeus_react/lucid_component/app_store_proxy.rb +37 -37
- data/lib/isomorfeus_react/lucid_component/base.rb +7 -7
- data/lib/isomorfeus_react/lucid_component/class_store_proxy.rb +44 -44
- data/lib/isomorfeus_react/lucid_component/initializer.rb +14 -14
- data/lib/isomorfeus_react/lucid_component/instance_store_proxy.rb +44 -44
- data/lib/isomorfeus_react/lucid_component/mixin.rb +22 -22
- data/lib/isomorfeus_react/lucid_component/native_component_constructor.rb +35 -35
- data/lib/isomorfeus_react/lucid_component/native_lucid_component_constructor.rb +82 -83
- data/lib/isomorfeus_react/lucid_component/styles_api.rb +34 -34
- data/lib/isomorfeus_react/lucid_func/base.rb +7 -7
- data/lib/isomorfeus_react/lucid_func/initializer.rb +11 -11
- data/lib/isomorfeus_react/lucid_func/mixin.rb +18 -18
- data/lib/isomorfeus_react/lucid_func/native_component_constructor.rb +81 -81
- data/lib/isomorfeus_react/react/function_component/api.rb +105 -105
- data/lib/isomorfeus_react/react/function_component/base.rb +8 -8
- data/lib/isomorfeus_react/react/function_component/initializer.rb +10 -10
- data/lib/isomorfeus_react/react/function_component/mixin.rb +17 -17
- data/lib/isomorfeus_react/react/function_component/native_component_constructor.rb +48 -48
- data/lib/isomorfeus_react/react/memo_component/base.rb +8 -8
- data/lib/isomorfeus_react/react/memo_component/mixin.rb +17 -17
- data/lib/isomorfeus_react/react/memo_component/native_component_constructor.rb +49 -49
- data/lib/isomorfeus_react_material/lucid_material/app/base.rb +8 -8
- data/lib/isomorfeus_react_material/lucid_material/app/mixin.rb +20 -20
- data/lib/isomorfeus_react_material/lucid_material/app/native_component_constructor.rb +50 -50
- data/lib/isomorfeus_react_material/lucid_material/component/base.rb +9 -9
- data/lib/isomorfeus_react_material/lucid_material/component/mixin.rb +19 -19
- data/lib/isomorfeus_react_material/lucid_material/component/native_component_constructor.rb +36 -36
- data/lib/isomorfeus_react_material/lucid_material/func/base.rb +9 -9
- data/lib/isomorfeus_react_material/lucid_material/func/mixin.rb +15 -15
- data/lib/isomorfeus_react_material/lucid_material/func/native_component_constructor.rb +83 -83
- data/lib/isomorfeus_react_paper/lucid_paper/app/base.rb +9 -9
- data/lib/isomorfeus_react_paper/lucid_paper/app/mixin.rb +19 -19
- data/lib/isomorfeus_react_paper/lucid_paper/app/native_component_constructor.rb +32 -32
- data/lib/isomorfeus_react_paper/lucid_paper/component/base.rb +9 -9
- data/lib/isomorfeus_react_paper/lucid_paper/component/mixin.rb +18 -18
- data/lib/isomorfeus_react_paper/lucid_paper/component/native_component_constructor.rb +25 -25
- data/lib/isomorfeus_react_paper/lucid_paper/func/base.rb +9 -9
- data/lib/isomorfeus_react_paper/lucid_paper/func/mixin.rb +14 -14
- data/lib/isomorfeus_react_paper/lucid_paper/func/native_component_constructor.rb +71 -71
- data/lib/lucid_app/context.rb +7 -7
- data/lib/lucid_prop_declaration/mixin.rb +126 -126
- data/lib/react/children.rb +34 -34
- data/lib/react/component/api.rb +134 -134
- data/lib/react/component/base.rb +8 -8
- data/lib/react/component/callbacks.rb +115 -115
- data/lib/react/component/elements.rb +60 -60
- data/lib/react/component/features.rb +48 -48
- data/lib/react/component/history.rb +69 -65
- data/lib/react/component/initializer.rb +11 -11
- data/lib/react/component/location.rb +19 -15
- data/lib/react/component/match.rb +35 -31
- data/lib/react/component/mixin.rb +20 -20
- data/lib/react/component/native_component_constructor.rb +69 -70
- data/lib/react/component/props.rb +83 -83
- data/lib/react/component/resolution.rb +97 -97
- data/lib/react/component/state.rb +58 -54
- data/lib/react/component/styles.rb +66 -66
- data/lib/react/context_wrapper.rb +48 -44
- data/lib/react/native_constant_wrapper.rb +29 -29
- data/lib/react/ref.rb +16 -12
- data/lib/react/synthetic_event.rb +52 -52
- data/lib/react/version.rb +3 -3
- data/lib/react.rb +296 -296
- data/lib/react_dom.rb +41 -41
- data/lib/react_dom_server.rb +18 -18
- data/lib/react_native/component/elements.rb +203 -203
- data/lib/react_native/lucid_app/react_native_component_constructor.rb +51 -51
- data/lib/react_native/lucid_component/react_native_component_constructor.rb +37 -37
- data/lib/react_native/lucid_func/react_native_component_constructor.rb +82 -82
- data/lib/react_native/react.rb +120 -120
- metadata +23 -17
|
@@ -1,231 +1,231 @@
|
|
|
1
|
-
module Isomorfeus
|
|
2
|
-
module ReactViewHelper
|
|
3
|
-
def cached_mount_component(component_name, props = {}, asset = 'web_ssr.js', static = false)
|
|
4
|
-
key = "#{component_name}#{props}#{asset}"
|
|
5
|
-
if Isomorfeus.production?
|
|
6
|
-
render_result, @ssr_response_status, @ssr_styles = component_cache.fetch(key)
|
|
7
|
-
return render_result if render_result
|
|
8
|
-
end
|
|
9
|
-
render_result = mount_component(component_name, props, asset, static)
|
|
10
|
-
status = ssr_response_status
|
|
11
|
-
component_cache.store(key, render_result, status, ssr_styles) if status >= 200 && status < 300
|
|
12
|
-
render_result
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def cached_mount_static_component(component_name, props = {}, asset = 'web_ssr.js')
|
|
16
|
-
cached_mount_component(component_name, props, asset, true)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def mount_component(component_name, props = {}, asset = 'web_ssr.js', static = false)
|
|
20
|
-
@ssr_response_status = nil
|
|
21
|
-
@ssr_styles = nil
|
|
22
|
-
thread_id_asset = "#{Thread.current.object_id}#{asset}"
|
|
23
|
-
render_result = if static
|
|
24
|
-
'<div>'
|
|
25
|
-
else
|
|
26
|
-
"<div data-iso-env=\"#{Isomorfeus.env}\" data-iso-root=\"#{component_name}\" data-iso-props='#{Oj.dump(props, mode: :strict)}'"
|
|
27
|
-
end
|
|
28
|
-
if Isomorfeus.server_side_rendering
|
|
29
|
-
|
|
30
|
-
if Isomorfeus.development?
|
|
31
|
-
# always create a new context, effectively reloading code
|
|
32
|
-
# delete the existing context first, saves memory
|
|
33
|
-
if Isomorfeus.ssr_contexts.key?(thread_id_asset)
|
|
34
|
-
uuid = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@uuid)
|
|
35
|
-
runtime = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@runtime)
|
|
36
|
-
runtime.vm.delete_context(uuid)
|
|
37
|
-
end
|
|
38
|
-
asset_path = "#{Isomorfeus.ssr_hot_asset_url}#{asset}"
|
|
39
|
-
begin
|
|
40
|
-
asset = Net::HTTP.get(URI(asset_path))
|
|
41
|
-
rescue Exception => e
|
|
42
|
-
Isomorfeus.raise_error(message: "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server. Error: #{e.message}", stack: e.backtrace )
|
|
43
|
-
end
|
|
44
|
-
if asset.strip.start_with?('<')
|
|
45
|
-
Isomorfeus.raise_error(message: "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server, asset is not javascript. Did the webpack build succeed?")
|
|
46
|
-
end
|
|
47
|
-
begin
|
|
48
|
-
Isomorfeus.ssr_contexts[thread_id_asset] = ExecJS.permissive_compile(asset)
|
|
49
|
-
rescue Exception => e
|
|
50
|
-
Isomorfeus.raise_error(message: "Server Side Rendering: Failed creating context for #{asset_path}. Error: #{e.message}", stack: e.backtrace)
|
|
51
|
-
end
|
|
52
|
-
else
|
|
53
|
-
# initialize speednode context
|
|
54
|
-
unless Isomorfeus.ssr_contexts.key?(thread_id_asset)
|
|
55
|
-
asset_file_name = OpalWebpackLoader::Manifest.lookup_path_for(asset)
|
|
56
|
-
Isomorfeus.raise_error(message: "Server Side Rendering: Build asset file not found for #{asset}. Has it been build?") unless asset_file_name
|
|
57
|
-
asset_path = File.join('public', asset_file_name)
|
|
58
|
-
Isomorfeus.ssr_contexts[thread_id_asset] = ExecJS.permissive_compile(File.read(asset_path))
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# build javascript for rendering first pass
|
|
63
|
-
# it will initialize buffers to guard against leaks, maybe caused by previous exceptions
|
|
64
|
-
javascript = <<~JAVASCRIPT
|
|
65
|
-
global.Opal.React.render_buffer = [];
|
|
66
|
-
global.Opal.React.active_components = [];
|
|
67
|
-
global.Opal.React.active_redux_components = [];
|
|
68
|
-
global.FirstPassFinished = false;
|
|
69
|
-
global.Exception = false;
|
|
70
|
-
global.IsomorfeusSessionId = '#{Thread.current[:isomorfeus_session_id]}';
|
|
71
|
-
global.Opal.Isomorfeus['$env=']('#{Isomorfeus.env}');
|
|
72
|
-
if (typeof global.Opal.Isomorfeus.$negotiated_locale === 'function') {
|
|
73
|
-
global.Opal.Isomorfeus["$negotiated_locale="]('#{props[:locale]}');
|
|
74
|
-
}
|
|
75
|
-
global.Opal.Isomorfeus['$force_init!']();
|
|
76
|
-
global.Opal.Isomorfeus['$ssr_response_status='](200);
|
|
77
|
-
global.Opal.Isomorfeus.TopLevel['$ssr_route_path=']('#{props[:location]}');
|
|
78
|
-
JAVASCRIPT
|
|
79
|
-
|
|
80
|
-
# if location_host and scheme are given and if Transport is loaded, connect and then render,
|
|
81
|
-
# otherwise do not render because only one pass is required
|
|
82
|
-
ws_scheme = props[:location_scheme] == 'https:' ? 'wss:' : 'ws:'
|
|
83
|
-
location_host = props[:location_host] ? props[:location_host] : 'localhost'
|
|
84
|
-
api_ws_path = Isomorfeus.respond_to?(:api_websocket_path) ? Isomorfeus.api_websocket_path : ''
|
|
85
|
-
transport_ws_url = ws_scheme + location_host + api_ws_path
|
|
86
|
-
javascript << <<~JAVASCRIPT
|
|
87
|
-
let api_ws_path = '#{api_ws_path}';
|
|
88
|
-
let exception;
|
|
89
|
-
if (typeof global.Opal.Isomorfeus.Transport !== 'undefined' && api_ws_path !== '') {
|
|
90
|
-
global.Opal.Isomorfeus.TopLevel["$transport_ws_url="]("#{transport_ws_url}");
|
|
91
|
-
global.Opal.send(global.Opal.Isomorfeus.Transport.$promise_connect(), 'then', [], ($$1 = function(){
|
|
92
|
-
try {
|
|
93
|
-
if (#{static}) { global.Opal.Isomorfeus.TopLevel.$render_component_to_static_markup('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
94
|
-
else { global.Opal.Isomorfeus.TopLevel.$render_component_to_string('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
95
|
-
global.FirstPassFinished = 'transport';
|
|
96
|
-
} catch (e) {
|
|
97
|
-
global.Exception = e;
|
|
98
|
-
global.FirstPassFinished = 'transport';
|
|
99
|
-
}
|
|
100
|
-
}, $$1.$$s = this, $$1.$$arity = 0, $$1))
|
|
101
|
-
} else { return global.FirstPassFinished = true; };
|
|
102
|
-
JAVASCRIPT
|
|
103
|
-
|
|
104
|
-
# execute first render pass
|
|
105
|
-
first_pass_skipped = Isomorfeus.ssr_contexts[thread_id_asset].exec(javascript)
|
|
106
|
-
|
|
107
|
-
# wait for first pass to finish
|
|
108
|
-
unless first_pass_skipped
|
|
109
|
-
first_pass_finished, exception = Isomorfeus.ssr_contexts[thread_id_asset].exec('return [global.FirstPassFinished, global.Exception ? { message: global.Exception.message, stack: global.Exception.stack } : false ]')
|
|
110
|
-
Isomorfeus.raise_error(message: "Server Side Rendering: #{exception['message']}", stack: exception['stack']) if exception
|
|
111
|
-
unless first_pass_finished
|
|
112
|
-
start_time = Time.now
|
|
113
|
-
while !first_pass_finished
|
|
114
|
-
break if (Time.now - start_time) > 10
|
|
115
|
-
sleep 0.01
|
|
116
|
-
first_pass_finished = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.FirstPassFinished')
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# wait for transport requests to finish
|
|
121
|
-
if first_pass_finished == 'transport'
|
|
122
|
-
transport_busy = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.Opal.Isomorfeus.Transport["$busy?"]()')
|
|
123
|
-
if transport_busy
|
|
124
|
-
start_time = Time.now
|
|
125
|
-
while transport_busy
|
|
126
|
-
break if (Time.now - start_time) > 10
|
|
127
|
-
sleep 0.01
|
|
128
|
-
transport_busy = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.Opal.Isomorfeus.Transport["$busy?"]()')
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# build javascript for second render pass
|
|
135
|
-
# guard against leaks from first pass, maybe because of a exception
|
|
136
|
-
javascript = <<~JAVASCRIPT
|
|
137
|
-
global.Opal.React.render_buffer = [];
|
|
138
|
-
global.Opal.React.active_components = [];
|
|
139
|
-
global.Opal.React.active_redux_components = [];
|
|
140
|
-
global.Exception = false;
|
|
141
|
-
let rendered_tree;
|
|
142
|
-
let ssr_styles;
|
|
143
|
-
let component;
|
|
144
|
-
if (typeof global.Opal.global.MuiStyles !== 'undefined' && typeof global.Opal.global.MuiStyles.ServerStyleSheets !== 'undefined') {
|
|
145
|
-
component = '#{component_name}'.split(".").reduce(function(o, x) {
|
|
146
|
-
return (o !== null && typeof o[x] !== "undefined" && o[x] !== null) ? o[x] : null;
|
|
147
|
-
}, global.Opal.global)
|
|
148
|
-
if (!component) { component = global.Opal.Isomorfeus.$cached_component_class('#{component_name}'); }
|
|
149
|
-
try {
|
|
150
|
-
let sheets = new global.Opal.global.MuiStyles.ServerStyleSheets();
|
|
151
|
-
let app = global.Opal.React.$create_element(component, global.Opal.Hash.$new(#{Oj.dump(props, mode: :strict)}));
|
|
152
|
-
if (#{static}) { rendered_tree = global.Opal.global.ReactDOMServer.renderToStaticMarkup(sheets.collect(app)); }
|
|
153
|
-
else { rendered_tree = global.Opal.global.ReactDOMServer.renderToString(sheets.collect(app)); }
|
|
154
|
-
ssr_styles = sheets.toString();
|
|
155
|
-
} catch (e) {
|
|
156
|
-
global.Exception = e;
|
|
157
|
-
}
|
|
158
|
-
} else if (typeof global.Opal.global.ReactJSS !== 'undefined' && typeof global.Opal.global.ReactJSS.SheetsRegistry !== 'undefined') {
|
|
159
|
-
component = '#{component_name}'.split(".").reduce(function(o, x) {
|
|
160
|
-
return (o !== null && typeof o[x] !== "undefined" && o[x] !== null) ? o[x] : null;
|
|
161
|
-
}, global.Opal.global)
|
|
162
|
-
if (!component) { component = global.Opal.Isomorfeus.$cached_component_class('#{component_name}'); }
|
|
163
|
-
try {
|
|
164
|
-
let sheets = new global.Opal.global.ReactJSS.SheetsRegistry();
|
|
165
|
-
let generate_id = global.Opal.global.ReactJSS.createGenerateId();
|
|
166
|
-
let app = global.Opal.React.$create_element(component, global.Opal.Hash.$new(#{Oj.dump(props, mode: :strict)}));
|
|
167
|
-
let element = global.Opal.global.React.createElement(global.Opal.global.ReactJSS.JssProvider, { registry: sheets, generateId: generate_id }, app);
|
|
168
|
-
if (#{static}) { rendered_tree = global.Opal.global.ReactDOMServer.renderToStaticMarkup(element); }
|
|
169
|
-
else { rendered_tree = global.Opal.global.ReactDOMServer.renderToString(element); }
|
|
170
|
-
ssr_styles = sheets.toString();
|
|
171
|
-
} catch (e) {
|
|
172
|
-
global.Exception = e;
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
try {
|
|
176
|
-
if (#{static}) { rendered_tree = global.Opal.Isomorfeus.TopLevel.$render_component_to_static_markup('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
177
|
-
else { rendered_tree = global.Opal.Isomorfeus.TopLevel.$render_component_to_string('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
178
|
-
} catch (e) {
|
|
179
|
-
global.Exception = e;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
let application_state = global.Opal.Isomorfeus.store.native.getState();
|
|
183
|
-
if (typeof global.Opal.Isomorfeus.Transport !== 'undefined') { global.Opal.Isomorfeus.Transport.$disconnect(); }
|
|
184
|
-
return [rendered_tree, application_state, ssr_styles, global.Opal.Isomorfeus['$ssr_response_status'](), global.Exception ? { message: global.Exception.message, stack: global.Exception.stack } : false];
|
|
185
|
-
JAVASCRIPT
|
|
186
|
-
|
|
187
|
-
# execute second render pass
|
|
188
|
-
rendered_tree, application_state, @ssr_styles, @ssr_response_status, exception = Isomorfeus.ssr_contexts[thread_id_asset].exec(javascript)
|
|
189
|
-
Isomorfeus.raise_error(message: exception['message'], stack: exception['stack']) if exception
|
|
190
|
-
|
|
191
|
-
# build result
|
|
192
|
-
unless static
|
|
193
|
-
render_result << " data-iso-hydrated='true'" if rendered_tree
|
|
194
|
-
if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous?
|
|
195
|
-
render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.to_sid, mode: :strict)}"
|
|
196
|
-
end
|
|
197
|
-
render_result << " data-iso-nloc='#{props[:locale]}'>"
|
|
198
|
-
end
|
|
199
|
-
render_result << (rendered_tree ? rendered_tree : "SSR didn't work")
|
|
200
|
-
else
|
|
201
|
-
if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous?
|
|
202
|
-
render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.to_sid, mode: :strict)}"
|
|
203
|
-
end
|
|
204
|
-
render_result << " data-iso-nloc='#{props[:locale]}'>" unless static
|
|
205
|
-
end
|
|
206
|
-
render_result << '</div>'
|
|
207
|
-
if Isomorfeus.server_side_rendering && !static
|
|
208
|
-
render_result = "<script type='application/javascript'>\nServerSideRenderingStateJSON = #{Oj.dump(application_state, mode: :strict)}\n</script>\n" << render_result
|
|
209
|
-
end
|
|
210
|
-
render_result
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def mount_static_component(component_name, props = {}, asset = 'web_ssr.js')
|
|
214
|
-
mount_component(component_name, props, asset, true)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def ssr_response_status
|
|
218
|
-
@ssr_response_status || 200
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def ssr_styles
|
|
222
|
-
@ssr_styles || ''
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
private
|
|
226
|
-
|
|
227
|
-
def component_cache
|
|
228
|
-
@_component_cache ||= Isomorfeus.component_cache_init_block.call
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
end
|
|
1
|
+
module Isomorfeus
|
|
2
|
+
module ReactViewHelper
|
|
3
|
+
def cached_mount_component(component_name, props = {}, asset = 'web_ssr.js', static = false)
|
|
4
|
+
key = "#{component_name}#{props}#{asset}"
|
|
5
|
+
if Isomorfeus.production?
|
|
6
|
+
render_result, @ssr_response_status, @ssr_styles = component_cache.fetch(key)
|
|
7
|
+
return render_result if render_result
|
|
8
|
+
end
|
|
9
|
+
render_result = mount_component(component_name, props, asset, static)
|
|
10
|
+
status = ssr_response_status
|
|
11
|
+
component_cache.store(key, render_result, status, ssr_styles) if status >= 200 && status < 300
|
|
12
|
+
render_result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cached_mount_static_component(component_name, props = {}, asset = 'web_ssr.js')
|
|
16
|
+
cached_mount_component(component_name, props, asset, true)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def mount_component(component_name, props = {}, asset = 'web_ssr.js', static = false)
|
|
20
|
+
@ssr_response_status = nil
|
|
21
|
+
@ssr_styles = nil
|
|
22
|
+
thread_id_asset = "#{Thread.current.object_id}#{asset}"
|
|
23
|
+
render_result = if static
|
|
24
|
+
'<div>'
|
|
25
|
+
else
|
|
26
|
+
"<div data-iso-env=\"#{Isomorfeus.env}\" data-iso-root=\"#{component_name}\" data-iso-props='#{Oj.dump(props, mode: :strict)}'"
|
|
27
|
+
end
|
|
28
|
+
if Isomorfeus.server_side_rendering
|
|
29
|
+
|
|
30
|
+
if Isomorfeus.development?
|
|
31
|
+
# always create a new context, effectively reloading code
|
|
32
|
+
# delete the existing context first, saves memory
|
|
33
|
+
if Isomorfeus.ssr_contexts.key?(thread_id_asset)
|
|
34
|
+
uuid = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@uuid)
|
|
35
|
+
runtime = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@runtime)
|
|
36
|
+
runtime.vm.delete_context(uuid)
|
|
37
|
+
end
|
|
38
|
+
asset_path = "#{Isomorfeus.ssr_hot_asset_url}#{asset}"
|
|
39
|
+
begin
|
|
40
|
+
asset = Net::HTTP.get(URI(asset_path))
|
|
41
|
+
rescue Exception => e
|
|
42
|
+
Isomorfeus.raise_error(message: "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server. Error: #{e.message}", stack: e.backtrace )
|
|
43
|
+
end
|
|
44
|
+
if asset.strip.start_with?('<')
|
|
45
|
+
Isomorfeus.raise_error(message: "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server, asset is not javascript. Did the webpack build succeed?")
|
|
46
|
+
end
|
|
47
|
+
begin
|
|
48
|
+
Isomorfeus.ssr_contexts[thread_id_asset] = ExecJS.permissive_compile(asset)
|
|
49
|
+
rescue Exception => e
|
|
50
|
+
Isomorfeus.raise_error(message: "Server Side Rendering: Failed creating context for #{asset_path}. Error: #{e.message}", stack: e.backtrace)
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
# initialize speednode context
|
|
54
|
+
unless Isomorfeus.ssr_contexts.key?(thread_id_asset)
|
|
55
|
+
asset_file_name = OpalWebpackLoader::Manifest.lookup_path_for(asset)
|
|
56
|
+
Isomorfeus.raise_error(message: "Server Side Rendering: Build asset file not found for #{asset}. Has it been build?") unless asset_file_name
|
|
57
|
+
asset_path = File.join('public', asset_file_name)
|
|
58
|
+
Isomorfeus.ssr_contexts[thread_id_asset] = ExecJS.permissive_compile(File.read(asset_path))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# build javascript for rendering first pass
|
|
63
|
+
# it will initialize buffers to guard against leaks, maybe caused by previous exceptions
|
|
64
|
+
javascript = <<~JAVASCRIPT
|
|
65
|
+
global.Opal.React.render_buffer = [];
|
|
66
|
+
global.Opal.React.active_components = [];
|
|
67
|
+
global.Opal.React.active_redux_components = [];
|
|
68
|
+
global.FirstPassFinished = false;
|
|
69
|
+
global.Exception = false;
|
|
70
|
+
global.IsomorfeusSessionId = '#{Thread.current[:isomorfeus_session_id]}';
|
|
71
|
+
global.Opal.Isomorfeus['$env=']('#{Isomorfeus.env}');
|
|
72
|
+
if (typeof global.Opal.Isomorfeus.$negotiated_locale === 'function') {
|
|
73
|
+
global.Opal.Isomorfeus["$negotiated_locale="]('#{props[:locale]}');
|
|
74
|
+
}
|
|
75
|
+
global.Opal.Isomorfeus['$force_init!']();
|
|
76
|
+
global.Opal.Isomorfeus['$ssr_response_status='](200);
|
|
77
|
+
global.Opal.Isomorfeus.TopLevel['$ssr_route_path=']('#{props[:location]}');
|
|
78
|
+
JAVASCRIPT
|
|
79
|
+
|
|
80
|
+
# if location_host and scheme are given and if Transport is loaded, connect and then render,
|
|
81
|
+
# otherwise do not render because only one pass is required
|
|
82
|
+
ws_scheme = props[:location_scheme] == 'https:' ? 'wss:' : 'ws:'
|
|
83
|
+
location_host = props[:location_host] ? props[:location_host] : 'localhost'
|
|
84
|
+
api_ws_path = Isomorfeus.respond_to?(:api_websocket_path) ? Isomorfeus.api_websocket_path : ''
|
|
85
|
+
transport_ws_url = ws_scheme + location_host + api_ws_path
|
|
86
|
+
javascript << <<~JAVASCRIPT
|
|
87
|
+
let api_ws_path = '#{api_ws_path}';
|
|
88
|
+
let exception;
|
|
89
|
+
if (typeof global.Opal.Isomorfeus.Transport !== 'undefined' && api_ws_path !== '') {
|
|
90
|
+
global.Opal.Isomorfeus.TopLevel["$transport_ws_url="]("#{transport_ws_url}");
|
|
91
|
+
global.Opal.send(global.Opal.Isomorfeus.Transport.$promise_connect(), 'then', [], ($$1 = function(){
|
|
92
|
+
try {
|
|
93
|
+
if (#{static}) { global.Opal.Isomorfeus.TopLevel.$render_component_to_static_markup('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
94
|
+
else { global.Opal.Isomorfeus.TopLevel.$render_component_to_string('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
95
|
+
global.FirstPassFinished = 'transport';
|
|
96
|
+
} catch (e) {
|
|
97
|
+
global.Exception = e;
|
|
98
|
+
global.FirstPassFinished = 'transport';
|
|
99
|
+
}
|
|
100
|
+
}, $$1.$$s = this, $$1.$$arity = 0, $$1))
|
|
101
|
+
} else { return global.FirstPassFinished = true; };
|
|
102
|
+
JAVASCRIPT
|
|
103
|
+
|
|
104
|
+
# execute first render pass
|
|
105
|
+
first_pass_skipped = Isomorfeus.ssr_contexts[thread_id_asset].exec(javascript)
|
|
106
|
+
|
|
107
|
+
# wait for first pass to finish
|
|
108
|
+
unless first_pass_skipped
|
|
109
|
+
first_pass_finished, exception = Isomorfeus.ssr_contexts[thread_id_asset].exec('return [global.FirstPassFinished, global.Exception ? { message: global.Exception.message, stack: global.Exception.stack } : false ]')
|
|
110
|
+
Isomorfeus.raise_error(message: "Server Side Rendering: #{exception['message']}", stack: exception['stack']) if exception
|
|
111
|
+
unless first_pass_finished
|
|
112
|
+
start_time = Time.now
|
|
113
|
+
while !first_pass_finished
|
|
114
|
+
break if (Time.now - start_time) > 10
|
|
115
|
+
sleep 0.01
|
|
116
|
+
first_pass_finished = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.FirstPassFinished')
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# wait for transport requests to finish
|
|
121
|
+
if first_pass_finished == 'transport'
|
|
122
|
+
transport_busy = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.Opal.Isomorfeus.Transport["$busy?"]()')
|
|
123
|
+
if transport_busy
|
|
124
|
+
start_time = Time.now
|
|
125
|
+
while transport_busy
|
|
126
|
+
break if (Time.now - start_time) > 10
|
|
127
|
+
sleep 0.01
|
|
128
|
+
transport_busy = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.Opal.Isomorfeus.Transport["$busy?"]()')
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# build javascript for second render pass
|
|
135
|
+
# guard against leaks from first pass, maybe because of a exception
|
|
136
|
+
javascript = <<~JAVASCRIPT
|
|
137
|
+
global.Opal.React.render_buffer = [];
|
|
138
|
+
global.Opal.React.active_components = [];
|
|
139
|
+
global.Opal.React.active_redux_components = [];
|
|
140
|
+
global.Exception = false;
|
|
141
|
+
let rendered_tree;
|
|
142
|
+
let ssr_styles;
|
|
143
|
+
let component;
|
|
144
|
+
if (typeof global.Opal.global.MuiStyles !== 'undefined' && typeof global.Opal.global.MuiStyles.ServerStyleSheets !== 'undefined') {
|
|
145
|
+
component = '#{component_name}'.split(".").reduce(function(o, x) {
|
|
146
|
+
return (o !== null && typeof o[x] !== "undefined" && o[x] !== null) ? o[x] : null;
|
|
147
|
+
}, global.Opal.global)
|
|
148
|
+
if (!component) { component = global.Opal.Isomorfeus.$cached_component_class('#{component_name}'); }
|
|
149
|
+
try {
|
|
150
|
+
let sheets = new global.Opal.global.MuiStyles.ServerStyleSheets();
|
|
151
|
+
let app = global.Opal.React.$create_element(component, global.Opal.Hash.$new(#{Oj.dump(props, mode: :strict)}));
|
|
152
|
+
if (#{static}) { rendered_tree = global.Opal.global.ReactDOMServer.renderToStaticMarkup(sheets.collect(app)); }
|
|
153
|
+
else { rendered_tree = global.Opal.global.ReactDOMServer.renderToString(sheets.collect(app)); }
|
|
154
|
+
ssr_styles = sheets.toString();
|
|
155
|
+
} catch (e) {
|
|
156
|
+
global.Exception = e;
|
|
157
|
+
}
|
|
158
|
+
} else if (typeof global.Opal.global.ReactJSS !== 'undefined' && typeof global.Opal.global.ReactJSS.SheetsRegistry !== 'undefined') {
|
|
159
|
+
component = '#{component_name}'.split(".").reduce(function(o, x) {
|
|
160
|
+
return (o !== null && typeof o[x] !== "undefined" && o[x] !== null) ? o[x] : null;
|
|
161
|
+
}, global.Opal.global)
|
|
162
|
+
if (!component) { component = global.Opal.Isomorfeus.$cached_component_class('#{component_name}'); }
|
|
163
|
+
try {
|
|
164
|
+
let sheets = new global.Opal.global.ReactJSS.SheetsRegistry();
|
|
165
|
+
let generate_id = global.Opal.global.ReactJSS.createGenerateId();
|
|
166
|
+
let app = global.Opal.React.$create_element(component, global.Opal.Hash.$new(#{Oj.dump(props, mode: :strict)}));
|
|
167
|
+
let element = global.Opal.global.React.createElement(global.Opal.global.ReactJSS.JssProvider, { registry: sheets, generateId: generate_id }, app);
|
|
168
|
+
if (#{static}) { rendered_tree = global.Opal.global.ReactDOMServer.renderToStaticMarkup(element); }
|
|
169
|
+
else { rendered_tree = global.Opal.global.ReactDOMServer.renderToString(element); }
|
|
170
|
+
ssr_styles = sheets.toString();
|
|
171
|
+
} catch (e) {
|
|
172
|
+
global.Exception = e;
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
try {
|
|
176
|
+
if (#{static}) { rendered_tree = global.Opal.Isomorfeus.TopLevel.$render_component_to_static_markup('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
177
|
+
else { rendered_tree = global.Opal.Isomorfeus.TopLevel.$render_component_to_string('#{component_name}', #{Oj.dump(props, mode: :strict)}); }
|
|
178
|
+
} catch (e) {
|
|
179
|
+
global.Exception = e;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
let application_state = global.Opal.Isomorfeus.store.native.getState();
|
|
183
|
+
if (typeof global.Opal.Isomorfeus.Transport !== 'undefined') { global.Opal.Isomorfeus.Transport.$disconnect(); }
|
|
184
|
+
return [rendered_tree, application_state, ssr_styles, global.Opal.Isomorfeus['$ssr_response_status'](), global.Exception ? { message: global.Exception.message, stack: global.Exception.stack } : false];
|
|
185
|
+
JAVASCRIPT
|
|
186
|
+
|
|
187
|
+
# execute second render pass
|
|
188
|
+
rendered_tree, application_state, @ssr_styles, @ssr_response_status, exception = Isomorfeus.ssr_contexts[thread_id_asset].exec(javascript)
|
|
189
|
+
Isomorfeus.raise_error(message: exception['message'], stack: exception['stack']) if exception
|
|
190
|
+
|
|
191
|
+
# build result
|
|
192
|
+
unless static
|
|
193
|
+
render_result << " data-iso-hydrated='true'" if rendered_tree
|
|
194
|
+
if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous?
|
|
195
|
+
render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.to_sid, mode: :strict)}"
|
|
196
|
+
end
|
|
197
|
+
render_result << " data-iso-nloc='#{props[:locale]}'>"
|
|
198
|
+
end
|
|
199
|
+
render_result << (rendered_tree ? rendered_tree : "SSR didn't work")
|
|
200
|
+
else
|
|
201
|
+
if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous?
|
|
202
|
+
render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.to_sid, mode: :strict)}"
|
|
203
|
+
end
|
|
204
|
+
render_result << " data-iso-nloc='#{props[:locale]}'>" unless static
|
|
205
|
+
end
|
|
206
|
+
render_result << '</div>'
|
|
207
|
+
if Isomorfeus.server_side_rendering && !static
|
|
208
|
+
render_result = "<script type='application/javascript'>\nServerSideRenderingStateJSON = #{Oj.dump(application_state, mode: :strict)}\n</script>\n" << render_result
|
|
209
|
+
end
|
|
210
|
+
render_result
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def mount_static_component(component_name, props = {}, asset = 'web_ssr.js')
|
|
214
|
+
mount_component(component_name, props, asset, true)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def ssr_response_status
|
|
218
|
+
@ssr_response_status || 200
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def ssr_styles
|
|
222
|
+
@ssr_styles || ''
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def component_cache
|
|
228
|
+
@_component_cache ||= Isomorfeus.component_cache_init_block.call
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|