class ReactOnRails::ServerRenderingPool::RubyEmbeddedJavaScript

rubocop:disable Metrics/ClassLength

Public Class Methods

console_polyfill() click to toggle source

Reimplement console methods for replaying on the client Save a handle to the original console if needed.

# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 203
        def console_polyfill
          <<~JS
            var debugConsole = console;
            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
create_js_context() click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 133
        def create_js_context
          return if ReactOnRails.configuration.server_bundle_js_file.blank?

          bundle_js_code = read_bundle_js_code
          base_js_code = <<~JS
            #{console_polyfill}
            #{execjs_timer_polyfills}
            #{bundle_js_code};
          JS

          file_name = "tmp/base_js_code.js"
          begin
            if ReactOnRails.configuration.trace
              Rails.logger.info do
                "[react_on_rails] Created JavaScript context with file " \
                  "#{ReactOnRails::Utils.server_bundle_js_file_path}"
              end
            end
            ExecJS.compile(base_js_code)
          rescue StandardError => e
            msg = "ERROR when compiling base_js_code! " \
                  "See file #{file_name} to " \
                  "correlate line numbers of error. Error is\n\n#{e.message}" \
                  "\n\n#{e.backtrace.join("\n")}"
            Rails.logger.error(msg)
            trace_js_code_used("Error when compiling JavaScript code for the context.", base_js_code,
                               file_name, force: true)
            raise e
          end
        end
eval_js(js_code, _render_options) click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 113
def eval_js(js_code, _render_options)
  @js_context_pool.with do |js_context|
    js_context.eval(js_code)
  end
end
exec_server_render_js(js_code, render_options, js_evaluator = nil) click to toggle source

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. rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity

# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 50
        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
          begin
            json_string = js_evaluator.eval_js(js_code, render_options)
          rescue StandardError => err
            msg = <<~MSG
              Error evaluating server bundle. Check your webpack configuration.
              ===============================================================
              Caught error:
              #{err.message}
              ===============================================================
            MSG

            if err.message.include?("ReferenceError: self is not defined")
              msg << "\nError indicates that you may have code-splitting incorrectly enabled.\n"
            end
            raise ReactOnRails::Error, msg, err.backtrace
          end
          result = nil
          begin
            result = JSON.parse(json_string)
          rescue JSON::ParserError => e
            raise ReactOnRails::JsonParseError.new(parse_error: e, json: 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>.*)"\]\);/
            console_script_lines&.each do |line|
              match = re.match(line)
              Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
            end
          end
          result
        end
execjs_timer_polyfills() click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 164
        def execjs_timer_polyfills
          <<~JS
            function getStackTrace () {
              var stack;
              try {
                throw new Error('');
              }
              catch (error) {
                stack = error.stack || '';
              }
              stack = stack.split('\\n').map(function (line) { return line.trim(); });
              return stack.splice(stack[0] == 'Error' ? 2 : 1);
            }

            function setInterval() {
              #{undefined_for_exec_js_logging('setInterval')}
            }

            function setTimeout() {
              #{undefined_for_exec_js_logging('setTimeout')}
            }

            function clearTimeout() {
              #{undefined_for_exec_js_logging('clearTimeout')}
            }
          JS
        end
read_bundle_js_code() click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 119
def read_bundle_js_code
  server_js_file = ReactOnRails::Utils.server_bundle_js_file_path
  if ReactOnRails::Utils.server_bundle_path_is_http?
    file_url_to_string(server_js_file)
  else
    File.read(server_js_file)
  end
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
reset_pool() click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 11
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_modified() click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 19
def reset_pool_if_server_bundle_was_modified
  return unless ReactOnRails.configuration.development_mode

  if ReactOnRails::Utils.server_bundle_path_is_http?
    return if @server_bundle_url == ReactOnRails::Utils.server_bundle_js_file_path

    @server_bundle_url = ReactOnRails::Utils.server_bundle_js_file_path
  else
    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
  end
  ReactOnRails::ServerRenderingPool.reset_pool
end
trace_js_code_used(msg, js_code, file_name = "tmp/server-generated.js", force: false) click to toggle source

rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity

# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 95
        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 = <<-MSG.strip_heredoc
            #{'Z' * 80}
            [react_on_rails] #{msg}
            JavaScript code used: #{file_name}
            #{'Z' * 80}
          MSG
          if force
            Rails.logger.error(msg)
          else
            Rails.logger.info(msg)
          end
        end
undefined_for_exec_js_logging(function_name) click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 192
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

Private Class Methods

file_url_to_string(url) click to toggle source
# File lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb, line 226
def file_url_to_string(url)
  response = Net::HTTP.get_response(URI.parse(url))
  content_type_header = response["content-type"]
  match = content_type_header.match(/\A.*; charset=(?<encoding>.*)\z/)
  encoding_type = match[:encoding]
  response.body.force_encoding(encoding_type)
rescue StandardError => e
  msg = "file_url_to_string #{url} failed\nError is: #{e}"
  raise ReactOnRails::Error, msg
end