feat(examples): add Vite SPA quickstart example - #1943
Merged
Conversation
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. |
Bring examples/vite-spa to the reviewed final state from the combined spa-examples PR (commit 627189a). - vercel.json: add the SPA fallback rewrite so client-side routes serve index.html instead of 404ing on deep links. - pnpm-lock.yaml: regenerate from the reviewed combined lockfile. The branch's prior lockfile was stale (its vite-spa entry no longer matched the manifest and failed a frozen install). Seeding from the reviewed lockfile keeps webpack deduplicated to a single instance, which the naive regeneration split into two peer-context variants and broke the gt-test-webpack-react build.
Contributor
Author
|
@greptileai review |
This was referenced Jul 20, 2026
Contributor
Author
|
@greptileai review |
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 -->
# Conflicts: # pnpm-lock.yaml
Contributor
Author
|
@greptileai review |
eoinest
reviewed
Jul 21, 2026
| @@ -0,0 +1,6 @@ | |||
| import { t } from 'gt-react'; | |||
Contributor
There was a problem hiding this comment.
we can actually do better here. in index.ts add import "gt-react/macros". This will attach the t tagged template literal to the global scope. We can then get away without needing to import the t` every in every file. So this file becomes just the following:
export const navigation = [
{ label: t`Home`, href: '/' },
{ label: t`About`, href: '/about' },
];
eoinest
reviewed
Jul 21, 2026
Contributor
Author
|
@greptileai review |
eoinest
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 (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 -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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.src/index.tsawaitsinitializeGTSPAthen dynamically imports the app, so module-levelt()resolves. No provider.<T>,t(), and<LocaleSelector>all demonstrated.viteplugin withparsingFlags.devHotReload, matching vite-create-app.src/_gt/are keyed by the real content hashes, so language switching works offline with no account or API key.npm run buildruns offline; a separatetranslatescript regenerates the files with a production key.vercel.jsonships an SPA fallback rewrite so deployed deep links and locale-switch reloads resolve.Testing
_hashvalues in the built bundle match the committed fixture keys, with the compiler on and with it removed (runtime hashing computes the same keys).Notes
parsingFlags.devHotReload, but module-levelt()strings won't hot-reload without it.Greptile Summary
This PR adds a new
examples/vite-spaexample — the finished product of the React SPA Quickstart — demonstratinggt-reactin a client-side-only Vite app. The runtime story uses top-levelawait initializeGTSPA()before dynamically importingmain.tsx, so module-levelt`...`calls innavigation.tsresolve correctly. Hand-written translation fixture files insrc/_gt/enable offline switching across four locales without an API key.src/index.ts: Importsgt-react/macros, awaitsinitializeGTSPA(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..oxlintrc.json: Addstas a globalreadonlyidentifier so the monorepo-wideno-undefrule does not flag the implicittmacro innavigation.ts.Confidence Score: 5/5
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.
Important Files Changed
Sequence Diagram
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 LocaleSelectorReviews (7): Last reviewed commit: "fix(examples): use the global t macro an..." | Re-trigger Greptile