From cf8815a09af6c5f5f3706da6e99d6104f7d3bbee Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Fri, 17 Apr 2026 17:19:04 -1000 Subject: [PATCH 01/10] docs: add legacy Webpacker and migration-fit guidance Add explicit guidance in the react-rails and vite_rails migration docs so teams pick a realistic first migration target: - react-rails: new "Pick the right first target" and "Preferred path for Webpacker-era apps" sections. Recommend migrating webpacker to shakapacker as a separate PR before adding react_on_rails, and spell out the compatibility shims required if a team intentionally stays on Webpack 4. - vite_rails: new "Two different starting points" section distinguishing Rails-owned island mounts from client-routed SPA shells. SPA shells should not be treated as a quick first migration. Fixes #3138. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../migrating/migrating-from-react-rails.md | 32 ++++++++++++++++++- .../migrating/migrating-from-vite-rails.md | 13 ++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index fdcc031f16..b854c78043 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -2,6 +2,32 @@ This migration is easiest when the app is already on a modern Rails + Shakapacker baseline. +## Pick the right first target + +Not every `react-rails` app is a good candidate for a low-friction first migration. Before you start, classify what you have: + +- **Rails-owned island mounts on Shakapacker 7+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. +- **Webpacker-era apps (`gem "webpacker"`, Webpack 4).** These can work with React on Rails, but often only with compatibility shims that are not obvious up front. The low-friction path is to migrate the bundler first, not to keep Webpacker. See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) below. +- **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Do not use this as the first migration slice. Treat it as an architecture case study, not a quick first migration. Pick a Rails-owned page that mounts a single component and migrate that slice first. + +The wrong first target usually leads to one of two misreadings: teams conclude "React on Rails is broken" when the real problem is legacy bundler compatibility, or they conclude "the migration path is heavy" when they chose a SPA-root rewrite instead of an island mount. + +## Preferred path for Webpacker-era apps + +If the app still uses `gem "webpacker"`, the recommended path is: + +1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. +2. **Then run the React on Rails install generator** against the Shakapacker-based app. + +If you intentionally stay on Webpacker / Webpack 4 for a first incremental slice (for example, because the rest of the app is not yet ready to move), expect to need package-specific compatibility config: + +- pinned `webpack`, `webpack-cli`, and loader versions that match Webpack 4 +- a `babel.config.js` (or `.babelrc`) that is compatible with the older toolchain +- manual updates to `config/webpack/*` because the Webpacker-generated files pre-date the Shakapacker conventions React on Rails expects +- explicit `server_bundle_output_path` in `config/initializers/react_on_rails.rb` because auto-detection assumes Shakapacker 9.0+ + +Plan this as a staged migration rather than a single PR. The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. + ## Preflight Before swapping gems, check these first: @@ -130,6 +156,8 @@ For a more recent Rails 7-era migration example (published under ShakaCode), see ## Practical checklist for Webpacker-era apps +See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) above for the recommended staging. The concrete checklist: + If your app looks like this: - `gem "webpacker"` in `Gemfile` @@ -139,9 +167,11 @@ If your app looks like this: then treat the migration as: -1. Move from `webpacker` to `shakapacker`. +1. Move from `webpacker` to `shakapacker` in its own PR. 2. If the app is still on Rails 5.1, upgrade Rails to 5.2+ before adding current `react_on_rails`. 3. Remove `react_ujs`. 4. Run the React on Rails install generator. 5. Replace helper syntax and component registration. 6. Review `bin/dev`, `config/shakapacker.yml`, and webpack config diffs before committing. + +If you intentionally keep Webpacker + Webpack 4 for this first slice, budget time for compatibility shims (pinned loader versions, an older-compatible Babel config, manual webpack config edits, and an explicit `server_bundle_output_path`). "Still on Webpacker" is supportable, but it is not the low-friction path. diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index 868263b904..a1f6d75e4b 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -13,6 +13,19 @@ React on Rails is a better fit when you want one or more of these: If your app is already happy with a Vite-only client-rendered setup, this migration is optional. +## Two different starting points + +Not all `vite_rails` + React apps are the same shape, and the migration effort is very different for each: + +- **Rails-owned island mounts.** Rails renders real ERB views and mounts one or more React components inside them. This is what the steps below are written for. The migration is incremental: you can cut over one page (or one mount) at a time. +- **Client-routed SPA shells.** Rails serves a minimal layout and a single `
`, and a client-side router (React Router / TanStack Router) owns everything after the first render. This is an **architecture case study**, not a quick first migration. Before you convert it, decide whether you are: + 1. moving Rails back to being view-owner and breaking the SPA into island mounts, or + 2. keeping the SPA shape and just replacing Vite's build integration. + +The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). + +If your app is a SPA shell, do not use it as the first proof of React on Rails adoption. Start with a Rails-owned island somewhere else in the app — even a small one — and migrate that first. + ## Preflight Before you start, make sure the current app still installs cleanly on the Ruby and Node versions you plan to use for the migration. From 02b66cd98916f9b5aba61ee283534f30043e86a2 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Fri, 17 Apr 2026 21:40:18 -1000 Subject: [PATCH 02/10] docs: address PR #3157 review feedback on migration guides - Remove inaccurate claim that Webpacker-era apps can work with current React on Rails; `doctor.rb` flags Webpacker as a removed breaking change and the gemspec requires `shakapacker >= 6.0`. - Clarify Shakapacker version expectations: 7+ baseline with a note that `server_bundle_output_path` auto-detection requires 9.0+. - Link to the Shakapacker upgrade guide in the Preferred path. - Replace Preflight step 1 with a back-reference to avoid duplicating the new Preferred path section. - Note that `import.meta.glob` has no direct Webpack equivalent and must be replaced with explicit `require.context` calls. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../migrating/migrating-from-react-rails.md | 19 ++++++------------- .../migrating/migrating-from-vite-rails.md | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index b854c78043..3fe6ad90ae 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -6,8 +6,8 @@ This migration is easiest when the app is already on a modern Rails + Shakapacke Not every `react-rails` app is a good candidate for a low-friction first migration. Before you start, classify what you have: -- **Rails-owned island mounts on Shakapacker 7+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. -- **Webpacker-era apps (`gem "webpacker"`, Webpack 4).** These can work with React on Rails, but often only with compatibility shims that are not obvious up front. The low-friction path is to migrate the bundler first, not to keep Webpacker. See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) below. +- **Rails-owned island mounts on Shakapacker 7+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. (Note: `server_bundle_output_path` auto-detection requires Shakapacker 9.0+; on 7–8, set it explicitly in the initializer.) +- **Webpacker-era apps (`gem "webpacker"`, Webpack 4).** Current React on Rails does not support Webpacker — `react_on_rails doctor` flags it as a removed breaking-change issue, and the gem requires `shakapacker >= 6.0`. You must migrate off Webpacker before installing current React on Rails. See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) below. - **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Do not use this as the first migration slice. Treat it as an architecture case study, not a quick first migration. Pick a Rails-owned page that mounts a single component and migrate that slice first. The wrong first target usually leads to one of two misreadings: teams conclude "React on Rails is broken" when the real problem is legacy bundler compatibility, or they conclude "the migration path is heavy" when they chose a SPA-root rewrite instead of an island mount. @@ -16,23 +16,16 @@ The wrong first target usually leads to one of two misreadings: teams conclude " If the app still uses `gem "webpacker"`, the recommended path is: -1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. +1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. See the [Shakapacker upgrade guide](https://github.com/shakacode/shakapacker#upgrading-from-webpacker) for the concrete steps. 2. **Then run the React on Rails install generator** against the Shakapacker-based app. -If you intentionally stay on Webpacker / Webpack 4 for a first incremental slice (for example, because the rest of the app is not yet ready to move), expect to need package-specific compatibility config: - -- pinned `webpack`, `webpack-cli`, and loader versions that match Webpack 4 -- a `babel.config.js` (or `.babelrc`) that is compatible with the older toolchain -- manual updates to `config/webpack/*` because the Webpacker-generated files pre-date the Shakapacker conventions React on Rails expects -- explicit `server_bundle_output_path` in `config/initializers/react_on_rails.rb` because auto-detection assumes Shakapacker 9.0+ - -Plan this as a staged migration rather than a single PR. The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. +The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. If you cannot migrate off Webpacker yet, pin React on Rails to a version that predates Webpacker removal rather than trying to use current React on Rails with Webpacker. ## Preflight Before swapping gems, check these first: -1. **Webpacker vs Shakapacker**: if the app still uses `webpacker`, migrate to `shakapacker` first. +1. **Webpacker vs Shakapacker**: if the app still uses `webpacker`, see [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) above. 2. **Bundler age**: some older `react-rails` apps still carry Bundler 1.x lockfiles. Those can fail on modern Ruby before you even reach the migration work. 3. **Rails age**: current `react_on_rails` requires Rails 5.2+. Rails 5.1 / Webpacker 3 apps are usually a staged migration, not a one-command migration. 4. **Package manager metadata**: if you have `yarn.lock`, `pnpm-lock.yaml`, or `bun.lock*`, ensure `package.json` has a matching `packageManager` field (for example `npm@10.9.2`, `yarn@1.22.22`, `pnpm@10.12.1`, or `bun@1.2.13`). @@ -174,4 +167,4 @@ then treat the migration as: 5. Replace helper syntax and component registration. 6. Review `bin/dev`, `config/shakapacker.yml`, and webpack config diffs before committing. -If you intentionally keep Webpacker + Webpack 4 for this first slice, budget time for compatibility shims (pinned loader versions, an older-compatible Babel config, manual webpack config edits, and an explicit `server_bundle_output_path`). "Still on Webpacker" is supportable, but it is not the low-friction path. +Current React on Rails does not install alongside `gem "webpacker"` — migrate to Shakapacker first (step 1 above) rather than budgeting time for Webpacker compatibility shims. diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index a1f6d75e4b..5d48703176 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -22,7 +22,7 @@ Not all `vite_rails` + React apps are the same shape, and the migration effort i 1. moving Rails back to being view-owner and breaking the SPA into island mounts, or 2. keeping the SPA shape and just replacing Vite's build integration. -The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). +The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). Note that `import.meta.glob` has no direct Webpack equivalent — it must be replaced with explicit `require.context` calls. If your app is a SPA shell, do not use it as the first proof of React on Rails adoption. Start with a Rails-owned island somewhere else in the app — even a small one — and migrate that first. From f4ed2256ea2f9f0bb17231a2fc3c25db9fe387a9 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Fri, 17 Apr 2026 21:51:07 -1000 Subject: [PATCH 03/10] docs: address additional PR #3157 review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Specify `< 16.0` as the Webpacker-supporting pin in the Webpacker fallback guidance (v16 is the release that removed Webpacker support per CHANGELOG.md). - Fix the broken Shakapacker upgrade anchor (`#upgrading-from-webpacker` does not exist); link to the Webpacker → Shakapacker v6 upgrade doc instead. - Expand the `require.context` note in the vite_rails migration doc to call out the API differences (sync/async, eager/lazy, manual key extraction) and link to the webpack docs. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oss/migrating/migrating-from-react-rails.md | 4 ++-- docs/oss/migrating/migrating-from-vite-rails.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index 3fe6ad90ae..761de5489c 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -16,10 +16,10 @@ The wrong first target usually leads to one of two misreadings: teams conclude " If the app still uses `gem "webpacker"`, the recommended path is: -1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. See the [Shakapacker upgrade guide](https://github.com/shakacode/shakapacker#upgrading-from-webpacker) for the concrete steps. +1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. See the [Shakapacker v6 upgrade guide](https://github.com/shakacode/shakapacker/blob/main/docs/v6_upgrade.md) for the concrete Webpacker → Shakapacker steps. 2. **Then run the React on Rails install generator** against the Shakapacker-based app. -The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. If you cannot migrate off Webpacker yet, pin React on Rails to a version that predates Webpacker removal rather than trying to use current React on Rails with Webpacker. +The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. If you cannot migrate off Webpacker yet, pin `react_on_rails` to `< 16.0` (v16 is the release that removed Webpacker support) rather than trying to use current React on Rails with Webpacker. ## Preflight diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index 5d48703176..d742e506b0 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -22,7 +22,7 @@ Not all `vite_rails` + React apps are the same shape, and the migration effort i 1. moving Rails back to being view-owner and breaking the SPA into island mounts, or 2. keeping the SPA shape and just replacing Vite's build integration. -The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). Note that `import.meta.glob` has no direct Webpack equivalent — it must be replaced with explicit `require.context` calls. +The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). Note that `import.meta.glob` has no direct Webpack equivalent — it must be replaced with explicit [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext) calls, which have a different API (sync vs. async, eager vs. lazy, manual key extraction). If your app is a SPA shell, do not use it as the first proof of React on Rails adoption. Start with a Rails-owned island somewhere else in the app — even a small one — and migrate that first. From fb6383e90a7577c304b7603d3384d1b7987c4b4b Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Fri, 17 Apr 2026 22:06:19 -1000 Subject: [PATCH 04/10] docs: tighten Shakapacker baseline, link stability, and require.context wording - Widen smooth-path baseline from "Shakapacker 7+" to "Shakapacker 6+" to match the `shakapacker >= 6.0` gemspec constraint; extend the server_bundle_output_path auto-detection note to cover 6.x. - Pin the Shakapacker v6 upgrade guide link to the v6.6.0 tag so the reference is stable against future repo reorganization. - Smooth the "Practical checklist for Webpacker-era apps" transition so the intro no longer promises a list that arrives as a conditional block. - Clarify the `require.context` vs `import.meta.glob` comparison: drop the "sync vs. async" framing (require.context is synchronous) and note that lazy loading must be driven by explicit dynamic `import()`. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oss/migrating/migrating-from-react-rails.md | 6 +++--- docs/oss/migrating/migrating-from-vite-rails.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index 761de5489c..2ae39ab61e 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -6,7 +6,7 @@ This migration is easiest when the app is already on a modern Rails + Shakapacke Not every `react-rails` app is a good candidate for a low-friction first migration. Before you start, classify what you have: -- **Rails-owned island mounts on Shakapacker 7+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. (Note: `server_bundle_output_path` auto-detection requires Shakapacker 9.0+; on 7–8, set it explicitly in the initializer.) +- **Rails-owned island mounts on Shakapacker 6+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. (Note: `server_bundle_output_path` auto-detection requires Shakapacker 9.0+; on 6–8, set it explicitly in the initializer.) - **Webpacker-era apps (`gem "webpacker"`, Webpack 4).** Current React on Rails does not support Webpacker — `react_on_rails doctor` flags it as a removed breaking-change issue, and the gem requires `shakapacker >= 6.0`. You must migrate off Webpacker before installing current React on Rails. See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) below. - **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Do not use this as the first migration slice. Treat it as an architecture case study, not a quick first migration. Pick a Rails-owned page that mounts a single component and migrate that slice first. @@ -16,7 +16,7 @@ The wrong first target usually leads to one of two misreadings: teams conclude " If the app still uses `gem "webpacker"`, the recommended path is: -1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. See the [Shakapacker v6 upgrade guide](https://github.com/shakacode/shakapacker/blob/main/docs/v6_upgrade.md) for the concrete Webpacker → Shakapacker steps. +1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. See the [Shakapacker v6 upgrade guide](https://github.com/shakacode/shakapacker/blob/v6.6.0/docs/v6_upgrade.md) for the concrete Webpacker → Shakapacker steps. 2. **Then run the React on Rails install generator** against the Shakapacker-based app. The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. If you cannot migrate off Webpacker yet, pin `react_on_rails` to `< 16.0` (v16 is the release that removed Webpacker support) rather than trying to use current React on Rails with Webpacker. @@ -149,7 +149,7 @@ For a more recent Rails 7-era migration example (published under ShakaCode), see ## Practical checklist for Webpacker-era apps -See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) above for the recommended staging. The concrete checklist: +See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) above for the recommended staging. The concrete checklist follows. If your app looks like this: diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index d742e506b0..a5f8c49fb0 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -22,7 +22,7 @@ Not all `vite_rails` + React apps are the same shape, and the migration effort i 1. moving Rails back to being view-owner and breaking the SPA into island mounts, or 2. keeping the SPA shape and just replacing Vite's build integration. -The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). Note that `import.meta.glob` has no direct Webpack equivalent — it must be replaced with explicit [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext) calls, which have a different API (sync vs. async, eager vs. lazy, manual key extraction). +The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). Note that `import.meta.glob` has no direct Webpack equivalent — it must be replaced with explicit [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext) calls, which use a different API: the glob pattern syntax differs, results are always synchronously resolved, and any lazy loading must be handled through explicit dynamic `import()` calls instead of a built-in lazy mode. If your app is a SPA shell, do not use it as the first proof of React on Rails adoption. Start with a Rails-owned island somewhere else in the app — even a small one — and migrate that first. From a1a3eb09b82b3d0dc2f89fed22262060124120d5 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 18 Apr 2026 09:55:55 -1000 Subject: [PATCH 05/10] docs: reframe SPA shells as a single-call migration pattern Per review feedback: a client-routed SPA shell can be migrated with a single react_component / react_component_hash call wrapping the top-level component, which is the pattern the largest React on Rails Pro deployment (Popmenu) uses in production. Previous guidance discouraged SPA shells as a first target; now both migration guides present it as a valid shape and scope the real friction to Vite-specific runtime behavior. Also corrects the require.context description to reflect its built-in mode argument. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oss/migrating/migrating-from-react-rails.md | 4 ++-- docs/oss/migrating/migrating-from-vite-rails.md | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index 2ae39ab61e..8817ab0499 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -8,9 +8,9 @@ Not every `react-rails` app is a good candidate for a low-friction first migrati - **Rails-owned island mounts on Shakapacker 6+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. (Note: `server_bundle_output_path` auto-detection requires Shakapacker 9.0+; on 6–8, set it explicitly in the initializer.) - **Webpacker-era apps (`gem "webpacker"`, Webpack 4).** Current React on Rails does not support Webpacker — `react_on_rails doctor` flags it as a removed breaking-change issue, and the gem requires `shakapacker >= 6.0`. You must migrate off Webpacker before installing current React on Rails. See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) below. -- **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Do not use this as the first migration slice. Treat it as an architecture case study, not a quick first migration. Pick a Rails-owned page that mounts a single component and migrate that slice first. +- **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Render the top-level SPA component from one ERB view using `react_component` (or `react_component_hash` when SSR needs to return multiple regions such as `componentHtml`, `title`, and other head tags). One React on Rails call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production (Popmenu), where the entire app is a single top-level component call. If you additionally want to break the SPA into several Rails-owned islands, treat that as a separate product decision rather than bundling it with the bundler/integration change. -The wrong first target usually leads to one of two misreadings: teams conclude "React on Rails is broken" when the real problem is legacy bundler compatibility, or they conclude "the migration path is heavy" when they chose a SPA-root rewrite instead of an island mount. +The wrong first target usually leads teams to conclude "React on Rails is broken" when the real problem is legacy bundler compatibility, or to bundle a SPA re-architecture into what should have been a bundler migration. ## Preferred path for Webpacker-era apps diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index a5f8c49fb0..4a466b241b 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -15,16 +15,14 @@ If your app is already happy with a Vite-only client-rendered setup, this migrat ## Two different starting points -Not all `vite_rails` + React apps are the same shape, and the migration effort is very different for each: +Not all `vite_rails` + React apps are the same shape, and the migration effort differs for each: -- **Rails-owned island mounts.** Rails renders real ERB views and mounts one or more React components inside them. This is what the steps below are written for. The migration is incremental: you can cut over one page (or one mount) at a time. -- **Client-routed SPA shells.** Rails serves a minimal layout and a single `
`, and a client-side router (React Router / TanStack Router) owns everything after the first render. This is an **architecture case study**, not a quick first migration. Before you convert it, decide whether you are: - 1. moving Rails back to being view-owner and breaking the SPA into island mounts, or - 2. keeping the SPA shape and just replacing Vite's build integration. +- **Rails-owned island mounts.** Rails renders real ERB views and mounts one or more React components inside them. The migration is incremental: you can cut over one page (or one mount) at a time. +- **Client-routed SPA shells.** Rails serves a minimal layout and a single `
`, and a client-side router (React Router / TanStack Router) owns everything after the first render. You have two reasonable migration shapes here: + 1. **Keep the SPA shape.** Render the top-level SPA component from a single ERB view using `react_component` (or `react_component_hash` when you need SSR that returns multiple regions such as `componentHtml`, `title`, and other head tags). One React on Rails call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production (Popmenu), where the entire app is a single top-level component call. + 2. **Break the SPA into island mounts** by moving Rails back to being the view-owner. This is a real product decision and should not be bundled with the bundler/integration change. -The first is a real product decision and should not be bundled with a bundler/integration change. The second is narrower but rarely a one-PR job either, because SPA shells usually depend on Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue). Note that `import.meta.glob` has no direct Webpack equivalent — it must be replaced with explicit [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext) calls, which use a different API: the glob pattern syntax differs, results are always synchronously resolved, and any lazy loading must be handled through explicit dynamic `import()` calls instead of a built-in lazy mode. - -If your app is a SPA shell, do not use it as the first proof of React on Rails adoption. Start with a Rails-owned island somewhere else in the app — even a small one — and migrate that first. +For most teams, option 1 is the fastest first step: you're swapping Vite's build integration for Shakapacker, not re-architecting the app. The main friction is usually not the Rails-side `react_component` call — it's the Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue) that the client code may depend on. In particular, `import.meta.glob` has no direct Webpack equivalent — it must be replaced with [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext), whose glob-pattern syntax differs and whose lazy/eager behavior is selected via a `mode` argument (`'sync'`, `'lazy'`, `'lazy-once'`, `'eager'`, `'weak'`) rather than the per-call options `import.meta.glob` exposes. ## Preflight From baf43554cb14c4713dc21a85c9676c972bb518ec Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 18 Apr 2026 10:02:44 -1000 Subject: [PATCH 06/10] docs: clarify SPA-shape option and relocate import.meta.glob detail Addresses review feedback on the Vite migration guide: - Name the "Keep the SPA shape" path explicitly in the follow-up paragraph so it does not read as ambiguous "option 1" (which could be misread as the first top-level bullet). - Move the require.context mode-argument breakdown out of the orientation section and into step 5 "Replace Vite-specific asset and env usage", which is the natural home for Vite-to-Webpack API mapping. - Add a concrete Popmenu example (110grill.com) in both migration guides so readers can see the single-top-level-component pattern in the wild. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oss/migrating/migrating-from-react-rails.md | 2 +- docs/oss/migrating/migrating-from-vite-rails.md | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index 8817ab0499..92487c2d44 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -8,7 +8,7 @@ Not every `react-rails` app is a good candidate for a low-friction first migrati - **Rails-owned island mounts on Shakapacker 6+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. (Note: `server_bundle_output_path` auto-detection requires Shakapacker 9.0+; on 6–8, set it explicitly in the initializer.) - **Webpacker-era apps (`gem "webpacker"`, Webpack 4).** Current React on Rails does not support Webpacker — `react_on_rails doctor` flags it as a removed breaking-change issue, and the gem requires `shakapacker >= 6.0`. You must migrate off Webpacker before installing current React on Rails. See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) below. -- **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Render the top-level SPA component from one ERB view using `react_component` (or `react_component_hash` when SSR needs to return multiple regions such as `componentHtml`, `title`, and other head tags). One React on Rails call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production (Popmenu), where the entire app is a single top-level component call. If you additionally want to break the SPA into several Rails-owned islands, treat that as a separate product decision rather than bundling it with the bundler/integration change. +- **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Render the top-level SPA component from one ERB view using `react_component` (or `react_component_hash` when SSR needs to return multiple regions such as `componentHtml`, `title`, and other head tags). One React on Rails call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production, Popmenu (for example, [110grill.com](https://www.110grill.com/) and other Popmenu-powered restaurant sites), where the entire app is a single top-level component call. If you additionally want to break the SPA into several Rails-owned islands, treat that as a separate product decision rather than bundling it with the bundler/integration change. The wrong first target usually leads teams to conclude "React on Rails is broken" when the real problem is legacy bundler compatibility, or to bundle a SPA re-architecture into what should have been a bundler migration. diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index 4a466b241b..5c979ae842 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -19,10 +19,10 @@ Not all `vite_rails` + React apps are the same shape, and the migration effort d - **Rails-owned island mounts.** Rails renders real ERB views and mounts one or more React components inside them. The migration is incremental: you can cut over one page (or one mount) at a time. - **Client-routed SPA shells.** Rails serves a minimal layout and a single `
`, and a client-side router (React Router / TanStack Router) owns everything after the first render. You have two reasonable migration shapes here: - 1. **Keep the SPA shape.** Render the top-level SPA component from a single ERB view using `react_component` (or `react_component_hash` when you need SSR that returns multiple regions such as `componentHtml`, `title`, and other head tags). One React on Rails call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production (Popmenu), where the entire app is a single top-level component call. + 1. **Keep the SPA shape.** Render the top-level SPA component from a single ERB view using `react_component` (or `react_component_hash` when you need SSR that returns multiple regions such as `componentHtml`, `title`, and other head tags). One React on Rails call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production, Popmenu (for example, [110grill.com](https://www.110grill.com/) and other Popmenu-powered restaurant sites), where the entire app is a single top-level component call. 2. **Break the SPA into island mounts** by moving Rails back to being the view-owner. This is a real product decision and should not be bundled with the bundler/integration change. -For most teams, option 1 is the fastest first step: you're swapping Vite's build integration for Shakapacker, not re-architecting the app. The main friction is usually not the Rails-side `react_component` call — it's the Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue) that the client code may depend on. In particular, `import.meta.glob` has no direct Webpack equivalent — it must be replaced with [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext), whose glob-pattern syntax differs and whose lazy/eager behavior is selected via a `mode` argument (`'sync'`, `'lazy'`, `'lazy-once'`, `'eager'`, `'weak'`) rather than the per-call options `import.meta.glob` exposes. +For most teams, the **Keep the SPA shape** path is the fastest first step: you're swapping Vite's build integration for Shakapacker, not re-architecting the app. The main friction is usually not the Rails-side `react_component` call — it's the Vite-specific runtime behavior (`import.meta.env`, `import.meta.glob`, Vite plugins with no direct Shakapacker analogue) that the client code may depend on. See [Replace Vite-specific asset and env usage](#5-replace-vite-specific-asset-and-env-usage) for the concrete replacements. ## Preflight @@ -138,6 +138,14 @@ Vite-specific `import.meta.env` usage needs to be replaced. In a React on Rails - `railsContext` for request-aware values - `process.env` in server-rendered bundles (available natively in Node); for client bundles, values must be injected via webpack's `DefinePlugin` or `EnvironmentPlugin` +### `import.meta.glob` + +`import.meta.glob` has no direct Webpack equivalent. Replace it with [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext): + +- the glob-pattern syntax differs (Webpack uses a regex argument, not a glob string) +- lazy/eager behavior is selected via a `mode` argument (`'sync'`, `'lazy'`, `'lazy-once'`, `'eager'`, `'weak'`) rather than the per-call options `import.meta.glob` exposes +- the returned context function requires explicit `.keys()` + key lookup, not the object-map shape `import.meta.glob` returns + ## 6. Replace the development workflow Vite apps usually have a dev command like: From 9f36b14c66d4d92d7d3ead035df7b9193710ea99 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 18 Apr 2026 23:40:01 -1000 Subject: [PATCH 07/10] docs: split SPA-shells bullet and add require.context example Addresses PR #3157 review feedback from @claude[bot]: - react-rails.md: split the long "Client-routed SPA shells" bullet into skimmable sub-bullets (mount strategy, Popmenu callout, separate-islands note). - vite-rails.md: add a concrete before/after JS snippet showing import.meta.glob vs require.context so readers don't have to infer the API shape from prose. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oss/migrating/migrating-from-react-rails.md | 4 +++- docs/oss/migrating/migrating-from-vite-rails.md | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index 92487c2d44..be6b8d0720 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -8,7 +8,9 @@ Not every `react-rails` app is a good candidate for a low-friction first migrati - **Rails-owned island mounts on Shakapacker 6+ and Rails 6+.** This is the smoothest path. The generator + the steps below usually get you there with small, localized edits. (Note: `server_bundle_output_path` auto-detection requires Shakapacker 9.0+; on 6–8, set it explicitly in the initializer.) - **Webpacker-era apps (`gem "webpacker"`, Webpack 4).** Current React on Rails does not support Webpacker — `react_on_rails doctor` flags it as a removed breaking-change issue, and the gem requires `shakapacker >= 6.0`. You must migrate off Webpacker before installing current React on Rails. See [Preferred path for Webpacker-era apps](#preferred-path-for-webpacker-era-apps) below. -- **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Render the top-level SPA component from one ERB view using `react_component` (or `react_component_hash` when SSR needs to return multiple regions such as `componentHtml`, `title`, and other head tags). One React on Rails call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production, Popmenu (for example, [110grill.com](https://www.110grill.com/) and other Popmenu-powered restaurant sites), where the entire app is a single top-level component call. If you additionally want to break the SPA into several Rails-owned islands, treat that as a separate product decision rather than bundling it with the bundler/integration change. +- **Client-routed SPA shells (Rails is mostly a shell around a React Router / TanStack Router app).** Render the top-level SPA component from one ERB view using `react_component` (or `react_component_hash` when SSR needs to return multiple regions such as `componentHtml`, `title`, and other head tags). + - One `react_component` call mounts the whole app — this is the pattern used by the largest React on Rails Pro deployment in production, Popmenu (for example, [110grill.com](https://www.110grill.com/) and other Popmenu-powered restaurant sites), where the entire app is a single top-level component call. + - If you additionally want to break the SPA into several Rails-owned islands, treat that as a separate product decision rather than bundling it with the bundler/integration change. The wrong first target usually leads teams to conclude "React on Rails is broken" when the real problem is legacy bundler compatibility, or to bundle a SPA re-architecture into what should have been a bundler migration. diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index 5c979ae842..0d32dbb881 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -146,6 +146,19 @@ Vite-specific `import.meta.env` usage needs to be replaced. In a React on Rails - lazy/eager behavior is selected via a `mode` argument (`'sync'`, `'lazy'`, `'lazy-once'`, `'eager'`, `'weak'`) rather than the per-call options `import.meta.glob` exposes - the returned context function requires explicit `.keys()` + key lookup, not the object-map shape `import.meta.glob` returns +A minimal before/after: + +```js +// Vite +const modules = import.meta.glob('./dir/**/*.js'); +// { './dir/foo.js': () => import('./dir/foo.js'), ... } + +// Webpack (Shakapacker) +const ctx = require.context('./dir', true, /\.js$/); +// ctx.keys() → ['./foo.js', ...] +// ctx('./foo.js') → the module +``` + ## 6. Replace the development workflow Vite apps usually have a dev command like: From 6a863487d9a96ae3064c73ad7256376e87538e72 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sun, 19 Apr 2026 18:36:13 -1000 Subject: [PATCH 08/10] docs: rephrase Webpacker support note and expand require.context example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR #3157 review feedback: - migrating-from-react-rails.md: rephrase "does not install alongside `gem \"webpacker\"`" — the install generator does not enforce a hard install-time block; the actual constraint is unsupported configuration surfaced via `react_on_rails doctor`. - migrating-from-vite-rails.md: expand the import.meta.glob → require.context example with the key-path-format mismatch (relative to caller vs context dir) and the sync-vs-async distinction (lazy import.meta.glob vs synchronous ctx(key)). Adds an explicit lazy-equivalent recipe using dynamic import(). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../migrating/migrating-from-react-rails.md | 2 +- .../migrating/migrating-from-vite-rails.md | 29 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index be6b8d0720..baedbc24b1 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -169,4 +169,4 @@ then treat the migration as: 5. Replace helper syntax and component registration. 6. Review `bin/dev`, `config/shakapacker.yml`, and webpack config diffs before committing. -Current React on Rails does not install alongside `gem "webpacker"` — migrate to Shakapacker first (step 1 above) rather than budgeting time for Webpacker compatibility shims. +Current React on Rails does not support `gem "webpacker"`. The install generator adds Shakapacker rather than enforcing a hard install-time block, and `react_on_rails doctor` flags Webpacker as a removed/breaking-change issue when it detects `config/webpacker.yml` or `bin/webpacker`. Migrate to Shakapacker first (step 1 above) rather than budgeting time for Webpacker compatibility shims. diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index 0d32dbb881..95c78db3c6 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -146,17 +146,36 @@ Vite-specific `import.meta.env` usage needs to be replaced. In a React on Rails - lazy/eager behavior is selected via a `mode` argument (`'sync'`, `'lazy'`, `'lazy-once'`, `'eager'`, `'weak'`) rather than the per-call options `import.meta.glob` exposes - the returned context function requires explicit `.keys()` + key lookup, not the object-map shape `import.meta.glob` returns -A minimal before/after: +A minimal before/after — note two semantic mismatches that bite during migration: + +1. **Key paths differ.** Vite returns paths relative to the _calling module_ (`'./dir/foo.js'`); `require.context` returns paths relative to the _context directory_ (`'./foo.js'`). Code that derives names from keys (routing, registration, etc.) needs to account for this. +2. **Sync vs async.** `import.meta.glob` is lazy by default — values are `() => import(...)` returning a Promise. `ctx(key)` is synchronous. A reader who replaces `await module()` with `ctx(key)` silently drops async behavior; for the lazy case, build a map of dynamic `import()` calls instead. + +Eager / synchronous case: + +```js +// Vite (eager) +const modules = import.meta.glob('./dir/**/*.js', { eager: true }); +// { './dir/foo.js': , ... } ← keys relative to current file + +// Webpack (Shakapacker) — synchronous equivalent +const ctx = require.context('./dir', true, /\.js$/); +// ctx.keys() → ['./foo.js', ...] ← keys relative to context dir, NOT './dir/foo.js' +// ctx('./foo.js') → the module (synchronous) +``` + +Lazy (default Vite) case — `require.context` is not a direct replacement; build a map of dynamic `import()` calls: ```js -// Vite +// Vite (lazy, the default) const modules = import.meta.glob('./dir/**/*.js'); // { './dir/foo.js': () => import('./dir/foo.js'), ... } -// Webpack (Shakapacker) +// Webpack (Shakapacker) — lazy equivalent const ctx = require.context('./dir', true, /\.js$/); -// ctx.keys() → ['./foo.js', ...] -// ctx('./foo.js') → the module +const lazyModules = Object.fromEntries( + ctx.keys().map((key) => [key, () => import(/* webpackMode: "lazy" */ `./dir/${key.slice(2)}`)]), +); ``` ## 6. Replace the development workflow From 2bf011b368bf815440629c114aeaa8879570524e Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Wed, 22 Apr 2026 19:02:02 -1000 Subject: [PATCH 09/10] Clarify legacy Webpacker version pin --- docs/oss/migrating/migrating-from-react-rails.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/oss/migrating/migrating-from-react-rails.md b/docs/oss/migrating/migrating-from-react-rails.md index 0cb01640de..d8b545b712 100644 --- a/docs/oss/migrating/migrating-from-react-rails.md +++ b/docs/oss/migrating/migrating-from-react-rails.md @@ -33,7 +33,7 @@ If the app still uses `gem "webpacker"`, the recommended path is: 1. **Migrate to Shakapacker first, as its own PR.** Keep the bundler change separate from the React on Rails change. This makes each step reviewable and isolates any compatibility issues. See the [Shakapacker v6 upgrade guide](https://github.com/shakacode/shakapacker/blob/v6.6.0/docs/v6_upgrade.md) for the concrete Webpacker → Shakapacker steps. 2. **Then run the React on Rails install generator** against the Shakapacker-based app. -The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. If you cannot migrate off Webpacker yet, pin `react_on_rails` to `< 16.0` (v16 is the release that removed Webpacker support) rather than trying to use current React on Rails with Webpacker. +The generator is not designed to bridge Webpack 4 + Webpacker to current React on Rails defaults for you — it assumes a Shakapacker baseline. If you cannot migrate off Webpacker yet, pin `react_on_rails` to `~> 14.2` (v15.0.0 is retracted; v16 is the release that removed Webpacker support) rather than trying to use current React on Rails with Webpacker. ## Preflight From 7fde5c2f517a6561f99c0e576641d21c2a21d8c3 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Wed, 22 Apr 2026 19:15:59 -1000 Subject: [PATCH 10/10] Clarify lazy require.context key paths --- docs/oss/migrating/migrating-from-vite-rails.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/oss/migrating/migrating-from-vite-rails.md b/docs/oss/migrating/migrating-from-vite-rails.md index bcf171b2f1..8766485752 100644 --- a/docs/oss/migrating/migrating-from-vite-rails.md +++ b/docs/oss/migrating/migrating-from-vite-rails.md @@ -183,6 +183,7 @@ const modules = import.meta.glob('./dir/**/*.js'); // Webpack (Shakapacker) — lazy equivalent const ctx = require.context('./dir', true, /\.js$/, 'lazy'); +// ctx.keys() → ['./foo.js', ...] ← keys relative to context dir, NOT './dir/foo.js' // ctx(key) now returns Promise, matching Vite's lazy semantics const lazyModules = Object.fromEntries(ctx.keys().map((key) => [key, () => ctx(key)])); ```