Skip to content

Commit

Permalink
Update heroku-deployment.md (+43 squashed commits)
Browse files Browse the repository at this point in the history
Squashed commits:
[15d3513] Update README.md

Fix links to installation-overview
[24a11c2] Update README.md
[73b5bdf] added babel runtime to root package.json
[3f94443] Moved api and generator docs to separate folders go keep readme cleaner.
[d2164a8] bugfix: execJS timeout
[03c5d0a] no message
[5785997] Update PROJECTS.md
[1e38e34] Extracts Options for react_component helper
[7bd0f94] Update build:client in generator to correctly have closing parenthesis
[e0775b7] linters
[f7e3d27] travis
[e8e4f7d] travis
[adbf2b9] added docs
[9a00d54] added node process startup
[2b1f619] added node process startup
[4b3e046] fixed node server
[7f2dc65] no message
[c663eaf] no message
[404c282] travis
[4db1366] travis
[114a40d] travis
[8c170c3] travis
[41aeac3] travis
[274ed74] new travis test
[135dc29] new travis
[0ae3113] minor fixes
[e77804a] travis
[ed911be] minor fixes
[f91350d] small fixes
[3d4f262] added benchmark test
[c2c3675] added config
[6812e13] node generator
[8ba518d] added node
[c4dad1e] new travis
[1338026] minor fixes
[6d9c23c] travis
[808573e] minor fixes
[4586800] small fixes
[0202cc3] added benchmark test
[ab50b77] added config
[ed82bc7] node generator
[16a018e] added node
[db5914b] Fix file exists check for server bundle, which returns true for a dir. (NOTE: Similar bugs may exist elsewhere in the code.)
  • Loading branch information
timscott authored and alexeuler committed Apr 29, 2016
1 parent 9995619 commit 41f6564
Show file tree
Hide file tree
Showing 15 changed files with 401 additions and 157 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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=

15 changes: 15 additions & 0 deletions docs/additional-reading/node-server-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Node Server Rendering

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`
6 changes: 5 additions & 1 deletion lib/generators/react_on_rails/base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 %>
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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": {
}
}
Original file line number Diff line number Diff line change
@@ -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();
});
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/react_on_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
9 changes: 6 additions & 3 deletions lib/react_on_rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -66,15 +67,15 @@ 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,
logging_on_server: nil, server_renderer_pool_size: 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
Expand All @@ -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
160 changes: 9 additions & 151 deletions lib/react_on_rails/server_rendering_pool.rb
Original file line number Diff line number Diff line change
@@ -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\] (?<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

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
Expand Down
Loading

0 comments on commit 41f6564

Please sign in to comment.