diff --git a/CHANGELOG.md b/CHANGELOG.md index c697f25cd..e6f2d7d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,23 @@ for details. * Removal of config.symlink_non_digested_assets_regex as it's no longer needed with rails/webpacker. If any business needs this, we can move the code to a separate gem. +#### Changed +- Added configuration option `same_bundle_for_client_and_server` with default `false` because + + 1. Production applications would typically have a server bundle that differs from the client bundle + 2. This change only affects trying to use HMR with react_on_rails with rails/webpacker. + + The previous behavior was to always go to the webpack-dev-server for the server bundle if the + webpack-dev-server was running _and_ the server bundle was found in the `manifest.json`. Because + the `assets:clean` enhancement of rails/webpacker deletes any files that are not in the `manifest.json`, + we recommend fingerprinting the server bundle and adding it to the manifest. + + If you are using the **same bundle for client and server rendering**, then set this configuration option + to `true`. By [justin808](https://github.com/justin808/1240). + +#### Improved +- Removed unnecessary restriction to keep the server bundle in the same directory with the client bundles. Rails/webpacker 4 has an advanced cleanup that will remove any files in the directory of other webpack files. Removing this restriction allows the server bundle to be created in a sibling directory. Note, the rails/webpacker cleanup will remove any files in child directories. By [justin808](https://github.com/justin808/1240). + ### [11.3.0] - 2019-05-24 #### Added - Added method for retrieving any option from `render_options` [PR 1213](https://github.com/shakacode/react_on_rails/pull/1213) diff --git a/docs/basics/configuration.md b/docs/basics/configuration.md index 98c6d5c9e..ceb9c029c 100644 --- a/docs/basics/configuration.md +++ b/docs/basics/configuration.md @@ -105,9 +105,11 @@ ReactOnRails.configure do |config| # The default is `%w( manifest.json )` as will be sufficient for most webpacker builds. # However, if you are generated a server bundle that is NOT hashed (present in manifest.json), # then include the file in this list like this: - # config.webpack_generated_files = %w( server-bundle.js manifest.json ) - + # Note, be sure NOT to include your server-bundle.js if it is hashed, or else React on Rails will + # think the server-bundle.js is missing every time for test runs. + # In the future, we'll require the server-bundle.js to be hashed + # You can optionally add values to your rails_context. See example below for RenderingExtension # config.rendering_extension = RenderingExtension @@ -126,7 +128,12 @@ ReactOnRails.configure do |config| # If you are hashing this file (supposing you are using the same file for client rendering), then # you should include a name that matches your bundle name in your webpack config. config.server_bundle_js_file = "server-bundle.js" - + + # This value only affects using the webpack-dev-server + # If you wanted to use the same bundle for client and server, you'd set this to `true`. + # Normally, you have different bundles for client and server, thus, the default is false. + config.same_bundle_for_client_and_server = false + # If set to true, this forces Rails to reload the server bundle if it is modified # Default value is Rails.env.development? # diff --git a/docs/tutorial.md b/docs/tutorial.md index 3d6a5ce5c..c93305244 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1,3 +1,10 @@ + + +Start work here and work through the tutorial and maybe add a simple configuration for having a different server +rendering file. + + + # React on Rails Basic Tutorial This tutorial guides you through setting up a new or existing Rails app with **React on Rails**, demonstrating Rails + React + Redux + Server Rendering. It is updated to 11.2.1. @@ -17,9 +24,9 @@ By the time you read this, the latest may have changed. Be sure to check the ver _Note: some of the screen images below show the "npm" command. react_on_rails 6.6.0 and greater uses `yarn`._ -## Setting up the environment +## Setting up your environment -Trying out **React on Rails** is super easy, so long as you have the basic prerequisites. This includes the basics for Rails 5.x and node version 6+. I recommend `rvm` and `nvm` to install Ruby and Node, and [brew](https://brew.sh/) to install [yarn](https://yarnpkg.com/en/docs/install#mac-tab). Rails can be installed as an ordinary gem. +Trying out **React on Rails** is super easy, so long as you have the basic prerequisites. This includes the basics for Rails 6.x and node version 13+. I recommend `rvm` and `nvm` to install Ruby and Node, and [brew](https://brew.sh/) to install [yarn](https://yarnpkg.com/en/docs/install#mac-tab). Rails can be installed as an ordinary gem. ``` nvm install node # download and install latest stable Node @@ -28,8 +35,8 @@ nvm list # check brew install yarn # you can use other installer if desired 11\.\d+\.\d+ -rvm install 2.5.0 # download and install latest stable Ruby (update to exact version) -rvm use 2.5.0 --default # use it and make it default +rvm install 2.6 # download and install latest stable Ruby (update to exact version) +rvm use 2.6 --default # use it and make it default rvm list # check gem install rails # download and install latest stable Rails @@ -44,13 +51,13 @@ First be sure to run `rails -v` and check you are using Rails 5.1.3 or above. If cd # any name you like for the rails app -rails new test-react-on-rails --webpack=react +rails new test-react-on-rails --webpack=react --skip-sprockets cd test-react-on-rails bundle ``` -Note: if you are installing React On Rails in an existing app or an app that uses **Rails pre 5.1.3** (*not for Rails > 5.2*), you will need to run these two commands as well: +Note: if you are adding React On Rails to an existing app you will instead to run these two commands as well: ``` bundle exec rails webpacker:install @@ -60,7 +67,7 @@ bundle exec rails webpacker:install:react Add the **React On Rails** gem to your `Gemfile`: ``` -gem 'react_on_rails', '11.2.2' # prefer exact gem version to match npm version +gem 'react_on_rails', '12.0.0' # prefer exact gem version to match npm version ``` Note: Latest released React On Rails version is considered stable. Please use the latest version to ensure you get all the security patches and the best support. @@ -81,7 +88,6 @@ Install React on Rails: `rails generate react_on_rails:install` or `rails genera ``` rails generate react_on_rails:install -bundle && yarn ``` Then run server with static client side files: @@ -96,27 +102,34 @@ foreman start -f Procfile.dev-server ``` Visit [http://localhost:3000/hello_world](http://localhost:3000/hello_world) and see your **React On Rails** app running! -Note, foreman defaults to PORT 5000 unless you set the value of PORT in your environment or in the Procfile. -## Using a pre-release of rails/webpacker -Until `rails/webpacker` v4 ships, or if you ever want to try out the master branch, you can modify the React on Rails tutorial instructions slightly. You can see the sequence of commits here. To summarize: +*Note, foreman may default to PORT 5000 unless you set the value of PORT in your environment or in the Procfile.* + +# HMR vs. React Hot Reloading + +First, check that the `hmr` option is `true` in your `config/webpacker.yml` file. + +The basic setup will have HMR working with the default webpacker setup. However, the basic will cause a full page refresh each time you save a file. + + + + + + + + + + + + + + + + -**Don't run `rails new` with the `--webpack=react` option**. Instead, add the webpacker gem to the Gemfile such that it points to master, like this if `11.2.1` is the version you want. -```ruby -gem 'webpacker', github: "rails/webpacker" -gem 'react_on_rails', '11.2.1' # always use exact version -``` -Then run these commands: -```sh -bundle exec rails webpacker:install -yarn add "rails/webpacker" # because the installer has a bug that puts in an invalid version in your package.json. -bundle exec rails webpacker:install:react -yarn add --dev webpack-dev-server -run rails generate react_on_rails:install && bundle && yarn -``` ### Custom IP & PORT setup (Cloud9 example) @@ -265,6 +278,16 @@ You can turn on server rendering by simply changing the `prerender` option to `t <%= react_component("HelloWorld", props: @hello_world_props, prerender: true) %> ``` +If you want to test this out with HMR, then you also need to add this line to your +`config/intializers/react_on_rails.rb` + +```ruby + config.same_bundle_for_client_and_server = true +``` + +More likely, you will create a different build file for server rendering. However, if you want to +use the same file from the webpack-dev-server, you'll need to add that line. + Then push to Heroku: ``` diff --git a/lib/react_on_rails/configuration.rb b/lib/react_on_rails/configuration.rb index 907a28c11..c71ee3c51 100644 --- a/lib/react_on_rails/configuration.rb +++ b/lib/react_on_rails/configuration.rb @@ -33,7 +33,8 @@ def self.configuration server_render_method: nil, build_test_command: "", build_production_command: "", - random_dom_id: DEFAULT_RANDOM_DOM_ID + random_dom_id: DEFAULT_RANDOM_DOM_ID, + same_bundle_for_client_and_server: false ) end @@ -46,7 +47,9 @@ class Configuration :webpack_generated_files, :rendering_extension, :build_test_command, :build_production_command, :i18n_dir, :i18n_yml_dir, - :server_render_method, :random_dom_id + :server_render_method, + :random_dom_id, + :same_bundle_for_client_and_server def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil, replay_console: nil, @@ -58,9 +61,9 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender rendering_extension: nil, build_test_command: nil, build_production_command: nil, i18n_dir: nil, i18n_yml_dir: nil, random_dom_id: nil, + same_bundle_for_client_and_server: nil, server_render_method: nil) self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root - self.server_bundle_js_file = server_bundle_js_file self.generated_assets_dirs = generated_assets_dirs self.generated_assets_dir = generated_assets_dir self.build_test_command = build_test_command @@ -82,6 +85,8 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender self.skip_display_none = skip_display_none # Server rendering: + self.server_bundle_js_file = server_bundle_js_file + self.same_bundle_for_client_and_server = same_bundle_for_client_and_server self.server_renderer_pool_size = self.development_mode ? 1 : server_renderer_pool_size self.server_renderer_timeout = server_renderer_timeout # seconds @@ -195,7 +200,7 @@ def configure_generated_assets_dirs_deprecation def ensure_webpack_generated_files_exists return unless webpack_generated_files.empty? - files = ["hello-world-bundle.js"] + files = ["manifest.json"] files << server_bundle_js_file if server_bundle_js_file.present? self.webpack_generated_files = files diff --git a/lib/react_on_rails/webpacker_utils.rb b/lib/react_on_rails/webpacker_utils.rb index 4e17022f6..9b96458ef 100644 --- a/lib/react_on_rails/webpacker_utils.rb +++ b/lib/react_on_rails/webpacker_utils.rb @@ -12,15 +12,19 @@ def self.dev_server_running? Webpacker.dev_server.running? end - # This returns either a URL for the webpack-dev-server or a file path - def self.bundle_js_uri_from_webpacker(bundle_name) + # This returns either a URL for the webpack-dev-server, non-server bundle or server bundle + # if using the same bundle for the client, and otherwise returns a file path. + def self.bundle_js_uri_from_webpacker(bundle_name, server_bundle: false) # Note Webpacker 3.4.3 manifest lookup is inside of the public_output_path # [2] (pry) ReactOnRails::WebpackerUtils: 0> Webpacker.manifest.lookup("app-bundle.js") # "/webpack/development/app-bundle-c1d2b6ab73dffa7d9c0e.js" # Next line will throw if the file or manifest does not exist hashed_bundle_name = Webpacker.manifest.lookup!(bundle_name) - if Webpacker.dev_server.running? + is_server_bundle = bundle_name == ReactOnRails.configuration.server_bundle_js_file + + if Webpacker.dev_server.running? && (!is_server_bundle || + ReactOnRails.configuration.same_bundle_for_client_and_server) "#{Webpacker.dev_server.protocol}://#{Webpacker.dev_server.host_with_port}#{hashed_bundle_name}" else File.expand_path(File.join("public", hashed_bundle_name)).to_s diff --git a/spec/react_on_rails/utils_spec.rb b/spec/react_on_rails/utils_spec.rb index 98ef7451b..2b7a73bcc 100644 --- a/spec/react_on_rails/utils_spec.rb +++ b/spec/react_on_rails/utils_spec.rb @@ -41,6 +41,8 @@ module ReactOnRails allow(Webpacker).to receive_message_chain("manifest.lookup!") .with("webpack-bundle.js") .and_return("/webpack/dev/webpack-bundle-0123456789abcdef.js") + allow(ReactOnRails).to receive_message_chain("configuration.server_bundle_js_file") + .and_return("server-bundle.js") end it { expect(subject).to eq("#{webpacker_public_output_path}/webpack-bundle-0123456789abcdef.js") } @@ -125,17 +127,61 @@ module ReactOnRails end end - context "With Webpacker enabled and server file in the manifest", :webpacker do + context "With Webpacker enabled and server file in the manifest, used for client", :webpacker do it "returns the correct path hashed server path" do allow(ReactOnRails).to receive_message_chain("configuration.server_bundle_js_file") .and_return("webpack-bundle.js") + allow(ReactOnRails).to receive_message_chain("configuration.same_bundle_for_client_and_server") + .and_return(true) allow(Webpacker).to receive_message_chain("manifest.lookup!") .with("webpack-bundle.js") .and_return("webpack/development/webpack-bundle-123456.js") path = Utils.server_bundle_js_file_path - expect(path).to end_with("public/webpack/development/webpack-bundle-123456.js") + expect(path).to start_with("/") + end + end + + context "With Webpacker enabled and server file in the manifest, used for client, "\ + " and webpack-dev-server running, and same file used for server and client", :webpacker do + it "returns the correct path hashed server path" do + allow(ReactOnRails).to receive_message_chain("configuration.server_bundle_js_file") + .and_return("webpack-bundle.js") + allow(ReactOnRails).to receive_message_chain("configuration.same_bundle_for_client_and_server") + .and_return(true) + allow(Webpacker).to receive_message_chain("dev_server.running?") + .and_return(true) + allow(Webpacker).to receive_message_chain("dev_server.protocol") + .and_return("http") + allow(Webpacker).to receive_message_chain("dev_server.host_with_port") + .and_return("localhost:3035") + allow(Webpacker).to receive_message_chain("manifest.lookup!") + .with("webpack-bundle.js") + .and_return("/webpack/development/webpack-bundle-123456.js") + + path = Utils.server_bundle_js_file_path + + expect(path).to eq("http://localhost:3035/webpack/development/webpack-bundle-123456.js") + end + end + + context "With Webpacker enabled, dev-server running, and server file in the manifest, and "\ + " separate client/server files", :webpacker do + it "returns the correct path hashed server path" do + allow(ReactOnRails).to receive_message_chain("configuration.server_bundle_js_file") + .and_return("server-bundle.js") + allow(ReactOnRails).to receive_message_chain("configuration.same_bundle_for_client_and_server") + .and_return(false) + allow(Webpacker).to receive_message_chain("manifest.lookup!") + .with("server-bundle.js") + .and_return("webpack/development/server-bundle-123456.js") + allow(Webpacker).to receive_message_chain("dev_server.running?") + .and_return(true) + + path = Utils.server_bundle_js_file_path + + expect(path).to end_with("/public/webpack/development/server-bundle-123456.js") end end end