Skip to content

Commit 1ac0712

Browse files
ryansolidclaude
andauthored
docs(v2): catch up with solid rc.14–next and router next.34–next.40 (#1653)
* reference: regenerate against solid next eb4e0192; dispositions for rc.14-rc.15 exports New pages: markRaw, onHydrationEnd, dispatchAsInteraction. dynamicComponent, lazyModule and reportRequestFailure join the dynamic, lazy and configureServerErrors pages; ssrStyleProperties is compiler ABI and hidden. The NoArray brand on the seedless derived-store form is dropped from rendered signatures. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y4HXPRqXPnqriNxDyin6eK * docs: catch up hand-written pages with solid rc.14-next and router next.34-next.40 Solid: seedless derived stores and complete seeds (#4073); storePath recipes become draft setters (#4053); respond() envelope and non-JSON bodies; performance-tracks defaults; kind "request" and reportRequestFailure; lowercase on* migration note; dynamicComponent, lazyModule and onHydrationEnd mentions. Router: link preloading is opt-in (preloadLinks strategies); data-pending needs pendingLinks; aria-current matches path and query; decoded params (#681); useInRouter (#675); action settle timing; liveQuery reference; anchor attributes typed by the router. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y4HXPRqXPnqriNxDyin6eK * docs(router): say held update, not transition, per the Solid Terms table Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y4HXPRqXPnqriNxDyin6eK --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent dd271a2 commit 1ac0712

49 files changed

Lines changed: 955 additions & 214 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎scripts/extract-solid-ref.mjs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ const CANONICAL_ROUTES = {
133133
"Components & Context",
134134
],
135135
dynamic: ["web-components/dynamic.mdx", "Components"],
136+
dynamicComponent: ["web-components/dynamic.mdx", "Components"],
136137
lazy: ["components-context/lazy.mdx", "Components & Context"],
138+
lazyModule: ["components-context/lazy.mdx", "Components & Context"],
137139
useContext: ["components-context/use-context.mdx", "Components & Context"],
138140

139141
Errored: ["components-jsx/errored.mdx", "Components (JSX)"],
@@ -198,6 +200,10 @@ const CANONICAL_ROUTES = {
198200
"request-response/configure-server-errors.mdx",
199201
"Request & response",
200202
],
203+
reportRequestFailure: [
204+
"request-response/configure-server-errors.mdx",
205+
"Request & response",
206+
],
201207
redirect: ["request-response/redirect.mdx", "Request & response"],
202208
reload: ["request-response/reload.mdx", "Request & response"],
203209
respond: ["request-response/respond.mdx", "Request & response"],
@@ -261,6 +267,10 @@ const ADVANCED_ROUTES = {
261267
"advanced/store-advanced/snapshot.mdx",
262268
"Advanced / Store Advanced",
263269
],
270+
markRaw: [
271+
"advanced/store-advanced/mark-raw.mdx",
272+
"Advanced / Store Advanced",
273+
],
264274
storePath: [
265275
"advanced/store-advanced/store-path.mdx",
266276
"Advanced / Store Advanced",
@@ -291,6 +301,10 @@ const ADVANCED_ROUTES = {
291301
"advanced/manual-hydration/no-hydration.mdx",
292302
"Advanced / Manual Hydration",
293303
],
304+
onHydrationEnd: [
305+
"advanced/manual-hydration/on-hydration-end.mdx",
306+
"Advanced / Manual Hydration",
307+
],
294308

295309
enableExternalSource: [
296310
"advanced/interop-async/enable-external-source.mdx",
@@ -315,6 +329,10 @@ const ADVANCED_ROUTES = {
315329
"advanced/diagnostics-dev-hooks/configure-client-errors.mdx",
316330
"Advanced / Diagnostics & Dev Hooks",
317331
],
332+
dispatchAsInteraction: [
333+
"advanced/diagnostics-dev-hooks/dispatch-as-interaction.mdx",
334+
"Advanced / Diagnostics & Dev Hooks",
335+
],
318336
attribution: [
319337
"advanced/diagnostics-dev-hooks/attribution.mdx",
320338
"Advanced / Diagnostics & Dev Hooks",
@@ -667,6 +685,7 @@ const HIDDEN_EXPORTS = new Set([
667685
"ssrHydrationKey",
668686
"ssrRunInScope",
669687
"ssrStyle",
688+
"ssrStyleProperties",
670689
"template",
671690
// Compiler and DOM-runtime ABI. These exports support generated code and
672691
// renderer integrations rather than application code.
@@ -2604,6 +2623,9 @@ const TYPE_TEXT_REWRITES = [
26042623
[/\bHydrationMemoOptions</g, "MemoOptions<"],
26052624
[/\bHydrationProjectionOptions\b/g, "ProjectionOptions"],
26062625
[/\bNoFn<([^<>]+)>/g, "$1"],
2626+
// The seedless derived form rejects array roots with an `& NoArray<T>`
2627+
// brand on its function; the prose says so, the signature need not.
2628+
[/\s*&\s*NoArray<[^<>]+>/g, ""],
26072629
[/\bRefreshable<((?:[^<>]|<[^<>]*>)+)>/g, "$1"],
26082630
// The core `Element` type is imported as `SolidElement` inside the
26092631
// package; app code knows it as `JSX.Element`.

‎src/routes/(2)concepts/(1)components-and-jsx.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,9 @@ export function Preview() {
504504
```
505505

506506
The source may also resolve to a native tag name or an async component.
507-
See the [`dynamic()` reference](/reference/solid-web/components/dynamic).
507+
Because a source may answer with a tag name, one `dynamic` on a page keeps the element runtime in the bundle.
508+
When the source only ever answers with a component, as `Result` does, `dynamicComponent` from `solid-js` takes the same source and options without that cost; a tag name is a type error there.
509+
See the [`dynamic()` reference](/reference/solid-web/components/dynamic), which covers both.
508510

509511
## Try it: remove a line from the cart
510512

‎src/routes/(2)concepts/(2)stores.mdx‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ Store writes are staged and applied in the same batch as signal writes, so a rea
150150
[When updates land](/concepts/reactivity#when-updates-land) explains the batch and when to call `flush()`.
151151

152152
:::note[Path setters from Solid 1]
153-
[`storePath`](/reference/solid-js/advanced/store-advanced/store-path) accepts the path-and-value form Solid 1 used, `setCart(storePath("coupon", "SAVE10"))`, for code that has not moved yet.
154-
New code uses the draft.
153+
The setter takes no path arguments.
154+
A Solid 1 path setter such as `setCart("coupon", "SAVE10")` becomes a draft write to the same path, `setCart((draft) => { draft.coupon = "SAVE10"; })`.
155+
Ranges and filters become ordinary loops over the draft, and setting a key to `undefined` to remove it becomes `delete`.
155156
:::
156157

157158
## Derive a store with a projection
@@ -160,7 +161,8 @@ A projection is a store whose value is computed from other reactive values.
160161
Where a memo derives one value, a projection derives an object or array whose properties are tracked separately, and whose items keep their identity from one computation to the next.
161162

162163
[`createProjection`](/reference/solid-js/stores/create-projection) takes a function and a seed.
163-
The function receives a draft of the seed and may mutate it or return a replacement; a returned array is reconciled into the store by `id`:
164+
The function receives a draft of the seed and may mutate it or return a replacement; a returned array is reconciled into the store by `id`.
165+
The seed is a complete value of the store's type, and an array projection always takes one, `[]`:
164166

165167
```tsx
166168
import { For, createProjection, createSignal, createStore } from "solid-js";
@@ -213,23 +215,23 @@ The projection function may return a promise.
213215
The function form of `createStore` is a projection with a setter, and it is the normal way to load server data into a store:
214216

215217
```ts
216-
const [cart, setCart] = createStore(async () => api.cart(), {
217-
items: [] as CartItem[],
218-
});
218+
const [cart, setCart] = createStore(async () => api.cart());
219219
```
220220

221221
The request starts when the store is created, and again whenever a reactive value the function read changes.
222222
Each response reconciles into the same proxy by `id`, so items the server did not change keep their identity and their DOM.
223223
A [`Loading`](/concepts/boundaries) boundary shows a fallback before the first response, [`isPending(() => cart.items)`](/reference/solid-js/reactivity/is-pending) reports a refetch, and [`refresh(cart)`](/reference/solid-js/lifecycle-actions/refresh) asks the server again.
224224

225-
With an async function, the seed is not shown as a first answer by default; readers wait for the first response the same way they wait for an async memo.
226-
[Async reactivity](/concepts/async-reactivity) explains that wait, and the `seedLoadingValue` option that makes the seed an acceptable first answer.
225+
This form takes no seed: the function reads no draft, and each response is a complete cart.
226+
Readers wait for the first response the same way they wait for an async memo.
227+
A store with an object root may omit the seed this way; an array root still takes `[]`, and a seed, when given, is a complete value of the store's type, not a partial one.
228+
[Async reactivity](/concepts/async-reactivity) explains that wait, and the `seedLoadingValue` option that makes a seed an acceptable first answer; that option needs a seed.
227229

228230
Split the request into its own memo only when a second reader shapes the same response differently:
229231

230232
```ts
231233
const response = createMemo(() => api.cart());
232-
const [cart] = createStore(() => response(), { items: [] as CartItem[] });
234+
const [cart] = createStore(() => response());
233235
const itemCount = createMemo(() => response().items.length);
234236
```
235237

@@ -243,9 +245,7 @@ It shows immediately, and when the action settles Solid removes it and shows the
243245
```ts
244246
import { action, createOptimisticStore, refresh } from "solid-js";
245247

246-
const [cart, setCart] = createOptimisticStore(async () => api.cart(), {
247-
items: [] as CartItem[],
248-
});
248+
const [cart, setCart] = createOptimisticStore(async () => api.cart());
249249

250250
const setQuantity = action(function* (id: string, quantity: number) {
251251
setCart((draft) => {
@@ -293,7 +293,7 @@ Reassign the property to a new instance through the setter to notify readers, or
293293
- Change state on the setter's draft; a write to the proxy itself is ignored.
294294
- Assign the one property that changed rather than rebuilding the collection, so rows keep their identity.
295295
- Derive a collection with `createProjection`, which reconciles results by `id`; derive a scalar with `createMemo`.
296-
- Load server data with `createStore(async () => ..., seed)`; each response reconciles into the same proxy.
296+
- Load server data with `createStore(async () => ...)`; each response reconciles into the same proxy, and an array root takes a `[]` seed.
297297
- Make optimistic writes inside an `action` on a `createOptimisticStore`, and `refresh` the source when the request settles.
298298

299299
## Next steps

‎src/routes/(3)building-apps/(3)server-functions/(3)mutations-and-responses.mdx‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,22 @@ export async function createProduct(input: CreateProductInput) {
4141
```
4242

4343
Call `createProduct(input)` from the admin page and the promise resolves to `product`, not to the envelope.
44-
The transport applies the 201 and the header to the HTTP response, and a plain HTTP caller such as `curl` receives a JSON body with the same product in it.
44+
`respond<T>()` is typed as `T`, so the function's inferred return type is the product's, and a direct call made on the server during SSR unwraps the envelope the same way.
45+
The transport applies the 201 and the header to the HTTP response.
46+
A plain HTTP caller such as `curl` receives a JSON body with the same product in it when JSON carries the value faithfully; a value JSON cannot represent, such as a `bigint`, is encoded the same way as a plain return.
4547

4648
:::deep-dive[The envelope on the wire]
47-
`respond()` returns a `ResponseEnvelope`, an object holding the `Response` that carries the metadata and the `value` the caller should see.
49+
At runtime `respond()` returns a `ResponseEnvelope`, a `Response` subclass that carries the status, headers, and body, plus the `value` the caller should see.
50+
Because it is a real `Response`, a fetch-style handler with no Solid knowledge can answer with it as it is.
4851
The server-function handler forwards the response's status and headers and encodes the value as the body.
4952
Integration code that has to recognize an envelope, such as a router applying revalidation, uses [`isResponseEnvelope()`](/reference/solid-web/request-response/respond); the check is a registered symbol, so it works when a bundle contains two copies of the runtime.
5053
Application code never touches the envelope directly.
5154
:::
5255

5356
## Redirect the caller
5457

55-
`redirect()` returns a `Response` with a `Location` header:
58+
`redirect()` returns a `Response` with a `Location` header.
59+
Its return type defaults to `never`, so a returned redirect drops out of the function's inferred return type; `reload()` is typed the same way:
5660

5761
```ts
5862
// src/data/account.ts

‎src/routes/(3)building-apps/(6)middleware-and-api-routes.mdx‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,21 @@ Production has no Vite pipeline, so every request that reaches the end of the ch
147147
A middleware that wraps `next()` in `try`/`catch` sees anything the rest of the chain throws:
148148

149149
```ts
150+
import { getRequestEvent, reportRequestFailure } from "@solidjs/web";
151+
150152
async function catchErrors(_request: Request, next: () => Promise<Response>) {
151153
try {
152154
return await next();
153155
} catch (error) {
154-
console.error(error);
156+
reportRequestFailure(error, getRequestEvent()!);
155157
return Response.json({ error: "Internal Server Error" }, { status: 500 });
156158
}
157159
}
158160
```
159161

160-
Log the real error and return a generic body.
162+
Report the real error and return a generic body.
163+
`reportRequestFailure` hands the error to the server error hook as `kind: "request"`, the same hook that hears render and server-function failures, and logs it with `console.error` when no hook is registered; [Observability](/guides/observability#hear-every-failure-the-server-handles) sets that hook up.
164+
An error the hook already heard, such as a failed render, is reported once.
161165
Exception messages can carry database, filesystem, or token details; the only errors whose text belongs in a response are the ones the app marked safe, which [Mutations and responses](/building-apps/server-functions/mutations-and-responses#handle-thrown-errors) covers for server functions.
162166
Production builds also wrap the generated page render in a default error boundary; set `start.errorBoundary: false` when this middleware owns errors, as [App structure](/building-apps/app-structure#common-problems) notes.
163167

‎src/routes/(4)routing/(1)solid-router/(0)index.mdx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const { paths } = Router;
3737
Because the route tree is a literal, `paths.products()` without an argument is a type error, and so is `paths.produts`.
3838

3939
`lazy` splits each page into its own chunk.
40-
The router loads a chunk when a link to it is hovered or when the route is matched, whichever comes first.
40+
The router loads a chunk when the route is matched, or earlier when a [link preload strategy](/routing/solid-router/data#preloading-from-links) warms it.
4141

4242
## Mount it and add a layout
4343

@@ -141,10 +141,11 @@ export const getProduct = query(async (id: string) => {
141141
```tsx
142142
// src/router.ts
143143
import { lazy } from "solid-js";
144-
import { createRouter } from "@solidjs/router";
144+
import { createRouter, intentPreload } from "@solidjs/router";
145145
import { getProduct } from "./data/products";
146146

147147
export const Router = createRouter({
148+
preloadLinks: intentPreload(),
148149
routes: [
149150
{ path: "/", component: lazy(() => import("./pages/Home")) },
150151
{
@@ -179,6 +180,7 @@ export default function Product(
179180
}
180181
```
181182

183+
`preloadLinks: intentPreload()` turns on link preloading, which is off by default.
182184
Hover **Featured** and watch the network tab: the product request starts before the click.
183185
Click, and the page renders with the data already there.
184186

‎src/routes/(4)routing/(1)solid-router/(1)setup.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ The other options exist for a specific situation each:
123123
| `base` | The app is served under a prefix such as `/app`; matching and `paths` both include it. |
124124
| `history` | The app is not a normal page: an Electron shell or `file://` needs `hashHistory()`, tests need `memoryHistory()`. |
125125
| `preload` | The root layout needs data of its own; it runs once per mount or server request and reaches the function child as `props.data`. |
126-
| `preloadLinks` | Hover and focus preloading costs more than it saves; set `false` and use `preload="false"` per link for finer control. |
126+
| `preloadLinks` | Links should warm route code and data before the click; pass `intentPreload()` or another strategy (off by default). |
127+
| `links` | Links should get `data-pending` during an in-flight navigation; pass `pendingLinks`. |
127128
| `explicitLinks` | Some anchors inside the router must stay as full page loads; only anchors with a `link` attribute are then handled by the router. |
128129
| `scrollRestoration` | The app manages scroll itself; set `false` to stop the router restoring the position on back and forward (on by default). |
129130
| `transformUrl` | Incoming pathnames need rewriting before matching, for example to strip a locale prefix. |

‎src/routes/(4)routing/(1)solid-router/(2)route-definitions.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ const orderRoute = defineRoute({
131131

132132
`/account/orders/42` matches; `/account/orders/latest` falls through to the next route that matches, usually the `*404` catch-all.
133133

134+
Filters test the decoded parameter value, so `{ tag: ["café"] }` matches `/tags/caf%C3%A9`.
135+
134136
The built-in `int` filter accepts integer strings at runtime.
135137
It also changes the corresponding `Router.paths` argument to `number`, so `paths.account.orders(42)` type-checks and `paths.account.orders("latest")` does not.
136138

@@ -193,7 +195,7 @@ export default defineRoutes([
193195
```
194196

195197
Visit `/` and the admin route table is not downloaded.
196-
Visit `/admin/products/mug`, or hover a link to it, and the thunk runs, the router adds the resolved subtree to the compiled route tree, and the match completes.
198+
Visit `/admin/products/mug`, or preload a link to it with a [link preload strategy](/routing/solid-router/data#preloading-from-links), and the thunk runs, the router adds the resolved subtree to the compiled route tree, and the match completes.
197199
TypeScript infers `paths.admin.products("mug")` through the import's promise type when the imported route array retains literal types.
198200

199201
Use a deterministic thunk such as `() => import("./admin/routes")`.

0 commit comments

Comments
 (0)