Skip to content

Commit 41f6564

Browse files
timscottalexeuler
authored andcommitted
Update heroku-deployment.md (+43 squashed commits)
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.)
1 parent 9995619 commit 41f6564

File tree

15 files changed

+401
-157
lines changed

15 files changed

+401
-157
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ script:
4444
notifications:
4545
slack:
4646
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=
47+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## Node Server Rendering
2+
3+
The default server rendering exploits ExecJS to render react components.
4+
Node server rendering allows you to use separate NodeJS process as a renderer. The process loads server-bundle.js and
5+
then executes javascript to render the component inside its environment. The communication between rails and node occurs
6+
via socket (`client/node/node.sock`)
7+
8+
### Getting started
9+
10+
To use node process just set `server_render_method = "NodeJS"` in `config/initializers/react_on_rails.rb`. To change back
11+
to ExecJS set `server_render_method = "ExecJS"`
12+
13+
### Configuration
14+
15+
To change the name of server bundle adjust npm start script in `client/node/package.json`

lib/generators/react_on_rails/base_generator.rb

+5-1
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,13 @@ def install_server_rendering_files_if_enabled
118118
return unless options.server_rendering?
119119
base_path = "base/server_rendering/"
120120
%w(client/webpack.server.rails.config.js
121-
client/app/bundles/HelloWorld/startup/serverRegistration.jsx).each do |file|
121+
client/app/bundles/HelloWorld/startup/serverRegistration.jsx
122+
client/node/package.json
123+
client/node/server.js).each do |file|
122124
copy_file(base_path + file, file)
123125
end
126+
127+
copy_file("base/base/lib/tasks/load_test.rake", "lib/tasks/load_test.rake")
124128
end
125129

126130
def template_assets_rake_file
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
web: rails s
22
client: sh -c 'rm app/assets/webpack/* || true && cd client && npm run build:dev:client'
3-
<%- if options.server_rendering? %>server: sh -c 'cd client && npm run build:dev:server'<%- end %>
3+
<%- if options.server_rendering? %>
4+
server: sh -c 'cd client && npm run build:dev:server'
5+
node: sh -c 'cd client/node && npm start'
6+
<%- end %>

lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt

+4-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ ReactOnRails.configure do |config|
2929
# For server rendering. This can be set to false so that server side messages are discarded.
3030
# Default is true. Be cautious about turning this off.
3131
config.replay_console = true
32-
# Default is true. Logs server rendering messags to Rails.logger.info
32+
# Default is true. Logs server rendering messages to Rails.logger.info
3333
config.logging_on_server = true
3434

3535
# The following options can be overriden by passing to the helper method:
@@ -40,4 +40,7 @@ ReactOnRails.configure do |config|
4040
config.trace = Rails.env.development?
4141
# Default is false, enable if your content security policy doesn't include `style-src: 'unsafe-inline'`
4242
config.skip_display_none = false
43+
44+
# The server render method - either ExecJS or NodeJS
45+
config.server_render_method = "ExecJS"
4346
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace :load_test do
2+
desc "Load test with apache benchmark"
3+
task :run, [:url, :count] do |_, args|
4+
url = args[:url] || "http://localhost:3000/hello_world"
5+
count = args[:count] || 500
6+
system("ab -c 10 -n #{count} #{url}")
7+
end
8+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "react_on_rails_node",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"start": "node ./server.js -s server-bundle.js"
7+
},
8+
"dependencies": {
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
var net = require('net');
2+
var fs = require('fs');
3+
4+
var bundlePath = '../../app/assets/webpack/';
5+
var bundleFileName = 'server-bundle.js';
6+
7+
var currentArg;
8+
9+
function Handler() {
10+
this.queue = [];
11+
this.initialized = false;
12+
}
13+
14+
Handler.prototype.handle = function (connection) {
15+
var callback = function () {
16+
connection.setEncoding('utf8');
17+
connection.on('data', (data)=> {
18+
console.log('Processing request: ' + data);
19+
var result = eval(data);
20+
connection.write(result);
21+
});
22+
};
23+
24+
if (this.initialized) {
25+
callback();
26+
} else {
27+
this.queue.push(callback);
28+
}
29+
};
30+
31+
Handler.prototype.initialize = function () {
32+
console.log('Processing ' + this.queue.length + ' pending requests');
33+
var callback;
34+
while (callback = this.queue.pop()) {
35+
callback();
36+
}
37+
38+
this.initialized = true;
39+
};
40+
41+
var handler = new Handler();
42+
43+
process.argv.forEach((val) => {
44+
if (val[0] == '-') {
45+
currentArg = val.slice(1);
46+
return;
47+
}
48+
49+
if (currentArg == 's') {
50+
bundleFileName = val;
51+
}
52+
});
53+
54+
try {
55+
fs.mkdirSync(bundlePath);
56+
} catch (e) {
57+
if (e.code != 'EEXIST') throw e;
58+
}
59+
60+
fs.watchFile(bundlePath + bundleFileName, (curr) => {
61+
if (curr && curr.blocks && curr.blocks > 0) {
62+
if (handler.initialized) {
63+
console.log('Reloading server bundle must be implemented by restarting the node process!');
64+
return;
65+
}
66+
67+
require(bundlePath + bundleFileName);
68+
console.log('Loaded server bundle: ' + bundlePath + bundleFileName);
69+
handler.initialize();
70+
}
71+
});
72+
73+
var unixServer = net.createServer(function (connection) {
74+
handler.handle(connection);
75+
});
76+
77+
unixServer.listen('node.sock');
78+
79+
process.on('SIGINT', () => {
80+
unixServer.close();
81+
process.exit();
82+
});

lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
RSpec.configure do |config|
3737
# Ensure that if we are running js tests, we are using latest webpack assets
3838
# This will use the defaults of :js and :server_rendering meta tags
39+
ReactOnRails::TestHelper.launch_node if ReactOnRails.configuration.server_render_method == "NodeJS"
3940
ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
4041

4142
# Remove this line if you"re not using ActiveRecord or ActiveRecord fixtures

lib/react_on_rails.rb

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
require "react_on_rails/test_helper/webpack_assets_status_checker"
1717
require "react_on_rails/test_helper/webpack_process_checker"
1818
require "react_on_rails/test_helper/ensure_assets_compiled"
19+
require "react_on_rails/test_helper/node_process_launcher"

lib/react_on_rails/configuration.rb

+6-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ def self.configuration
5656
server_renderer_timeout: 20,
5757
skip_display_none: false,
5858
webpack_generated_files: [],
59-
rendering_extension: nil
59+
rendering_extension: nil,
60+
server_render_method: ""
6061
)
6162
end
6263

@@ -66,15 +67,15 @@ class Configuration
6667
:logging_on_server, :server_renderer_pool_size,
6768
:server_renderer_timeout, :raise_on_prerender_error,
6869
:skip_display_none, :generated_assets_dirs, :generated_assets_dir,
69-
:webpack_generated_files, :rendering_extension
70+
:webpack_generated_files, :rendering_extension, :server_render_method
7071

7172
def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil,
7273
trace: nil, development_mode: nil,
7374
logging_on_server: nil, server_renderer_pool_size: nil,
7475
server_renderer_timeout: nil, raise_on_prerender_error: nil,
7576
skip_display_none: nil, generated_assets_dirs: nil,
7677
generated_assets_dir: nil, webpack_generated_files: nil,
77-
rendering_extension: nil)
78+
rendering_extension: nil, server_render_method: nil)
7879
self.server_bundle_js_file = server_bundle_js_file
7980
self.generated_assets_dirs = generated_assets_dirs
8081
self.generated_assets_dir = generated_assets_dir
@@ -97,6 +98,8 @@ def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil,
9798

9899
self.webpack_generated_files = webpack_generated_files
99100
self.rendering_extension = rendering_extension
101+
102+
self.server_render_method = server_render_method
100103
end
101104
end
102105
end

lib/react_on_rails/server_rendering_pool.rb

+9-151
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,23 @@
11
require "connection_pool"
2+
require_relative "server_rendering_pool/exec"
3+
require_relative "server_rendering_pool/node"
24

35
# Based on the react-rails gem.
46
# None of these methods should be called directly.
57
# See app/helpers/react_on_rails_helper.rb
68
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
5510
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
14414
else
145-
""
15+
ServerRenderingPool::Exec
14616
end
14717
end
14818

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
16321
end
16422
end
16523
end

0 commit comments

Comments
 (0)