diff --git a/CHANGELOG.md b/CHANGELOG.md index 11fc15789..deb6c99ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Changes since last non-beta release. *Please add entries here for your pull requests that are not yet released.* +#### Added +- Added 2 cache helpers: ReactOnRails::Utils.bundle_file_name(bundle_name) and ReactOnRails::Utils.server_bundle_file_name +for easy access to the hashed filenames for use in cache keys by [justin808](https://github.com/justin808). + #### Fixed - Fixed `Utils.bundle_js_file_path` generating the incorrect path for `manifest.json` in webpacker projects: [Issue #1011](https://github.com/shakacode/react_on_rails/issues/1011) by [elstgav](https://github.com/elstgav) diff --git a/README.md b/README.md index 1e52bb101..990a47db6 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ A key decision in your use React on Rails is whether you go with the rails/webpa Until version 9, all React on Rails apps used the `/client` directory for configuring React on Rails in terms of the configuration of Webpack and location of your JavaScript and Webpack files, including the node_modules directory. Version 9 changed the default to `/` for the `node_modules` location using this value in `config/initializers/react_on_rails.rb`: `config.node_modules_location`. -The [ShakaCode Team](http://www.shakacode.com) recommends this approach projects beyond the simplest cases as it provides the greatest transparency in your webpack and overall client-side setup. The *big advantage* to this is that almost everything within the `/client` directory will apply if you wish to convert your client-side code to a pure Single Page Application that runs without Rails. This allows you to google for how to do something with Webpack configuration and what applies to a non-Rails app will apply just as well to a React on Rails app. +The [ShakaCode Team](http://www.shakacode.com) _recommends_ this approach for projects beyond the simplest cases as it provides the greatest transparency in your webpack and overall client-side setup. The *big advantage* to this is that almost everything within the `/client` directory will apply if you wish to convert your client-side code to a pure Single Page Application that runs without Rails. This allows you to google for how to do something with Webpack configuration and what applies to a non-Rails app will apply just as well to a React on Rails app. The two best examples of this patten are the [react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial) and the integration test example in [spec/dummy](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy). @@ -679,8 +679,11 @@ If you are using [jquery-ujs](https://github.com/rails/jquery-ujs) for AJAX call 1. Examples in [spec/dummy/app/views/react_router](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/app/views/react_router) and follow to the JavaScript code in the [spec/dummy/client/app/startup/ServerRouterApp.jsx](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/client/app/startup/ServerRouterApp.jsx). 1. [Code Splitting docs](./docs/additional-reading/code-splitting.md) for information about how to set up code splitting for server rendered routes. +## Caching and Performance +Consider fragment and http caching of pages that contain React on Rails components. See [Caching and Performance](./docs/additional-reading/caching-and-performance.md) for more details. + ## Deployment -* Version 6.0 puts the necessary precompile steps automatically in the rake precompile step. You can, however, disable this by setting certain values to nil in the [config/initializers/react_on_rails.rb](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/config/initializers/react_on_rails.rb). +* React on Rails puts the necessary precompile steps automatically in the rake precompile step. You can, however, disable this by setting certain values to nil in the [config/initializers/react_on_rails.rb](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/config/initializers/react_on_rails.rb). * `config.symlink_non_digested_assets_regex`: Set to nil to turn off the setup of non-js assets. * `build_production_command`: Set to nil to turn off the precompilation of the js assets. * See the [Heroku Deployment](./docs/additional-reading/heroku-deployment.md) doc for specifics regarding Heroku. The information here should apply to other deployments. @@ -724,6 +727,7 @@ If you want to use a node server for server rendering, [get in touch](mailto:jus + [Hot Reloading of Assets For Rails Development](./docs/additional-reading/hot-reloading-rails-development.md) + [Heroku Deployment](./docs/additional-reading/heroku-deployment.md) + [Updating Dependencies](./docs/additional-reading/updating-dependencies.md) + + [Caching and Performance](./docs/additional-reading/caching-and-performance.md) + **API** + [JavaScript API](./docs/api/javascript-api.md) diff --git a/docs/additional-reading/caching-and-performance.md b/docs/additional-reading/caching-and-performance.md new file mode 100644 index 000000000..82e41081b --- /dev/null +++ b/docs/additional-reading/caching-and-performance.md @@ -0,0 +1,31 @@ +# Caching and Performance + + +## Caching + +### Fragment Caching + +If you wish to do fragment caching that includes React on Rails rendered components, be sure to +include the bundle name of your server rendering bundle in your cache key. This is analogous to +how Rails puts an MD5 hash of your views in the cache key so that if the views change, then your +cache is busted. In the case of React code, if your React code changes, then your bundle name will +change due to the typical inclusion of a hash in the name. + +Call this method to get the server bundle file name: + +```ruby +# Returns the hashed file name of the server bundle when using webpacker. +# Nececessary fragment-caching keys. +ReactOnRails::Utils.server_bundle_file_name +``` + +### HTTP Caching + +When creating a HTTP cache, you want the cache key to include your client bundle files. + +Call this method to get the client bundle file name. Note, you have to pass which bundle name. + +```ruby +# Returns the hashed file name when using webpacker. Useful for creating cache keys. +ReactOnRails::Utils..bundle_file_name(bundle_name) +``` diff --git a/docs/api/ruby-api.md b/docs/api/ruby-api.md index 92deccb45..f264b26fb 100644 --- a/docs/api/ruby-api.md +++ b/docs/api/ruby-api.md @@ -1,3 +1,8 @@ -PENDING: +# View Helpers +See the [app/helpers/react_on_rails_helper.rb](../../app/helpers/react_on_rails_helper.rb) source. -Should we document the view helpers here concisely? +# Controller Helpers +See the [lib/react_on_rails/controller.rb](../../lib/react_on_rails/controller.rb) source. + +# Utility Methods +See the [lib/react_on_rails/utils.rb](../../lib/react_on_rails/utils.rb) source. diff --git a/lib/react_on_rails/utils.rb b/lib/react_on_rails/utils.rb index 2ad248c6c..61490f5f6 100644 --- a/lib/react_on_rails/utils.rb +++ b/lib/react_on_rails/utils.rb @@ -8,6 +8,33 @@ module ReactOnRails module Utils + ########################################################### + # PUBLIC API + ########################################################### + + # Returns the hashed file name when using webpacker. Useful for creating cache keys. + def self.bundle_file_name(bundle_name) + raise "Only call bundle_file_name if using webpacker" unless using_webpacker? + full_path = bundle_js_file_path_from_webpacker(bundle_name) + pathname = Pathname.new(full_path) + pathname.basename.to_s + end + + # Returns the hashed file name of the server bundle when using webpacker. + # Nececessary fragment-caching keys. + def self.server_bundle_file_name + return @server_bundle_hash if @server_bundle_hash && !Rails.env.development? + + @server_bundle_hash = begin + server_bundle_name = ReactOnRails.configuration.server_bundle_js_file + bundle_file_name(server_bundle_name) + end + end + + ########################################################### + # PRIVATE API -- Subject to change + ########################################################### + # https://forum.shakacode.com/t/yak-of-the-week-ruby-2-4-pathname-empty-changed-to-look-at-file-size/901 # return object if truthy, else return nil def self.truthy_presence(obj) diff --git a/spec/react_on_rails/utils_spec.rb b/spec/react_on_rails/utils_spec.rb index 4213f9f77..4298f7d7b 100644 --- a/spec/react_on_rails/utils_spec.rb +++ b/spec/react_on_rails/utils_spec.rb @@ -1,9 +1,50 @@ # frozen_string_literal: true +# rubocop:disable Metrics/ModuleLength +# rubocop:disable Metrics/BlockLength + require_relative "spec_helper" module ReactOnRails RSpec.describe Utils do + describe "cache helpers .server_bundle_file_name and .bundle_file_name" do + context "and file in manifest", :webpacker do + before do + allow(Rails).to receive(:root).and_return(Pathname.new(".")) + allow(ReactOnRails).to receive_message_chain("configuration.generated_assets_dir") + .and_return("public/webpack/production") + allow(Webpacker).to receive_message_chain("config.public_output_path") + .and_return("public/webpack/production") + allow(Utils).to receive(:using_webpacker?).and_return(true) + end + describe ".bundle_file_name" do + before do + allow(Webpacker).to receive_message_chain("manifest.lookup") + .with("client-bundle.js") + .and_return("/webpack/production/client-bundle-0123456789abcdef.js") + end + subject do + Utils.bundle_file_name("client-bundle.js") + end + it { expect(subject).to eq("client-bundle-0123456789abcdef.js") } + end + + describe ".server_bundle_file_name" do + before do + allow(ReactOnRails).to receive_message_chain("configuration.server_bundle_js_file") + .and_return("server-bundle.js") + allow(Webpacker).to receive_message_chain("manifest.lookup") + .with("server-bundle.js") + .and_return("/webpack/production/server-bundle-0123456789abcdef.js") + end + subject do + Utils.server_bundle_file_name + end + it { expect(subject).to eq("server-bundle-0123456789abcdef.js") } + end + end + end + describe ".bundle_js_file_path" do before do allow(ReactOnRails).to receive_message_chain(:configuration, :generated_assets_dir) @@ -231,3 +272,6 @@ module ReactOnRails end end end + +# rubocop:enable Metrics/ModuleLength +# rubocop:enable Metrics/BlockLength