-
-
Notifications
You must be signed in to change notification settings - Fork 631
/
Copy pathserver_rendering_pool.rb
135 lines (124 loc) · 4.65 KB
/
server_rendering_pool.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
require "connection_pool"
# Based on the react-rails gem.
# None of these methods should be called directly.
# See app/helpers/react_on_rails_helper.rb
module ReactOnRails
class ServerRenderingPool
def self.reset_pool
options = { size: ReactOnRails.configuration.server_renderer_pool_size,
timeout: ReactOnRails.configuration.server_renderer_pool_size }
@js_context_pool = ConnectionPool.new(options) { create_js_context }
end
def self.reset_pool_if_server_bundle_was_modified
return unless ReactOnRails.configuration.development_mode
file_mtime = File.mtime(ReactOnRails::Utils.default_server_bundle_js_file_path)
@server_bundle_timestamp ||= file_mtime
return if @server_bundle_timestamp == file_mtime
ReactOnRails::ServerRenderingPool.reset_pool
@server_bundle_timestamp = file_mtime
end
# js_code: JavaScript expression that returns a string.
# Returns a Hash:
# html: string of HTML for direct insertion on the page by evaluating js_code
# consoleReplayScript: script for replaying console
# hasErrors: true if server rendering errors
# Note, js_code does not have to be based on React.
# js_code MUST RETURN json stringify Object
# Calling code will probably call 'html_safe' on return value before rendering to the view.
def self.server_render_js_with_console_logging(js_code)
trace_messsage(js_code)
json_string = eval_js(js_code)
result = JSON.parse(json_string)
if ReactOnRails.configuration.logging_on_server
console_script = result["consoleReplayScript"]
console_script_lines = console_script.split("\n")
console_script_lines = console_script_lines[2..-2]
re = /console\.log\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
if console_script_lines
console_script_lines.each do |line|
match = re.match(line)
Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
end
end
end
result
end
class << self
private
def trace_messsage(js_code, file_name = "tmp/server-generated.js")
return unless ENV["TRACE_REACT_ON_RAILS"].present?
# Set to anything to print generated code.
puts "Z" * 80
puts "react_renderer.rb: 92"
puts "wrote file #{file_name}"
File.write(file_name, js_code)
puts "Z" * 80
end
def eval_js(js_code)
@js_context_pool.with do |js_context|
result = js_context.eval(js_code)
js_context.eval("console.history = []")
result
end
end
def create_js_context
server_js_file = ReactOnRails::Utils.default_server_bundle_js_file_path
if server_js_file.present? && File.exist?(server_js_file)
bundle_js_code = File.read(server_js_file)
base_js_code = <<-JS
#{console_polyfill}
#{execjs_timer_polyfills}
#{bundle_js_code};
JS
begin
ExecJS.compile(base_js_code)
rescue => e
file_name = "tmp/base_js_code.js"
msg = "ERROR when compiling base_js_code! See #{file_name} to "\
"ERROR when compiling base_js_code! See #{file_name} to "\
"correlate line numbers of error. Error is\n\n#{e.message}"\
"\n\n#{e.backtrace.join("\n")}"
puts msg
Rails.logger.error(msg)
trace_messsage(base_js_code, file_name)
raise e
end
else
if server_js_file.present?
msg = "You specified server rendering JS file: #{server_js_file}, but it cannot be "\
"read. You may set the server_bundle_js_file in your configuration to be \"\" to "\
"avoid this warning"
Rails.logger.warn msg
puts msg
end
ExecJS.compile("")
end
end
def execjs_timer_polyfills
<<-JS
function setInterval() {
console.error('setInterval is not defined for execJS. See https://github.com/sstephenson/execjs#faq');
}
function setTimeout() {
console.error('setTimeout is not defined for execJS. See https://github.com/sstephenson/execjs#faq');
}
JS
end
# Reimplement console methods for replaying on the client
def console_polyfill
<<-JS
var console = { history: [] };
['error', 'log', 'info', 'warn'].forEach(function (level) {
console[level] = function () {
var argArray = Array.prototype.slice.call(arguments);
if (argArray.length > 0) {
argArray[0] = '[SERVER] ' + argArray[0];
}
console.history.push({level: level, arguments: argArray});
};
});
JS
end
end
end
end