diff --git a/lib/assets/javascripts/react_ujs.js b/lib/assets/javascripts/react_ujs.js
index 3480af23..ee472397 100644
--- a/lib/assets/javascripts/react_ujs.js
+++ b/lib/assets/javascripts/react_ujs.js
@@ -125,15 +125,16 @@ module.exports = function(ujs) {
// Assume className is simple and can be found at top-level (window).
// Fallback to eval to handle cases like 'My.React.ComponentName'.
// Also, try to gracefully import Babel 6 style default exports
+var topLevel = typeof window === "undefined" ? this : window;
+
module.exports = function(className) {
var constructor;
- var topLevel = typeof window === "undefined" ? this : window;
// Try to access the class globally first
constructor = topLevel[className];
// If that didn't work, try eval
if (!constructor) {
- constructor = eval.call(topLevel, className);
+ constructor = eval(className);
}
// Lastly, if there is a default attribute try that
@@ -308,6 +309,12 @@ if (typeof window !== "undefined") {
detectEvents(ReactRailsUJS)
}
+// It's a bit of a no-no to populate the global namespace,
+// but we really need it!
+// We need access to this object for server rendering, and
+// we can't do a dynamic `require`, so we'll grab it from here:
+this.ReactRailsUJS = ReactRailsUJS
+
module.exports = ReactRailsUJS
diff --git a/lib/react/rails/railtie.rb b/lib/react/rails/railtie.rb
index ee3e5044..bd75eb1f 100644
--- a/lib/react/rails/railtie.rb
+++ b/lib/react/rails/railtie.rb
@@ -20,6 +20,10 @@ class Railtie < ::Rails::Railtie
# Changing files with these extensions in these directories will cause the server renderer to reload:
config.react.server_renderer_directories = ["/app/assets/javascripts/"]
config.react.server_renderer_extensions = ["jsx"]
+ if defined?(Webpacker)
+ config.react.server_renderer_directories << "app/javascript"
+ config.react.server_renderer_extensions << "js"
+ end
# View helper implementation:
config.react.view_helper_implementation = nil # Defaults to ComponentMount
@@ -30,6 +34,7 @@ class Railtie < ::Rails::Railtie
memo[app_dir] = config.react.server_renderer_extensions
memo
end
+
app.reloaders << ActiveSupport::FileUpdateChecker.new([], reload_paths) do
React::ServerRendering.reset_pool
end
diff --git a/lib/react/server_rendering/exec_js_renderer.rb b/lib/react/server_rendering/exec_js_renderer.rb
index 0afba4d3..bc8ef755 100644
--- a/lib/react/server_rendering/exec_js_renderer.rb
+++ b/lib/react/server_rendering/exec_js_renderer.rb
@@ -41,7 +41,7 @@ def render_from_parts(before, main, after)
def main_render(component_name, props, prerender_options)
render_function = prerender_options.fetch(:render_function, "renderToString")
- "ReactRailsUJS.serverRender('#{render_function}', #{component_name}, #{props})"
+ "this.ReactRailsUJS.serverRender('#{render_function}', '#{component_name}', #{props})"
end
def compose_js(before, main, after)
diff --git a/lib/react/server_rendering/sprockets_renderer.rb b/lib/react/server_rendering/sprockets_renderer.rb
index ed43c20b..b6d69360 100644
--- a/lib/react/server_rendering/sprockets_renderer.rb
+++ b/lib/react/server_rendering/sprockets_renderer.rb
@@ -1,5 +1,6 @@
require "react/server_rendering/environment_container"
require "react/server_rendering/manifest_container"
+require "react/server_rendering/webpacker_manifest_container"
require "react/server_rendering/yaml_manifest_container"
module React
@@ -57,19 +58,7 @@ class << self
#
# @return [#find_asset(logical_path)] An object that returns asset contents by logical path
def asset_container
- @asset_container ||= if self.class.asset_container_class.present?
- self.class.asset_container_class.new
- elsif assets_precompiled? && ManifestContainer.compatible?
- ManifestContainer.new
- elsif assets_precompiled? && YamlManifestContainer.compatible?
- YamlManifestContainer.new
- else
- EnvironmentContainer.new
- end
- end
-
- def assets_precompiled?
- !::Rails.application.config.assets.compile
+ @asset_container ||= asset_container_class.new
end
private
@@ -97,6 +86,29 @@ def render_function(opts)
def prepare_props(props)
props.is_a?(String) ? props : props.to_json
end
+
+ def assets_precompiled?
+ !::Rails.application.config.assets.compile
+ end
+
+ def asset_container_class
+ if self.class.asset_container_class.present?
+ self.class.asset_container_class
+ elsif WebpackerManifestContainer.compatible?
+ WebpackerManifestContainer
+ elsif assets_precompiled?
+ if ManifestContainer.compatible?
+ ManifestContainer
+ elsif YamlManifestContainer.compatible?
+ YamlManifestContainer
+ else
+ # Even though they are precompiled, we can't find them :S
+ EnvironmentContainer
+ end
+ else
+ EnvironmentContainer
+ end
+ end
end
end
end
diff --git a/lib/react/server_rendering/webpacker_manifest_container.rb b/lib/react/server_rendering/webpacker_manifest_container.rb
new file mode 100644
index 00000000..5cac670e
--- /dev/null
+++ b/lib/react/server_rendering/webpacker_manifest_container.rb
@@ -0,0 +1,27 @@
+require "open-uri"
+
+module React
+ module ServerRendering
+ # Get a compiled file from Webpacker
+ class WebpackerManifestContainer
+ def find_asset(logical_path)
+ asset_path = Webpacker::Manifest.lookup(logical_path) # raises if not found
+ if asset_path.start_with?("http")
+ # TODO: this includes webpack-dev-server code which causes ExecJS to 💥
+ dev_server_asset = open(asset_path).read
+ else
+ full_path = File.join(
+ # TODO: using `.parent` here won't work for nonstandard configurations
+ Webpacker::Configuration.output_path.parent,
+ asset_path
+ )
+ File.read(full_path)
+ end
+ end
+
+ def self.compatible?
+ !!defined?(Webpacker)
+ end
+ end
+ end
+end
diff --git a/lib/react/server_rendering/yaml_manifest_container.rb b/lib/react/server_rendering/yaml_manifest_container.rb
index 8f5634cf..9d51cb79 100644
--- a/lib/react/server_rendering/yaml_manifest_container.rb
+++ b/lib/react/server_rendering/yaml_manifest_container.rb
@@ -1,6 +1,6 @@
module React
module ServerRendering
- # Get asset content by reading the compiled file from disk using the generated maniftest.yml file
+ # Get asset content by reading the compiled file from disk using the generated manifest.yml file
#
# This is good for Rails production when assets are compiled to public/assets
# but sometimes, they're compiled to other directories (or other servers)
diff --git a/react_ujs/dist/react_ujs.js b/react_ujs/dist/react_ujs.js
index 3480af23..ee472397 100644
--- a/react_ujs/dist/react_ujs.js
+++ b/react_ujs/dist/react_ujs.js
@@ -125,15 +125,16 @@ module.exports = function(ujs) {
// Assume className is simple and can be found at top-level (window).
// Fallback to eval to handle cases like 'My.React.ComponentName'.
// Also, try to gracefully import Babel 6 style default exports
+var topLevel = typeof window === "undefined" ? this : window;
+
module.exports = function(className) {
var constructor;
- var topLevel = typeof window === "undefined" ? this : window;
// Try to access the class globally first
constructor = topLevel[className];
// If that didn't work, try eval
if (!constructor) {
- constructor = eval.call(topLevel, className);
+ constructor = eval(className);
}
// Lastly, if there is a default attribute try that
@@ -308,6 +309,12 @@ if (typeof window !== "undefined") {
detectEvents(ReactRailsUJS)
}
+// It's a bit of a no-no to populate the global namespace,
+// but we really need it!
+// We need access to this object for server rendering, and
+// we can't do a dynamic `require`, so we'll grab it from here:
+this.ReactRailsUJS = ReactRailsUJS
+
module.exports = ReactRailsUJS
diff --git a/react_ujs/index.js b/react_ujs/index.js
index 5934ac0e..3cc3ead6 100644
--- a/react_ujs/index.js
+++ b/react_ujs/index.js
@@ -109,4 +109,10 @@ if (typeof window !== "undefined") {
detectEvents(ReactRailsUJS)
}
+// It's a bit of a no-no to populate the global namespace,
+// but we really need it!
+// We need access to this object for server rendering, and
+// we can't do a dynamic `require`, so we'll grab it from here:
+self.ReactRailsUJS = ReactRailsUJS
+
module.exports = ReactRailsUJS
diff --git a/react_ujs/src/getConstructor/fromGlobal.js b/react_ujs/src/getConstructor/fromGlobal.js
index 90ddb2cf..5c4aa3ff 100644
--- a/react_ujs/src/getConstructor/fromGlobal.js
+++ b/react_ujs/src/getConstructor/fromGlobal.js
@@ -1,15 +1,16 @@
// Assume className is simple and can be found at top-level (window).
// Fallback to eval to handle cases like 'My.React.ComponentName'.
// Also, try to gracefully import Babel 6 style default exports
+var topLevel = typeof window === "undefined" ? this : window;
+
module.exports = function(className) {
var constructor;
- var topLevel = typeof window === "undefined" ? this : window;
// Try to access the class globally first
constructor = topLevel[className];
// If that didn't work, try eval
if (!constructor) {
- constructor = eval.call(topLevel, className);
+ constructor = eval(className);
}
// Lastly, if there is a default attribute try that
diff --git a/react_ujs/yarn.lock b/react_ujs/yarn.lock
index e3e3e5c4..52133cbf 100644
--- a/react_ujs/yarn.lock
+++ b/react_ujs/yarn.lock
@@ -993,8 +993,8 @@ ms@0.7.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
nan@^2.3.0:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2"
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.1.tgz#8c84f7b14c96b89f57fbc838012180ec8ca39a01"
node-libs-browser@^2.0.0:
version "2.0.0"
@@ -1252,8 +1252,8 @@ randombytes@^2.0.0, randombytes@^2.0.1:
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
rc@^1.1.7:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.0.tgz#c7de973b7b46297c041366b2fd3d2363b1697c66"
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
dependencies:
deep-extend "~0.4.0"
ini "~1.3.0"
@@ -1275,7 +1275,7 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
-"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@^2.1.4:
+"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.4, readable-stream@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.6.tgz#8b43aed76e71483938d12a8d46c6cf1a00b1f816"
dependencies:
@@ -1447,12 +1447,12 @@ stream-browserify@^2.0.1:
readable-stream "^2.0.2"
stream-http@^2.3.1:
- version "2.6.3"
- resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.6.3.tgz#4c3ddbf9635968ea2cfd4e48d43de5def2625ac3"
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.0.tgz#cec1f4e3b494bc4a81b451808970f8b20b4ed5f6"
dependencies:
builtin-status-codes "^3.0.0"
inherits "^2.0.1"
- readable-stream "^2.1.0"
+ readable-stream "^2.2.6"
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
diff --git a/test/dummy/app/controllers/server_controller.rb b/test/dummy/app/controllers/server_controller.rb
index f72d1952..de3cb5af 100644
--- a/test/dummy/app/controllers/server_controller.rb
+++ b/test/dummy/app/controllers/server_controller.rb
@@ -1,5 +1,6 @@
class ServerController < ApplicationController
def show
+ @component_name = params[:component_name] || "TodoList"
@todos = %w{todo1 todo2 todo3}
end
diff --git a/test/dummy/app/javascript/components/GreetingMessage.js b/test/dummy/app/javascript/components/GreetingMessage.js
new file mode 100644
index 00000000..387550df
--- /dev/null
+++ b/test/dummy/app/javascript/components/GreetingMessage.js
@@ -0,0 +1,23 @@
+var React = require("react")
+
+module.exports = React.createClass({
+ getInitialState: function() {
+ var initialGreeting = 'Hello';
+ if (typeof global !== "undefined" && global.ctx && global.ctx.greeting) {
+ initialGreeting = global.ctx.greeting
+ }
+
+ return {
+ greeting: initialGreeting
+ }
+ },
+ goodbye: function() {
+ this.setState({greeting: 'Goodbye'});
+ },
+ render: function() {
+ return React.DOM.div({},
+ React.DOM.div({}, this.state.greeting, ' ', this.props.name),
+ React.DOM.button({onClick: this.goodbye}, 'Goodbye')
+ );
+ }
+});
diff --git a/test/dummy/app/javascript/components/Todo.js b/test/dummy/app/javascript/components/Todo.js
new file mode 100644
index 00000000..5c16713d
--- /dev/null
+++ b/test/dummy/app/javascript/components/Todo.js
@@ -0,0 +1,6 @@
+var React = require("react")
+module.exports = React.createClass({
+ render: function() {
+ return React.createElement("li", null, this.props.todo)
+ }
+})
diff --git a/test/dummy/app/javascript/components/TodoList.js b/test/dummy/app/javascript/components/TodoList.js
new file mode 100644
index 00000000..1a751260
--- /dev/null
+++ b/test/dummy/app/javascript/components/TodoList.js
@@ -0,0 +1,21 @@
+var React = require("react")
+
+module.exports = React.createClass({
+ getInitialState: function() {
+ return({mounted: "nope"});
+ },
+ componentWillMount: function() {
+ this.setState({mounted: 'yep'});
+ },
+ render: function() {
+ return (
+
+ - {this.state.mounted}
+ {this.props.todos.map(function(todo, i) {
+ return (- {todo}
)
+ })}
+ - From Webpacker
+
+ )
+ }
+})
diff --git a/test/dummy/app/javascript/components/TodoListWithConsoleLog.js b/test/dummy/app/javascript/components/TodoListWithConsoleLog.js
new file mode 100644
index 00000000..1d426869
--- /dev/null
+++ b/test/dummy/app/javascript/components/TodoListWithConsoleLog.js
@@ -0,0 +1,25 @@
+var React = require("react")
+
+module.exports = React.createClass({
+ getInitialState: function() {
+ console.log('got initial state');
+ return({mounted: "nope"});
+ },
+ componentWillMount: function() {
+ console.warn('mounted component');
+ this.setState({mounted: 'yep'});
+ },
+ render: function() {
+ var x = 'foo';
+ console.error('rendered!', x);
+ return (
+
+ - Console Logged
+ - {this.state.mounted}
+ {this.props.todos.map(function(todo, i) {
+ return (- {todo}
)
+ })}
+
+ )
+ }
+})
diff --git a/test/dummy/app/javascript/components/WithSetTimeout.js b/test/dummy/app/javascript/components/WithSetTimeout.js
new file mode 100644
index 00000000..2adf058e
--- /dev/null
+++ b/test/dummy/app/javascript/components/WithSetTimeout.js
@@ -0,0 +1,10 @@
+var React = require("react")
+module.exports = React.createClass({
+ componentWillMount: function () {
+ setTimeout(function () {}, 1000)
+ clearTimeout(0)
+ },
+ render: function () {
+ return I am rendered!
+ }
+})
diff --git a/test/dummy/app/javascript/packs/server_rendering.js b/test/dummy/app/javascript/packs/server_rendering.js
new file mode 100644
index 00000000..fe6fbdd1
--- /dev/null
+++ b/test/dummy/app/javascript/packs/server_rendering.js
@@ -0,0 +1,5 @@
+// By default, this pack is loaded for server-side rendering.
+// It must expose react_ujs as `ReactRailsUJS` and prepare a require context.
+var componentRequireContext = require.context("components", true)
+var ReactRailsUJS = require("../../../../../react_ujs/index")
+ReactRailsUJS.loadContext(componentRequireContext)
diff --git a/test/dummy/app/views/server/show.html.erb b/test/dummy/app/views/server/show.html.erb
index 67985739..168559a7 100644
--- a/test/dummy/app/views/server/show.html.erb
+++ b/test/dummy/app/views/server/show.html.erb
@@ -1 +1 @@
-<%= react_component "TodoList", {todos: @todos}, {prerender: true} %>
+<%= react_component @component_name, {todos: @todos}, {prerender: true} %>
diff --git a/test/helper_files/TodoListWithUpdates.js b/test/helper_files/TodoListWithUpdates.js
new file mode 100644
index 00000000..1b923380
--- /dev/null
+++ b/test/helper_files/TodoListWithUpdates.js
@@ -0,0 +1,10 @@
+var React = require("react")
+module.exports = React.createClass({
+ render: function() {
+ return (
+
+ )
+ }
+})
diff --git a/test/react/rails/component_mount_test.rb b/test/react/rails/component_mount_test.rb
index d603f98d..3c14a6c4 100644
--- a/test/react/rails/component_mount_test.rb
+++ b/test/react/rails/component_mount_test.rb
@@ -3,6 +3,7 @@
when_sprockets_available do
class ComponentMountTest < ActionDispatch::IntegrationTest
setup do
+ WebpackerHelpers.compile_if_missing
@helper = React::Rails::ComponentMount.new
end
diff --git a/test/react/rails/controller_lifecycle_test.rb b/test/react/rails/controller_lifecycle_test.rb
index 2b5c236f..983f602d 100644
--- a/test/react/rails/controller_lifecycle_test.rb
+++ b/test/react/rails/controller_lifecycle_test.rb
@@ -24,6 +24,7 @@ def react_component(*args)
class ControllerLifecycleTest < ActionDispatch::IntegrationTest
def setup
+ WebpackerHelpers.compile_if_missing
@previous_helper_implementation = React::Rails::ViewHelper.helper_implementation_class
React::Rails::ViewHelper.helper_implementation_class = DummyHelperImplementation
end
diff --git a/test/react/rails/pages_controller_test.rb b/test/react/rails/pages_controller_test.rb
index 89a7743a..45faf15a 100644
--- a/test/react/rails/pages_controller_test.rb
+++ b/test/react/rails/pages_controller_test.rb
@@ -1,6 +1,10 @@
require 'test_helper'
class PagesControllerTest < ActionController::TestCase
+ setup do
+ WebpackerHelpers.compile_if_missing
+ end
+
test 'renders successfully' do
get :show, id: 1
assert_equal(200, response.status)
diff --git a/test/react/rails/react_rails_ujs_test.rb b/test/react/rails/react_rails_ujs_test.rb
index 77135068..9aefcff9 100644
--- a/test/react/rails/react_rails_ujs_test.rb
+++ b/test/react/rails/react_rails_ujs_test.rb
@@ -6,6 +6,7 @@ class ReactRailsUJSTest < ActionDispatch::IntegrationTest
setup do
Capybara.current_driver = Capybara.javascript_driver
+ WebpackerHelpers.compile_if_missing
end
test 'ujs object present on the global React object and has our methods' do
diff --git a/test/react/rails/webpacker_test.rb b/test/react/rails/webpacker_test.rb
index 2f278f38..1bc9b13d 100644
--- a/test/react/rails/webpacker_test.rb
+++ b/test/react/rails/webpacker_test.rb
@@ -1,12 +1,12 @@
require 'test_helper'
-
WebpackerHelpers.when_webpacker_available do
class ReactRailsWebpackerTest < ActionDispatch::IntegrationTest
include Capybara::DSL
setup do
Capybara.current_driver = Capybara.javascript_driver
+ WebpackerHelpers.compile
end
teardown do
diff --git a/test/react/server_rendering/exec_js_renderer_test.rb b/test/react/server_rendering/exec_js_renderer_test.rb
index 7aa4c405..f45f881e 100644
--- a/test/react/server_rendering/exec_js_renderer_test.rb
+++ b/test/react/server_rendering/exec_js_renderer_test.rb
@@ -5,7 +5,7 @@
var React = {
createElement: function() {},
}
-var ReactRailsUJS = {
+this.ReactRailsUJS = {
serverRender: function() {
return 'serverRender was called'
},
@@ -50,7 +50,6 @@ def @renderer.after_render(name, props, opts)
assert_no_match(/assigned_after_render/, error.message)
end
-
test '#after_render is called after #before_render' do
def @renderer.before_render(name, props, opts)
"var beforeRenderVar = 'assigned_before_render'"
diff --git a/test/react/server_rendering/sprockets_renderer_test.rb b/test/react/server_rendering/sprockets_renderer_test.rb
index bb954e08..f2793255 100644
--- a/test/react/server_rendering/sprockets_renderer_test.rb
+++ b/test/react/server_rendering/sprockets_renderer_test.rb
@@ -4,7 +4,12 @@
class SprocketsRendererTest < ActiveSupport::TestCase
CALLBACKS = [:before_render, :after_render]
+ webpacker_compiled = false
setup do
+ if WebpackerHelpers.available? && !webpacker_compiled
+ WebpackerHelpers.compile
+ webpacker_compiled = true
+ end
@renderer = React::ServerRendering::SprocketsRenderer.new({})
end
@@ -62,8 +67,14 @@ class SprocketsRendererTest < ActiveSupport::TestCase
err = assert_raises React::ServerRendering::PrerenderError do
@renderer.render("NonExistentComponent", {}, nil)
end
- assert_match(/ReferenceError/, err.to_s)
- assert_match(/NonExistentComponent/, err.to_s, "it names the component")
+
+ if WebpackerHelpers.available?
+ assert_includes err.message, "Cannot find module './NonExistentComponent'"
+ else
+ assert_match(/ReferenceError/, err.to_s)
+ assert_match(/NonExistentComponent/, err.to_s, "it names the component")
+ end
+
assert_match(/\n/, err.to_s, "it includes the multi-line backtrace")
end
@@ -77,41 +88,45 @@ class SprocketsRendererTest < ActiveSupport::TestCase
assert_match(/console.error.apply\(console, \["setTimeout #{message}"\]\);$/, result)
end
- test '.new accepts additional code to add to the JS context' do
- additional_code = File.read(File.expand_path("../../../helper_files/WithoutSprockets.js", __FILE__))
+ if !WebpackerHelpers.available?
+ # This doesn't work with webpacker since finding components is based on filename
+ test '.new accepts additional code to add to the JS context' do
+ additional_code = File.read(File.expand_path("../../../helper_files/WithoutSprockets.js", __FILE__))
- additional_renderer = React::ServerRendering::SprocketsRenderer.new(code: additional_code)
+ additional_renderer = React::ServerRendering::SprocketsRenderer.new(code: additional_code)
- assert_match(/drink more caffeine<\/span>/, additional_renderer.render("WithoutSprockets", {label: "drink more caffeine"}, nil))
- end
+ assert_match(/drink more caffeine<\/span>/, additional_renderer.render("WithoutSprockets", {label: "drink more caffeine"}, nil))
+ end
- test '.new accepts any filenames' do
- limited_renderer = React::ServerRendering::SprocketsRenderer.new(files: ["react-server.js", "react_ujs.js", "components/Todo.js"])
- assert_match(/get a real job<\/li>/, limited_renderer.render("Todo", {todo: "get a real job"}, nil))
- err = assert_raises React::ServerRendering::PrerenderError do
- limited_renderer.render("TodoList", {todos: []}, nil)
+ # These use cases don't apply to Webpacker since the require.context comes from a pack:
+ test '.new accepts any filenames' do
+ limited_renderer = React::ServerRendering::SprocketsRenderer.new(files: ["react-server.js", "react_ujs.js", "components/Todo.js"])
+ assert_match(/get a real job<\/li>/, limited_renderer.render("Todo", {todo: "get a real job"}, nil))
+ err = assert_raises React::ServerRendering::PrerenderError do
+ limited_renderer.render("TodoList", {todos: []}, nil)
+ end
+ assert_match(/ReferenceError/, err.to_s, "it doesnt load other files")
end
- assert_match(/ReferenceError/, err.to_s, "it doesnt load other files")
- end
- test '#render returns html when config.assets.compile is false' do
- begin
- legacy_rendering_files = ["react-server.js", "react_ujs.js", "components.js"]
- Rails.application.config.assets.precompile += legacy_rendering_files
+ test '#render returns html when config.assets.compile is false' do
+ begin
+ legacy_rendering_files = ["react-server.js", "react_ujs.js", "components.js"]
+ Rails.application.config.assets.precompile += legacy_rendering_files
- precompile_assets
+ precompile_assets
- Rails.application.config.assets.compile = false
+ Rails.application.config.assets.compile = false
- @renderer = React::ServerRendering::SprocketsRenderer.new(files: legacy_rendering_files)
+ @renderer = React::ServerRendering::SprocketsRenderer.new(files: legacy_rendering_files)
- result = @renderer.render("Todo", {todo: "write tests"}, nil)
- assert_match(//, result)
- assert_match(/data-react-checksum/, result)
- ensure
- Rails.application.config.assets.compile = true
+ result = @renderer.render("Todo", {todo: "write tests"}, nil)
+ assert_match(//, result)
+ assert_match(/data-react-checksum/, result)
+ ensure
+ Rails.application.config.assets.compile = true
- clear_precompiled_assets
+ clear_precompiled_assets
+ end
end
end
end
diff --git a/test/react/server_rendering/webpacker_manifest_container_test.rb b/test/react/server_rendering/webpacker_manifest_container_test.rb
new file mode 100644
index 00000000..585ce4d2
--- /dev/null
+++ b/test/react/server_rendering/webpacker_manifest_container_test.rb
@@ -0,0 +1,72 @@
+require "test_helper"
+require "open-uri"
+
+WebpackerHelpers.when_webpacker_available do
+ class WebpackerManifestContainerTest < ActiveSupport::TestCase
+ setup do
+ WebpackerHelpers.clear_webpacker_packs
+ end
+
+ test "it loads JS from the webpacker container" do
+ WebpackerHelpers.compile
+ container = React::ServerRendering::WebpackerManifestContainer.new
+ js_file = container.find_asset("application.js")
+ # Main file:
+ assert_includes js_file, "ReactRailsUJS.loadContext(ctx)"
+ # Bundled dependencies:
+ assert_includes js_file, "ExportDefaultComponent"
+ end
+
+ def test_it_loads_from_webpack_dev_server
+ webpack_dev_server = fork do
+ Dir.chdir("test/dummy") do
+ exec "RAILS_ENV=development ./bin/webpack-dev-server "
+ end
+ end
+
+ detected_dev_server = false
+ 60.times do |i|
+ begin
+ # Make sure that the manifest has been updated:
+ Webpacker::Manifest.load("./test/dummy/public/packs/manifest.json")
+ webpack_manifest = Webpacker::Manifest.instance.data
+ example_asset_path = webpack_manifest.values.first
+ if example_asset_path.nil?
+ puts "Manifest is blank, all manifests:"
+ Dir.glob("./test/dummy/public/packs/*.json").each do |f|
+ puts f
+ puts File.read(f)
+ end
+ next
+ end
+ assert_includes example_asset_path, "http://localhost:8080"
+ # Make sure the dev server is up:
+ open("http://localhost:8080/application.js")
+ detected_dev_server = true
+ break
+ rescue StandardError => err
+ puts err.message
+ ensure
+ sleep 0.5
+ puts i
+ end
+ end
+
+ # If we didn't hook up with a dev server after 10s,
+ # fail loudly.
+ assert detected_dev_server
+
+ container = React::ServerRendering::WebpackerManifestContainer.new
+ js_file = container.find_asset("application.js")
+ # Main file:
+ assert_includes js_file, "ReactRailsUJS.loadContext(ctx)"
+ # Bundled dependencies:
+ assert_includes js_file, "ExportDefaultComponent"
+ ensure
+ Process.kill(9, webpack_dev_server)
+ Process.wait
+ # Remove the dev-server packs:
+ WebpackerHelpers.clear_webpacker_packs
+ end
+ end
+end
diff --git a/test/server_rendered_html_test.rb b/test/server_rendered_html_test.rb
index 2aa174ed..41c4af05 100644
--- a/test/server_rendered_html_test.rb
+++ b/test/server_rendered_html_test.rb
@@ -10,10 +10,22 @@ def wait_to_ensure_asset_pipeline_detects_changes
sleep(1)
end
+ setup do
+ if WebpackerHelpers.available?
+ WebpackerHelpers.compile
+ end
+ end
+
test 'react server rendering reloads jsx after changes to the jsx files' do
- file_with_updates = File.expand_path('../helper_files/TodoListWithUpdates.js.jsx', __FILE__)
- file_without_updates = File.expand_path('../helper_files/TodoListWithoutUpdates.js.jsx', __FILE__)
- app_file = File.expand_path('../dummy/app/assets/javascripts/components/TodoList.js.jsx', __FILE__)
+ if WebpackerHelpers.available?
+ file_with_updates = File.expand_path('../helper_files/TodoListWithUpdates.js', __FILE__)
+ file_without_updates = File.expand_path('../helper_files/TodoListWithoutUpdates.js', __FILE__)
+ app_file = File.expand_path('../dummy/app/javascript/components/TodoList.js', __FILE__)
+ else
+ file_with_updates = File.expand_path('../helper_files/TodoListWithUpdates.js.jsx', __FILE__)
+ file_without_updates = File.expand_path('../helper_files/TodoListWithoutUpdates.js.jsx', __FILE__)
+ app_file = File.expand_path('../dummy/app/assets/javascripts/components/TodoList.js.jsx', __FILE__)
+ end
FileUtils.cp app_file, file_without_updates
FileUtils.touch app_file
@@ -25,7 +37,11 @@ def wait_to_ensure_asset_pipeline_detects_changes
FileUtils.cp file_with_updates, app_file
FileUtils.touch app_file
- wait_to_ensure_asset_pipeline_detects_changes
+ if WebpackerHelpers.available?
+ WebpackerHelpers.compile
+ else
+ wait_to_ensure_asset_pipeline_detects_changes
+ end
get '/server/1'
assert_match(/Updated/, response.body)
@@ -37,21 +53,36 @@ def wait_to_ensure_asset_pipeline_detects_changes
end
end
- test 'it reloads when new jsx files are added' do
+ test 'it reloads when new jsx files are added to the asset pipeline' do
begin
- get '/server/1'
- refute_match(/Overwritten List/, response.body)
+ assert_raises(ActionView::Template::Error) {
+ get '/server/1?component_name=NewList'
+ }
- # Make it alphabetically last so it will override the preceeding one:
- new_file_path = File.expand_path('../dummy/app/assets/javascripts/components/ZZ_NewComponent.js.jsx', __FILE__)
- File.write new_file_path, <<-JS
- var TodoList = function() { return "Overwritten List" }
- JS
+ if WebpackerHelpers.available?
+ new_file_path = '../dummy/app/javascript/components/NewList.js'
+ new_file_contents = <<-JS
+ var React = require("react")
+ module.exports = function() { return "New List" }
+ JS
+ else
+ new_file_path = '../dummy/app/assets/javascripts/components/ZZ_NewComponent.js.jsx'
+ new_file_contents = <<-JS
+ var NewList = function() { return "New List" }
+ JS
+ end
- wait_to_ensure_asset_pipeline_detects_changes
+ new_file_path = File.expand_path(new_file_path, __FILE__)
+ File.write new_file_path, new_file_contents
- get '/server/1'
- assert_match(/Overwritten List/, response.body)
+ if WebpackerHelpers.available?
+ WebpackerHelpers.compile
+ else
+ wait_to_ensure_asset_pipeline_detects_changes
+ end
+
+ get '/server/1?component_name=NewList'
+ assert_match(/New List/, response.body)
ensure
FileUtils.rm_rf(new_file_path)
wait_to_ensure_asset_pipeline_detects_changes
diff --git a/test/support/webpacker_helpers.rb b/test/support/webpacker_helpers.rb
index d6b8eaa4..b95f4817 100644
--- a/test/support/webpacker_helpers.rb
+++ b/test/support/webpacker_helpers.rb
@@ -1,4 +1,6 @@
module WebpackerHelpers
+ PACKS_DIRECTORY = File.expand_path("../../dummy/public/packs", __FILE__)
+
module_function
def available?
defined?(Webpacker)
@@ -6,18 +8,30 @@ def available?
def when_webpacker_available
if available?
- clear_webpacker_packs
- Dir.chdir("./test/dummy") do
+ yield
+ end
+ end
+
+ def compile
+ return if !available?
+ clear_webpacker_packs
+ Dir.chdir("./test/dummy") do
+ capture_io do
+ Rake::Task['webpacker:compile'].reenable
Rake::Task['webpacker:compile'].invoke
end
- # Reload cached JSON manifest:
- Webpacker::Manifest.load
- yield
+ end
+ # Reload cached JSON manifest:
+ Webpacker::Manifest.load
+ end
+
+ def compile_if_missing
+ if !File.exist?(PACKS_DIRECTORY)
+ compile
end
end
def clear_webpacker_packs
- packs_directory = File.expand_path("../dummy/public/packs", __FILE__)
- FileUtils.rm_rf(packs_directory)
+ FileUtils.rm_rf(PACKS_DIRECTORY)
end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 9da299b8..a1425d8b 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -20,6 +20,8 @@
require(f)
end
+WebpackerHelpers.clear_webpacker_packs
+
Capybara.javascript_driver = :poltergeist
Capybara.app = Rails.application
@@ -64,16 +66,22 @@ def asset.fresh?(env); false; end
def precompile_assets
capture_io do
- ENV['RAILS_GROUPS'] = 'assets' # required for Rails 3.2
- Rake::Task['assets:precompile'].reenable
+ # Changing directories is required because:
+ # - assets:precompile runs webpacker:compile when availabled
+ # - webpacker:compile depends on `./bin/webpack`, so `.` must be the app root
+ Dir.chdir("./test/dummy") do
- if Rails::VERSION::MAJOR == 3
- Rake::Task['assets:precompile:all'].reenable
- Rake::Task['assets:precompile:primary'].reenable
- Rake::Task['assets:precompile:nondigest'].reenable
- end
+ ENV['RAILS_GROUPS'] = 'assets' # required for Rails 3.2
+ Rake::Task['assets:precompile'].reenable
- Rake::Task['assets:precompile'].invoke
+ if Rails::VERSION::MAJOR == 3
+ Rake::Task['assets:precompile:all'].reenable
+ Rake::Task['assets:precompile:primary'].reenable
+ Rake::Task['assets:precompile:nondigest'].reenable
+ end
+
+ Rake::Task['assets:precompile'].invoke
+ end
end
if Rails.application.respond_to?(:assets_manifest)