diff --git a/lib/assets/javascripts/react_ujs.js b/lib/assets/javascripts/react_ujs.js index 311342e5c..3480af23a 100644 --- a/lib/assets/javascripts/react_ujs.js +++ b/lib/assets/javascripts/react_ujs.js @@ -1,13 +1,13 @@ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("react"), require("react-dom")); + module.exports = factory(require("react"), require("react-dom"), require("react-dom/server")); else if(typeof define === 'function' && define.amd) - define(["react", "react-dom"], factory); + define(["react", "react-dom", "react-dom/server"], factory); else if(typeof exports === 'object') - exports["ReactRailsUJS"] = factory(require("react"), require("react-dom")); + exports["ReactRailsUJS"] = factory(require("react"), require("react-dom"), require("react-dom/server")); else - root["ReactRailsUJS"] = factory(root["React"], root["ReactDOM"]); -})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__) { + root["ReactRailsUJS"] = factory(root["React"], root["ReactDOM"], root["ReactDOMServer"]); +})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_5__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; @@ -73,18 +73,18 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 5); +/******/ return __webpack_require__(__webpack_require__.s = 6); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { -var nativeEvents = __webpack_require__(6) -var pjaxEvents = __webpack_require__(7) -var turbolinksEvents = __webpack_require__(8) -var turbolinksClassicDeprecatedEvents = __webpack_require__(10) -var turbolinksClassicEvents = __webpack_require__(9) +var nativeEvents = __webpack_require__(7) +var pjaxEvents = __webpack_require__(8) +var turbolinksEvents = __webpack_require__(9) +var turbolinksClassicDeprecatedEvents = __webpack_require__(11) +var turbolinksClassicEvents = __webpack_require__(10) // see what things are globally available // and setup event handlers to those things @@ -127,13 +127,13 @@ module.exports = function(ujs) { // Also, try to gracefully import Babel 6 style default exports module.exports = function(className) { var constructor; - + var topLevel = typeof window === "undefined" ? this : window; // Try to access the class globally first - constructor = window[className]; + constructor = topLevel[className]; // If that didn't work, try eval if (!constructor) { - constructor = eval.call(window, className); + constructor = eval.call(topLevel, className); } // Lastly, if there is a default attribute try that @@ -160,7 +160,6 @@ module.exports = function(reqctx) { var parts = className.split(".") var filename = parts.shift() var keys = parts - console.log(filename, keys) // Load the module: var component = reqctx("./" + filename) // Then access each key: @@ -190,10 +189,17 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_4__; /***/ }), /* 5 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_5__; + +/***/ }), +/* 6 */ /***/ (function(module, exports, __webpack_require__) { var React = __webpack_require__(3) var ReactDOM = __webpack_require__(4) +var ReactDOMServer = __webpack_require__(5) var detectEvents = __webpack_require__(0) var constructorFromGlobal = __webpack_require__(1) @@ -209,7 +215,7 @@ var ReactRailsUJS = { PROPS_ATTR: 'data-react-props', // If jQuery is detected, save a reference to it for event handlers - jQuery: (typeof window.jQuery !== 'undefined') && window.jQuery, + jQuery: (typeof window !== 'undefined') && (typeof window.jQuery !== 'undefined') && window.jQuery, // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements @@ -252,6 +258,14 @@ var ReactRailsUJS = { this.getConstructor = constructorFromRequireContext(req) }, + // Render `componentName` with `props` to a string, + // using the specified `renderFunction` from `react-dom/server`. + serverRender: function(renderFunction, componentName, props) { + var componentClass = this.getConstructor(componentName) + var element = React.createElement(componentClass, props) + return ReactDOMServer[renderFunction](element) + }, + // Within `searchSelector`, find nodes which should have React components // inside them, and mount them with their props. mountComponents: function(searchSelector) { @@ -289,13 +303,16 @@ var ReactRailsUJS = { }, } -detectEvents(ReactRailsUJS) +if (typeof window !== "undefined") { + // Only setup events for browser (not server-rendering) + detectEvents(ReactRailsUJS) +} module.exports = ReactRailsUJS /***/ }), -/* 6 */ +/* 7 */ /***/ (function(module, exports) { module.exports = { @@ -316,7 +333,7 @@ module.exports = { /***/ }), -/* 7 */ +/* 8 */ /***/ (function(module, exports) { module.exports = { @@ -330,7 +347,7 @@ module.exports = { /***/ }), -/* 8 */ +/* 9 */ /***/ (function(module, exports) { module.exports = { @@ -343,7 +360,7 @@ module.exports = { /***/ }), -/* 9 */ +/* 10 */ /***/ (function(module, exports) { module.exports = { @@ -357,7 +374,7 @@ module.exports = { /***/ }), -/* 10 */ +/* 11 */ /***/ (function(module, exports) { module.exports = { diff --git a/lib/generators/react/component_generator.rb b/lib/generators/react/component_generator.rb index 22fdf1deb..5b079aa23 100644 --- a/lib/generators/react/component_generator.rb +++ b/lib/generators/react/component_generator.rb @@ -4,7 +4,7 @@ class ComponentGenerator < ::Rails::Generators::NamedBase source_root File.expand_path '../../templates', __FILE__ desc <<-DESC.strip_heredoc Description: - Scaffold a react component into app/assets/javascripts/components. + Scaffold a React component into `components/` of your Webpacker source or asset pipeline. The generated component will include a basic render function and a PropTypes hash to help with development. @@ -90,17 +90,29 @@ class ComponentGenerator < ::Rails::Generators::NamedBase } def create_component_file - extension = case - when options[:es6] - 'es6.jsx' - when options[:coffee] - 'js.jsx.coffee' - else - 'js.jsx' - end - - file_path = File.join('app/assets/javascripts/components', "#{file_name}.#{extension}") - template("component.#{extension}", file_path) + template_extension = case + when options[:es6] + 'es6.jsx' + when options[:coffee] + 'js.jsx.coffee' + else + 'js.jsx' + end + + # Prefer webpacker to sprockets: + if defined?(Webpacker) + extension = options[:coffee] ? "coffee" : "js" + target_dir = Webpacker::Configuration.source_path + .join("components") + .relative_path_from(::Rails.root) + .to_s + else + extension = template_extension + target_dir = 'app/assets/javascripts/components' + end + + file_path = File.join(target_dir, "#{file_name}.#{extension}") + template("component.#{template_extension}", file_path) end private diff --git a/lib/generators/react/install_generator.rb b/lib/generators/react/install_generator.rb index 7653e4fc4..5d4b49d77 100644 --- a/lib/generators/react/install_generator.rb +++ b/lib/generators/react/install_generator.rb @@ -16,15 +16,60 @@ class InstallGenerator < ::Rails::Generators::Base default: false, desc: "Don't generate server_rendering.js or config/initializers/react_server_rendering.rb" + # Make an empty `components/` directory in the right place: def create_directory - empty_directory 'app/assets/javascripts/components' + empty_directory File.join(javascript_dir, 'components') if !options[:skip_git] - create_file 'app/assets/javascripts/components/.gitkeep' + create_file File.join(javascript_dir, 'components/.gitkeep') end end - def inject_react - require_react = "//= require react\n" + # Add requires, setup UJS + def setup_react + if webpacker? + setup_react_webpacker + else + setup_react_sprockets + end + end + + def create_server_rendering + if options[:skip_server_rendering] + return + elsif webpacker? + ssr_manifest_path = File.join(javascript_dir, "server_rendering.js") + template("server_rendering_pack.js", ssr_manifest_path) + else + ssr_manifest_path = File.join(javascript_dir, "server_rendering.js") + template("server_rendering.js", ssr_manifest_path) + initializer_path = "config/initializers/react_server_rendering.rb" + template("react_server_rendering.rb", initializer_path) + end + end + + private + + def webpacker? + !!defined?(Webpacker) + end + + def javascript_dir + if webpacker? + Webpacker::Configuration.source_path + .join(Webpacker::Configuration.entry_path) + .relative_path_from(::Rails.root) + .to_s + else + 'app/assets/javascripts' + end + end + + def manifest + Pathname.new(destination_root).join(javascript_dir, 'application.js') + end + + def setup_react_sprockets + require_react = "//= require react\n//= require react_ujs\n//= require components\n" if manifest.exist? manifest_contents = File.read(manifest) @@ -39,34 +84,27 @@ def inject_react else create_file manifest, require_react end - end - - def inject_components - inject_into_file manifest, "//= require components\n", {after: "//= require react\n"} - end - - def inject_react_ujs - inject_into_file manifest, "//= require react_ujs\n", {after: "//= require react\n"} - end - def create_components components_js = "//= require_tree ./components\n" - components_file = File.join(*%w(app assets javascripts components.js)) + components_file = File.join(javascript_dir, "components.js") create_file components_file, components_js end - def create_server_rendering - return if options[:skip_server_rendering] - manifest_path = "app/assets/javascripts/server_rendering.js" - template("server_rendering.js", manifest_path) - initializer_path = "config/initializers/react_server_rendering.rb" - template("react_server_rendering.rb", initializer_path) - end - - private + WEBPACKER_SETUP_UJS = <<-JS +// Support component names relative to this directory: +var componentRequireContext = require.context("components", true) +var ReactRailsUJS = require("react_ujs") +ReactRailsUJS.loadContext(componentRequireContext) +JS - def manifest - Pathname.new(destination_root).join('app/assets/javascripts', 'application.js') + def setup_react_webpacker + yarn_binstub = File.expand_path("./bin/yarn", ::Rails.root) + `#{yarn_binstub} add react_ujs` + if manifest.exist? + append_file(manifest, WEBPACKER_SETUP_UJS) + else + create_file(manifest, WEBPACKER_SETUP_UJS) + end end end end diff --git a/lib/generators/templates/server_rendering.js b/lib/generators/templates/server_rendering.js index 78bd57a30..aa54d4e19 100644 --- a/lib/generators/templates/server_rendering.js +++ b/lib/generators/templates/server_rendering.js @@ -1,4 +1,5 @@ //= require react-server +//= require react_ujs //= require ./components // // By default, this file is loaded for server-side rendering. diff --git a/lib/generators/templates/server_rendering_pack.js b/lib/generators/templates/server_rendering_pack.js new file mode 100644 index 000000000..5962708d2 --- /dev/null +++ b/lib/generators/templates/server_rendering_pack.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") +ReactRailsUJS.loadContext(componentRequireContext) diff --git a/lib/react/server_rendering/exec_js_renderer.rb b/lib/react/server_rendering/exec_js_renderer.rb index cb3a64a27..d79f9c558 100644 --- a/lib/react/server_rendering/exec_js_renderer.rb +++ b/lib/react/server_rendering/exec_js_renderer.rb @@ -38,7 +38,7 @@ def render_from_parts(before, main, after) def main_render(component_name, props, prerender_options) render_function = prerender_options.fetch(:render_function, "renderToString") - "ReactDOMServer.#{render_function}(React.createElement(#{component_name}, #{props}))" + "ReactRailsUJS.serverRender('#{render_function}', #{component_name}, #{props})" end def compose_js(before, main, after) diff --git a/react_ujs/dist/react_ujs.js b/react_ujs/dist/react_ujs.js index 311342e5c..3480af23a 100644 --- a/react_ujs/dist/react_ujs.js +++ b/react_ujs/dist/react_ujs.js @@ -1,13 +1,13 @@ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("react"), require("react-dom")); + module.exports = factory(require("react"), require("react-dom"), require("react-dom/server")); else if(typeof define === 'function' && define.amd) - define(["react", "react-dom"], factory); + define(["react", "react-dom", "react-dom/server"], factory); else if(typeof exports === 'object') - exports["ReactRailsUJS"] = factory(require("react"), require("react-dom")); + exports["ReactRailsUJS"] = factory(require("react"), require("react-dom"), require("react-dom/server")); else - root["ReactRailsUJS"] = factory(root["React"], root["ReactDOM"]); -})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__) { + root["ReactRailsUJS"] = factory(root["React"], root["ReactDOM"], root["ReactDOMServer"]); +})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_5__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; @@ -73,18 +73,18 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 5); +/******/ return __webpack_require__(__webpack_require__.s = 6); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { -var nativeEvents = __webpack_require__(6) -var pjaxEvents = __webpack_require__(7) -var turbolinksEvents = __webpack_require__(8) -var turbolinksClassicDeprecatedEvents = __webpack_require__(10) -var turbolinksClassicEvents = __webpack_require__(9) +var nativeEvents = __webpack_require__(7) +var pjaxEvents = __webpack_require__(8) +var turbolinksEvents = __webpack_require__(9) +var turbolinksClassicDeprecatedEvents = __webpack_require__(11) +var turbolinksClassicEvents = __webpack_require__(10) // see what things are globally available // and setup event handlers to those things @@ -127,13 +127,13 @@ module.exports = function(ujs) { // Also, try to gracefully import Babel 6 style default exports module.exports = function(className) { var constructor; - + var topLevel = typeof window === "undefined" ? this : window; // Try to access the class globally first - constructor = window[className]; + constructor = topLevel[className]; // If that didn't work, try eval if (!constructor) { - constructor = eval.call(window, className); + constructor = eval.call(topLevel, className); } // Lastly, if there is a default attribute try that @@ -160,7 +160,6 @@ module.exports = function(reqctx) { var parts = className.split(".") var filename = parts.shift() var keys = parts - console.log(filename, keys) // Load the module: var component = reqctx("./" + filename) // Then access each key: @@ -190,10 +189,17 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_4__; /***/ }), /* 5 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_5__; + +/***/ }), +/* 6 */ /***/ (function(module, exports, __webpack_require__) { var React = __webpack_require__(3) var ReactDOM = __webpack_require__(4) +var ReactDOMServer = __webpack_require__(5) var detectEvents = __webpack_require__(0) var constructorFromGlobal = __webpack_require__(1) @@ -209,7 +215,7 @@ var ReactRailsUJS = { PROPS_ATTR: 'data-react-props', // If jQuery is detected, save a reference to it for event handlers - jQuery: (typeof window.jQuery !== 'undefined') && window.jQuery, + jQuery: (typeof window !== 'undefined') && (typeof window.jQuery !== 'undefined') && window.jQuery, // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements @@ -252,6 +258,14 @@ var ReactRailsUJS = { this.getConstructor = constructorFromRequireContext(req) }, + // Render `componentName` with `props` to a string, + // using the specified `renderFunction` from `react-dom/server`. + serverRender: function(renderFunction, componentName, props) { + var componentClass = this.getConstructor(componentName) + var element = React.createElement(componentClass, props) + return ReactDOMServer[renderFunction](element) + }, + // Within `searchSelector`, find nodes which should have React components // inside them, and mount them with their props. mountComponents: function(searchSelector) { @@ -289,13 +303,16 @@ var ReactRailsUJS = { }, } -detectEvents(ReactRailsUJS) +if (typeof window !== "undefined") { + // Only setup events for browser (not server-rendering) + detectEvents(ReactRailsUJS) +} module.exports = ReactRailsUJS /***/ }), -/* 6 */ +/* 7 */ /***/ (function(module, exports) { module.exports = { @@ -316,7 +333,7 @@ module.exports = { /***/ }), -/* 7 */ +/* 8 */ /***/ (function(module, exports) { module.exports = { @@ -330,7 +347,7 @@ module.exports = { /***/ }), -/* 8 */ +/* 9 */ /***/ (function(module, exports) { module.exports = { @@ -343,7 +360,7 @@ module.exports = { /***/ }), -/* 9 */ +/* 10 */ /***/ (function(module, exports) { module.exports = { @@ -357,7 +374,7 @@ module.exports = { /***/ }), -/* 10 */ +/* 11 */ /***/ (function(module, exports) { module.exports = { diff --git a/react_ujs/index.js b/react_ujs/index.js index 6a7970015..5934ac0eb 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -1,5 +1,6 @@ var React = require("react") var ReactDOM = require("react-dom") +var ReactDOMServer = require("react-dom/server") var detectEvents = require("./src/events/detect") var constructorFromGlobal = require("./src/getConstructor/fromGlobal") @@ -15,7 +16,7 @@ var ReactRailsUJS = { PROPS_ATTR: 'data-react-props', // If jQuery is detected, save a reference to it for event handlers - jQuery: (typeof window.jQuery !== 'undefined') && window.jQuery, + jQuery: (typeof window !== 'undefined') && (typeof window.jQuery !== 'undefined') && window.jQuery, // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements @@ -58,6 +59,14 @@ var ReactRailsUJS = { this.getConstructor = constructorFromRequireContext(req) }, + // Render `componentName` with `props` to a string, + // using the specified `renderFunction` from `react-dom/server`. + serverRender: function(renderFunction, componentName, props) { + var componentClass = this.getConstructor(componentName) + var element = React.createElement(componentClass, props) + return ReactDOMServer[renderFunction](element) + }, + // Within `searchSelector`, find nodes which should have React components // inside them, and mount them with their props. mountComponents: function(searchSelector) { @@ -95,6 +104,9 @@ var ReactRailsUJS = { }, } -detectEvents(ReactRailsUJS) +if (typeof window !== "undefined") { + // Only setup events for browser (not server-rendering) + detectEvents(ReactRailsUJS) +} module.exports = ReactRailsUJS diff --git a/react_ujs/src/getConstructor/fromGlobal.js b/react_ujs/src/getConstructor/fromGlobal.js index 38faa7f71..90ddb2cf2 100644 --- a/react_ujs/src/getConstructor/fromGlobal.js +++ b/react_ujs/src/getConstructor/fromGlobal.js @@ -3,13 +3,13 @@ // Also, try to gracefully import Babel 6 style default exports module.exports = function(className) { var constructor; - + var topLevel = typeof window === "undefined" ? this : window; // Try to access the class globally first - constructor = window[className]; + constructor = topLevel[className]; // If that didn't work, try eval if (!constructor) { - constructor = eval.call(window, className); + constructor = eval.call(topLevel, className); } // Lastly, if there is a default attribute try that diff --git a/react_ujs/webpack.config.js b/react_ujs/webpack.config.js index 024251573..ebaba33b1 100644 --- a/react_ujs/webpack.config.js +++ b/react_ujs/webpack.config.js @@ -19,6 +19,12 @@ module.exports = { commonjs2: 'react-dom', commonjs: 'react-dom', amd: 'react-dom' + }, + 'react-dom/server': { + root: 'ReactDOMServer', + commonjs2: 'react-dom/server', + commonjs: 'react-dom/server', + amd: 'react-dom/server' } } }; diff --git a/test/dummy/app/assets/javascripts/server_rendering.js b/test/dummy/app/assets/javascripts/server_rendering.js index 689430aa7..bf2903b5d 100644 --- a/test/dummy/app/assets/javascripts/server_rendering.js +++ b/test/dummy/app/assets/javascripts/server_rendering.js @@ -1,2 +1,3 @@ //= require react-server +//= require react_ujs //= require ./components diff --git a/test/dummy/package.json b/test/dummy/package.json index cf01fcfed..a9124cee5 100644 --- a/test/dummy/package.json +++ b/test/dummy/package.json @@ -21,6 +21,7 @@ "rails-erb-loader": "^4.0.0", "react": "^15.4.2", "react-dom": "^15.4.2", + "react_ujs": "^0.1.2", "sass-loader": "^6.0.3", "style-loader": "^0.16.1", "webpack": "^2.3.3", diff --git a/test/dummy/yarn.lock b/test/dummy/yarn.lock index 84eeaf7af..4f5f04642 100644 --- a/test/dummy/yarn.lock +++ b/test/dummy/yarn.lock @@ -3450,6 +3450,10 @@ react@^15.4.2: loose-envify "^1.1.0" object-assign "^4.1.0" +react_ujs@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/react_ujs/-/react_ujs-0.1.2.tgz#8d3ff185d8a22d1b53c6e7a6b6362be3425e6f01" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" diff --git a/test/generators/coffee_component_generator_test.rb b/test/generators/coffee_component_generator_test.rb index de101f8c9..d9d944ff2 100644 --- a/test/generators/coffee_component_generator_test.rb +++ b/test/generators/coffee_component_generator_test.rb @@ -6,10 +6,15 @@ class CoffeeComponentGeneratorTest < Rails::Generators::TestCase setup :prepare_destination tests React::Generators::ComponentGenerator - def filename - 'app/assets/javascripts/components/generated_component.js.jsx.coffee' + if WebpackerHelpers.available? + def filename + "app/javascript/components/generated_component.coffee" + end + else + def filename + 'app/assets/javascripts/components/generated_component.js.jsx.coffee' + end end - def class_name 'GeneratedComponent' end diff --git a/test/generators/component_generator_test.rb b/test/generators/component_generator_test.rb index 173ce34df..006a42201 100644 --- a/test/generators/component_generator_test.rb +++ b/test/generators/component_generator_test.rb @@ -6,8 +6,14 @@ class ComponentGeneratorTest < Rails::Generators::TestCase setup :prepare_destination tests React::Generators::ComponentGenerator - def filename - 'app/assets/javascripts/components/generated_component.js.jsx' + if WebpackerHelpers.available? + def filename + "app/javascript/components/generated_component.js" + end + else + def filename + 'app/assets/javascripts/components/generated_component.js.jsx' + end end test "creates the component file" do diff --git a/test/generators/es6_component_generator_test.rb b/test/generators/es6_component_generator_test.rb index 9431809ed..6f89b804e 100644 --- a/test/generators/es6_component_generator_test.rb +++ b/test/generators/es6_component_generator_test.rb @@ -6,8 +6,14 @@ class Es6ComponentGeneratorTest < Rails::Generators::TestCase setup :prepare_destination tests React::Generators::ComponentGenerator - def filename - 'app/assets/javascripts/components/generated_component.es6.jsx' + if WebpackerHelpers.available? + def filename + "app/javascript/components/generated_component.js" + end + else + def filename + 'app/assets/javascripts/components/generated_component.es6.jsx' + end end def class_name diff --git a/test/generators/install_generator_sprockets_test.rb b/test/generators/install_generator_sprockets_test.rb new file mode 100644 index 000000000..cf3f0febe --- /dev/null +++ b/test/generators/install_generator_sprockets_test.rb @@ -0,0 +1,102 @@ +require 'test_helper' +require 'generators/react/install_generator' + +# If webpacker is available, its setup is preferred +if !WebpackerHelpers.available? + class InstallGeneratorSprocketsTest < Rails::Generators::TestCase + destination File.join(Rails.root, 'tmp', 'generator_test_output') + tests React::Generators::InstallGenerator + setup :prepare_destination + + def copy_directory(dir) + source = Rails.root.join(dir) + dest = Rails.root.join(destination_root, File.dirname(dir)) + + FileUtils.mkdir_p dest + FileUtils.cp_r source, dest + end + + test "adds requires to `application.js`" do + run_generator + assert_application_file_created + end + + test "it modifes an existing 'application.js'" do + copy_directory('app/assets/javascripts/application.js') + run_generator + assert_application_file_modified + end + + test "creates `application.js` if it doesn't exist" do + copy_directory('app/assets/javascripts/application.js') + File.delete destination_root + '/app/assets/javascripts/application.js' + + run_generator + assert_application_file_created + end + + test "modifies `application.js` if it's empty" do + init_application_js '' + + run_generator + assert_application_file_created + end + + test "updates `application.js` if require_tree is commented" do + init_application_js <<-END + // + // require_tree . + // + END + + run_generator + assert_application_file_modified + end + + test "updates `application.js` if require turbolinks has extra spaces" do + init_application_js <<-END + // + //#{"= require turbolinks "} + // + END + + run_generator + assert_application_file_modified + end + + test "creates server_rendering.js with default requires" do + run_generator + server_rendering_file_path = "app/assets/javascripts/server_rendering.js" + assert_file server_rendering_file_path, %r{//= require react-server\n} + assert_file server_rendering_file_path, %r{//= require ./components\n} + end + + test "creates server rendering initializer" do + run_generator + initializer_path = "config/initializers/react_server_rendering.rb" + assert_file(initializer_path, %r{Rails.application.config.assets.precompile \+= \["server_rendering.js"\]}) + end + + test "skipping server rendering" do + run_generator %w(--skip-server-rendering) + assert_no_file "config/initializers/react_server_rendering.rb" + assert_no_file "app/assets/javascripts/server_rendering.js" + end + + def init_application_js(content) + FileUtils.mkdir_p destination_root + '/app/assets/javascripts/' + File.write destination_root + '/app/assets/javascripts/application.js', content + end + + def assert_application_file_created + assert_file 'app/assets/javascripts/application.js', + %r{//= require react\n//= require react_ujs\n//= require components\n} + end + + def assert_application_file_modified + assert_file 'app/assets/javascripts/application.js', %r{\n//= require react\n} + assert_file 'app/assets/javascripts/application.js', %r{\n//= require react_ujs\n} + assert_file 'app/assets/javascripts/application.js', %r{\n//= require components\n} + end + end +end diff --git a/test/generators/install_generator_test.rb b/test/generators/install_generator_test.rb deleted file mode 100644 index 809862a13..000000000 --- a/test/generators/install_generator_test.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'test_helper' -require 'generators/react/install_generator' - -class InstallGeneratorTest < Rails::Generators::TestCase - destination File.join(Rails.root, 'tmp', 'generator_test_output') - tests React::Generators::InstallGenerator - setup :prepare_destination - - def copy_directory(dir) - source = Rails.root.join(dir) - dest = Rails.root.join(destination_root, File.dirname(dir)) - - FileUtils.mkdir_p dest - FileUtils.cp_r source, dest - end - - test "adds requires to `application.js`" do - run_generator - assert_application_file_created - end - - test "it modifes an existing 'application.js'" do - copy_directory('app/assets/javascripts/application.js') - run_generator - assert_application_file_modified - end - - test "creates `application.js` if it doesn't exist" do - copy_directory('app/assets/javascripts/application.js') - File.delete destination_root + '/app/assets/javascripts/application.js' - - run_generator - assert_application_file_created - end - - test "modifies `application.js` if it's empty" do - init_application_js '' - - run_generator - assert_application_file_created - end - - test "updates `application.js` if require_tree is commented" do - init_application_js <<-END - // - // require_tree . - // - END - - run_generator - assert_application_file_modified - end - - test "updates `application.js` if require turbolinks has extra spaces" do - init_application_js <<-END - // - //#{"= require turbolinks "} - // - END - - run_generator - assert_application_file_modified - end - - test "creates server_rendering.js with default requires" do - run_generator - server_rendering_file_path = "app/assets/javascripts/server_rendering.js" - assert_file server_rendering_file_path, %r{//= require react-server\n} - assert_file server_rendering_file_path, %r{//= require ./components\n} - end - - test "creates server rendering initializer" do - run_generator - initializer_path = "config/initializers/react_server_rendering.rb" - assert_file(initializer_path, %r{Rails.application.config.assets.precompile \+= \["server_rendering.js"\]}) - end - - test "skipping server rendering" do - run_generator %w(--skip-server-rendering) - assert_no_file "config/initializers/react_server_rendering.rb" - assert_no_file "app/assets/javascripts/server_rendering.js" - end - - def init_application_js(content) - FileUtils.mkdir_p destination_root + '/app/assets/javascripts/' - File.write destination_root + '/app/assets/javascripts/application.js', content - end - - def assert_application_file_created - assert_file 'app/assets/javascripts/application.js', - %r{//= require react\n//= require react_ujs\n//= require components\n} - end - - def assert_application_file_modified - assert_file 'app/assets/javascripts/application.js', %r{\n//= require react\n} - assert_file 'app/assets/javascripts/application.js', %r{\n//= require react_ujs\n} - assert_file 'app/assets/javascripts/application.js', %r{\n//= require components\n} - end -end diff --git a/test/generators/install_generator_webpacker_test.rb b/test/generators/install_generator_webpacker_test.rb new file mode 100644 index 000000000..2e8666c47 --- /dev/null +++ b/test/generators/install_generator_webpacker_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' +require 'generators/react/install_generator' + +WebpackerHelpers.when_webpacker_available do + class InstallGeneratorWebpackerTest < Rails::Generators::TestCase + destination File.join(Rails.root, 'tmp', 'generator_test_output') + tests React::Generators::InstallGenerator + setup :prepare_destination + + EXPECTED_SETUP = %|// Support component names relative to this directory: +var componentRequireContext = require.context("components", true) +var ReactRailsUJS = require("react_ujs") +ReactRailsUJS.loadContext(componentRequireContext) +| + + DEFAULT_SERVER_RENDERING_PACK_PATH = "app/javascript/packs/server_rendering.js" + + def copy_directory(dir) + source = Rails.root.join(dir) + dest = Rails.root.join(destination_root, File.dirname(dir)) + + FileUtils.mkdir_p dest + FileUtils.cp_r source, dest + end + + test "adds requires to `application.js`" do + run_generator + assert_file "app/javascript/packs/application.js", EXPECTED_SETUP + end + + test "creates server_rendering.js with default requires" do + run_generator + assert_file DEFAULT_SERVER_RENDERING_PACK_PATH do |contents| + assert_includes contents, "var componentRequireContext = require.context(\"components\", true)\n" + assert_includes contents, "var ReactRailsUJS = require(\"react_ujs\")\n" + assert_includes contents, "ReactRailsUJS.loadContext(componentRequireContext)\n" + end + end + + test "skipping server rendering" do + run_generator %w(--skip-server-rendering) + assert_no_file DEFAULT_SERVER_RENDERING_PACK_PATH + end + end +end diff --git a/test/react/server_rendering/exec_js_renderer_test.rb b/test/react/server_rendering/exec_js_renderer_test.rb index d8d8d1ef7..7aa4c405e 100644 --- a/test/react/server_rendering/exec_js_renderer_test.rb +++ b/test/react/server_rendering/exec_js_renderer_test.rb @@ -5,9 +5,9 @@ var React = { createElement: function() {}, } -var ReactDOMServer = { - renderToString: function() { - return 'renderToString was called' +var ReactRailsUJS = { + serverRender: function() { + return 'serverRender was called' }, } " @@ -15,8 +15,9 @@ class ExecJSRendererTest < ActiveSupport::TestCase setup do react_server_source = File.read(File.expand_path("../../../../lib/assets/react-source/production/react-server.js", __FILE__)) + react_ujs_source = File.read(File.expand_path("../../../../lib/assets/javascripts/react_ujs.js", __FILE__)) todo_component_source = File.read(File.expand_path("../../../dummy/app/assets/javascripts/components/PlainJSTodo.js", __FILE__)) - code = react_server_source + todo_component_source + code = react_server_source + react_ujs_source + todo_component_source @renderer = React::ServerRendering::ExecJSRenderer.new(code: code) end @@ -70,6 +71,6 @@ def @renderer.after_render(name, props, opts) test '.new accepts code:' do dummy_renderer = React::ServerRendering::ExecJSRenderer.new(code: DUMMY_IMPLEMENTATION) result = dummy_renderer.render("Todo", {todo: "get a real job"}.to_json, {}) - assert_equal("renderToString was called", result) + assert_equal("serverRender was called", result) end end diff --git a/test/react/server_rendering/sprockets_renderer_test.rb b/test/react/server_rendering/sprockets_renderer_test.rb index 6cb541a2a..bb954e082 100644 --- a/test/react/server_rendering/sprockets_renderer_test.rb +++ b/test/react/server_rendering/sprockets_renderer_test.rb @@ -86,7 +86,7 @@ class SprocketsRendererTest < ActiveSupport::TestCase end test '.new accepts any filenames' do - limited_renderer = React::ServerRendering::SprocketsRenderer.new(files: ["react-server.js", "components/Todo.js"]) + 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) @@ -96,7 +96,7 @@ class SprocketsRendererTest < ActiveSupport::TestCase test '#render returns html when config.assets.compile is false' do begin - legacy_rendering_files = ["react-server.js", "components.js"] + legacy_rendering_files = ["react-server.js", "react_ujs.js", "components.js"] Rails.application.config.assets.precompile += legacy_rendering_files precompile_assets diff --git a/test/support/webpacker_helpers.rb b/test/support/webpacker_helpers.rb index c6160f338..d6b8eaa4b 100644 --- a/test/support/webpacker_helpers.rb +++ b/test/support/webpacker_helpers.rb @@ -1,8 +1,11 @@ module WebpackerHelpers module_function + def available? + defined?(Webpacker) + end def when_webpacker_available - if defined?(Webpacker) + if available? clear_webpacker_packs Dir.chdir("./test/dummy") do Rake::Task['webpacker:compile'].invoke