@solidjs/vite-plugin 3.0.0-next.27
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 +711 -0
- package/boundary-modules.d.ts +15 -0
- package/dist/cjs/index.cjs +3412 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/esm/index.mjs +3386 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/types/cypress/e2e/examples.cy.d.ts +0 -0
- package/dist/types/cypress/support/e2e.d.ts +0 -0
- package/dist/types/src/boundary-modules.d.ts +18 -0
- package/dist/types/src/dev-manifest.d.ts +128 -0
- package/dist/types/src/environment.d.ts +13 -0
- package/dist/types/src/http.d.ts +10 -0
- package/dist/types/src/index.d.ts +220 -0
- package/dist/types/src/server-functions/compile.d.ts +38 -0
- package/dist/types/src/server-functions/index.d.ts +145 -0
- package/dist/types/src/server-functions/xxhash32.d.ts +5 -0
- package/dist/types/src/ssr/index.d.ts +162 -0
- package/dist/types/src/start-env.d.ts +10 -0
- package/package.json +98 -0
- package/virtual-solid-manifest.d.ts +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
<p>
|
|
2
|
+
<img width="100%" src="https://raw.githubusercontent.com/solidjs/solid-vite-plugin/master/banner.png" alt="Solid Vite Plugin">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# ⚡ @solidjs/vite-plugin
|
|
6
|
+
|
|
7
|
+
> **Renamed from `vite-plugin-solid`.** This package was previously published as
|
|
8
|
+
> [`vite-plugin-solid`](https://www.npmjs.com/package/vite-plugin-solid). To migrate,
|
|
9
|
+
> swap the dependency and the import — `npm install -D @solidjs/vite-plugin` and
|
|
10
|
+
> `import solid from '@solidjs/vite-plugin'` — nothing else changes.
|
|
11
|
+
|
|
12
|
+
A simple integration to run [solid-js](https://github.com/solidjs/solid) with [vite](https://github.com/vitejs/vite)
|
|
13
|
+
|
|
14
|
+
<img alt="HMR gif demonstrationdemodemodemo" src=".github/hmr.gif">
|
|
15
|
+
|
|
16
|
+
# Got a question? / Need help?
|
|
17
|
+
|
|
18
|
+
Join [solid discord](https://discord.com/invite/solidjs) and check the [troubleshooting section](#troubleshooting) to see if your question hasn't been already answered.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- HMR with no configuration needed
|
|
23
|
+
- Drop-in installation as a vite plugin
|
|
24
|
+
- Minimal bundle size
|
|
25
|
+
- Support typescript (`.tsx`) out of the box
|
|
26
|
+
- Support code splitting out of the box
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
This module is 100% ESM compatible and requires NodeJS `14.18.0` or later.
|
|
31
|
+
|
|
32
|
+
You can check your current version of NodeJS by typing `node -v` in your terminal. If your version is below that one version I'd encourage you to either do an update globally or use a NodeJS version management tool such as [Volta](https://volta.sh/) or [nvm](https://github.com/nvm-sh/nvm).
|
|
33
|
+
|
|
34
|
+
Supported Vite versions: **Vite 6, 7 and 8**. Support for Vite 3–5 was
|
|
35
|
+
dropped; if you are on an older Vite, stay on an earlier release of this
|
|
36
|
+
plugin (2.x) or upgrade Vite.
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
You can use the [vite-template-solid](https://github.com/solidjs/templates) starter templates similar to CRA:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
$ npx degit solidjs/templates/js my-solid-project
|
|
44
|
+
$ cd my-solid-project
|
|
45
|
+
$ npm install # or pnpm install or yarn install
|
|
46
|
+
$ npm run start # starts dev-server with hot-module-reloading
|
|
47
|
+
$ npm run build # builds to /dist
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
Install `vite`, `@solidjs/vite-plugin` as dev dependencies.
|
|
53
|
+
|
|
54
|
+
Install `solid-js` as dependency.
|
|
55
|
+
|
|
56
|
+
You have to install those so that you are in control to which solid version is used to compile your code.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# with npm
|
|
60
|
+
$ npm install -D vite @solidjs/vite-plugin
|
|
61
|
+
$ npm install solid-js
|
|
62
|
+
|
|
63
|
+
# with pnpm
|
|
64
|
+
$ pnpm add -D vite @solidjs/vite-plugin
|
|
65
|
+
$ pnpm add solid-js
|
|
66
|
+
|
|
67
|
+
# with yarn
|
|
68
|
+
$ yarn add -D vite @solidjs/vite-plugin
|
|
69
|
+
$ yarn add solid-js
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Add it as plugin to `vite.config.js`
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// vite.config.ts
|
|
76
|
+
import { defineConfig } from 'vite';
|
|
77
|
+
import solidPlugin from '@solidjs/vite-plugin';
|
|
78
|
+
|
|
79
|
+
export default defineConfig({
|
|
80
|
+
plugins: [solidPlugin()],
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Run
|
|
85
|
+
|
|
86
|
+
Just use regular `vite` or `vite build` commands
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"scripts": {
|
|
91
|
+
"dev": "vite",
|
|
92
|
+
"build": "vite build"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API
|
|
98
|
+
|
|
99
|
+
### options
|
|
100
|
+
|
|
101
|
+
- Type: Object
|
|
102
|
+
- Default: {}
|
|
103
|
+
|
|
104
|
+
#### options.include
|
|
105
|
+
|
|
106
|
+
- Type: (string | RegExp)[] | string | RegExp | null
|
|
107
|
+
- Default: undefined
|
|
108
|
+
|
|
109
|
+
A [picomatch](https://github.com/micromatch/picomatch) pattern, or array of patterns, which specifies the files the plugin should operate on.
|
|
110
|
+
|
|
111
|
+
#### options.exclude
|
|
112
|
+
|
|
113
|
+
- Type: (string | RegExp)[] | string | RegExp | null
|
|
114
|
+
- Default: undefined
|
|
115
|
+
|
|
116
|
+
A [picomatch](https://github.com/micromatch/picomatch) pattern, or array of patterns, which specifies the files to be ignored by the plugin.
|
|
117
|
+
|
|
118
|
+
#### options.dev
|
|
119
|
+
|
|
120
|
+
- Type: Boolean
|
|
121
|
+
- Default: true
|
|
122
|
+
|
|
123
|
+
This will inject `solid-js/dev` in place of `solid-js` in dev mode. Has no effect in prod.
|
|
124
|
+
If set to false, it won't inject it in dev.
|
|
125
|
+
This is useful for extra logs and debug.
|
|
126
|
+
|
|
127
|
+
#### options.hot
|
|
128
|
+
|
|
129
|
+
- Type: Boolean
|
|
130
|
+
- Default: true
|
|
131
|
+
|
|
132
|
+
This will inject HMR runtime in dev mode. Has no effect in prod.
|
|
133
|
+
If set to false, it won't inject the runtime in dev.
|
|
134
|
+
|
|
135
|
+
#### options.ssr
|
|
136
|
+
|
|
137
|
+
- Type: Boolean
|
|
138
|
+
- Default: false
|
|
139
|
+
|
|
140
|
+
Whether the app is server-rendered — one meaning everywhere.
|
|
141
|
+
|
|
142
|
+
Without [`start`](#optionsstart), `ssr: true` enables the SSR transforms
|
|
143
|
+
(hydratable client code, SSR server code); you provide the entries and the
|
|
144
|
+
server yourself, as before. With `start`, the boolean selects the start
|
|
145
|
+
mode: `ssr: true` is SSR start mode, `ssr: false`/omitted is client start
|
|
146
|
+
mode — see below.
|
|
147
|
+
|
|
148
|
+
Objects are no longer accepted (config-time error): the start-mode options
|
|
149
|
+
that used to live on `ssr: { ... }` moved to `start: { ... }`, with
|
|
150
|
+
`ssr: true` set alongside.
|
|
151
|
+
|
|
152
|
+
#### options.start
|
|
153
|
+
|
|
154
|
+
- Type: Boolean | Object
|
|
155
|
+
- Default: undefined
|
|
156
|
+
|
|
157
|
+
**Start is now a mode of the plugin**: the serving layer that
|
|
158
|
+
replaces SolidStart. The plugin owns entries, dev serving, and the build —
|
|
159
|
+
no entry files, no `index.html`, no dev server script. `start: true` is the
|
|
160
|
+
zero-config spelling; add `ssr: true` for streaming SSR:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// vite.config.ts
|
|
164
|
+
import { defineConfig } from 'vite';
|
|
165
|
+
import solidPlugin from '@solidjs/vite-plugin';
|
|
166
|
+
|
|
167
|
+
export default defineConfig({
|
|
168
|
+
plugins: [solidPlugin({ start: true, ssr: true })],
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
One set of conventions serves both rendering modes, and the
|
|
173
|
+
[`ssr`](#optionsssr) boolean picks between them: `ssr: true` streams
|
|
174
|
+
server-side rendering with zero wiring; without it the same app is
|
|
175
|
+
client-rendered onto a prerendered static shell. Flipping a project between
|
|
176
|
+
SPA and SSR is toggling that one boolean — same `App`, same `Document`,
|
|
177
|
+
same server functions.
|
|
178
|
+
|
|
179
|
+
The object form carries the options (`start: true` is pure sugar for
|
|
180
|
+
`start: {}` — both mean the identical start mode with defaults, and
|
|
181
|
+
`false`/absent means off): `app`, `document`, `entryServer`, `entryClient`,
|
|
182
|
+
`middleware`, `env`, `external`, all documented below.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
// src/App.tsx — the entire app: a plain content component
|
|
186
|
+
export default function App() {
|
|
187
|
+
return <h1>Hello SSR</h1>;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
With `ssr: true` — **SSR start mode**:
|
|
192
|
+
|
|
193
|
+
- **Dev**: `vite` just works — a middleware on the dev server streams the
|
|
194
|
+
rendered app for HTML-accepting GET requests through the SSR environment,
|
|
195
|
+
injecting the Vite client (HMR, error overlay) and the dev style patch
|
|
196
|
+
into `<head>`. SSR errors render Vite's error page with the overlay.
|
|
197
|
+
- **Build**: a plain `vite build` produces both bundles via the
|
|
198
|
+
environments/builder API — client assets (+ manifest) to `dist/client` and
|
|
199
|
+
the server bundle to `dist/server/server.js`. (`vite build --app`, or the
|
|
200
|
+
classic `vite build` + `vite build --ssr` two-step, work too.)
|
|
201
|
+
- **Build ordering**: server builds read the client manifest, so with `ssr`
|
|
202
|
+
enabled the plugin also orders builder-mode (environments API) app builds
|
|
203
|
+
client-first via a `buildApp` hook (Vite 7.1+). That covers composed
|
|
204
|
+
setups whose own orchestrator builds server environments before the
|
|
205
|
+
client — e.g. @cloudflare/vite-plugin — with no hand-written ordering
|
|
206
|
+
plugin; setups without another orchestrator keep Vite's stock
|
|
207
|
+
build-everything behavior, just client-first.
|
|
208
|
+
- **Prod**: the server bundle's entry is `virtual:solid-ssr-handler`.
|
|
209
|
+
Its named `handleRequest(request)` export maps a web-standard `Request`
|
|
210
|
+
to a streamed `Response`; its default `{ fetch(request) }` export provides
|
|
211
|
+
the same handler in the Fetchable shape used by Workers, Nitro, Netlify
|
|
212
|
+
Functions, Bun, and `deno serve`:
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
import app, { handleRequest } from './dist/server/server.js';
|
|
216
|
+
// serve dist/client statically, everything else:
|
|
217
|
+
const response = await handleRequest(request);
|
|
218
|
+
const sameResponse = await app.fetch(request);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The Fetchable wrapper deliberately accepts only the request. Hosts may pass
|
|
222
|
+
environment or execution-context arguments after it; those are not the
|
|
223
|
+
Solid options accepted by `handleRequest`'s second parameter.
|
|
224
|
+
|
|
225
|
+
- **Preview**: `vite build && vite preview` runs the production artifact
|
|
226
|
+
with no server file — Vite's preview statics serve `dist/client`, and
|
|
227
|
+
everything else (pages, the server-function endpoint, middleware)
|
|
228
|
+
dispatches through the built handler.
|
|
229
|
+
|
|
230
|
+
Each request is scoped with `provideRequestEvent`, so `getRequestEvent()`
|
|
231
|
+
works during the render; hashed client assets (entry script, CSS) are
|
|
232
|
+
resolved through the build manifest and injected into `<head>`.
|
|
233
|
+
|
|
234
|
+
Every dispatch runs under a stub-backed request event
|
|
235
|
+
(`createRequestEvent` from `@solidjs/web`), and page responses go through
|
|
236
|
+
the runtime's response-head lifecycle (`createSSRResponse`):
|
|
237
|
+
`httpStatus()` / `httpHeader()` writes made during the render land on the
|
|
238
|
+
wire at shell flush, a `Location` header set before the flush becomes a
|
|
239
|
+
real 3xx redirect, and one set after it (streamed responses) falls back to
|
|
240
|
+
a `<script>window.location=...</script>` tail.
|
|
241
|
+
|
|
242
|
+
**`middleware`** points at a server-only module default-exporting one
|
|
243
|
+
fetch-style middleware — `(request, next) => Response | Promise<Response>`
|
|
244
|
+
— or an array of them, composed in order:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
// vite.config.ts
|
|
248
|
+
solid({ start: { middleware: './src/middleware.ts' }, ssr: true });
|
|
249
|
+
|
|
250
|
+
// src/middleware.ts
|
|
251
|
+
import { getRequestEvent } from '@solidjs/web';
|
|
252
|
+
|
|
253
|
+
export default async function auth(request: Request, next) {
|
|
254
|
+
getRequestEvent().locals.user = await userFromCookie(request);
|
|
255
|
+
try {
|
|
256
|
+
const response = await next();
|
|
257
|
+
response.headers.set('server-timing', 'app'); // pre-wire window
|
|
258
|
+
return response;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return new Response('oops', { status: 500 });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The chain fronts every request the plugin dispatches — page SSR and the
|
|
266
|
+
server-function endpoint, dev, production, and preview alike — and runs
|
|
267
|
+
inside the request-event scope, so `getRequestEvent()` works exactly as in
|
|
268
|
+
application code (the endpoint shares the chain's event, so `locals`
|
|
269
|
+
decoration is visible to server functions too). Nothing reaches the wire
|
|
270
|
+
until the outermost middleware returns: headers stay mutable after
|
|
271
|
+
`next()` even for streamed responses.
|
|
272
|
+
|
|
273
|
+
**`setup`** points at a server-only module default-exporting a per-request
|
|
274
|
+
app-setup hook: `(event, App) => Component | void | Promise<Component |
|
|
275
|
+
void>`. The generated server entry awaits it after the middleware chain has
|
|
276
|
+
dispatched to the page render and immediately before `renderToStream` — the
|
|
277
|
+
seam for routers that must prepare an app instance per request before SSR
|
|
278
|
+
can begin (create a router bound to the request, `await router.load()`,
|
|
279
|
+
then render):
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
// vite.config.ts
|
|
283
|
+
solid({ start: { setup: './src/setup.tsx' }, ssr: true });
|
|
284
|
+
|
|
285
|
+
// src/setup.tsx
|
|
286
|
+
import type { Component } from 'solid-js';
|
|
287
|
+
import type { RequestEvent } from '@solidjs/web';
|
|
288
|
+
|
|
289
|
+
export default async function setup(event: RequestEvent, App: Component) {
|
|
290
|
+
const router = createRouter({ url: event.request.url });
|
|
291
|
+
await router.load(); // async work completes before the shell streams
|
|
292
|
+
return () => <App router={router} />; // rendered in the app's place
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`event` is the shared request event — the same one the middleware chain
|
|
297
|
+
decorated, so `locals` are visible — and the hook runs inside the request
|
|
298
|
+
scope (`getRequestEvent()` answers in anything it calls). Return a
|
|
299
|
+
component and the generated entry renders it inside the Document where
|
|
300
|
+
`<App />` would have been; return nothing and `<App />` renders unchanged,
|
|
301
|
+
so a pure side-effect setup (seeding a per-request cache) needs no return.
|
|
302
|
+
Zero-config apps are untouched: without the option the generated entry is
|
|
303
|
+
byte-identical to before.
|
|
304
|
+
|
|
305
|
+
Two boundaries to know: the hook is a page-render seam — the middleware
|
|
306
|
+
chain and the server-function endpoint run without it — and it only exists
|
|
307
|
+
in generated entries (an authored `entry-server` owns `render()` already;
|
|
308
|
+
configuring both is an error). And as with any server-side tree shaping,
|
|
309
|
+
whatever the hook renders must be matched client-side for hydration —
|
|
310
|
+
routers that own both sides (their client entry re-creates the router and
|
|
311
|
+
hydrates the same tree) fit naturally.
|
|
312
|
+
|
|
313
|
+
**`env`** — first-party typed environment variables. A schema file at the
|
|
314
|
+
project root — `env.ts` (or `env.js`), probed automatically; point
|
|
315
|
+
elsewhere with `start: { env: './path' }`, disable with `env: false` —
|
|
316
|
+
default-exports `server` and `client` maps of
|
|
317
|
+
[Standard Schema](https://standardschema.dev) validators (zod, valibot,
|
|
318
|
+
arktype — even mixed per key; nothing is imported from the plugin):
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
// env.ts
|
|
322
|
+
import { z } from 'zod';
|
|
323
|
+
|
|
324
|
+
export default {
|
|
325
|
+
server: {
|
|
326
|
+
DATABASE_URL: z.url(),
|
|
327
|
+
SESSION_SECRET: z.string().min(32),
|
|
328
|
+
},
|
|
329
|
+
client: {
|
|
330
|
+
VITE_APP_NAME: z.string().min(1),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The validated values come back through two fully typed virtual modules:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
// server-only modules (middleware, "use server" modules, the server entry)
|
|
339
|
+
import { env } from 'virtual:env/server'; // every var
|
|
340
|
+
|
|
341
|
+
// anywhere
|
|
342
|
+
import { env } from 'virtual:env/client'; // the VITE_-prefixed client vars
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
- **Validation is node-only and layered.** The plugin loads the `.env*`
|
|
346
|
+
files through Vite's `loadEnv` (with `process.env` winning, so CI
|
|
347
|
+
secrets take precedence), folds them into `process.env` itself — no
|
|
348
|
+
`loadEnv` one-liner in vite.config, and server code reading
|
|
349
|
+
`process.env` directly sees the file-loaded vars too — and validates
|
|
350
|
+
before anything builds. In dev every failure renders the error overlay
|
|
351
|
+
with the per-key report, and `.env*`/schema edits revalidate live. In a
|
|
352
|
+
build, `client` failures fail the build (those values are baked);
|
|
353
|
+
`server` failures only warn — a build machine may legitimately not have
|
|
354
|
+
the production secrets — and boot validation enforces them.
|
|
355
|
+
- **Client values are baked, server values are runtime.** That's what the
|
|
356
|
+
public `VITE_` prefix means: `virtual:env/client` is the validated
|
|
357
|
+
output serialized as plain JSON (defaults applied, coercions done) with
|
|
358
|
+
zero schema-library bytes. `virtual:env/server` is not baked — it reads
|
|
359
|
+
`process.env` when the server boots and validates through your own
|
|
360
|
+
schema (imported into the server bundle, where shipping the validator
|
|
361
|
+
is fine). Platform-injected vars that don't exist at build time work,
|
|
362
|
+
secrets rotate without a rebuild, and no secret value exists in any
|
|
363
|
+
dist artifact; an invalid server environment fails boot with the same
|
|
364
|
+
per-key report.
|
|
365
|
+
- **Leaks are errors.** Importing `virtual:env/server` from a client
|
|
366
|
+
module graph is a hard error naming the importer (the app root and
|
|
367
|
+
everything it imports hydrate — they are client code; keep server env
|
|
368
|
+
in middleware, `"use server"` modules, or an authored server entry).
|
|
369
|
+
Client keys must carry the public prefix (`VITE_`, or your `envPrefix`)
|
|
370
|
+
— enforced at config time. And a client-build scan fails the build when
|
|
371
|
+
a server var's literal value shows up quoted in a client chunk.
|
|
372
|
+
- **Types are generated by inference.** A `solid-env.d.ts` is written next
|
|
373
|
+
to the schema file (keep both inside your tsconfig `include`): it
|
|
374
|
+
derives each var's type from your own schema through the Standard
|
|
375
|
+
Schema output type, so `env.VITE_APP_NAME` is whatever your validator
|
|
376
|
+
outputs — with any compliant library and no per-library plumbing.
|
|
377
|
+
|
|
378
|
+
Env works identically in both `start` modes (a client-mode static build
|
|
379
|
+
carries only the client vars); it is a start-mode feature, so without `start`
|
|
380
|
+
there is no env layer. See `examples/start-env` for the full story,
|
|
381
|
+
including the failure modes.
|
|
382
|
+
|
|
383
|
+
Design credit: the shape of this feature — the schema-file convention,
|
|
384
|
+
the `virtual:env/*` module names (kept identical on purpose), baked
|
|
385
|
+
client values, the leak scan — follows
|
|
386
|
+
[@vite-env/core](https://github.com/pyyupsk/vite-env) (MIT), the
|
|
387
|
+
design-correct prior art, reimplemented on this plugin's machinery with
|
|
388
|
+
Standard Schema as the only contract (and runtime-read server values).
|
|
389
|
+
|
|
390
|
+
**Entry resolution** (all paths relative to the Vite root):
|
|
391
|
+
|
|
392
|
+
1. Explicit `start.entryServer` / `start.entryClient` options.
|
|
393
|
+
2. Conventional files: `src/entry-server.{tsx,jsx,ts,js,mjs}` and
|
|
394
|
+
`src/entry-client.{tsx,jsx,ts,js,mjs}`. Entry files come in pairs —
|
|
395
|
+
providing only one is an error. The server entry must export
|
|
396
|
+
`render(request?, context?)` returning a `renderToStream` result, an HTML
|
|
397
|
+
string, or a `Response`; `context.clientEntry` carries the resolved
|
|
398
|
+
client entry URL, and in production any literal
|
|
399
|
+
`"/src/entry-client.tsx"` reference in the rendered HTML is rewritten to
|
|
400
|
+
the hashed asset (the classic harness convention keeps working).
|
|
401
|
+
3. Generated entries (the zero-config path): when no entry files exist, both
|
|
402
|
+
are generated from a root component — `start.app`, defaulting to
|
|
403
|
+
`src/App.{tsx,jsx,ts,js}` (or lowercase `src/app.*`) — wrapped in a
|
|
404
|
+
document shell: `start.document`, defaulting to `src/Document.{tsx,jsx}`,
|
|
405
|
+
else a built-in minimal shell. A custom document receives the app as
|
|
406
|
+
`props.children` and must render the full `<html>` document including
|
|
407
|
+
`<HydrationScript />`; the client entry script is injected into `<head>`
|
|
408
|
+
automatically.
|
|
409
|
+
|
|
410
|
+
With [`serverFunctions`](#optionsserverfunctions) also enabled the two
|
|
411
|
+
compose: `handleRequest` serves the endpoint on every surface (in dev the
|
|
412
|
+
server-function middleware pre-loads the referenced module, then dispatches
|
|
413
|
+
through the same handler), so one middleware chain and one request event
|
|
414
|
+
front pages and server functions identically.
|
|
415
|
+
|
|
416
|
+
The normal `ssr` environment exposes the default Fetchable handler as its
|
|
417
|
+
`index` service entry in development and production. Provider Vite plugins
|
|
418
|
+
can adopt that environment directly: they supply its runtime and build
|
|
419
|
+
orchestration while Solid continues to supply the application entry,
|
|
420
|
+
manifest, middleware, and server-function dispatch. When a provider replaces
|
|
421
|
+
the development environment with a non-runnable one, Solid detects that
|
|
422
|
+
ownership and stands its HTTP middlewares down automatically.
|
|
423
|
+
|
|
424
|
+
Two explicit switches remain for custom host setups:
|
|
425
|
+
|
|
426
|
+
1. **`start.external: true`** — hands the whole server side to a host that
|
|
427
|
+
does not adopt Solid's normal `ssr` environment. Solid skips its
|
|
428
|
+
server-build wiring and stands its development middlewares down, while
|
|
429
|
+
continuing to provide the generated entries, client manifest, and
|
|
430
|
+
`virtual:solid-ssr-handler`. This is mainly for differently named or
|
|
431
|
+
independently configured environments.
|
|
432
|
+
2. **[`serverFunctions.devMiddleware: false`](#optionsserverfunctions)** —
|
|
433
|
+
the narrow, endpoint-only switch: keeps start mode's server build and SSR
|
|
434
|
+
serving, hands only server-function dispatch in dev to the host. For
|
|
435
|
+
setups without `start`, or when only the endpoint should move.
|
|
436
|
+
|
|
437
|
+
Without `ssr: true` — **client mode** (experimental), the same conventions
|
|
438
|
+
with client-only rendering:
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
export default defineConfig({
|
|
442
|
+
plugins: [solidPlugin({ start: true })],
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
- **Dev**: every HTML-accepting GET streams the rendered document shell —
|
|
447
|
+
without the app, which never renders on the server — with the entry
|
|
448
|
+
graph's CSS inlined; deep links get the same shell (history-fallback
|
|
449
|
+
semantics). The generated client entry `render()`s (not hydrates) the app
|
|
450
|
+
into `document.body`.
|
|
451
|
+
- **Build**: `vite build` emits a purely static `dist/client` — the shell is
|
|
452
|
+
prerendered once through the built handler into `dist/client/index.html`,
|
|
453
|
+
with the hashed entry script and the entry graph's CSS links — deployable
|
|
454
|
+
to any static host. No server bundle remains unless `serverFunctions` is
|
|
455
|
+
enabled, in which case `dist/server` is kept and its `handleRequest`
|
|
456
|
+
serves the endpoint (pages stay static).
|
|
457
|
+
- **Transforms**: client code compiles exactly like a plain SPA today
|
|
458
|
+
(`generate: 'dom'`, non-hydratable); only the document shell goes through
|
|
459
|
+
the SSR transforms.
|
|
460
|
+
- **`vite preview`** serves the static build with history fallback (and
|
|
461
|
+
dispatches the server-function endpoint through the kept handler).
|
|
462
|
+
- Server-only options are inert here rather than errors, so a config
|
|
463
|
+
survives the flip untouched: `start.entryServer` (and conventional
|
|
464
|
+
`src/entry-server.*` files) are ignored — the shell render is always
|
|
465
|
+
generated — and so is `start.external`. An authored `src/entry-client.*`
|
|
466
|
+
stands alone and owns the mount.
|
|
467
|
+
|
|
468
|
+
The point is the migration story: an app born with `start: true` moves to
|
|
469
|
+
server rendering by setting `ssr: true` — same `App`, same `Document`,
|
|
470
|
+
same routes, same server functions; the plugin swaps render for hydrate,
|
|
471
|
+
turns the hydratable transforms on, and ships the server bundle. (A
|
|
472
|
+
`Document` authored for SSR carries `<HydrationScript />`; in client mode
|
|
473
|
+
the plugin strips its script from the served shell — nothing hydrates, so
|
|
474
|
+
a shared `Document` costs nothing — and the built-in shell omits it.)
|
|
475
|
+
|
|
476
|
+
Start-mode serving is opt-in via `start`, so bare `ssr: true` setups keep the
|
|
477
|
+
transform-only behavior. See `examples/turnkey` for a complete SSR app
|
|
478
|
+
(including a one-file production server and server functions),
|
|
479
|
+
`examples/start-client` for client mode (whose test flips the same app
|
|
480
|
+
between the modes), and `examples/ssr` for the manual `ssr: true` wiring.
|
|
481
|
+
|
|
482
|
+
#### options.serverFunctions
|
|
483
|
+
|
|
484
|
+
- Type: Boolean | Object
|
|
485
|
+
- Default: undefined
|
|
486
|
+
|
|
487
|
+
Enables `"use server"` server function compilation (experimental). Pass
|
|
488
|
+
`true` for the defaults (runtime from `@solidjs/web/server-functions`,
|
|
489
|
+
endpoint `/_server`) or an options object (`runtime`, `endpoint`, `filter`,
|
|
490
|
+
`directive`, `manifest`, `devMiddleware`, `configure`) to customize.
|
|
491
|
+
|
|
492
|
+
The setup is zero-config: in dev a middleware on the Vite server handles the
|
|
493
|
+
endpoint end to end — no server-function code needed in your server entry.
|
|
494
|
+
For production SSR builds, either use SSR start mode ([`start`](#optionsstart)
|
|
495
|
+
with `ssr: true`, whose handler serves the endpoint automatically) or
|
|
496
|
+
import `virtual:solid-server-function-handler` in your server entry and
|
|
497
|
+
mount its `handleServerFunctionRequest(request)` export on the endpoint.
|
|
498
|
+
|
|
499
|
+
**`devMiddleware: false`** hands endpoint dispatch in dev to a host instead
|
|
500
|
+
of the plugin's middleware. The middleware executes functions in Vite's
|
|
501
|
+
node-side SSR environment; when another plugin's server environment should
|
|
502
|
+
run them — e.g. @cloudflare/vite-plugin, so functions see workerd bindings
|
|
503
|
+
(`env`/`ctx`) in dev exactly like production — turn it off and let the host
|
|
504
|
+
dispatch: it loads `virtual:solid-server-function-handler` through its own
|
|
505
|
+
environment and calls `handleServerFunctionRequest(request)`, the same
|
|
506
|
+
contract as production. Compilation and the virtual modules keep working;
|
|
507
|
+
endpoint requests simply fall through to the host. Since the middleware's
|
|
508
|
+
on-demand module loading is off too, a host owning dev dispatch should
|
|
509
|
+
side-effect import `virtual:solid-server-function-manifest` in its server
|
|
510
|
+
entry so functions referenced only by client code still register. (When a
|
|
511
|
+
provider owns the `ssr` environment outright — it isn't runnable — the
|
|
512
|
+
middleware already stands down automatically; see the `external` option
|
|
513
|
+
under [`start`](#optionsstart) for the whole-server switch and how the
|
|
514
|
+
three options relate.)
|
|
515
|
+
|
|
516
|
+
**`configure: './src/server-config.ts'`** pins a server-only module (path
|
|
517
|
+
resolved against the Vite root) into the handler graph: the generated
|
|
518
|
+
`virtual:solid-server-function-handler` module side-effect imports it before
|
|
519
|
+
dispatching anything. It's the guaranteed pre-dispatch home for server-side
|
|
520
|
+
runtime registration — e.g. a router's single-flight collector:
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
// src/server-config.ts
|
|
524
|
+
import { configureServerFunctionsServer } from '@solidjs/web/server-functions/server';
|
|
525
|
+
configureServerFunctionsServer({ collectFlightData: createFlightDataCollector(router) });
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Registration living in the app graph only loads with the first page render,
|
|
529
|
+
so after a dev-server restart the first mutation can race it; the handler
|
|
530
|
+
graph loads before the first dispatch on every surface (dev middleware and
|
|
531
|
+
production handler alike), and edits to the module hot-invalidate the
|
|
532
|
+
handler in dev. Config calls merge per key, so it composes with the
|
|
533
|
+
plugin's own runtime configuration.
|
|
534
|
+
|
|
535
|
+
Meta-frameworks that need to control plugin ordering and dispatch requests
|
|
536
|
+
through their own server should use the standalone `serverFunctions()`
|
|
537
|
+
export instead, which never installs the dev middleware. See
|
|
538
|
+
`examples/turnkey` for a complete app.
|
|
539
|
+
|
|
540
|
+
**Server components (experimental):** `serverFunctions: { components: true }`
|
|
541
|
+
lets a `"use server"` function return a component. Server components ride
|
|
542
|
+
server functions — same endpoint, same compilation — with zero extra plugin
|
|
543
|
+
config: responses for component-returning functions are served as streamed
|
|
544
|
+
HTML that the client applies in place (client state and DOM identity inside
|
|
545
|
+
survive updates), and the plugin's dev middleware and production handler
|
|
546
|
+
handle that automatically. Combined with SSR start mode
|
|
547
|
+
([`start`](#optionsstart) with `ssr: true`) and generated entries, the
|
|
548
|
+
document wiring is emitted too: server components render inline in the
|
|
549
|
+
SSR'd document and are adopted
|
|
550
|
+
at boot with zero endpoint requests. With authored entries, the app-side
|
|
551
|
+
pieces (the render plugin, the bootstrap script, and the client's
|
|
552
|
+
`installServerComponents()` call, all from `@solidjs/web/frames`) live in
|
|
553
|
+
your entry files instead. See `examples/turnkey` for a complete page.
|
|
554
|
+
|
|
555
|
+
#### options.compiler
|
|
556
|
+
|
|
557
|
+
- Type: `"babel" | "native"`
|
|
558
|
+
- Default: `"native"`
|
|
559
|
+
|
|
560
|
+
Choose the JSX compiler backend. The default `"native"` compiles JSX through
|
|
561
|
+
the native compiler from `@dom-expressions/compiler`. `"babel"` runs
|
|
562
|
+
`babel-preset-solid` instead and only switches the JSX transform — every
|
|
563
|
+
other pass (the `lazy()` module-URL transform and the solid-refresh HMR
|
|
564
|
+
transform) is native in both modes.
|
|
565
|
+
|
|
566
|
+
`"babel"` is the escape hatch: if the native output ever differs from what
|
|
567
|
+
you expect, set `compiler: 'babel'` and file an issue — the behavioral diff
|
|
568
|
+
between the two modes is the bug report. Platforms without a prebuilt native
|
|
569
|
+
binary (for example StackBlitz WebContainers) automatically fall back to the
|
|
570
|
+
`@dom-expressions/compiler-wasm32-wasi` build, so no configuration is needed
|
|
571
|
+
there.
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
import { defineConfig } from 'vite';
|
|
575
|
+
import solidPlugin from '@solidjs/vite-plugin';
|
|
576
|
+
|
|
577
|
+
export default defineConfig({
|
|
578
|
+
plugins: [solidPlugin({ compiler: 'babel' })],
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
#### options.babel
|
|
583
|
+
|
|
584
|
+
- Type: Babel.TransformOptions
|
|
585
|
+
- Default: {}
|
|
586
|
+
|
|
587
|
+
Pass any additional [babel transform options](https://babeljs.io/docs/en/options). Those will be merged with the transformations required by Solid.
|
|
588
|
+
|
|
589
|
+
#### options.solid
|
|
590
|
+
|
|
591
|
+
- Type: [@dom-expressions/compiler](https://github.com/ryansolid/dom-expressions/tree/main/packages/compiler#options) / [@dom-expressions/babel-plugin-jsx](https://github.com/ryansolid/dom-expressions/tree/main/packages/babel-plugin-jsx#plugin-options)
|
|
592
|
+
- Default: {}
|
|
593
|
+
|
|
594
|
+
Pass additional DOM Expressions JSX compiler options. They will be merged with
|
|
595
|
+
Solid's defaults (`moduleName: "@solidjs/web"`, Solid built-ins, custom-element
|
|
596
|
+
context, and conditional wrapping) and applied to whichever compiler backend is
|
|
597
|
+
selected.
|
|
598
|
+
|
|
599
|
+
#### options.typescript
|
|
600
|
+
|
|
601
|
+
- Type: [@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript)
|
|
602
|
+
- Default: {}
|
|
603
|
+
|
|
604
|
+
Pass any additional [@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript).
|
|
605
|
+
|
|
606
|
+
#### options.extensions
|
|
607
|
+
|
|
608
|
+
- Type: (string, [string, { typescript: boolean }])[]
|
|
609
|
+
- Default: []
|
|
610
|
+
|
|
611
|
+
An array of custom extension that will be passed through the solid compiler.
|
|
612
|
+
By default, the plugin only transform `jsx` and `tsx` files.
|
|
613
|
+
This is useful if you want to transform `mdx` files for example.
|
|
614
|
+
|
|
615
|
+
## `server-only` and `client-only` boundary markers
|
|
616
|
+
|
|
617
|
+
The plugin always claims the bare specifiers `server-only` and `client-only`
|
|
618
|
+
as marker modules. Import one to pin a module to an environment:
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
import 'server-only'; // this module must never be bundled for the client
|
|
622
|
+
|
|
623
|
+
export const dbClient = createDbClient(process.env.DATABASE_URL);
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Importing `server-only` from a module that ends up in a client bundle fails
|
|
627
|
+
the build with an error naming the importer (and vice versa for
|
|
628
|
+
`client-only`); in the allowed environment the marker resolves to an empty
|
|
629
|
+
module. This turns "server code silently shipped to the browser and crashed
|
|
630
|
+
at runtime" into a build-time error at the exact import edge.
|
|
631
|
+
|
|
632
|
+
For TypeScript, the ambient declarations ship with the plugin — add to an
|
|
633
|
+
`env.d.ts`:
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
/// <reference types="@solidjs/vite-plugin/boundary-modules" />
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Note: these markers shadow React's `server-only` / `client-only` npm
|
|
640
|
+
packages if they happen to be installed; the semantics are the same, and the
|
|
641
|
+
plugin's errors are prefixed `[@solidjs/vite-plugin]`.
|
|
642
|
+
|
|
643
|
+
## Note on HMR
|
|
644
|
+
|
|
645
|
+
Starting from version `1.1.0`, this plugin handles automatic HMR. The refresh
|
|
646
|
+
transform is compiled natively by `@dom-expressions/compiler` and drives the
|
|
647
|
+
dev-only `solid-js/refresh` runtime entry that ships with Solid (the
|
|
648
|
+
standalone [solid-refresh](https://github.com/solidjs/solid-refresh) package
|
|
649
|
+
is no longer used).
|
|
650
|
+
|
|
651
|
+
At this stage it's still early work but provide basic HMR. In order to get the best out of it there are couple of things to keep in mind:
|
|
652
|
+
|
|
653
|
+
- When you modify a file every state below this component will be reset to default state (including the current file). The state in parent component is preserved.
|
|
654
|
+
|
|
655
|
+
- The entrypoint can't benefit from HMR yet and will force a hard reload of the entire app. This is still really fast thanks to browser caching.
|
|
656
|
+
|
|
657
|
+
If at least one of this point is blocking to you, you can revert to the old behavior by [opting out the automatic HMR](#options) and placing the following snippet in your entry point:
|
|
658
|
+
|
|
659
|
+
```jsx
|
|
660
|
+
const dispose = render(() => <App />, document.body);
|
|
661
|
+
|
|
662
|
+
if (import.meta.hot) {
|
|
663
|
+
import.meta.hot.accept();
|
|
664
|
+
import.meta.hot.dispose(dispose);
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
# Troubleshooting
|
|
669
|
+
|
|
670
|
+
- It appears that Webstorm generate some weird triggers when saving a file. In order to prevent that you can follow [this thread](https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000154544-I-m-having-a-huge-problem-with-Webstorm-and-react-hot-loader-) and disable the **"Safe Write"** option in **"Settings | Appearance & Behavior | System Settings"**.
|
|
671
|
+
|
|
672
|
+
- If one of your dependency spit out React code instead of Solid that means that they don't expose JSX properly. To get around it, you might want to manually exclude it from the [dependencies optimization](https://vitejs.dev/config/dep-optimization-options.html#optimizedeps-exclude)
|
|
673
|
+
|
|
674
|
+
- If you are trying to make [directives](https://www.solidjs.com/docs/latest/api#use%3A___) work, and they somehow don't try setting the `options.typescript.onlyRemoveTypeImports` option to `true`
|
|
675
|
+
|
|
676
|
+
## Migration from v1
|
|
677
|
+
|
|
678
|
+
The master branch now target vite 2.
|
|
679
|
+
|
|
680
|
+
The main breaking change from previous version is that the package has been renamed from `@amoutonbrady/vite-plugin-solid` to `vite-plugin-solid` (since renamed again to `@solidjs/vite-plugin` — see the note at the top).
|
|
681
|
+
|
|
682
|
+
For other breaking changes, check [the migration guide of vite](https://vitejs.dev/guide/migration.html).
|
|
683
|
+
|
|
684
|
+
# Testing
|
|
685
|
+
|
|
686
|
+
If you are using [vitest](https://vitest.dev/), this plugin already injects the necessary configuration for you. It even automatically detects if you have `@testing-library/jest-dom` installed in your project and automatically adds it to the `setupFiles`. All you need to add (if you want) is `globals`, `coverage`, and other testing configuration of your choice. If you can live without those, enjoy using vitest without the need to configure it yourself.
|
|
687
|
+
|
|
688
|
+
Tests default to the client posture, regardless of the app's `ssr` flag: DOM codegen, non-hydratable (nothing hydrates in a test), browser export conditions, and a `jsdom` default `test.environment`. A server-rendered app needs no `ssr: mode !== 'test'` workaround — DOM component tests just work.
|
|
689
|
+
|
|
690
|
+
Server-runtime unit tests (server functions, sessions, `renderToString` — anything that needs `isServer` to be `true` and the real server build of the framework) opt out per [vitest project](https://vitest.dev/guide/projects) by setting `test.environment: 'node'` (or `'edge-runtime'`) explicitly. That project gets the server posture end to end: server export conditions, ssr codegen, and the framework inlined so the whole graph — request-event storage included — resolves into one server-build instance. No inline/alias configuration needed. Both postures coexist in one workspace:
|
|
691
|
+
|
|
692
|
+
```ts
|
|
693
|
+
// vite.config.ts
|
|
694
|
+
test: {
|
|
695
|
+
projects: [
|
|
696
|
+
{
|
|
697
|
+
extends: true,
|
|
698
|
+
test: { name: 'client', environment: 'jsdom', include: ['src/**/*.test.tsx'] },
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
extends: true,
|
|
702
|
+
test: { name: 'server', environment: 'node', include: ['src/server/**/*.test.ts'] },
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
},
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
# Credits
|
|
709
|
+
|
|
710
|
+
- [solid-js](https://github.com/solidjs/solid)
|
|
711
|
+
- [vite](https://github.com/vitejs/vite)
|