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