@alxia/react-router 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -10
- package/dist/chunks/index-59vhfj6n.js +10 -0
- package/dist/chunks/index-59vhfj6n.js.map +10 -0
- package/dist/chunks/index-5gqcpyyg.js +34 -0
- package/dist/chunks/index-5gqcpyyg.js.map +10 -0
- package/dist/chunks/index-9xv2a8et.js +108 -0
- package/dist/chunks/index-9xv2a8et.js.map +11 -0
- package/dist/chunks/index-mppz0806.js +119 -0
- package/dist/chunks/index-mppz0806.js.map +11 -0
- package/dist/chunks/index-rfr02vg4.js +40 -0
- package/dist/chunks/index-rfr02vg4.js.map +10 -0
- package/dist/chunks/index-zdbqzcjp.js +97 -0
- package/dist/chunks/index-zdbqzcjp.js.map +12 -0
- package/dist/pwa/build/build-worker.d.ts +41 -0
- package/dist/pwa/build/build-worker.d.ts.map +1 -0
- package/dist/pwa/build/index.d.ts +9 -0
- package/dist/pwa/build/index.d.ts.map +1 -0
- package/dist/pwa/build/index.js +9 -0
- package/dist/pwa/build/index.js.map +9 -0
- package/dist/pwa/build/precache.d.ts +17 -0
- package/dist/pwa/build/precache.d.ts.map +1 -0
- package/dist/pwa/index.d.ts +14 -0
- package/dist/pwa/index.d.ts.map +1 -0
- package/dist/pwa/index.js +65 -0
- package/dist/pwa/index.js.map +10 -0
- package/dist/pwa/model/caching.d.ts +46 -0
- package/dist/pwa/model/caching.d.ts.map +1 -0
- package/dist/pwa/model/clear-app-caches.d.ts +9 -0
- package/dist/pwa/model/clear-app-caches.d.ts.map +1 -0
- package/dist/pwa/model/manifest.d.ts +80 -0
- package/dist/pwa/model/manifest.d.ts.map +1 -0
- package/dist/pwa/model/precache-list.d.ts +40 -0
- package/dist/pwa/model/precache-list.d.ts.map +1 -0
- package/dist/pwa/model/registration.d.ts +39 -0
- package/dist/pwa/model/registration.d.ts.map +1 -0
- package/dist/pwa/model/start-service-worker.d.ts +24 -0
- package/dist/pwa/model/start-service-worker.d.ts.map +1 -0
- package/dist/pwa/model/storable.d.ts +8 -0
- package/dist/pwa/model/storable.d.ts.map +1 -0
- package/dist/pwa/model/update-notice.d.ts +38 -0
- package/dist/pwa/model/update-notice.d.ts.map +1 -0
- package/dist/pwa/model/worker-contract.d.ts +61 -0
- package/dist/pwa/model/worker-contract.d.ts.map +1 -0
- package/dist/pwa/react/index.d.ts +9 -0
- package/dist/pwa/react/index.d.ts.map +1 -0
- package/dist/pwa/react/index.js +75 -0
- package/dist/pwa/react/index.js.map +13 -0
- package/dist/pwa/react/use-online.d.ts +6 -0
- package/dist/pwa/react/use-online.d.ts.map +1 -0
- package/dist/pwa/react/use-service-worker.d.ts +33 -0
- package/dist/pwa/react/use-service-worker.d.ts.map +1 -0
- package/dist/pwa/react/use-update-notice.d.ts +14 -0
- package/dist/pwa/react/use-update-notice.d.ts.map +1 -0
- package/dist/pwa/server/index.d.ts +7 -0
- package/dist/pwa/server/index.d.ts.map +1 -0
- package/dist/pwa/server/index.js +18 -0
- package/dist/pwa/server/index.js.map +10 -0
- package/dist/pwa/server/no-cache.d.ts +22 -0
- package/dist/pwa/server/no-cache.d.ts.map +1 -0
- package/dist/pwa/worker/index.d.ts +9 -0
- package/dist/pwa/worker/index.d.ts.map +1 -0
- package/dist/pwa/worker/index.js +144 -0
- package/dist/pwa/worker/index.js.map +12 -0
- package/dist/pwa/worker/injected.d.ts +15 -0
- package/dist/pwa/worker/injected.d.ts.map +1 -0
- package/dist/pwa/worker/plan.d.ts +65 -0
- package/dist/pwa/worker/plan.d.ts.map +1 -0
- package/dist/pwa/worker/scope.d.ts +18 -0
- package/dist/pwa/worker/scope.d.ts.map +1 -0
- package/dist/pwa/worker/start-worker.d.ts +17 -0
- package/dist/pwa/worker/start-worker.d.ts.map +1 -0
- package/dist/vite/index.d.ts +10 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +49 -3
- package/dist/vite/index.js.map +6 -5
- package/dist/vite/runtime.d.ts +1 -1
- package/dist/vite/runtime.d.ts.map +1 -1
- package/dist/vite/worker.d.ts +16 -0
- package/dist/vite/worker.d.ts.map +1 -0
- package/docs/README.md +12 -3
- package/docs/guide/beside-the-pages.md +178 -0
- package/docs/guide/csp-nonce.md +100 -0
- package/docs/guide/customising-the-server.md +292 -0
- package/docs/guide/deploying.md +99 -0
- package/docs/guide/getting-started.md +112 -0
- package/docs/guide/how-it-works.md +268 -0
- package/docs/guide/openapi.md +51 -0
- package/docs/guide/pwa.md +636 -0
- package/docs/guide/testing.md +42 -0
- package/docs/guide/typing-the-loaders.md +311 -0
- package/docs/guide.md +27 -1391
- package/docs/roadmap.md +39 -1
- package/docs/troubleshooting.md +402 -11
- package/package.json +70 -6
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
`@alxia/react-router` serves a React Router **framework-mode** app with
|
|
4
|
+
server rendering from an alxia app, under Bun. This page covers starting one:
|
|
5
|
+
a new app with `bun create @alxia`, the official template with alxia added
|
|
6
|
+
to it, or an app you already have.
|
|
7
|
+
|
|
8
|
+
## A new app: `bun create @alxia`
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
bun create @alxia my-app --template react-router
|
|
12
|
+
cd my-app
|
|
13
|
+
bun dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
[`@alxia/create`](https://www.npmjs.com/package/@alxia/create) copies React
|
|
17
|
+
Router's official template with alxia's layer already applied: the steps
|
|
18
|
+
of the next section, done.
|
|
19
|
+
|
|
20
|
+
- `@alxia/core` and `@alxia/react-router` installed, and every dependency
|
|
21
|
+
moved to the newest release alxia accepts (see
|
|
22
|
+
[`@alxia/create`'s Versions](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md#versions));
|
|
23
|
+
- `alxia()` in `vite.config.ts`, the `bunfig.toml`, and `start` running
|
|
24
|
+
`NODE_ENV=production bun build/server/index.js`;
|
|
25
|
+
- Biome, with `lint`, `format`, `check`, `check:ci` and `verify` scripts;
|
|
26
|
+
- a `Dockerfile` that builds on `oven/bun:1` and runs `build/` alone on
|
|
27
|
+
`oven/bun:1-alpine`, as [Deploying](deploying.md#deploying) describes.
|
|
28
|
+
|
|
29
|
+
`bun run verify` checks, typechecks and builds it.
|
|
30
|
+
[`@alxia/create`'s guide](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md#running-it)
|
|
31
|
+
lists the command's options. The template keeps `@react-router/serve` in its
|
|
32
|
+
dependencies, unused: remove it if you like.
|
|
33
|
+
|
|
34
|
+
## An existing React Router app
|
|
35
|
+
|
|
36
|
+
Start from React Router's official template, or your own app:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
bunx create-react-router@latest my-app
|
|
40
|
+
cd my-app
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then make three changes.
|
|
44
|
+
|
|
45
|
+
**1. Install** alxia and this package:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
bun add @alxia/core @alxia/react-router
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**2. Add `alxia()`** to the plugins in `vite.config.ts`:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// vite.config.ts
|
|
55
|
+
import { alxia } from '@alxia/react-router/vite';
|
|
56
|
+
import { reactRouter } from '@react-router/dev/vite';
|
|
57
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
58
|
+
import { defineConfig } from 'vite';
|
|
59
|
+
|
|
60
|
+
export default defineConfig({
|
|
61
|
+
plugins: [tailwindcss(), reactRouter(), alxia()],
|
|
62
|
+
resolve: {
|
|
63
|
+
tsconfigPaths: true,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The order does not matter. The plugin always runs before React Router's:
|
|
69
|
+
React Router's build reads its server input from it, and its dev
|
|
70
|
+
middleware must come after alxia's.
|
|
71
|
+
|
|
72
|
+
**3. Start with Bun**, in `package.json`:
|
|
73
|
+
|
|
74
|
+
```jsonc
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "react-router build",
|
|
77
|
+
"dev": "react-router dev",
|
|
78
|
+
"start": "NODE_ENV=production bun build/server/index.js",
|
|
79
|
+
"typecheck": "react-router typegen && tsc"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Only `start` changes. The `react-router` and `vite` CLIs start with
|
|
84
|
+
`#!/usr/bin/env node`, so where a node is installed `bun run` would run
|
|
85
|
+
them on Node, and alxia's server needs Bun. A `bunfig.toml` beside
|
|
86
|
+
`package.json` makes `bun run` start them on Bun, for every script:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
# bunfig.toml, beside package.json
|
|
90
|
+
[run]
|
|
91
|
+
bun = true
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then `bun run dev`, `bun run build` and `bun run typecheck` work as they
|
|
95
|
+
are, with or without a node installed. Without that file, run them as
|
|
96
|
+
`bun --bun react-router dev`. The plugin refuses a dev or preview server
|
|
97
|
+
running on Node at startup, rather than failing on the first request:
|
|
98
|
+
see [the troubleshooting entry](../troubleshooting.md#alxia-react-router--is-running-on-node-and-alxias-server-runs-on-bun-). The
|
|
99
|
+
template's `@react-router/serve` is no longer used, and you can remove it.
|
|
100
|
+
|
|
101
|
+
What the template keeps:
|
|
102
|
+
|
|
103
|
+
- `@react-router/node`, which React Router's default `entry.server`
|
|
104
|
+
renders with: it streams under Bun.
|
|
105
|
+
- Vite 7 or 8 (the template ships 8).
|
|
106
|
+
- With `bunx create-react-router`, its `typescript` 5.9. This package's
|
|
107
|
+
peer asks for 6 or 7, so `bun add` warns, but the template's code
|
|
108
|
+
typechecks with 5.9. `bun add -d typescript@^6` silences the warning;
|
|
109
|
+
`bun create @alxia` writes 6 or 7 already.
|
|
110
|
+
|
|
111
|
+
React Router 8 is required: its loaders receive a `RouterContextProvider`,
|
|
112
|
+
and alxia's context is set on it.
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# How it works
|
|
2
|
+
|
|
3
|
+
This page covers what the plugin does in dev, in a build and under
|
|
4
|
+
`vite preview`, and what it sets so the build runs on Bun with no
|
|
5
|
+
`node_modules`.
|
|
6
|
+
|
|
7
|
+
With no `app/server.ts`, the plugin serves the app with
|
|
8
|
+
`createServer()`, from this package, with no options: a fresh alxia app,
|
|
9
|
+
the client's files, and the pages as a catch-all behind it.
|
|
10
|
+
|
|
11
|
+
## In dev
|
|
12
|
+
|
|
13
|
+
`react-router dev` starts Vite's server. Vite answers its own requests
|
|
14
|
+
first: modules, `/@fs/…`, `public/`, the HMR socket. The plugin hands
|
|
15
|
+
every other request to the server: pages, single-fetch data, and alxia's
|
|
16
|
+
routes. The server is loaded through Vite's SSR runner, so:
|
|
17
|
+
|
|
18
|
+
- **HMR** works as in any React Router app: a component edit reaches the
|
|
19
|
+
browser over Vite's socket.
|
|
20
|
+
- **An edit to `app/server.ts`, or to a module it imports, is live on the
|
|
21
|
+
next request**, with no restart: the runner reloads what changed.
|
|
22
|
+
- **Creating or deleting `app/server.ts`** takes effect on the next
|
|
23
|
+
request.
|
|
24
|
+
- **The app's own context keys work**: the server and the routes are one
|
|
25
|
+
module graph. See [The app's own context keys](typing-the-loaders.md#the-apps-own-context-keys).
|
|
26
|
+
- **Errors** go to Vite's error page, with the stack mapped to the source.
|
|
27
|
+
- **alxia's `ws` routes connect**: an upgrade Vite's HMR does not claim
|
|
28
|
+
goes to the app, as from the build. See [WebSockets](beside-the-pages.md#websockets).
|
|
29
|
+
|
|
30
|
+
HTTP requests reach the app through `app.fetch`, not through `listen`,
|
|
31
|
+
given the loopback server the sockets are relayed to as `ctx.server`
|
|
32
|
+
([The server](typing-the-loaders.md#the-server-server)). So `page()` is not served in dev; see
|
|
33
|
+
[the troubleshooting entry](../troubleshooting.md#a-page-is-not-served-under-react-router-dev).
|
|
34
|
+
|
|
35
|
+
## In a build
|
|
36
|
+
|
|
37
|
+
`react-router build` builds the client as usual. It builds the server
|
|
38
|
+
from `app/server.ts`, or from the default server, with React Router's
|
|
39
|
+
server build inside it, into one file:
|
|
40
|
+
|
|
41
|
+
- **`build/server/index.js`'s default export is the alxia app.** Beside it
|
|
42
|
+
are React Router's own exports, so the file is a server build too, and
|
|
43
|
+
React Router's `prerender` reads it. The server file's other exports
|
|
44
|
+
stay out of it.
|
|
45
|
+
- **Run, it listens**: `bun build/server/index.js` listens on `PORT`
|
|
46
|
+
(3000 by default) and `HOST` (`0.0.0.0`). It prints
|
|
47
|
+
`alxia listening on <url>`, and in dev — under `react-router dev`; a
|
|
48
|
+
production build never is, whatever `NODE_ENV` says — the route table
|
|
49
|
+
([`@alxia/core`'s Development](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/development.md#the-route-table)). On `SIGINT` or `SIGTERM` it shuts the app
|
|
50
|
+
down as `@alxia/core`'s `listen` does: readiness turns 503, new
|
|
51
|
+
connections are refused, the requests in flight finish within
|
|
52
|
+
`shutdownTimeout` (10 s; `listen: { shutdownTimeout }` sets it), the
|
|
53
|
+
`onStop` hooks run, and the process exits
|
|
54
|
+
([core's guide](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/health-and-shutdown.md)).
|
|
55
|
+
- **Imported, it starts nothing**: prerendering, a test or another server
|
|
56
|
+
can import it safely, since it listens only when it is the process's
|
|
57
|
+
entry point (`import.meta.main`).
|
|
58
|
+
- **The mode follows the command**: `development` under `react-router dev`,
|
|
59
|
+
`production` in a build, whatever `NODE_ENV` says. The app's dev switch
|
|
60
|
+
follows it: `alxia({ dev: mode === 'development' })`, so a 500 of the
|
|
61
|
+
build never shows its stack.
|
|
62
|
+
- **The client folder is resolved against the built file**: `../client`,
|
|
63
|
+
from React Router's `buildDirectory`, so it is found wherever the
|
|
64
|
+
process starts.
|
|
65
|
+
- **It is self-contained**: every package the server imports, React,
|
|
66
|
+
React Router and alxia included, is bundled into it
|
|
67
|
+
([Self-contained](#self-contained)), so `build/` runs with no
|
|
68
|
+
`node_modules`.
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
bun run build
|
|
72
|
+
PORT=8080 bun run start
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
After React Router's builder has written the client, the plugin builds the
|
|
76
|
+
service worker when the app has an `app/service-worker.ts` (`worker: false`
|
|
77
|
+
turns it off): `build/client/service-worker.js`, with the precache list read
|
|
78
|
+
from the client build ([the PWA guide](pwa.md#zero-config-alxia-builds-it)).
|
|
79
|
+
|
|
80
|
+
React Router's `serverBundles` split the server build in several, and
|
|
81
|
+
alxia serves one: the plugin refuses them. With `ssr: false`, a
|
|
82
|
+
single-page app, the plugin does nothing but build the service worker (when
|
|
83
|
+
`app/service-worker.ts` exists).
|
|
84
|
+
|
|
85
|
+
## Under `vite preview`
|
|
86
|
+
|
|
87
|
+
After a build, Vite's preview server serves the built server:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
bun run build
|
|
91
|
+
bunx --bun vite preview
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`--bun` runs Vite under Bun even where Node is installed, since Vite's bin
|
|
95
|
+
asks for Node: the built server runs inside Vite's process, and it needs
|
|
96
|
+
Bun's APIs. With the [`bunfig.toml`](getting-started.md#an-existing-react-router-app) (`[run]`,
|
|
97
|
+
`bun = true`), or with no Node installed, a `"preview": "vite preview"`
|
|
98
|
+
script run with `bun run preview` works too, as the template's other
|
|
99
|
+
scripts do.
|
|
100
|
+
|
|
101
|
+
The plugin loads `build/server/index.js` on the first request and hands
|
|
102
|
+
every request to its default export, the alxia app, before Vite's own
|
|
103
|
+
files. So the preview answers as `bun run start` does: the pages and their
|
|
104
|
+
data, `/api`, `build/client` with the cache headers of
|
|
105
|
+
[the client's files](beside-the-pages.md#the-clients-files), and every middleware of `beforeAll`
|
|
106
|
+
and `configure`. Requests, their bodies and every `Set-Cookie` pass
|
|
107
|
+
through whole, and pages stream.
|
|
108
|
+
|
|
109
|
+
What differs from `bun run start`:
|
|
110
|
+
|
|
111
|
+
- **Vite listens**, on `preview.port` (4173), `preview.host` and
|
|
112
|
+
`preview.https`. `listen`, `onListen`, `PORT` and `HOST` are not read.
|
|
113
|
+
- **Requests arrive through `app.fetch`**, as under `react-router dev`:
|
|
114
|
+
`page()` and an HTTP request's `ctx.server` are absent. alxia's `ws`
|
|
115
|
+
routes connect, relayed to a `Bun.serve` of the built app as in dev
|
|
116
|
+
([WebSockets](beside-the-pages.md#websockets)); an upgrade `preview.proxy` relays stays
|
|
117
|
+
Vite's.
|
|
118
|
+
- **Vite's preview options that come after the plugin never run**:
|
|
119
|
+
`preview.proxy` for HTTP requests, `preview.headers` and Vite's file
|
|
120
|
+
serving.
|
|
121
|
+
`preview.cors` and `preview.allowedHosts` still apply.
|
|
122
|
+
- **The build is loaded once**: after `bun run build` again, restart the
|
|
123
|
+
preview.
|
|
124
|
+
|
|
125
|
+
React Router prerenders the `prerender` paths of `react-router.config.ts`
|
|
126
|
+
through the same preview server, during `react-router build`. A
|
|
127
|
+
prerendered page is therefore rendered by the built server too: its loader
|
|
128
|
+
reads `alxiaOf(context)` and `getLoadContext`'s keys, and `beforeAll`'s
|
|
129
|
+
and `configure`'s middlewares run around it.
|
|
130
|
+
|
|
131
|
+
## Built for Bun
|
|
132
|
+
|
|
133
|
+
`build/server/index.js` runs on Bun, and the plugin builds it for Bun,
|
|
134
|
+
with nothing to configure. It adds to Vite's `ssr` environment, under
|
|
135
|
+
`react-router dev` and `react-router build` alike but for the last row,
|
|
136
|
+
the build's alone:
|
|
137
|
+
|
|
138
|
+
| option | what the plugin adds | why |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `resolve.conditions` | `bun` | a package bundled into the server whose `exports` has a `bun` condition is bundled as its Bun variant |
|
|
141
|
+
| `resolve.externalConditions` | `bun` | a dependency left external, as every one is in dev and one the app names in `ssr.external` is in the build, is loaded as its Bun variant, as Bun loads it |
|
|
142
|
+
| `resolve.builtins` | `bun`, and `bun:*` (`bun:sqlite`, `bun:ffi`, …) | Bun's own modules stay imports of the build, whichever runtime runs Vite. Vite knows `bun:*`, and bare `bun` only when it runs on Bun |
|
|
143
|
+
| `build.target` | `esnext` | the newest syntax is left as written: Bun runs it, and Vite's default targets browsers |
|
|
144
|
+
| `resolve.noExternal` | `true`, under `react-router build` only | every package is bundled into `build/server/index.js`, so `build/` runs with no `node_modules` ([Self-contained](#self-contained)) |
|
|
145
|
+
|
|
146
|
+
A package written for Bun, then, gives the build the variant Bun would
|
|
147
|
+
load, and Bun's own modules are used as they are:
|
|
148
|
+
|
|
149
|
+
```jsonc
|
|
150
|
+
// node_modules/some-package/package.json
|
|
151
|
+
{ "exports": { "bun": "./bun.js", "default": "./node.js" } }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// app/server.ts
|
|
156
|
+
import { createServer } from '@alxia/react-router';
|
|
157
|
+
import { Database } from 'bun:sqlite';
|
|
158
|
+
|
|
159
|
+
const db = new Database(':memory:');
|
|
160
|
+
|
|
161
|
+
export default createServer({
|
|
162
|
+
configure: (app) =>
|
|
163
|
+
app.get('/api/count', ({ reply }) =>
|
|
164
|
+
reply.ok(db.query('select sqlite_version() as version').get()),
|
|
165
|
+
),
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
// build/server/index.js, in part
|
|
171
|
+
import { Database } from "bun:sqlite";
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Which variant wins is still the package's own order: `exports` is read
|
|
175
|
+
top to bottom, and the first condition the build has wins. A package that
|
|
176
|
+
lists `node` before `bun` gives its Node variant, as Bun itself would load
|
|
177
|
+
it.
|
|
178
|
+
|
|
179
|
+
What the plugin leaves alone:
|
|
180
|
+
|
|
181
|
+
- **`ssr.target` stays `node`.** Bun runs Node's modules, and `node`
|
|
182
|
+
keeps `node:*` external and resolves packages as a server does;
|
|
183
|
+
`webworker` would bundle every dependency with the browser's conditions.
|
|
184
|
+
An app that sets `ssr.target: 'webworker'` itself gets none of the
|
|
185
|
+
above: that environment keeps Vite's own defaults.
|
|
186
|
+
- **No polyfill is added.** Vite adds none to a server build, and the
|
|
187
|
+
plugin adds none.
|
|
188
|
+
- **The client build** is a browser's, as before: none of this reaches
|
|
189
|
+
it. A Bun module imported by code that reaches the client is replaced
|
|
190
|
+
by an empty module there: Vite 7 fails the build, Vite 8 fails in the
|
|
191
|
+
browser ([troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#module--has-been-externalized-for-browser-compatibility-imported-by-)).
|
|
192
|
+
|
|
193
|
+
What the app sets wins. The plugin adds to the `ssr` environment once
|
|
194
|
+
every plugin's `config` has run, and Vite merges what it adds by
|
|
195
|
+
concatenating arrays, so conditions and builtins of the app's own are
|
|
196
|
+
kept, and `bun` is added only when they lack it. A `build.target`, at the
|
|
197
|
+
top level or on the environment, is kept as it is:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
// vite.config.ts
|
|
201
|
+
export default defineConfig({
|
|
202
|
+
// Kept: the build resolves `worker`, then `bun`, and targets es2022.
|
|
203
|
+
environments: {
|
|
204
|
+
ssr: {
|
|
205
|
+
resolve: { conditions: ['worker'] },
|
|
206
|
+
build: { target: 'es2022' },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
plugins: [reactRouter(), alxia()],
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`ssr.resolve.conditions` at the top level is the same option, and is kept
|
|
214
|
+
the same way.
|
|
215
|
+
|
|
216
|
+
### Self-contained
|
|
217
|
+
|
|
218
|
+
Under `react-router build`, the plugin sets `resolve.noExternal: true` on
|
|
219
|
+
the `ssr` environment: Vite bundles every package the server imports into
|
|
220
|
+
`build/server/index.js`, where it would otherwise leave each one under
|
|
221
|
+
`node_modules` as an `import`. What the file still imports is Node's and
|
|
222
|
+
Bun's own modules:
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
// build/server/index.js, its imports
|
|
226
|
+
import { createRequire } from "node:module";
|
|
227
|
+
import { PassThrough } from "node:stream";
|
|
228
|
+
// … and no "react", "react-router" or "@alxia/core"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
So `build/` is all a server needs, beside Bun: a Docker image's last
|
|
232
|
+
stage copies it alone ([Docker](deploying.md#docker)). React and `react-dom/server`,
|
|
233
|
+
React Router, alxia and its plugins bundle as they are, CommonJS included;
|
|
234
|
+
the `bun` condition above picks each package's Bun variant. One copy of
|
|
235
|
+
each package is in the file, so `alxiaContext` and the app's own keys are
|
|
236
|
+
one object for the server and the routes. React Router's prerendering and
|
|
237
|
+
`vite preview` load the same file.
|
|
238
|
+
|
|
239
|
+
`react-router dev` is left as it was: Vite's SSR runner loads the
|
|
240
|
+
packages from `node_modules`.
|
|
241
|
+
|
|
242
|
+
What the app sets wins. Vite reads `external` before `noExternal`:
|
|
243
|
+
|
|
244
|
+
- **`ssr.external: ['sharp']`**, or the same on
|
|
245
|
+
`environments.ssr.resolve.external`, keeps those packages external,
|
|
246
|
+
imported from `node_modules` at runtime. Use it for a package that
|
|
247
|
+
cannot be bundled: a native addon (a `.node` file), or one that reads
|
|
248
|
+
files of its own folder at runtime. The image must then hold that
|
|
249
|
+
package ([Docker](deploying.md#docker)).
|
|
250
|
+
- **`ssr.external: true`** keeps every package external, Vite's own
|
|
251
|
+
behaviour: the plugin adds no `noExternal` then, and the server needs
|
|
252
|
+
the production `node_modules` beside `build/`.
|
|
253
|
+
- **`ssr.noExternal`**, a list, changes nothing: everything is bundled
|
|
254
|
+
already.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
// vite.config.ts
|
|
258
|
+
export default defineConfig({
|
|
259
|
+
// Bundled but for sharp, a native addon.
|
|
260
|
+
ssr: { external: ['sharp'] },
|
|
261
|
+
plugins: [reactRouter(), alxia()],
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
A package the build could not bundle and was not told to leave external
|
|
266
|
+
fails at runtime, from `build/` alone, with Bun's
|
|
267
|
+
`Cannot find package '…'`: see
|
|
268
|
+
[the troubleshooting entry](../troubleshooting.md#error-cannot-find-package--from-appbuildserverindexjs).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# OpenAPI
|
|
2
|
+
|
|
3
|
+
This page covers writing the API of an app that serves pages spec first:
|
|
4
|
+
binding its routes to the generated operations, and checking them with
|
|
5
|
+
`matchesSpec`.
|
|
6
|
+
|
|
7
|
+
alxia is OpenAPI spec first: the document is written by hand, the API's
|
|
8
|
+
routes are bound to the operations generated from it, and
|
|
9
|
+
[`@alxia/openapi`](https://www.npmjs.com/package/@alxia/openapi)'s
|
|
10
|
+
`matchesSpec` checks, in a test, that every operation has its route; with
|
|
11
|
+
`strict: true`, that `app.routes` and the operations match both ways. The
|
|
12
|
+
catch-all and the client's files are routes of `app.routes`, and no
|
|
13
|
+
operation of the document. Under `strict`, leave them out with
|
|
14
|
+
`isReactRouterRoute`, which is true for each route this package declared:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// app/server.ts
|
|
18
|
+
import { createServer } from '@alxia/react-router';
|
|
19
|
+
import { operations } from './generated/alxia';
|
|
20
|
+
|
|
21
|
+
export default createServer({
|
|
22
|
+
configure: (app) =>
|
|
23
|
+
app
|
|
24
|
+
.get('/api/health', ({ reply }) => reply.ok({ ok: true }))
|
|
25
|
+
.route(operations.listOrders, ({ reply }) => reply.ok([])),
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// app/server.test.ts
|
|
31
|
+
import { test } from 'bun:test';
|
|
32
|
+
import { matchesSpec } from '@alxia/openapi';
|
|
33
|
+
import { isReactRouterRoute } from '@alxia/react-router';
|
|
34
|
+
import type { ServerBuild } from 'react-router';
|
|
35
|
+
import { operations } from './generated/alxia';
|
|
36
|
+
import server from './server';
|
|
37
|
+
|
|
38
|
+
test('the API routes every operation of openapi.yaml, and nothing else', async () => {
|
|
39
|
+
const build: ServerBuild = await import(new URL('../build/server/index.js', import.meta.url).href);
|
|
40
|
+
const app = server.create({ build });
|
|
41
|
+
matchesSpec(app, operations, {
|
|
42
|
+
strict: true,
|
|
43
|
+
exclude: (route) => isReactRouterRoute(route) || route.path === '/api/health',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When the app puts the operations under a prefix the document's paths
|
|
49
|
+
leave out, `app.group('/api', (api) => api.route(…))`, give `matchesSpec`
|
|
50
|
+
the same `prefix: '/api'`. See
|
|
51
|
+
[`@alxia/openapi`'s matching](https://github.com/softistx/alxia/blob/develop/packages/openapi/docs/guide/matching.md#under-a-prefix).
|