react_on_rails 5.2.0 → 6.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +1 -0
  4. data/CHANGELOG.md +31 -0
  5. data/PROJECTS.md +11 -2
  6. data/README.md +33 -203
  7. data/app/helpers/react_on_rails_helper.rb +32 -49
  8. data/docs/additional-reading/heroku-deployment.md +2 -35
  9. data/docs/additional-reading/node-server-rendering.md +30 -0
  10. data/docs/additional-reading/rails-assets.md +19 -0
  11. data/docs/additional-reading/rspec-configuration.md +38 -4
  12. data/docs/additional-reading/webpack-dev-server.md +16 -0
  13. data/docs/additional-reading/webpack.md +0 -10
  14. data/docs/api/javascript-api.md +37 -0
  15. data/docs/api/ruby-api-hot-reload-view-helpers.md +42 -0
  16. data/docs/api/ruby-api.md +3 -0
  17. data/docs/basics/generator.md +49 -0
  18. data/docs/{additional-reading → basics}/installation-overview.md +0 -0
  19. data/docs/basics/migrating-from-react-rails.md +17 -0
  20. data/docs/contributor-info/contributing.md +11 -0
  21. data/docs/{coding-style → contributor-info}/linters.md +0 -0
  22. data/lib/generators/USAGE +3 -95
  23. data/lib/generators/react_on_rails/base_generator.rb +10 -43
  24. data/lib/generators/react_on_rails/dev_tests_generator.rb +17 -1
  25. data/lib/generators/react_on_rails/install_generator.rb +0 -6
  26. data/lib/generators/react_on_rails/react_no_redux_generator.rb +3 -17
  27. data/lib/generators/react_on_rails/react_with_redux_generator.rb +4 -21
  28. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +2 -2
  29. data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +3 -4
  30. data/lib/generators/react_on_rails/templates/base/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx.tt +1 -11
  31. data/lib/generators/react_on_rails/templates/base/base/client/node/package.json +10 -0
  32. data/lib/generators/react_on_rails/templates/base/base/client/node/server.js +82 -0
  33. data/lib/generators/react_on_rails/templates/base/base/client/package.json.tt +3 -20
  34. data/lib/generators/react_on_rails/templates/base/base/client/webpack.config.js +58 -0
  35. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +60 -26
  36. data/lib/generators/react_on_rails/templates/base/base/lib/tasks/assets.rake.tt +1 -4
  37. data/lib/generators/react_on_rails/templates/base/base/lib/tasks/load_test.rake +8 -0
  38. data/lib/generators/react_on_rails/templates/base/base/package.json.tt +0 -12
  39. data/lib/generators/react_on_rails/templates/dev_tests/spec/features/hello_world_spec.rb +1 -1
  40. data/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb +1 -0
  41. data/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx +1 -7
  42. data/lib/generators/react_on_rails/templates/{base/base/client/app/bundles/HelloWorld/startup/clientRegistration.jsx.tt → no_redux/base/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx.tt} +7 -1
  43. data/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/startup/{HelloWorldAppClient.jsx.tt → HelloWorldApp.jsx.tt} +5 -1
  44. data/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/store/helloWorldStore.jsx +1 -5
  45. data/lib/react_on_rails.rb +2 -1
  46. data/lib/react_on_rails/configuration.rb +17 -5
  47. data/lib/react_on_rails/react_component/options.rb +76 -0
  48. data/lib/react_on_rails/server_rendering_pool.rb +9 -151
  49. data/lib/react_on_rails/server_rendering_pool/exec.rb +166 -0
  50. data/lib/react_on_rails/server_rendering_pool/node.rb +77 -0
  51. data/lib/react_on_rails/test_helper.rb +1 -6
  52. data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +4 -77
  53. data/lib/react_on_rails/test_helper/node_process_launcher.rb +12 -0
  54. data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +4 -28
  55. data/lib/react_on_rails/version.rb +1 -1
  56. data/lib/tasks/assets.rake +115 -0
  57. data/package.json +2 -1
  58. data/rakelib/example_type.rb +3 -10
  59. data/rakelib/examples_config.yml +2 -2
  60. data/react_on_rails.gemspec +1 -0
  61. metadata +41 -20
  62. data/lib/generators/react_on_rails/templates/base/base/REACT_ON_RAILS.md +0 -16
  63. data/lib/generators/react_on_rails/templates/base/base/client/REACT_ON_RAILS_CLIENT_README.md +0 -3
  64. data/lib/generators/react_on_rails/templates/base/base/client/webpack.client.base.config.js +0 -65
  65. data/lib/generators/react_on_rails/templates/base/base/client/webpack.client.rails.config.js +0 -53
  66. data/lib/generators/react_on_rails/templates/base/server_rendering/client/app/bundles/HelloWorld/startup/serverRegistration.jsx +0 -4
  67. data/lib/generators/react_on_rails/templates/base/server_rendering/client/webpack.server.rails.config.js +0 -39
  68. data/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx.tt +0 -6
  69. data/lib/generators/react_on_rails/templates/no_redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx +0 -6
  70. data/lib/generators/react_on_rails/templates/redux/base/client/app/lib/middlewares/loggerMiddleware.js +0 -21
  71. data/lib/generators/react_on_rails/templates/redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx +0 -19
  72. data/lib/react_on_rails/test_helper/webpack_process_checker.rb +0 -54
@@ -6,6 +6,7 @@ require "react_on_rails/version_checker"
6
6
  require "react_on_rails/configuration"
7
7
  require "react_on_rails/server_rendering_pool"
8
8
  require "react_on_rails/engine"
9
+ require "react_on_rails/react_component/options"
9
10
  require "react_on_rails/version_syntax_converter"
10
11
  require "react_on_rails/test_helper"
11
12
  require "react_on_rails/git_utils"
@@ -13,5 +14,5 @@ require "react_on_rails/utils"
13
14
  require "react_on_rails/test_helper"
14
15
  require "react_on_rails/test_helper/webpack_assets_compiler"
15
16
  require "react_on_rails/test_helper/webpack_assets_status_checker"
16
- require "react_on_rails/test_helper/webpack_process_checker"
17
17
  require "react_on_rails/test_helper/ensure_assets_compiled"
18
+ require "react_on_rails/test_helper/node_process_launcher"
@@ -8,7 +8,7 @@ module ReactOnRails
8
8
 
9
9
  def self.setup_config_values
10
10
  if @configuration.webpack_generated_files.empty?
11
- files = ["client-bundle.js"]
11
+ files = ["webpack-bundle.js"]
12
12
  if @configuration.server_bundle_js_file.present?
13
13
  files << @configuration.server_bundle_js_file
14
14
  end
@@ -44,7 +44,6 @@ module ReactOnRails
44
44
 
45
45
  # generated_assets_dirs is deprecated
46
46
  generated_assets_dir: "",
47
-
48
47
  server_bundle_js_file: "",
49
48
  prerender: false,
50
49
  replay_console: true,
@@ -56,7 +55,11 @@ module ReactOnRails
56
55
  server_renderer_timeout: 20,
57
56
  skip_display_none: false,
58
57
  webpack_generated_files: [],
59
- rendering_extension: nil
58
+ rendering_extension: nil,
59
+ server_render_method: "",
60
+ symlink_non_digested_assets_regex: /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg)/,
61
+ npm_build_test_command: "",
62
+ npm_build_production_command: ""
60
63
  )
61
64
  end
62
65
 
@@ -66,7 +69,9 @@ module ReactOnRails
66
69
  :logging_on_server, :server_renderer_pool_size,
67
70
  :server_renderer_timeout, :raise_on_prerender_error,
68
71
  :skip_display_none, :generated_assets_dirs, :generated_assets_dir,
69
- :webpack_generated_files, :rendering_extension
72
+ :webpack_generated_files, :rendering_extension, :npm_build_test_command,
73
+ :npm_build_production_command,
74
+ :server_render_method, :symlink_non_digested_assets_regex
70
75
 
71
76
  def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil,
72
77
  trace: nil, development_mode: nil,
@@ -74,10 +79,14 @@ module ReactOnRails
74
79
  server_renderer_timeout: nil, raise_on_prerender_error: nil,
75
80
  skip_display_none: nil, generated_assets_dirs: nil,
76
81
  generated_assets_dir: nil, webpack_generated_files: nil,
77
- rendering_extension: nil)
82
+ rendering_extension: nil, npm_build_test_command: nil,
83
+ npm_build_production_command: nil,
84
+ server_render_method: nil, symlink_non_digested_assets_regex: nil)
78
85
  self.server_bundle_js_file = server_bundle_js_file
79
86
  self.generated_assets_dirs = generated_assets_dirs
80
87
  self.generated_assets_dir = generated_assets_dir
88
+ self.npm_build_test_command = npm_build_test_command
89
+ self.npm_build_production_command = npm_build_production_command
81
90
 
82
91
  self.prerender = prerender
83
92
  self.replay_console = replay_console
@@ -97,6 +106,9 @@ module ReactOnRails
97
106
 
98
107
  self.webpack_generated_files = webpack_generated_files
99
108
  self.rendering_extension = rendering_extension
109
+
110
+ self.server_render_method = server_render_method
111
+ self.symlink_non_digested_assets_regex = symlink_non_digested_assets_regex
100
112
  end
101
113
  end
102
114
  end
@@ -0,0 +1,76 @@
1
+ module ReactOnRails
2
+ module ReactComponent
3
+ class Options
4
+ NO_PROPS = {}.freeze
5
+ HIDDEN = "display:none".freeze
6
+
7
+ attr_reader :index
8
+
9
+ def initialize(name:, index:, options:)
10
+ @name = name
11
+ @index = index
12
+ @options = options
13
+ end
14
+
15
+ def props
16
+ options.fetch(:props) { NO_PROPS }
17
+ end
18
+
19
+ def name
20
+ @name.camelize
21
+ end
22
+
23
+ def dom_id
24
+ options.fetch(:id) { default_dom_id }
25
+ end
26
+
27
+ def html_options
28
+ options[:html_options].to_h
29
+ end
30
+
31
+ def prerender
32
+ retrieve_key(:prerender)
33
+ end
34
+
35
+ def trace
36
+ retrieve_key(:trace)
37
+ end
38
+
39
+ def replay_console
40
+ retrieve_key(:replay_console)
41
+ end
42
+
43
+ def raise_on_prerender_error
44
+ retrieve_key(:raise_on_prerender_error)
45
+ end
46
+
47
+ def data
48
+ {
49
+ component_name: name,
50
+ props: props,
51
+ trace: trace,
52
+ dom_id: dom_id
53
+ }
54
+ end
55
+
56
+ def style
57
+ return nil if ReactOnRails.configuration.skip_display_none
58
+ HIDDEN
59
+ end
60
+
61
+ private
62
+
63
+ attr_reader :options
64
+
65
+ def default_dom_id
66
+ "#{@name}-react-component-#{@index}"
67
+ end
68
+
69
+ def retrieve_key(key)
70
+ options.fetch(key) do
71
+ ReactOnRails.configuration.public_send(key)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,165 +1,23 @@
1
1
  require "connection_pool"
2
+ require_relative "server_rendering_pool/exec"
3
+ require_relative "server_rendering_pool/node"
2
4
 
3
5
  # Based on the react-rails gem.
4
6
  # None of these methods should be called directly.
5
7
  # See app/helpers/react_on_rails_helper.rb
6
8
  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_pool_size }
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
55
10
  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.exist?(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
144
14
  else
145
- ""
15
+ ServerRenderingPool::Exec
146
16
  end
147
17
  end
148
18
 
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
163
21
  end
164
22
  end
165
23
  end
@@ -0,0 +1,166 @@
1
+ module ReactOnRails
2
+ module ServerRenderingPool
3
+ # This implementation of the rendering pool uses ExecJS to execute javasript code
4
+ class Exec
5
+ def self.reset_pool
6
+ options = {
7
+ size: ReactOnRails.configuration.server_renderer_pool_size,
8
+ timeout: ReactOnRails.configuration.server_renderer_timeout
9
+ }
10
+ @js_context_pool = ConnectionPool.new(options) { create_js_context }
11
+ end
12
+
13
+ def self.reset_pool_if_server_bundle_was_modified
14
+ return unless ReactOnRails.configuration.development_mode
15
+ file_mtime = File.mtime(ReactOnRails::Utils.default_server_bundle_js_file_path)
16
+ @server_bundle_timestamp ||= file_mtime
17
+ return if @server_bundle_timestamp == file_mtime
18
+ ReactOnRails::ServerRenderingPool.reset_pool
19
+ @server_bundle_timestamp = file_mtime
20
+ end
21
+
22
+ # js_code: JavaScript expression that returns a string.
23
+ # Returns a Hash:
24
+ # html: string of HTML for direct insertion on the page by evaluating js_code
25
+ # consoleReplayScript: script for replaying console
26
+ # hasErrors: true if server rendering errors
27
+ # Note, js_code does not have to be based on React.
28
+ # js_code MUST RETURN json stringify Object
29
+ # Calling code will probably call 'html_safe' on return value before rendering to the view.
30
+ def self.server_render_js_with_console_logging(js_code)
31
+ if trace_react_on_rails?
32
+ @file_index ||= 1
33
+ trace_messsage(js_code, "tmp/server-generated-#{@file_index % 10}.js")
34
+ @file_index += 1
35
+ end
36
+ json_string = eval_js(js_code)
37
+ result = JSON.parse(json_string)
38
+
39
+ if ReactOnRails.configuration.logging_on_server
40
+ console_script = result["consoleReplayScript"]
41
+ console_script_lines = console_script.split("\n")
42
+ console_script_lines = console_script_lines[2..-2]
43
+ re = /console\.log\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
44
+ if console_script_lines
45
+ console_script_lines.each do |line|
46
+ match = re.match(line)
47
+ Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
48
+ end
49
+ end
50
+ end
51
+ result
52
+ end
53
+
54
+ class << self
55
+ private
56
+
57
+ def trace_messsage(js_code, file_name = "tmp/server-generated.js", force = false)
58
+ return unless trace_react_on_rails? || force
59
+ # Set to anything to print generated code.
60
+ puts "Z" * 80
61
+ puts "react_renderer.rb: 92"
62
+ puts "wrote file #{file_name}"
63
+ File.write(file_name, js_code)
64
+ puts "Z" * 80
65
+ end
66
+
67
+ def trace_react_on_rails?
68
+ ENV["TRACE_REACT_ON_RAILS"].present?
69
+ end
70
+
71
+ def eval_js(js_code)
72
+ @js_context_pool.with do |js_context|
73
+ result = js_context.eval(js_code)
74
+ js_context.eval("console.history = []")
75
+ result
76
+ end
77
+ end
78
+
79
+ def create_js_context
80
+ server_js_file = ReactOnRails::Utils.default_server_bundle_js_file_path
81
+ if server_js_file.present? && File.exist?(server_js_file)
82
+ bundle_js_code = File.read(server_js_file)
83
+ base_js_code = <<-JS
84
+ #{console_polyfill}
85
+ #{execjs_timer_polyfills}
86
+ #{bundle_js_code};
87
+ JS
88
+ file_name = "tmp/base_js_code.js"
89
+ begin
90
+ trace_messsage(base_js_code, file_name)
91
+ ExecJS.compile(base_js_code)
92
+ rescue => e
93
+ msg = "ERROR when compiling base_js_code! "\
94
+ "See file #{file_name} to "\
95
+ "correlate line numbers of error. Error is\n\n#{e.message}"\
96
+ "\n\n#{e.backtrace.join("\n")}"
97
+ puts msg
98
+ Rails.logger.error(msg)
99
+ trace_messsage(base_js_code, file_name, true)
100
+ raise e
101
+ end
102
+ else
103
+ if server_js_file.present?
104
+ msg = "You specified server rendering JS file: #{server_js_file}, but it cannot be "\
105
+ "read. You may set the server_bundle_js_file in your configuration to be \"\" to "\
106
+ "avoid this warning"
107
+ Rails.logger.warn msg
108
+ puts msg
109
+ end
110
+ ExecJS.compile("")
111
+ end
112
+ end
113
+
114
+ def execjs_timer_polyfills
115
+ <<-JS
116
+ function getStackTrace () {
117
+ var stack;
118
+ try {
119
+ throw new Error('');
120
+ }
121
+ catch (error) {
122
+ stack = error.stack || '';
123
+ }
124
+ stack = stack.split('\\n').map(function (line) { return line.trim(); });
125
+ return stack.splice(stack[0] == 'Error' ? 2 : 1);
126
+ }
127
+
128
+ function setInterval() {
129
+ #{undefined_for_exec_js_logging('setInterval')}
130
+ }
131
+
132
+ function setTimeout() {
133
+ #{undefined_for_exec_js_logging('setTimeout')}
134
+ }
135
+ JS
136
+ end
137
+
138
+ def undefined_for_exec_js_logging(function_name)
139
+ if trace_react_on_rails?
140
+ "console.error('#{function_name} is not defined for execJS. See "\
141
+ "https://github.com/sstephenson/execjs#faq. Note babel-polyfill may call this.');\n"\
142
+ " console.error(getStackTrace().join('\\n'));"
143
+ else
144
+ ""
145
+ end
146
+ end
147
+
148
+ # Reimplement console methods for replaying on the client
149
+ def console_polyfill
150
+ <<-JS
151
+ var console = { history: [] };
152
+ ['error', 'log', 'info', 'warn'].forEach(function (level) {
153
+ console[level] = function () {
154
+ var argArray = Array.prototype.slice.call(arguments);
155
+ if (argArray.length > 0) {
156
+ argArray[0] = '[SERVER] ' + argArray[0];
157
+ }
158
+ console.history.push({level: level, arguments: argArray});
159
+ };
160
+ });
161
+ JS
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end