diff --git a/.travis.yml b/.travis.yml index d5c4c7e3b..019df864f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,3 +44,4 @@ script: notifications: slack: secure: LfcUk4AJ4vAxWwRIyw4tFh8QNbYefMwfG/oLfsN3CdRMWMOtCOHR1GGsRhAOlfVVJ/FvHqVqWj5gK7z7CaO5Uvl7rD3/zJ8QzExKx/iH9yWj55iIPuKLzwFNnBwRpFW/cqyU2lFPPRxGD50BUn3c+qybkuSqtKZ6qtTowwqlxLa5iyM3N95aZp7MEIKCP7cPcnHfLbJyP8wBpotp/rtw62eXM2HIRJJwgjcp+n+My7VFR9DnBXNFf6R91aZHM4U4cHHDbu15HFtH8honVrzK1JQdyqMNHga+j04dFuaS7z9Q369/hsELMOBp/227+Pz7ZRfWZFK4UASguOvyeX7RmGTRpTuWLm1XJeUzfsPZVROecaSVQBve+U7F12yKqilt97QlvRXn2EGyBILqvxtFNNR4S9kgAf72/6EFgiM1TKq7i9zy6lVOnagU2+7amq7UeopX1uoFsUfNKMR7YbgV1WjF0IK95UP0b0/7ZOJlPYgi5zzkQi129qAFWSMmxGk+ZpsttHh/tjJtvAh0A3mHq/zb5w4ub/MbSyZqeDUNgGj72QArOWUFSAStQT1ybsVLeDoKPgOvVq7OV1D64rpcHjBXcqOCit8tDZ+TqkFhcYJo2cITSaqE4zJXn+4F5s7So5O8CyfKYQq+kFJCooYGmfgTUckJpGl7eIvKmL4TN9Q= + diff --git a/CHANGELOG.md b/CHANGELOG.md index e1aa3afca..8ce124d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Contributors: please follow the recommendations outlined at [keepachangelog.com] ## [5.2.0] - 2016-04-08 ##### Added - Support for React 15.0 to react_on_rails. See [#379](https://github.com/shakacode/react_on_rails/pull/379) by [brucek](https://github.com/brucek). +- Support for Node.js server side rendering. See [#380](https://github.com/shakacode/react_on_rails/pull/380) by [alleycat](https://github.com/alleycat-at-git) and [doc](https://github.com/shakacode/react_on_rails/blob/master/docs/additional-reading/node-server-rendering.md) ##### Removed - Generator removals to simplify installer. See [#363](https://github.com/shakacode/react_on_rails/pull/363) by [jbhatab](https://github.com/jbhatab). diff --git a/docs/additional-reading/node-server-rendering.md b/docs/additional-reading/node-server-rendering.md new file mode 100644 index 000000000..647b0761b --- /dev/null +++ b/docs/additional-reading/node-server-rendering.md @@ -0,0 +1,17 @@ +## Node Server Rendering + +### Warning: this is an experimental feature + +The default server rendering exploits ExecJS to render react components. +Node server rendering allows you to use separate NodeJS process as a renderer. The process loads server-bundle.js and +then executes javascript to render the component inside its environment. The communication between rails and node occurs +via socket (`client/node/node.sock`) + +### Getting started + +To use node process just set `server_render_method = "NodeJS"` in `config/initializers/react_on_rails.rb`. To change back +to ExecJS set `server_render_method = "ExecJS"` + +### Configuration + +To change the name of server bundle adjust npm start script in `client/node/package.json` diff --git a/lib/generators/react_on_rails/base_generator.rb b/lib/generators/react_on_rails/base_generator.rb index 884f72e9b..4a9d05352 100644 --- a/lib/generators/react_on_rails/base_generator.rb +++ b/lib/generators/react_on_rails/base_generator.rb @@ -118,9 +118,13 @@ def install_server_rendering_files_if_enabled return unless options.server_rendering? base_path = "base/server_rendering/" %w(client/webpack.server.rails.config.js - client/app/bundles/HelloWorld/startup/serverRegistration.jsx).each do |file| + client/app/bundles/HelloWorld/startup/serverRegistration.jsx + client/node/package.json + client/node/server.js).each do |file| copy_file(base_path + file, file) end + + copy_file("base/base/lib/tasks/load_test.rake", "lib/tasks/load_test.rake") end def template_assets_rake_file diff --git a/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt b/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt index 27e91f4af..f9436206c 100644 --- a/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +++ b/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt @@ -1,3 +1,6 @@ web: rails s client: sh -c 'rm app/assets/webpack/* || true && cd client && npm run build:dev:client' -<%- if options.server_rendering? %>server: sh -c 'cd client && npm run build:dev:server'<%- end %> +<%- if options.server_rendering? %> +server: sh -c 'cd client && npm run build:dev:server' +node: sh -c 'cd client/node && npm start' +<%- end %> diff --git a/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt b/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt index 2966f1fbb..7a45e40d0 100644 --- a/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +++ b/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt @@ -29,7 +29,7 @@ ReactOnRails.configure do |config| # For server rendering. This can be set to false so that server side messages are discarded. # Default is true. Be cautious about turning this off. config.replay_console = true - # Default is true. Logs server rendering messags to Rails.logger.info + # Default is true. Logs server rendering messages to Rails.logger.info config.logging_on_server = true # The following options can be overriden by passing to the helper method: @@ -40,4 +40,7 @@ ReactOnRails.configure do |config| config.trace = Rails.env.development? # Default is false, enable if your content security policy doesn't include `style-src: 'unsafe-inline'` config.skip_display_none = false + + # The server render method - either ExecJS or NodeJS + config.server_render_method = "ExecJS" end diff --git a/lib/generators/react_on_rails/templates/base/base/lib/tasks/load_test.rake b/lib/generators/react_on_rails/templates/base/base/lib/tasks/load_test.rake new file mode 100644 index 000000000..13c00c68a --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/lib/tasks/load_test.rake @@ -0,0 +1,8 @@ +namespace :load_test do + desc "Load test with apache benchmark" + task :run, [:url, :count] do |_, args| + url = args[:url] || "http://localhost:3000/hello_world" + count = args[:count] || 500 + system("ab -c 10 -n #{count} #{url}") + end +end diff --git a/lib/generators/react_on_rails/templates/base/server_rendering/client/node/package.json b/lib/generators/react_on_rails/templates/base/server_rendering/client/node/package.json new file mode 100644 index 000000000..3a4478210 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/server_rendering/client/node/package.json @@ -0,0 +1,10 @@ +{ + "name": "react_on_rails_node", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./server.js -s server-bundle.js" + }, + "dependencies": { + } +} diff --git a/lib/generators/react_on_rails/templates/base/server_rendering/client/node/server.js b/lib/generators/react_on_rails/templates/base/server_rendering/client/node/server.js new file mode 100644 index 000000000..b7b541235 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/server_rendering/client/node/server.js @@ -0,0 +1,82 @@ +var net = require('net'); +var fs = require('fs'); + +var bundlePath = '../../app/assets/webpack/'; +var bundleFileName = 'server-bundle.js'; + +var currentArg; + +function Handler() { + this.queue = []; + this.initialized = false; +} + +Handler.prototype.handle = function (connection) { + var callback = function () { + connection.setEncoding('utf8'); + connection.on('data', (data)=> { + console.log('Processing request: ' + data); + var result = eval(data); + connection.write(result); + }); + }; + + if (this.initialized) { + callback(); + } else { + this.queue.push(callback); + } +}; + +Handler.prototype.initialize = function () { + console.log('Processing ' + this.queue.length + ' pending requests'); + var callback; + while (callback = this.queue.pop()) { + callback(); + } + + this.initialized = true; +}; + +var handler = new Handler(); + +process.argv.forEach((val) => { + if (val[0] == '-') { + currentArg = val.slice(1); + return; + } + + if (currentArg == 's') { + bundleFileName = val; + } +}); + +try { + fs.mkdirSync(bundlePath); +} catch (e) { + if (e.code != 'EEXIST') throw e; +} + +fs.watchFile(bundlePath + bundleFileName, (curr) => { + if (curr && curr.blocks && curr.blocks > 0) { + if (handler.initialized) { + console.log('Reloading server bundle must be implemented by restarting the node process!'); + return; + } + + require(bundlePath + bundleFileName); + console.log('Loaded server bundle: ' + bundlePath + bundleFileName); + handler.initialize(); + } +}); + +var unixServer = net.createServer(function (connection) { + handler.handle(connection); +}); + +unixServer.listen('node.sock'); + +process.on('SIGINT', () => { + unixServer.close(); + process.exit(); +}); diff --git a/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb b/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb index 0853335cd..04257a5e9 100644 --- a/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb +++ b/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb @@ -36,6 +36,7 @@ RSpec.configure do |config| # Ensure that if we are running js tests, we are using latest webpack assets # This will use the defaults of :js and :server_rendering meta tags + ReactOnRails::TestHelper.launch_node if ReactOnRails.configuration.server_render_method == "NodeJS" ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) # Remove this line if you"re not using ActiveRecord or ActiveRecord fixtures diff --git a/lib/react_on_rails.rb b/lib/react_on_rails.rb index 1bd5f4976..44bf9b9aa 100644 --- a/lib/react_on_rails.rb +++ b/lib/react_on_rails.rb @@ -16,3 +16,4 @@ require "react_on_rails/test_helper/webpack_assets_status_checker" require "react_on_rails/test_helper/webpack_process_checker" require "react_on_rails/test_helper/ensure_assets_compiled" +require "react_on_rails/test_helper/node_process_launcher" diff --git a/lib/react_on_rails/configuration.rb b/lib/react_on_rails/configuration.rb index c4965726d..4fd9e080a 100644 --- a/lib/react_on_rails/configuration.rb +++ b/lib/react_on_rails/configuration.rb @@ -56,7 +56,8 @@ def self.configuration server_renderer_timeout: 20, skip_display_none: false, webpack_generated_files: [], - rendering_extension: nil + rendering_extension: nil, + server_render_method: "" ) end @@ -66,7 +67,7 @@ class Configuration :logging_on_server, :server_renderer_pool_size, :server_renderer_timeout, :raise_on_prerender_error, :skip_display_none, :generated_assets_dirs, :generated_assets_dir, - :webpack_generated_files, :rendering_extension + :webpack_generated_files, :rendering_extension, :server_render_method def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil, trace: nil, development_mode: nil, @@ -74,7 +75,7 @@ def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil, server_renderer_timeout: nil, raise_on_prerender_error: nil, skip_display_none: nil, generated_assets_dirs: nil, generated_assets_dir: nil, webpack_generated_files: nil, - rendering_extension: nil) + rendering_extension: nil, server_render_method: nil) self.server_bundle_js_file = server_bundle_js_file self.generated_assets_dirs = generated_assets_dirs self.generated_assets_dir = generated_assets_dir @@ -97,6 +98,8 @@ def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil, self.webpack_generated_files = webpack_generated_files self.rendering_extension = rendering_extension + + self.server_render_method = server_render_method end end end diff --git a/lib/react_on_rails/server_rendering_pool.rb b/lib/react_on_rails/server_rendering_pool.rb index 89b544111..88ce04a99 100644 --- a/lib/react_on_rails/server_rendering_pool.rb +++ b/lib/react_on_rails/server_rendering_pool.rb @@ -1,165 +1,23 @@ require "connection_pool" +require_relative "server_rendering_pool/exec" +require_relative "server_rendering_pool/node" # 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_timeout } - @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) - if trace_react_on_rails? - @file_index ||= 1 - trace_messsage(js_code, "tmp/server-generated-#{@file_index % 10}.js") - @file_index += 1 - end - 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\] (?.*)"\]\);/ - 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 - + module ServerRenderingPool class << self - private - - def trace_messsage(js_code, file_name = "tmp/server-generated.js", force = false) - return unless trace_react_on_rails? || force - # 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 trace_react_on_rails? - ENV["TRACE_REACT_ON_RAILS"].present? - 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.file?(server_js_file) - bundle_js_code = File.read(server_js_file) - base_js_code = <<-JS -#{console_polyfill} -#{execjs_timer_polyfills} - #{bundle_js_code}; - JS - file_name = "tmp/base_js_code.js" - begin - trace_messsage(base_js_code, file_name) - ExecJS.compile(base_js_code) - rescue => 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")}" - puts msg - Rails.logger.error(msg) - trace_messsage(base_js_code, file_name, true) - 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 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')} -} - JS - end - - def undefined_for_exec_js_logging(function_name) - if trace_react_on_rails? - "console.error('#{function_name} is not defined for execJS. See "\ - "https://github.com/sstephenson/execjs#faq. Note babel-polyfill may call this.');\n"\ - " console.error(getStackTrace().join('\\n'));" + def pool + if ReactOnRails.configuration.server_render_method == "NodeJS" + ServerRenderingPool::Node else - "" + ServerRenderingPool::Exec end 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 + def method_missing(sym, *args, &block) + pool.send sym, *args, &block end end end diff --git a/lib/react_on_rails/server_rendering_pool/exec.rb b/lib/react_on_rails/server_rendering_pool/exec.rb new file mode 100644 index 000000000..28b15f18a --- /dev/null +++ b/lib/react_on_rails/server_rendering_pool/exec.rb @@ -0,0 +1,166 @@ +module ReactOnRails + module ServerRenderingPool + # This implementation of the rendering pool uses ExecJS to execute javasript code + class Exec + def self.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 + + 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) + if trace_react_on_rails? + @file_index ||= 1 + trace_messsage(js_code, "tmp/server-generated-#{@file_index % 10}.js") + @file_index += 1 + end + 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\] (?.*)"\]\);/ + 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", force = false) + return unless trace_react_on_rails? || force + # 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 trace_react_on_rails? + ENV["TRACE_REACT_ON_RAILS"].present? + 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 + file_name = "tmp/base_js_code.js" + begin + trace_messsage(base_js_code, file_name) + ExecJS.compile(base_js_code) + rescue => 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")}" + puts msg + Rails.logger.error(msg) + trace_messsage(base_js_code, file_name, true) + 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 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')} +} + JS + end + + def undefined_for_exec_js_logging(function_name) + if trace_react_on_rails? + "console.error('#{function_name} is not defined for execJS. See "\ + "https://github.com/sstephenson/execjs#faq. Note babel-polyfill may call this.');\n"\ + " console.error(getStackTrace().join('\\n'));" + else + "" + end + 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 +end diff --git a/lib/react_on_rails/server_rendering_pool/node.rb b/lib/react_on_rails/server_rendering_pool/node.rb new file mode 100644 index 000000000..47da51f5e --- /dev/null +++ b/lib/react_on_rails/server_rendering_pool/node.rb @@ -0,0 +1,77 @@ +module ReactOnRails + module ServerRenderingPool + class Node + # This implementation of the rendering pool uses NodeJS to execute javasript code + def self.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 + + def self.reset_pool_if_server_bundle_was_modified + # No need for this method, the server bundle is automatically reset by node when changes + # Empty implementation to conform to ServerRenderingPool interface + 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) + if trace_react_on_rails? + @file_index ||= 1 + trace_messsage(js_code, "tmp/server-generated-#{@file_index % 10}.js") + @file_index += 1 + end + json_string = eval_js(js_code) + JSON.parse(json_string) + end + + class << self + private + + def trace_messsage(js_code, file_name = "tmp/server-generated.js", force = false) + return unless trace_react_on_rails? || force + # 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 trace_react_on_rails? + ENV["TRACE_REACT_ON_RAILS"].present? + end + + def eval_js(js_code) + max_int = (2**(32 - 2) - 1) + @js_context_pool.with do |js_context| + js_context.send(js_code, 0) + result = js_context.recv(max_int) + result + end + end + + def create_js_context + begin + client = UNIXSocket.new("client/node/node.sock") + client.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) + rescue StandardError => e + Rails.logger.error("Unable to connect to socket: client/node/node.sock. +Make sure node server is up and running.") + Rails.logger.error(e) + raise e + end + client + end + end + end + end +end diff --git a/lib/react_on_rails/test_helper/node_process_launcher.rb b/lib/react_on_rails/test_helper/node_process_launcher.rb new file mode 100644 index 000000000..96a33f4e3 --- /dev/null +++ b/lib/react_on_rails/test_helper/node_process_launcher.rb @@ -0,0 +1,12 @@ +module ReactOnRails + module TestHelper + def self.launch_node + if ReactOnRails.configuration.server_render_method == "NodeJS" + path = "#{::Rails.root}/client/node" + puts "Launching NodeJS server at #{path}" + system("cd #{path} && npm start &") + sleep(1) + end + end + end +end