Class: ReactOnRails::ServerRenderingPool::RubyEmbeddedJavaScript

Inherits:
Object
  • Object
show all
Defined in:
lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb

Class Method Summary collapse

Class Method Details

.console_polyfillObject

Reimplement console methods for replaying on the client



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 176

def console_polyfill
  # rubocop:disable Layout/IndentHeredoc
  "var console = { history: [] };\n['error', 'log', 'info', 'warn'].forEach(function (level) {\n  console[level] = function () {\n    var argArray = Array.prototype.slice.call(arguments);\n    if (argArray.length > 0) {\n      argArray[0] = '[SERVER] ' + argArray[0];\n    }\n    console.history.push({level: level, arguments: argArray});\n  };\n});\n  JS\n  # rubocop:enable Layout/IndentHeredoc\nend\n"

.create_js_contextObject



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
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 98

def create_js_context
  return if ReactOnRails.configuration.server_bundle_js_file.blank?

  server_js_file = ReactOnRails::Utils.server_bundle_js_file_path

  begin
    bundle_js_code = File.read(server_js_file)
  rescue StandardError => e
    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.\nError is: #{e}"
    raise ReactOnRails::Error, msg
  end
  # rubocop:disable Layout/IndentHeredoc
  base_js_code = "\#{console_polyfill}\n\#{execjs_timer_polyfills}\n\#{bundle_js_code};\n  JS\n  # rubocop:enable Layout/IndentHeredoc\n  file_name = \"tmp/base_js_code.js\"\n  begin\n    if ReactOnRails.configuration.trace\n      Rails.logger.info { \"[react_on_rails] Created JavaScript context with file \#{server_js_file}\" }\n    end\n    ExecJS.compile(base_js_code)\n  rescue StandardError => e\n    msg = \"ERROR when compiling base_js_code! \"\\\n      \"See file \#{file_name} to \"\\\n      \"correlate line numbers of error. Error is\\n\\n\#{e.message}\"\\\n      \"\\n\\n\#{e.backtrace.join(\"\\n\")}\"\n    Rails.logger.error(msg)\n    trace_js_code_used(\"Error when compiling JavaScript code for the context.\", base_js_code,\n                       file_name, force: true)\n    raise e\n  end\nend\n"

.eval_js(js_code, _render_options) ⇒ Object



90
91
92
93
94
95
96
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 90

def eval_js(js_code, _render_options)
  @js_context_pool.with do |js_context|
    result = js_context.eval(js_code)
    js_context.eval("console.history = []")
    result
  end
end

.exec_server_render_js(js_code, render_options, js_evaluator = nil) ⇒ Object

js_code: JavaScript expression that returns a string. render_options: lib/react_on_rails/react_component/render_options.rb Using these options:

trace: saves the executed JS to a file, used in development
logging_on_server: put on server logs, not just in browser console

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.



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
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 42

def exec_server_render_js(js_code, render_options, js_evaluator = nil)
  js_evaluator ||= self
  if render_options.trace
    @file_index ||= 1
    trace_js_code_used("Evaluating code to server render.", js_code,
                       "tmp/server-generated-#{@file_index % 10}.js")
    @file_index += 1
  end
  json_string = js_evaluator.eval_js(js_code, render_options)
  result = nil
  begin
    result = JSON.parse(json_string)
  rescue JSON::ParserError => e
    raise ReactOnRails::JsonParseError.new(e, json_string)
  end

  if render_options.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|error)\.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

.execjs_timer_polyfillsObject



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 136

def execjs_timer_polyfills
  # rubocop:disable Layout/IndentHeredoc
  "function getStackTrace () {\n  var stack;\n  try {\n    throw new Error('');\n  }\n  catch (error) {\n    stack = error.stack || '';\n  }\n  stack = stack.split('\\\\n').map(function (line) { return line.trim(); });\n  return stack.splice(stack[0] == 'Error' ? 2 : 1);\n}\n\nfunction setInterval() {\n  \#{undefined_for_exec_js_logging('setInterval')}\n}\n\nfunction setTimeout() {\n  \#{undefined_for_exec_js_logging('setTimeout')}\n}\n\nfunction clearTimeout() {\n  \#{undefined_for_exec_js_logging('clearTimeout')}\n}\n  JS\n  # rubocop:enable Layout/IndentHeredoc\nend\n"

.reset_poolObject



9
10
11
12
13
14
15
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 9

def reset_pool
  options = {
    size: ReactOnRails.configuration.server_renderer_pool_size,
    timeout: ReactOnRails.configuration.server_renderer_timeout
  }
  @js_context_pool = ConnectionPool.new(options) { create_js_context }
end

.reset_pool_if_server_bundle_was_modifiedObject



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 17

def reset_pool_if_server_bundle_was_modified
  return unless ReactOnRails.configuration.development_mode

  file_mtime = File.mtime(ReactOnRails::Utils.server_bundle_js_file_path)
  @server_bundle_timestamp ||= file_mtime
  return if @server_bundle_timestamp == file_mtime

  @server_bundle_timestamp = file_mtime

  ReactOnRails::ServerRenderingPool.reset_pool
end

.trace_js_code_used(msg, js_code, file_name = "tmp/server-generated.js", force: false) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 73

def trace_js_code_used(msg, js_code, file_name = "tmp/server-generated.js", force: false)
  return unless ReactOnRails.configuration.trace || force
  # Set to anything to print generated code.
  File.write(file_name, js_code)
  msg = "    \#{'Z' * 80}\n    [react_on_rails] \#{msg}\n    JavaScript code used: \#{file_name}\n    \#{'Z' * 80}\n  MSG\n  if force\n    Rails.logger.error(msg)\n  else\n    Rails.logger.info(msg)\n  end\nend\n".strip_heredoc

.undefined_for_exec_js_logging(function_name) ⇒ Object



166
167
168
169
170
171
172
173
# File 'lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb', line 166

def undefined_for_exec_js_logging(function_name)
  if ReactOnRails.configuration.trace
    "console.error('[React on Rails Rendering] #{function_name} is not defined for server rendering.');\n"\
    "  console.error(getStackTrace().join('\\n'));"
  else
    ""
  end
end