# <%= appTitle %>

A multi-page React 19 application generated by `lhx-cli create -t react-mpa`.
The build pipeline is driven by [`@lhx-kit/vite-plugin`](../../packages/vite-plugin)
reading a single source of truth: `project.config.ts`.

## Quick start

```bash
pnpm install
pnpm dev                 # start all pages
lhx-cli dev --page=home  # start only one page
pnpm build               # build every registered page
```

- Dev server URLs: `/.lhx-kit/pages/home.html`, `/.lhx-kit/pages/settings.html`
- Production output: `dist/<page>/index.html` + `dist/shared/` (chunks shared across pages)

## Project layout

```
<%= projectName %>/
├── project.config.ts        # Single source of truth: pages, aliases, envs, proxy, cdn
├── template.html            # HTML shell (`{{ title }}` / `{{ entry }}` placeholders)
├── vite.config.ts           # `defineConfig({plugins: [lhxKit(), react()]})`
├── tsconfig.json
├── biome.json               # Lint + format config (Biome)
├── .env.dev / .env.prod
└── src/
    ├── env.d.ts             # Type-level declarations (virtual module, LhxCdn)
    ├── bootstrap.ts         # App-wide setup shared by every page entry
    ├── components/          # Shared components (Heading / Card / Paragraph)
    ├── services/            # Axios-like http client + domain services
    ├── stores/              # Zustand stores
    ├── mocks/               # MSW browser worker + handlers
    └── pages/
        ├── home/            # entry.tsx + router.tsx + render.json + views/
        └── settings/        # entry.tsx (single-screen, no in-page router)
```

Default aliases (set by `@lhx-kit/config`):

| Alias        | Target             |
|--------------|--------------------|
| `@`          | `src/`             |
| `@pages`     | `src/pages/`       |
| `@components`| `src/components/`  |

The template also declares `@stores`, `@services`, `@schemas`, `@mocks` in
`project.config.ts` — declare more in that file when needed.

## Common commands

```bash
# Info / doctor
lhx-cli info
lhx-cli doctor

# Pages
lhx-cli add page dashboard --title='Dashboard'   # ts-morph upserts project.config.ts
lhx-cli add page reports --offline                # also adds to offline.whitelistPages if present
lhx-cli dev --page=home --page=dashboard          # filter pages (via LHX_PAGES)

# Other generators
lhx-cli add component Breadcrumb
lhx-cli add api products
lhx-cli add store user            # Zustand (framework: react)
lhx-cli add schema dashboard
```

## Adding a renderer-driven page

`lhx-cli add page myPage` scaffolds the full layout in one shot:

```
src/pages/myPage/
├── entry.tsx            # bootstrap + mount router
├── router.tsx           # HashRouter with / and /about
├── render.json          # renderer schema for the landing view
└── views/
    ├── MyPageLanding.tsx # imports ../render.json + registry
    └── MyPageAbout.tsx
```

Edit `render.json` to tweak the declarative tree. Register more components in
the Landing view's `createRegistry()` call to make them addressable from the
schema. `lhx-cli add schema myOtherPage` drops a blank `render.json` into an
existing page directory.

## Build optimizations (auto-applied)

`lhxKit()` automatically applies the following at `pnpm build`:

| Optimization | Default | How to override |
|---|---|---|
| `assetsInlineLimit` | `8 KB` | `vite.config.ts` `build.assetsInlineLimit` |
| `target` | `es2018` | `build.target` |
| `cssCodeSplit` | `true` | `build.cssCodeSplit` |
| `manualChunks` (1 chunk per top-level npm package) | on | rollupOptions.output.manualChunks |
| Drop `console.log`/`console.debug`/`console.trace` in production | on | `build.minify`, `esbuild.pure` |
| gzip + brotli pre-compression of every chunk ≥ 1 KB | on | `lhxKit({compress: false})` |
| Soft warning for chunks > 50 KB | on | informational only |

## CDN externalisation (advanced, off by default)

React 19 no longer ships a UMD build, so the classic "externalise React
from the bundle and serve it as `<script>`" trick is not available for
the default stack. This template therefore ships CDN **off** — React,
react-dom, and react-router-dom all travel inside the per-page bundle.

If you absolutely need CDN externalisation, lhx-kit supports a Preact +
`preact/compat` recipe that uses three per-entry extensions:

- `aliasGlobals` – alias `window.preactCompat` onto `window.React` /
  `window.ReactDOM` so the plugin's import rewrite (`import {useState}
  from 'react'` → `const {useState} = window.React`) works transparently.
- `initScript` – polyfill React 18's `createRoot` on top of
  preact/compat's classic `render(vnode, container)` API.
- `localFallback` – pick the exact on-disk file used for offline fallback
  (needed because Preact's `package.json` `exports` field blocks
  `require.resolve('preact/compat/dist/compat.umd.js')`).

A copy-paste-ready example lives in `project.config.ts` as a commented
block — uncomment it to opt in.

Caveats before you enable it:

1. React 19-only features (`use()`, `useOptimistic`, `useActionState`,
   Server Components client hooks, the new Suspense semantics) are **not**
   reproduced by preact/compat.
2. You must add `compilerOptions.paths` in `tsconfig.json` mapping
   `react` / `react-dom` to `preact/compat` so TypeScript agrees with
   the runtime wiring.
3. `@vitejs/plugin-react` should be replaced with `@preact/preset-vite`.
4. `react-router-dom`'s UMD depends on three pre-existing globals
   (`React`, `ReactRouter`, and `@remix-run/router`), only two of which
   ship a UMD. Keep react-router-dom inside the per-page bundle — the
   example commented config does exactly that.

The lhx-kit CDN loader public API (`window[<globalNamespace>]`) is
available once the entries listed in `cdn.entries` finish loading:

```ts
await window.LhxCdn.whenReady(['preact', 'preact-compat']);
window.LhxCdn.on('fallback', ({name, url}) =>
  console.warn(`[cdn] ${name} fell back to ${url}`)
);
window.LhxCdn.state['preact-compat'];  // 'pending' | 'ok' | 'fallback' | 'failed'
```

Offline packages (`lhx-cli offline build`) automatically blank out the
CDN URL list and force the local-vendor path regardless of the above,
so the app keeps working with no network at all.

## Offline packaging (opt-in)

Create `offline.config.ts` next to `project.config.ts` (or scaffold it via
`lhx-cli create --features=offline` on a fresh project), declare `whitelistPages`
and `versions`, then run:

```bash
lhx-cli offline build --hybrid-type=prod
```

See the lhx-kit docs for the manifest / prefetch / rollback contract.

## Resources

- 🐛 https://github.com/juwenzhang/lhx-kit/issues
- 📖 https://juwenzhang.github.io/lhx-kit/
