Skip to content

feat(examples): add React Router SPA example - #1946

Open
JoshKappler wants to merge 13 commits into
mainfrom
feat/example-react-router-spa
Open

feat(examples): add React Router SPA example#1946
JoshKappler wants to merge 13 commits into
mainfrom
feat/example-react-router-spa

Conversation

@JoshKappler

@JoshKappler JoshKappler commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Addition, take or leave. One of five SPA example PRs (vite #1943, webpack #1944, rollup #1945, rsbuild #1951 on the compiler adapter #1950).

Summary

  • New example examples/react-router-spa: React Router v7 framework mode with ssr: false, two client routes, gt-react wired through initializeGTSPA.
  • The initialization point is the interesting part: app/entry.client.tsx awaits initializeGTSPA and only then dynamically imports the hydrate step, so no route module (and no module-level t()) evaluates before translations are ready. root.tsx stays gt-free so the prerendered shell never touches gt-react.
  • Demonstrates <T>, <Num>, <Var>, module-level and in-render t(), <LocaleSelector>, and an HtmlLangSync that keeps <html lang> and dir on the active locale, which the static shell cannot do.
  • Pinned to 7.x deliberately (v8 is now npm latest; the README says so and notes the expected future-flag warnings). npm run build runs offline; a separate translate script regenerates the translation files with a production key.

Testing

  • typegen, tsc, oxlint, offline build (the prerender emits build/client/index.html), frozen-lockfile install: all pass. CI lints examples but does not build them.
  • Chunk-graph proof on the build output: the boot path's static import closure contains no gt-react; it loads only behind the dynamic hydrate import and the lazy route chunks.
  • Real browser, dev and prod, all five locales: no hydration warnings, no console errors, navigation keeps translations resolved, locale switching works from both routes.

Notes

  • vercel.json carries SPA fallback rewrites. Without them a deployed app 404s on deep links and on every locale switch from a subroute, since gt-react switches locale via location.reload().
  • @react-router/node and isbot look unused but are required: with ssr: false and no custom entry.server, the toolchain generates a server entry importing both to prerender the shell.
  • Lockfile carries a 3-line transitive prettier dedup from adding @react-router/dev.
  • No changeset (example only).

Greptile Summary

Adds a new examples/react-router-spa demonstrating gt-react internationalization in a React Router v7 SPA (framework mode, ssr: false). The key design — awaiting initializeGTSPA in entry.client.tsx before dynamically importing the hydrate step — guarantees that no route module (and no module-level t()) evaluates before translations are ready, which is clearly explained and correctly implemented.

  • entry.client.tsx gates hydration behind initializeGTSPA, and main().catch(console.error) ensures initialization failures are surfaced rather than silently dropped.
  • HtmlLangSync updates html[lang] and html[dir] from route context, cleanly working around the build-time prerender constraint in root.tsx.
  • gt (a CLI-only tool) is placed in dependencies rather than devDependencies, inconsistent with the analogous vite-spa example where it correctly lives in devDependencies.

Confidence Score: 5/5

Safe to merge — this is a self-contained example with no changes to library code or shared infrastructure.

The initialization sequencing is correct, the prerender/hydration split is well-reasoned, and the only finding is a minor dependency categorization mismatch that has no runtime impact.

Files Needing Attention: package.json — gt belongs in devDependencies (CLI-only tool), consistent with vite-spa.

Important Files Changed

Filename Overview
examples/react-router-spa/app/entry.client.tsx Browser entry point; awaits initializeGTSPA before dynamically importing the hydrate step — correct ordering, errors surfaced via main().catch(console.error)
examples/react-router-spa/app/hydrate.tsx Clean hydration shim — dynamically imported after GT init, wraps HydratedRouter in StrictMode inside startTransition
examples/react-router-spa/app/root.tsx Intentionally gt-react-free; static lang=en is updated client-side by HtmlLangSync in each route
examples/react-router-spa/app/components/HtmlLangSync.tsx Syncs html[lang] and html[dir] from useLocale/useLocaleDirection; correctly lives in route graph rather than root to avoid prerender-time hook calls
examples/react-router-spa/app/loadTranslations.ts Dynamic import of locale JSON files; gracefully returns empty object on missing locale with a console.warn
examples/react-router-spa/package.json gt CLI tool is listed in dependencies instead of devDependencies, unlike the analogous vite-spa example where it correctly lives in devDependencies
examples/react-router-spa/vercel.json SPA fallback rewrite to index.html is correct; build/install/outputDirectory config looks right for the monorepo turbo setup
examples/react-router-spa/app/messages.ts Demonstrates module-level t() usage; correctly depends on initializeGTSPA completing before module evaluation via the dynamic import gate in entry.client.tsx
examples/react-router-spa/react-router.config.ts Sets ssr: false for SPA mode; minimal and correct
examples/react-router-spa/vite.config.ts Composes reactRouter() and gtCompiler() plugins; dedupe for react/react-dom prevents duplicate React instances from workspace symlinks

Sequence Diagram

sequenceDiagram
    participant Browser
    participant entry.client.tsx
    participant initializeGTSPA
    participant loadTranslations
    participant hydrate.tsx
    participant HydratedRouter
    participant RouteModule

    Browser->>entry.client.tsx: module evaluation (import 'gt-react/macros')
    entry.client.tsx->>initializeGTSPA: "await initializeGTSPA({ loadTranslations, ... })"
    initializeGTSPA->>loadTranslations: loadTranslations(locale)
    loadTranslations-->>initializeGTSPA: "locale JSON (app/_gt/<locale>.json)"
    initializeGTSPA-->>entry.client.tsx: resolved (translations ready)
    entry.client.tsx->>hydrate.tsx: dynamic import('./hydrate')
    hydrate.tsx->>HydratedRouter: hydrateRoot(document, HydratedRouter)
    HydratedRouter->>RouteModule: lazy load home.tsx / about.tsx
    RouteModule->>RouteModule: module-level t() in messages.ts resolves correctly
    RouteModule-->>Browser: rendered UI with translations
Loading

Reviews (12): Last reviewed commit: "chore: trim comments to the 3-line house..." | Re-trigger Greptile

Demonstrates gt-react in a React Router v7 single-page app in framework
mode with ssr: false. gt-react initializes once in entry.client.tsx
before the router hydrates, then translates content with no provider.
Covers <T>, <Num>, <Var>, module-level and in-render t(), useLocale, and
<LocaleSelector> across two client-side routes. Wires the
@generaltranslation/compiler Vite plugin alongside @react-router/dev, and
ships hand-written translation fixtures (es, fr, ja, zh) so locale
switching works without any API access.
@JoshKappler

Copy link
Copy Markdown
Contributor Author

Folded into #1949 along with the other three SPA examples to keep the review in one place. The commit and write-up carry over unchanged.

react-router-spa example, taken from the round-2 review on feat/spa-examples (commit 627189a):

- README.md: correct the quickstart clone path to gt/examples/react-router-spa
- app/entry.client.tsx: surface hydration errors with main().catch(console.error) instead of void main()
@JoshKappler JoshKappler reopened this Jul 20, 2026
@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

JoshKappler added a commit that referenced this pull request Jul 21, 2026
Addition, take or leave. One of five SPA example PRs from this morning's
list (vite #1943, rollup #1945, react-router #1946, rsbuild #1951 on top
of the compiler adapter #1950).

## Summary

- New example `examples/webpack-spa`: plain React 19 bundled with
webpack 5, no framework, client side only.
- Runtime story: `initializeGTSPA` entry module (no provider), six `<T>`
blocks including a `<Var>` runtime value, one module-level `t()`,
`<LocaleSelector>`.
- Buildtime story: the compiler's `webpack` export as the first plugin.
Credentials come in through dotenv plus DefinePlugin, since webpack has
no `import.meta.env`, and production builds inline empty strings so no
key ever lands in a production bundle.
- Hand-written fixtures under `src/_gt/` keyed by real content hashes;
locale switching works offline. `npm run build` runs offline; a separate
`translate` script regenerates the files with a production key.

## Testing

- typecheck, oxlint, offline production build, frozen-lockfile install:
all pass on the current head. All 7 injected `_hash` values match the
fixture keys; verified in a real browser across all five locales.
- Grepped the production bundle with a planted `GT_DEV_API_KEY` set: the
value does not appear (dev builds embed it by design, that is the
documented dev workflow on every bundler).
- The internal `tests/apps/webpack-react` build passes on this branch
(see the postcss note below).
- CI lints examples but does not build them, so the build evidence is
from manual runs.

## Notes

- One non-example change, disclosed plainly: `postcss` is pinned in the
workspace root devDependencies. Without it, this example's css-loader
pulls postcss into the dependency graph and splits webpack into two
peer-keyed instances, which breaks the `tests/apps/webpack-react` build
with an instanceof error. The same line ships in #1945 (its postcss
plugin has the same effect); whichever merges first carries it and the
other becomes a no-op. If there's a preferred way to manage the peer
graph, happy to redo this part.
- A CodeQL alert flags the dev-key DefinePlugin line. Production builds
provably embed nothing (see Testing); dev builds embedding the dev key
is the documented design, so the alert needs a dismissal rather than a
code change.
- `vercel.json` sets `outputDirectory: dist` (webpack gets no Vercel
preset) plus an SPA fallback rewrite.
- No changeset (example only).

<!-- greptile_comment -->

<h3>Greptile Summary</h3>

This PR adds a new `examples/webpack-spa` directory: a plain React 19
SPA bundled with webpack 5, demonstrating the GT i18n runtime and
compiler without any framework. A single non-example change pins
`postcss` at the workspace root to prevent a peer-dependency split that
broke the existing `tests/apps/webpack-react` build.

- **New example**: `initializeGTSPA` entry pattern with top-level
`await`, six `<T>` blocks (including a `<Var>` runtime value), one
module-level `t()`, and `<LocaleSelector>` — all exercised against
hand-written fixture files in `src/_gt/` that enable offline locale
switching.
- **Credential handling**: `dotenv` + `DefinePlugin` replaces
`import.meta.env` (unavailable in webpack); production builds
unconditionally inline empty strings so no API key ever lands in a
production bundle.
- **Workspace fix**: `postcss@^8.5.19` pinned at the repo root resolves
a duplicate peer-keyed instance that caused an `instanceof` error in
`tests/apps/webpack-react`.

<details open><summary><h3>Confidence Score: 5/5</h3></summary>

Safe to merge — this is a new example directory plus a targeted
workspace-root dependency pin with no changes to library source code.

All changes are additive: a self-contained example app and a single
postcss pin that fixes an existing peer-dependency conflict. The
credential-handling logic is correct (production builds embed empty
strings), the GT initialization pattern matches the documented SPA
quickstart, and the hand-written fixtures are valid. No library code is
modified.

No files require special attention.
</details>

<details><summary><h3>Important Files Changed</h3></summary>

| Filename | Overview |
|----------|----------|
| examples/webpack-spa/webpack.config.mjs | Webpack config with GT
compiler integration, DefinePlugin for credentials, and react alias for
monorepo isolation — correctly gates API keys to dev builds only |
| examples/webpack-spa/src/index.ts | Top-level-await entry module that
initializes GT before dynamically importing the React app, ensuring
module-level t() calls resolve correctly |
| examples/webpack-spa/src/loadTranslations.ts | Dynamic locale import
with graceful fallback to empty object on missing files; webpack context
module will bundle all _gt/*.json at build time |
| examples/webpack-spa/src/App.tsx | Demo component exercising T, Var,
t(), useLocale, and LocaleSelector with a module-level t() call that
relies on GT being initialized before import |
| package.json | Pins postcss@^8.5.19 at the workspace root to prevent
duplicate peer-keyed postcss instances from breaking the
tests/apps/webpack-react build |
| examples/webpack-spa/vercel.json | Vercel deployment config with turbo
build command, frozen-lockfile install, correct outputDirectory, and SPA
catch-all rewrite |

</details>

<details><summary><h3>Flowchart</h3></summary>

<a href="#gh-light-mode-only">

```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Browser loads index.html"] --> B["webpack bundle executes src/index.ts"]
    B --> C["dotenv values injected via DefinePlugin\n(dev: from .env.local, prod: empty strings)"]
    C --> D["initializeGTSPA()\nawait — blocks until GT is ready"]
    D --> E{Locale?}
    E -->|non-default| F["loadTranslations(locale)\nimport('./_gt/locale.json')"]
    E -->|default/en| G["No translation file needed"]
    F --> H["webpack context module\nbundles all _gt/*.json at build time"]
    H --> I["translations returned to GT runtime"]
    G --> I
    I --> J["await import('./main')\nReact app mounts AFTER GT init"]
    J --> K["App.tsx renders\n<T>, t(), <Var>, <LocaleSelector>"]
    K --> L["Module-level t() resolves correctly\n(GT already initialized)"]
```

</a>
<a href="#gh-dark-mode-only">

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["Browser loads index.html"] --> B["webpack bundle executes src/index.ts"]
    B --> C["dotenv values injected via DefinePlugin\n(dev: from .env.local, prod: empty strings)"]
    C --> D["initializeGTSPA()\nawait — blocks until GT is ready"]
    D --> E{Locale?}
    E -->|non-default| F["loadTranslations(locale)\nimport('./_gt/locale.json')"]
    E -->|default/en| G["No translation file needed"]
    F --> H["webpack context module\nbundles all _gt/*.json at build time"]
    H --> I["translations returned to GT runtime"]
    G --> I
    I --> J["await import('./main')\nReact app mounts AFTER GT init"]
    J --> K["App.tsx renders\n<T>, t(), <Var>, <LocaleSelector>"]
    K --> L["Module-level t() resolves correctly\n(GT already initialized)"]
```

</a>
</details>

<sub>Reviews (5): Last reviewed commit: ["fix(examples): correct
monorepo install
..."](c959f60)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=45751556)</sub>

<!-- /greptile_comment -->
@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

@eoinest eoinest left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some feedback

// against loaded translations. Because gt-react reloads the page when the
// locale changes, this module is re-evaluated on the next load and the value
// re-resolves for the newly selected locale.
export const moduleLevelHeading = t(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not correct logo

@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

eoinest
eoinest previously approved these changes Jul 22, 2026
JoshKappler added a commit that referenced this pull request Jul 22, 2026
Addition, take or leave. One of five SPA example PRs from this morning's
list (webpack #1944, rollup #1945, react-router #1946, rsbuild #1951 on
top of the compiler adapter #1950).

## Summary

- New example `examples/vite-spa`: the finished product of the React SPA
Quickstart plus the "Developing with SPA translations" guide, built to
follow the docs faithfully so docs and example can't drift apart.
- Runtime story: `src/index.ts` awaits `initializeGTSPA` then
dynamically imports the app, so module-level `t()` resolves. No
provider. `<T>`, `t()`, and `<LocaleSelector>` all demonstrated.
- Buildtime story: the compiler's `vite` plugin with
`parsingFlags.devHotReload`, matching vite-create-app.
- Hand-written translation files under `src/_gt/` are keyed by the real
content hashes, so language switching works offline with no account or
API key. `npm run build` runs offline; a separate `translate` script
regenerates the files with a production key.
- `vercel.json` ships an SPA fallback rewrite so deployed deep links and
locale-switch reloads resolve.

## Testing

- typecheck, oxlint, offline build, and a frozen-lockfile install all
pass on the current head.
- Injected `_hash` values in the built bundle match the committed
fixture keys, with the compiler on and with it removed (runtime hashing
computes the same keys).
- Headless Chromium: switching the LocaleSelector rendered en, es, and
ja correctly with zero console errors.
- CI lints examples but does not build them, so the build evidence above
is from manual runs.

## Notes

- This is a second Vite example on purpose: vite-create-app shows GT
retrofitted onto a create-vite scaffold, vite-spa is the from-scratch
docs companion. Both READMEs say which to use. If one Vite example is
enough, happy to fold or drop this one.
- Docs gap found while building, filed separately: the dev guide never
mentions `parsingFlags.devHotReload`, but module-level `t()` strings
won't hot-reload without it.
- No changeset (example only, matches #1908/#1927).

<!-- greptile_comment -->

<h3>Greptile Summary</h3>

This PR adds a new `examples/vite-spa` example — the finished product of
the React SPA Quickstart — demonstrating `gt-react` in a
client-side-only Vite app. The runtime story uses top-level `await
initializeGTSPA()` before dynamically importing `main.tsx`, so
module-level `` t`...` `` calls in `navigation.ts` resolve correctly.
Hand-written translation fixture files in `src/_gt/` enable offline
switching across four locales without an API key.

- **`src/index.ts`**: Imports `gt-react/macros`, awaits
`initializeGTSPA` (passing config + env vars), then dynamically imports
`./main` — ensuring GT is fully initialized before the component tree
renders.
- **`src/loadTranslations.ts`**: Dynamic-imports the per-locale JSON at
runtime, returning `{}` on any missing locale so the app degrades
gracefully.
- **Root `.oxlintrc.json`**: Adds `t` as a global `readonly` identifier
so the monorepo-wide `no-undef` rule does not flag the implicit `t`
macro in `navigation.ts`.

<details open><summary><h3>Confidence Score: 5/5</h3></summary>

Safe to merge. The new example is a self-contained addition with no
changes to production library code, and the one shared-config tweak is
additive.

All source changes are confined to the new examples/vite-spa directory.
The only repo-wide modification is adding t to the oxlint globals block,
which is additive and doesn't affect runtime behavior or library
packages. The initialization sequence (macros → initializeGTSPA →
dynamic import of main) is architecturally sound, offline build is
verified, and the lockfile correctly reflects the package.json
dependency placement.

.oxlintrc.json — the t global is broader than needed; worth scoping to
SPA examples via an override, but not a blocker.
</details>

<details><summary><h3>Important Files Changed</h3></summary>

| Filename | Overview |
|----------|----------|
| examples/vite-spa/src/index.ts | Entry module: correctly sequences
macro import, initializeGTSPA await, and dynamic app import to guarantee
GT is ready before any module-level t`` calls execute. |
| examples/vite-spa/src/navigation.ts | Uses implicit global t`` macro
(attached by gt-react/macros in index.ts); intentional SPA-only pattern,
well-documented in README. |
| examples/vite-spa/src/loadTranslations.ts | Dynamic-imports locale
JSON from src/_gt/; returns {} on missing locale. console.warn is
explicitly allowed by the no-console lint rule. |
| .oxlintrc.json | Adds 't' as a monorepo-wide readonly global so CI
no-undef checks pass for navigation.ts; broader than strictly necessary
since t is only a valid implicit global in SPA entry contexts. |
| examples/vite-spa/vite.config.ts | Wires react() and
gtCompiler(gtConfig) plugins; dedupe for react/react-dom handles
workspace-link resolution correctly. |
| examples/vite-spa/vercel.json | Uses turbo build command with
--env-mode=loose; includes SPA fallback rewrite; matches conventions of
sibling examples. |
| examples/vite-spa/package.json | gt is correctly placed under
devDependencies (matches its lockfile placement); gt-react in
dependencies; all workspace:* pins consistent. |
| examples/vite-spa/gt.config.json | Configures defaultLocale, four
target locales, output path, and parsingFlags.devHotReload for
hot-reload of module-level t`` strings during development. |

</details>

<details><summary><h3>Sequence Diagram</h3></summary>

```mermaid
sequenceDiagram
    participant Browser
    participant index.ts
    participant loadTranslations.ts
    participant _gt/[locale].json
    participant initializeGTSPA
    participant main.tsx
    participant App

    Browser->>index.ts: load (module script)
    index.ts->>index.ts: import 'gt-react/macros' (attaches global t``)
    index.ts->>initializeGTSPA: "await initializeGTSPA({ ...gtConfig, projectId, devApiKey, loadTranslations })"
    initializeGTSPA->>loadTranslations.ts: call loadTranslations(locale)
    loadTranslations.ts->>_gt/[locale].json: dynamic import
    _gt/[locale].json-->>loadTranslations.ts: translation map
    loadTranslations.ts-->>initializeGTSPA: resolved translations
    initializeGTSPA-->>index.ts: resolved
    index.ts->>main.tsx: await import('./main')
    main.tsx->>App: createRoot().render(App)
    App->>App: navigation uses t`Home`, t`About` (resolved)
    App->>App: Welcome renders T and LocaleSelector
```
</details>

<sub>Reviews (7): Last reviewed commit: ["fix(examples): use the global
t macro
an..."](3535245)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=45751551)</sub>

<!-- /greptile_comment -->
JoshKappler added a commit that referenced this pull request Jul 22, 2026
Addition, take or leave. One of five SPA example PRs from this morning's
list (vite #1943, webpack #1944, react-router #1946, rsbuild #1951 on
top of the compiler adapter #1950).

## Summary

- New example `examples/rollup-spa`: plain React 19 bundled with pure
Rollup, wired from the proven `tests/apps/rollup-react` config
(compiler's `rollup` export first, then replace, nodeResolve, commonjs,
json, typescript, postcss, and a small guarded html emitter with a
single shared CSS path constant).
- Runtime story: `initializeGTSPA` entry module, `<T>`, `<Var>`,
module-level `t()`, `<LocaleSelector>`.
- `loadTranslations` uses an explicit per-locale static import map
instead of the docs' interpolated dynamic import, because Rollup can't
code-split a fully dynamic path. Each locale still gets its own lazy
chunk, unknown locales warn, and the README explains the divergence.
- Dev workflow: `rollup -c -w` with serve and livereload, dotenv wired
so the documented `.env` step actually loads. `npm run build` runs
offline; a separate `translate` script regenerates the translation files
with a production key.

## Testing

- typecheck, oxlint, offline build, frozen-lockfile install: all pass on
the current head. Injected `_hash` values match the fixture keys;
verified in a real browser across en, fr, ja including a `<Var>`
mid-sentence.
- The dotenv wiring was proven dynamically: a throwaway `.env` value
inlines into the bundle and disappears on a clean rebuild.
- The internal `tests/apps/webpack-react` build passes on this branch
(see the postcss note below).
- CI lints examples but does not build them.

## Notes

- One non-example change, disclosed plainly: `postcss` is pinned in the
workspace root devDependencies so every webpack consumer resolves a
single instance. Without it, this example's postcss plugin splits
webpack's peer context and breaks the `tests/apps/webpack-react` build.
The same line ships in #1944; whichever merges first carries it and the
other becomes a no-op. If there's a preferred way to manage the peer
graph, happy to redo this part.
- Honest limitation, documented in the README: on-the-fly dev
translation fetching does not activate under plain Rollup, because
nothing supplies gt-react an env signal in the browser. The committed
files are what renders. A dev define injected by the compiler's rollup
plugin would fix this generally; that would be a library change.
- Lockfile note: installing this example dedupes gt-sanity's build
toolchain from rollup 4.60.3 to 4.62.2 (semver-minor, reproducible,
frozen installs pass). The gt-sanity build failure visible locally is
pre-existing on clean main and unrelated.
- No changeset (example only).

<!-- greptile_comment -->

<h3>Greptile Summary</h3>

This PR adds a new `examples/rollup-spa` example: a React 19 SPA bundled
with pure Rollup and wired with `gt-react` for build-time i18n via the
GT compiler plugin. It is the Rollup counterpart to the concurrent
Vite/Webpack/RSBuild SPA PRs.

- **Build pipeline** (`rollup.config.mjs`): GT compiler runs first,
followed by `replace`, `nodeResolve`, `commonjs`, `json`, `typescript`,
`postcss` (extraction), and a small custom `htmlPlugin`. The CSS extract
path is shared via `CSS_ASSET_PATH` to prevent drift between the PostCSS
target and the injected `<link>` href.
- **Translation loading** (`src/loadTranslations.ts`): Uses an explicit
per-locale static import map instead of a template-literal dynamic
import, because Rollup cannot statically analyze a fully dynamic path;
each entry produces its own lazy chunk.
- **Non-example change** (`package.json` root): `postcss` is pinned as a
workspace-root devDependency to ensure a single peer instance across
Webpack consumers; an identical change ships in the sibling #1944 PR.

<details open><summary><h3>Confidence Score: 5/5</h3></summary>

This is a well-isolated example addition; no production library code is
touched and the one non-example change (pinning postcss at the workspace
root) is a safe deduplication.

All changes are scoped to a new example directory plus two one-line
updates to workspace config files. The Rollup pipeline is correct, the
CSS path constant properly guards the extract/inject pair, and the
translation loader map matches the locales declared in gt.config.json.

No files require special attention; the pnpm-lock.yaml churn is expected
from the rollup deduplication described in the PR notes.
</details>

<details><summary><h3>Important Files Changed</h3></summary>

| Filename | Overview |
|----------|----------|
| examples/rollup-spa/rollup.config.mjs | Rollup build config with
correct plugin order, shared CSS_ASSET_PATH constant, and guarded
isWatch dev-server conditional; NODE_ENV defaults to 'production' in
watch mode (no explicit dev override) |
| examples/rollup-spa/src/loadTranslations.ts | Explicit static-import
locale map that lets Rollup code-split per-locale chunks; returns {}
with a console.warn for unknown locales; 'en' (default locale) is
correctly absent since the SPA runtime uses source strings for it |
| examples/rollup-spa/src/index.ts | Entry module using top-level await
to sequence initializeGTSPA before React mounts; gt-react/macros import
attaches the global t macro; correct ESM pattern for this setup |
| examples/rollup-spa/src/App.tsx | Demonstrates T component, Var
mid-sentence, and module-level t macro; locale switch triggers full page
reload as expected for gt-react SPA mode |
| examples/rollup-spa/vercel.json | Vercel deployment config using turbo
build with --env-mode=loose, SPA fallback rewrite to index.html;
consistent with other example deployments in the monorepo |
| .oxlintrc.json | Adds t as a readonly global to prevent
undefined-variable lint errors from the module-level t macro used by the
SPA examples |
| .changeset/config.json | Adds the five new SPA example package names
to the changeset ignore list; correct for example-only packages |

</details>

<details><summary><h3>Sequence Diagram</h3></summary>

```mermaid
sequenceDiagram
    participant Browser
    participant index.ts as src/index.ts
    participant GTRuntime as initializeGTSPA (gt-react)
    participant loadTranslations as loadTranslations.ts
    participant JSON as _gt/<locale>.json chunk

    Browser->>index.ts: load ESM bundle
    index.ts->>GTRuntime: "await initializeGTSPA({ projectId, loadTranslations })"
    GTRuntime->>loadTranslations: loadTranslations(currentLocale)
    alt locale found in map (zh/fr/es/ja)
        loadTranslations->>JSON: "dynamic import('./_gt/<locale>.json')"
        JSON-->>loadTranslations: translations object
        loadTranslations-->>GTRuntime: translations
    else locale not in map
        loadTranslations-->>GTRuntime: "{} (+ console.warn)"
    end
    GTRuntime-->>index.ts: runtime ready
    index.ts->>index.ts: await import('./main')
    index.ts->>Browser: React tree mounts with translations
```
</details>

<sub>Reviews (7): Last reviewed commit: ["fix(examples): use the global
t macro
in..."](7b6105c)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=45751569)</sub>

<!-- /greptile_comment -->
@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

@JoshKappler

Copy link
Copy Markdown
Contributor Author

@greptileai review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants