diff --git a/CHANGELOG.md b/CHANGELOG.md index 33900fcbb..ef4c65302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ If you need help upgrading `react-rails`, `webpacker` to `shakapacker`, or JS pa - Camelizes keys with primitive values, in addition to hashes #946 - Expose alternative implementations for `ReactUJS.getConstructor` #1050 - Include turbolinks in dev and update webdrivers #1174 +- Add support for multiple `require.context` with addition of `useContexts` #1144 +- Update dependencies #### Update dependencies - react to 17.0.2 #1218 diff --git a/README.md b/README.md index a851f5fc6..e03608769 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,24 @@ ReactRailsUJS.useContext(myCustomContext) If `require` fails to find your component, [`ReactRailsUJS`](#ujs) falls back to the global namespace, described in [Use with Asset Pipeline](#use-with-asset-pipeline). +In some cases, having multiple `require.context` entries may be desired. Examples of this include: + +- Refactoring a typical Rails application into a Rails API with an (eventually) separate Single Page Application (SPA). For this use case, one can add a separate pack in addition to the typical `application` one. React components can be shared between the packs but the new pack can use a minimal Rails view layout, different default styling, etc. +- In a larger application, you might find it helpful to split your JavaScript by routes/controllers to avoid serving unused components and improve your site performance by keeping bundles smaller. For example, you might have separate bundles for homepage, search, and checkout routes. In that scenario, you can add an array of `require.context` component directory paths via `useContexts` to `server_rendering.js`, to allow for [Server-Side Rendering](#server-side-rendering) across your application: + +```js +// server_rendering.js +var homepageRequireContext = require.context('homepage', true); +var searchRequireContext = require.context('search', true); +var checkoutRequireContext = require.context('checkout', true); + +var ReactRailsUJS = require('react_ujs'); +ReactRailsUJS.useContexts([ + homepageRequireContext, + searchRequireContext, + checkoutRequireContext +]); +``` ### File naming React-Rails supports plenty of file extensions such as: .js, .jsx.js, .js.jsx, .es6.js, .coffee, etcetera! diff --git a/react_ujs/index.js b/react_ujs/index.js index 4c4c991fb..6b8effc85 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -6,6 +6,7 @@ var detectEvents = require("./src/events/detect") var constructorFromGlobal = require("./src/getConstructor/fromGlobal") var constructorFromRequireContext = require("./src/getConstructor/fromRequireContext") var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") +var constructorFromRequireContextsWithGlobalFallback = require("./src/getConstructor/fromRequireContextsWithGlobalFallback") const { supportsHydration, reactHydrate, createReactRootLike } = require("./src/renderHelpers") var ReactRailsUJS = { @@ -79,6 +80,13 @@ var ReactRailsUJS = { this.getConstructor = constructorFromRequireContextWithGlobalFallback(requireContext) }, + // Given an array of Webpack `require.context`, + // try finding components with `require`, + // then falling back to global lookup. + useContexts: function(requireContexts) { + this.getConstructor = constructorFromRequireContextsWithGlobalFallback(requireContexts) + }, + // Render `componentName` with `props` to a string, // using the specified `renderFunction` from `react-dom/server`. serverRender: function(renderFunction, componentName, props) { diff --git a/react_ujs/src/getConstructor/fromRequireContextsWithGlobalFallback.js b/react_ujs/src/getConstructor/fromRequireContextsWithGlobalFallback.js new file mode 100644 index 000000000..ab4b936c6 --- /dev/null +++ b/react_ujs/src/getConstructor/fromRequireContextsWithGlobalFallback.js @@ -0,0 +1,39 @@ +// Make a function which: +// - First tries to require the name +// - Then falls back to global lookup +var fromGlobal = require("./fromGlobal") +var fromRequireContext = require("./fromRequireContext") + +module.exports = function(reqctxs) { + var fromCtxs = reqctxs.map((reqctx) => fromRequireContext(reqctx)) + return function(className) { + var component; + try { + var index = 0, fromCtx, firstErr; + do { + fromCtx = fromCtxs[index]; + + try { + // `require` will raise an error if this className isn't found: + component = fromCtx(className) + } catch (fromCtxErr) { + if (!firstErr) { + firstErr = fromCtxErr; + } + } + + index += 1; + } while (index < fromCtxs.length); + if (!component) throw firstErr; + } catch (firstErr) { + // fallback to global: + try { + component = fromGlobal(className) + } catch (secondErr) { + console.error(firstErr) + console.error(secondErr) + } + } + return component + } +}