@uniflowed/vite 0.0.0-alpha.34 → 0.0.0-alpha.35

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.
@@ -0,0 +1,622 @@
1
+ // @noflow
2
+ //
3
+ // Plain JavaScript: executed by the host that runs Vite, before any transform.
4
+ //
5
+ // React Server Components, as the bundler applies them (ubugeeei-prod/uf#519,
6
+ // ubugeeei-prod/uf#252).
7
+ //
8
+ // # A second module graph
9
+ //
10
+ // React's Flight renderer only runs where `react` is the build with no
11
+ // `useState` in it — the one a package exports under the `react-server`
12
+ // condition — and the HTML renderer and the browser only run where it is not.
13
+ // No single module graph can hold both, so a uf application is three:
14
+ //
15
+ // * **`rsc`**, a Vite environment of its own, resolved under `react-server`.
16
+ // It holds the route table, every page, layout and loader, and the Flight
17
+ // renderer (`@uniflowed/router/rsc`). A module that opens with the use
18
+ // client directive is not evaluated here: it is replaced by a client
19
+ // reference per export, naming the chunk the browser loads it from.
20
+ // * **`ssr`**, Vite's own server environment. It holds the HTML renderer, the
21
+ // route handlers, the middleware, the action table — and the *server copy*
22
+ // of every client module, which is what renders a client component into
23
+ // HTML. It reaches the rsc graph through one module, the bridge.
24
+ // * **`client`**, the browser's. Its entry hydrates from the payload the
25
+ // document carries, and it holds no page, layout or loader — only the
26
+ // client modules, each an entry of its own, loaded when a payload names it.
27
+ //
28
+ // Vite is still the whole bundler: every graph is an environment, every
29
+ // resolution is Vite's, and uf adds a transform, four virtual modules and the
30
+ // order the builds run in. `docs/architecture.md` has the picture.
31
+ //
32
+ // # A build is three passes, in this order
33
+ //
34
+ // 1. **rsc** records every client module it replaced, and leaves the manifest
35
+ // those references read as an import, because no chunk URL exists yet.
36
+ // 2. **client** builds the entry and one entry per recorded client module, with
37
+ // their export names kept, so each chunk still has the export a reference
38
+ // asks for.
39
+ // 3. **ssr** bundles the rsc output in through the bridge, and resolves the
40
+ // manifest the rsc output left open to the client build's chunk URLs.
41
+ //
42
+ // # Stylesheets come from the rsc graph
43
+ //
44
+ // A layout's stylesheet is imported by the layout, and the layout is in the rsc
45
+ // graph now, so the client build never sees it. The rsc build emits its assets,
46
+ // the driver copies them beside the client's, and the document links every
47
+ // stylesheet the rsc build emitted — the rule `assetsFromManifest` already
48
+ // applies to the client build, which links a route's stylesheet on every page.
49
+ // In development the rsc environment's module graph is read instead, once the
50
+ // route's modules have been imported.
51
+
52
+ import path from "node:path";
53
+
54
+ import {
55
+ createRunnableDevEnvironment,
56
+ defaultServerConditions,
57
+ isCSSRequest,
58
+ parseAst,
59
+ } from "vite";
60
+
61
+ /** The environment the Flight renderer runs in. */
62
+ export const RSC_ENVIRONMENT = "rsc";
63
+
64
+ /** The virtual modules this file generates; `./routes.js`'s `VIRTUAL` has the rest. */
65
+ export const FLIGHT_VIRTUAL = Object.freeze({
66
+ /** The rsc graph's entry: the Flight renderer over the whole route table. */
67
+ entry: "virtual:uf/rsc",
68
+ /** The ssr graph's one door into the rsc graph. */
69
+ bridge: "virtual:uf/rsc-bridge",
70
+ /** The chunk URL of every client module, for the references a build writes. */
71
+ manifest: "virtual:uf/client-manifest",
72
+ /** The server copy of every client module, keyed by that URL. */
73
+ references: "virtual:uf/client-references",
74
+ /** `react/compiler-runtime`, as the rsc graph gets it; see `compilerRuntimeSource`. */
75
+ compilerRuntime: "virtual:uf/rsc-compiler-runtime",
76
+ });
77
+
78
+ /**
79
+ * `react/compiler-runtime` in the rsc graph.
80
+ *
81
+ * Every Flow module goes through the React Compiler, and what it emits calls
82
+ * `c(size)` from `react/compiler-runtime` for a component's memo cache. React
83
+ * 19.3's runtime reads that cache through `react`'s *client* internals, and the
84
+ * `react` a graph resolved under `react-server` has only server internals — so
85
+ * every compiled server component threw `Cannot read properties of undefined
86
+ * (reading 'H')` before it rendered a byte.
87
+ *
88
+ * A server component renders once per request and never again, so there is
89
+ * nothing for a cache to remember: this is the cache React's Flight renderer
90
+ * itself hands `useMemoCache`, every slot the sentinel the compiled code tests
91
+ * for, fresh on each call.
92
+ */
93
+ export function compilerRuntimeSource() {
94
+ return `const sentinel = Symbol.for("react.memo_cache_sentinel");
95
+ export function c(size) {
96
+ const cache = new Array(size);
97
+ for (let index = 0; index < size; index += 1) cache[index] = sentinel;
98
+ return cache;
99
+ }
100
+ `;
101
+ }
102
+
103
+ /**
104
+ * The global a development server leaves for the ssr graph to reach the rsc
105
+ * graph by.
106
+ *
107
+ * A function that imports the rsc entry through the rsc environment's module
108
+ * runner each time it is called, so an edit to a server component is in the
109
+ * next render the way an edit to anything else is.
110
+ */
111
+ export const DEV_RSC_HOOK = "uf.dev.rsc";
112
+
113
+ /**
114
+ * The last segment of a route's payload URL.
115
+ *
116
+ * A third spelling, beside `packages/router/internal/flight.js` and
117
+ * `packages/server/internal/flight.js`, because this file is plain JavaScript
118
+ * that Vite imports before any Flow transform exists. `packages/server/flight.test.js`
119
+ * holds all three to one answer.
120
+ */
121
+ export const FLIGHT_SEGMENT = "__uf.flight";
122
+
123
+ /** The document a payload path is for, or `null` for any other path. */
124
+ export function flightDocumentPath(pathname) {
125
+ const suffix = `/${FLIGHT_SEGMENT}`;
126
+ if (!pathname.endsWith(suffix)) return null;
127
+ const document = pathname.slice(0, -suffix.length);
128
+ return document === "" ? "/" : document;
129
+ }
130
+
131
+ /**
132
+ * The directive, spelled without quotes.
133
+ *
134
+ * `uf lint`'s `server/no-server-only-import-in-client` decides a file is a
135
+ * client module by finding the directive in quotes on any line of code, and
136
+ * this file, which imports `node:path`, is not one.
137
+ */
138
+ const USE_CLIENT = `use client`;
139
+
140
+ /**
141
+ * Whether an application renders through React Server Components.
142
+ *
143
+ * On unless `app.rsc` is `false`, read as `!== false` for the reason every
144
+ * default-on flag in `../index.js` is. A single-page build (`modes: ["csr"]`)
145
+ * renders nothing on a server, so it has no payload to render, and a native
146
+ * target has no document to write one into.
147
+ */
148
+ export function rendersFlight(app, { mount, routeTarget }) {
149
+ return app?.rsc !== false && mount === "hydrate" && routeTarget === "web";
150
+ }
151
+
152
+ /**
153
+ * The rsc environment, as `config()` declares it.
154
+ *
155
+ * `noExternal: true` because every module has to be resolved under
156
+ * `react-server`: a dependency left to Node would be resolved by Node, under
157
+ * the default conditions, and would import the `react` with `useState` in it.
158
+ * `optimizeDeps` names React and the Flight server because both are CommonJS
159
+ * and the module runner runs ES modules; uf's own packages are excluded for the
160
+ * reason the client excludes them — they ship Flow.
161
+ *
162
+ * `process.env.NODE_ENV` is fixed in a build. A server bundle reads it at run
163
+ * time otherwise, and a server started without it runs React's development
164
+ * build, whose payload carries every server component's source location and
165
+ * every error's stack — to the browser.
166
+ *
167
+ * @param {{ production: boolean, exclude: Array<string> }} options
168
+ */
169
+ export function rscEnvironment({ production, exclude }) {
170
+ return {
171
+ consumer: "server",
172
+ resolve: {
173
+ conditions: ["react-server", ...defaultServerConditions],
174
+ externalConditions: ["react-server", ...defaultServerConditions],
175
+ noExternal: true,
176
+ },
177
+ optimizeDeps: {
178
+ include: [
179
+ "react",
180
+ "react/jsx-runtime",
181
+ "react/jsx-dev-runtime",
182
+ "react-server-dom-parcel/server",
183
+ ],
184
+ exclude,
185
+ },
186
+ define: production ? { "process.env.NODE_ENV": JSON.stringify("production") } : {},
187
+ dev: {
188
+ createEnvironment(name, config) {
189
+ return createRunnableDevEnvironment(name, config);
190
+ },
191
+ },
192
+ build: {
193
+ // Its stylesheets and images, which only this graph imports; see the
194
+ // header. The manifest is how the driver finds the stylesheets.
195
+ emitAssets: true,
196
+ manifest: true,
197
+ },
198
+ };
199
+ }
200
+
201
+ /**
202
+ * The state the plugins and the driver share for one application.
203
+ *
204
+ * `clientModules` is filled by the rsc graph's transform and read by the client
205
+ * build, which makes each of them an entry. `chunkUrls` is filled by the driver
206
+ * from the client build's manifest, and is what the ssr build resolves every
207
+ * reference to. `rscOutput` is the rsc build's entry file, which the ssr build
208
+ * bundles in; `null` under `uf dev`, where the rsc graph runs in-process.
209
+ *
210
+ * @param {{ root: string }} options
211
+ */
212
+ export function createFlightState({ root }) {
213
+ return {
214
+ root,
215
+ base: "/",
216
+ production: false,
217
+ clientModules: new Set(),
218
+ chunkUrls: new Map(),
219
+ rscOutput: null,
220
+ };
221
+ }
222
+
223
+ /**
224
+ * The plugin that replaces a client module with references, in the rsc graph.
225
+ *
226
+ * After `uf:flow`, which is a `pre` plugin, so a Flow module is JavaScript by
227
+ * the time its exports are read — and after Vite's own transforms, so a `.jsx`
228
+ * module is too. The module is parsed with Vite's parser rather than scanned:
229
+ * the directive only counts as the first statement of the module, and an
230
+ * export list is not something a regular expression reads correctly.
231
+ *
232
+ * In development a reference names the URL Vite serves the module at, which is
233
+ * the URL the browser already imports it by from any other client module, so
234
+ * both reach one instance. In a build it names the client manifest, which the
235
+ * ssr build resolves once the client build has written the chunks.
236
+ *
237
+ * @param {ReturnType<typeof createFlightState>} state
238
+ */
239
+ export function clientReferencePlugin(state) {
240
+ return {
241
+ name: "uf:rsc-references",
242
+ applyToEnvironment(environment) {
243
+ return environment.name === RSC_ENVIRONMENT;
244
+ },
245
+ transform(code, id) {
246
+ const file = cleanId(id);
247
+ let program = null;
248
+ if (
249
+ code.includes(USE_CLIENT) &&
250
+ !id.startsWith("\0") &&
251
+ !isCSSRequest(file) &&
252
+ SCRIPT.test(file)
253
+ ) {
254
+ try {
255
+ program = parseAst(code);
256
+ } catch {
257
+ program = null;
258
+ }
259
+ }
260
+ if (program == null || !opensWithUseClient(program)) {
261
+ // Forgotten as well as not recorded: a module whose directive was
262
+ // removed under `uf dev` is a server module from that edit on, so
263
+ // `hotUpdate` reloads the page for its next edit instead of leaving it
264
+ // to Fast Refresh, which has nothing of it in the browser to replace.
265
+ state.clientModules.delete(file);
266
+ return null;
267
+ }
268
+ state.clientModules.add(file);
269
+ const names = clientExportNames(program, projectPath(state.root, file));
270
+ const lines = [`import { createClientReference } from "react-server-dom-parcel/server";`];
271
+ if (state.production) {
272
+ lines.push(`import { clientUrl } from ${JSON.stringify(FLIGHT_VIRTUAL.manifest)};`);
273
+ lines.push(`const url = clientUrl(${JSON.stringify(file)});`);
274
+ } else {
275
+ lines.push(`const url = ${JSON.stringify(devUrlOf(state.root, state.base, file))};`);
276
+ }
277
+ names.forEach((name, index) => {
278
+ lines.push(
279
+ `const reference${index} = createClientReference(url, ${JSON.stringify(name)}, [url]);`,
280
+ );
281
+ lines.push(`export { reference${index} as ${JSON.stringify(name)} };`);
282
+ });
283
+ return { code: `${lines.join("\n")}\n`, map: null };
284
+ },
285
+ };
286
+ }
287
+
288
+ /** The module kinds a reference can stand in for. */
289
+ const SCRIPT = /\.(?:[cm]?js|jsx|mdx)$/;
290
+
291
+ /**
292
+ * Whether a module's directive prologue holds the use client directive.
293
+ *
294
+ * The prologue rather than the first statement, because `"use strict"` may
295
+ * come before it and is still a directive.
296
+ */
297
+ export function opensWithUseClient(program) {
298
+ for (const statement of program.body) {
299
+ if (statement.type !== "ExpressionStatement" || typeof statement.directive !== "string") {
300
+ return false;
301
+ }
302
+ if (statement.directive === USE_CLIENT) return true;
303
+ }
304
+ return false;
305
+ }
306
+
307
+ /**
308
+ * Every name a client module exports, which is every reference it becomes.
309
+ *
310
+ * `export *` is refused rather than followed: a reference is one per export,
311
+ * and the names behind a star are another module's, which this transform would
312
+ * have to resolve and parse before it could write this one.
313
+ *
314
+ * @param {object} program
315
+ * @param {string} file the module, as the error names it
316
+ */
317
+ export function clientExportNames(program, file) {
318
+ const names = [];
319
+ for (const node of program.body) {
320
+ if (node.type === "ExportDefaultDeclaration") {
321
+ names.push("default");
322
+ } else if (node.type === "ExportNamedDeclaration") {
323
+ const declaration = node.declaration;
324
+ if (declaration != null) {
325
+ if (declaration.type === "VariableDeclaration") {
326
+ for (const declarator of declaration.declarations) bindingNames(declarator.id, names);
327
+ } else if (declaration.id != null) {
328
+ names.push(declaration.id.name);
329
+ }
330
+ }
331
+ for (const specifier of node.specifiers ?? []) names.push(exportedName(specifier.exported));
332
+ } else if (node.type === "ExportAllDeclaration") {
333
+ if (node.exported != null) {
334
+ names.push(exportedName(node.exported));
335
+ continue;
336
+ }
337
+ throw new Error(
338
+ `uf: ${file} is a client module and re-exports everything from ` +
339
+ `${JSON.stringify(node.source.value)}. A client module becomes one reference per ` +
340
+ "export, so each export has to be named: write `export { A, B } from " +
341
+ `${JSON.stringify(node.source.value)}\` instead.`,
342
+ );
343
+ }
344
+ }
345
+ return [...new Set(names)];
346
+ }
347
+
348
+ function exportedName(node) {
349
+ return node.type === "Identifier" ? node.name : String(node.value);
350
+ }
351
+
352
+ function bindingNames(pattern, names) {
353
+ if (pattern == null) return;
354
+ switch (pattern.type) {
355
+ case "Identifier":
356
+ names.push(pattern.name);
357
+ break;
358
+ case "ObjectPattern":
359
+ for (const property of pattern.properties) {
360
+ bindingNames(property.type === "RestElement" ? property.argument : property.value, names);
361
+ }
362
+ break;
363
+ case "ArrayPattern":
364
+ for (const element of pattern.elements) bindingNames(element, names);
365
+ break;
366
+ case "RestElement":
367
+ bindingNames(pattern.argument, names);
368
+ break;
369
+ case "AssignmentPattern":
370
+ bindingNames(pattern.left, names);
371
+ break;
372
+ default:
373
+ break;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * The URL Vite serves `file` at in development.
379
+ *
380
+ * Root-relative for a file under the project, and `/@fs/` for one outside it —
381
+ * a workspace package, which Vite resolves to its real path — which is the URL
382
+ * any client module importing it is rewritten to as well.
383
+ */
384
+ export function devUrlOf(root, base, file) {
385
+ const relative = path.relative(root, file);
386
+ const inside = relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
387
+ const forward = file.split(path.sep).join("/");
388
+ // `/@fs/` and then the path. On Windows the path starts at its drive letter,
389
+ // `C:/work/button.js`, with no slash of its own to follow the prefix.
390
+ const pathname = inside
391
+ ? `/${relative.split(path.sep).join("/")}`
392
+ : `/@fs${forward.startsWith("/") ? "" : "/"}${forward}`;
393
+ return `${base.replace(/\/$/, "")}${pathname}`;
394
+ }
395
+
396
+ /**
397
+ * The file a `/@fs/` URL's path names: the inverse of [`devUrlOf`] for a file
398
+ * outside the project, read the way Vite reads that prefix. A POSIX path gets
399
+ * its leading slash back; a Windows path starts at its drive letter.
400
+ *
401
+ * Also the body of the loader [`devReferencesSource`] generates, which is why
402
+ * it closes over nothing.
403
+ */
404
+ export function fsFileOf(pathname) {
405
+ const rest = pathname.slice("/@fs/".length);
406
+ return /^[A-Za-z]:\//.test(rest) ? rest : `/${rest}`;
407
+ }
408
+
409
+ /** `virtual:uf/rsc`: the Flight renderer over the rsc graph's route table. */
410
+ export function rscEntrySource(routesId) {
411
+ return `import { createFlightRenderer } from "@uniflowed/router/rsc";
412
+ import { routes, notFound, errors } from ${JSON.stringify(routesId)};
413
+ export { routes, notFound, errors };
414
+ export const renderFlight = createFlightRenderer({ routes, notFound, errors });
415
+ `;
416
+ }
417
+
418
+ /**
419
+ * `virtual:uf/rsc-bridge` under `uf dev`: the rsc graph, through its runner.
420
+ *
421
+ * `renderFlight` imports the rsc entry on every call rather than once, which
422
+ * costs a map lookup when nothing changed and is what puts an edited server
423
+ * component in the next render. The route table is read once, because nothing
424
+ * under `uf dev` reads it — the driver's build is its reader.
425
+ */
426
+ export function devBridgeSource() {
427
+ return `const load = globalThis[Symbol.for(${JSON.stringify(DEV_RSC_HOOK)})];
428
+ if (typeof load !== "function") {
429
+ throw new Error(
430
+ "uf: the rsc environment is not running, so there is nothing to render a route with. " +
431
+ "This module is served by uf dev, which starts that environment first.",
432
+ );
433
+ }
434
+ export async function renderFlight(url, options) {
435
+ return (await load()).renderFlight(url, options);
436
+ }
437
+ export const { routes, notFound, errors } = await load();
438
+ `;
439
+ }
440
+
441
+ /** `virtual:uf/rsc-bridge` in a build: the rsc build's output, bundled in. */
442
+ export function builtBridgeSource(rscOutput) {
443
+ return `export { renderFlight, routes, notFound, errors } from ${JSON.stringify(rscOutput)};\n`;
444
+ }
445
+
446
+ /**
447
+ * `virtual:uf/client-manifest` in the ssr build: every client module's chunk.
448
+ *
449
+ * Keyed by absolute path, which is what the rsc build's references were written
450
+ * with. The paths stay in the server bundle; what reaches a payload, and so a
451
+ * browser, is the URL.
452
+ *
453
+ * @param {Map<string, string>} chunkUrls
454
+ */
455
+ export function clientManifestSource(chunkUrls) {
456
+ return `const urls = new Map(${JSON.stringify([...chunkUrls])});
457
+ export function clientUrl(file) {
458
+ const url = urls.get(file);
459
+ if (url == null) {
460
+ throw new Error(
461
+ "uf: the client build wrote no chunk for " + file + ", which a server component " +
462
+ "renders as a client component. Its passes disagree; run uf build again.",
463
+ );
464
+ }
465
+ return url;
466
+ }
467
+ `;
468
+ }
469
+
470
+ /** `virtual:uf/client-references` under `uf dev`: the module at a dev URL. */
471
+ export function devReferencesSource(root, base) {
472
+ return `const root = ${JSON.stringify(root)};
473
+ const base = ${JSON.stringify(base)};
474
+ const fsFileOf = ${fsFileOf.toString()};
475
+ export function loadClientModule(url) {
476
+ const pathname = url.startsWith(base) ? url.slice(base.length - 1) : url;
477
+ const file = pathname.startsWith("/@fs/") ? fsFileOf(pathname) : root + decodeURI(pathname);
478
+ return import(/* @vite-ignore */ file);
479
+ }
480
+ `;
481
+ }
482
+
483
+ /**
484
+ * `virtual:uf/client-references` in a build: the server copy at a chunk URL.
485
+ *
486
+ * One `import()` per client module, so a server bundle loads a client
487
+ * component's server copy the first time a payload names it and not before.
488
+ *
489
+ * @param {Map<string, string>} chunkUrls
490
+ */
491
+ export function builtReferencesSource(chunkUrls) {
492
+ const entries = [...chunkUrls].map(
493
+ ([file, url]) => ` [${JSON.stringify(url)}, () => import(${JSON.stringify(file)})],`,
494
+ );
495
+ return `const table = new Map([
496
+ ${entries.join("\n")}
497
+ ]);
498
+ export function loadClientModule(url) {
499
+ const load = table.get(url);
500
+ if (load == null) {
501
+ return Promise.reject(
502
+ new Error("uf: a payload named the client chunk " + url + ", and this server has no copy of it"),
503
+ );
504
+ }
505
+ return load();
506
+ }
507
+ `;
508
+ }
509
+
510
+ /**
511
+ * `virtual:uf/client` for an application React Server Components render.
512
+ *
513
+ * No route table: the browser resolves no route and imports no page. What it
514
+ * has is the application root and the payload the document carries, which
515
+ * `hydrateFlight` reads. Strict Mode and navigation are generated constants
516
+ * for the reasons `clientModuleSource` in `./routes.js` gives.
517
+ */
518
+ export function flightClientSource(appEntry, options = {}) {
519
+ const strictMode = options.strictMode === true ? ", strictMode: true" : "";
520
+ const navigation = options.navigation === "document" ? ', navigation: "document"' : "";
521
+ return `import { hydrateFlight } from "@uniflowed/router/client";
522
+ import App from ${JSON.stringify(appEntry)};
523
+ hydrateFlight({ App${strictMode}${navigation} });
524
+ `;
525
+ }
526
+
527
+ /**
528
+ * `virtual:uf/server` for an application React Server Components render.
529
+ *
530
+ * The exports and their order are `serverModuleSource`'s in `./routes.js`, and
531
+ * that comment is the argument for them. Two things differ. The renderer is
532
+ * `createDocumentRenderer`, which renders the payload the rsc graph writes
533
+ * rather than the route's modules, and adds `flight` for a browser that is
534
+ * navigating. And `routes`, `notFound` and `errors` come through the bridge,
535
+ * because the page modules they import are the rsc graph's: the driver reads a
536
+ * page's `generateStaticParams` from the graph that renders it.
537
+ */
538
+ export function flightServerSource(appEntry, routesId, actionsId) {
539
+ return `import {
540
+ createActionDispatcher,
541
+ createDispatcher,
542
+ createDocumentRenderer,
543
+ createMiddlewareRunner,
544
+ } from "@uniflowed/router/server";
545
+ import { handlers, middleware } from ${JSON.stringify(routesId)};
546
+ import { actions } from ${JSON.stringify(actionsId)};
547
+ import { renderFlight, routes, notFound, errors } from ${JSON.stringify(FLIGHT_VIRTUAL.bridge)};
548
+ import { loadClientModule } from ${JSON.stringify(FLIGHT_VIRTUAL.references)};
549
+ import App from ${JSON.stringify(appEntry)};
550
+ export { routes, handlers, middleware, notFound, errors };
551
+ export { beginRequest } from "@uniflowed/router/server";
552
+ const renderer = createDocumentRenderer({ App, renderFlight, loadClientModule });
553
+ export const render = renderer.render;
554
+ export const prerender = renderer.prerender;
555
+ export const flight = renderer.flight;
556
+ export { shellDocument } from "@uniflowed/router/server";
557
+ export const dispatch = createDispatcher({ handlers });
558
+ export const callAction = createActionDispatcher({ actions });
559
+ export const runMiddleware = createMiddlewareRunner({ middleware });
560
+ `;
561
+ }
562
+
563
+ /**
564
+ * The stylesheets the rsc graph has imported so far, as development URLs.
565
+ *
566
+ * Read after the route's modules have been imported — `createDocumentRenderer`
567
+ * reads a document's assets once the payload's route has resolved — so a
568
+ * layout's stylesheet is in the graph by the time its document's head is
569
+ * written. Every stylesheet the graph holds, in the order it met them, which is
570
+ * the development version of the build's rule and cascades the same way.
571
+ */
572
+ export function devStylesheets(server) {
573
+ const environment = server.environments?.[RSC_ENVIRONMENT];
574
+ if (environment == null) return [];
575
+ const { root, base } = server.config;
576
+ const urls = [];
577
+ for (const [id, module] of environment.moduleGraph.idToModuleMap) {
578
+ if (id.includes("?") || !isCSSRequest(cleanId(id))) continue;
579
+ // From the file rather than the graph's own `url`, which is not the URL the
580
+ // browser can fetch for a stylesheet outside the project — a workspace
581
+ // package's, which Vite serves under `/@fs/` — and the same rule a client
582
+ // reference's URL follows. A stylesheet with no file is a virtual one.
583
+ urls.push(
584
+ typeof module.file === "string" && module.file !== ""
585
+ ? devUrlOf(root, base, module.file)
586
+ : `${base.replace(/\/$/, "")}/@id/${id.replace(/\0/g, "__x00__")}`,
587
+ );
588
+ }
589
+ return urls;
590
+ }
591
+
592
+ /**
593
+ * `head` with a stylesheet link for each of `hrefs`, before `</head>` when the
594
+ * head is closed and at its end when it is not.
595
+ *
596
+ * The document opening a development server transforms may stop inside the
597
+ * head, which is why "at its end" is an answer rather than an error.
598
+ */
599
+ export function linkStylesheets(head, hrefs) {
600
+ if (hrefs.length === 0) return head;
601
+ const links = hrefs
602
+ .map(
603
+ (href) =>
604
+ `<link rel="stylesheet" href="${href.replace(/&/g, "&amp;").replace(/"/g, "&quot;")}">`,
605
+ )
606
+ .join("");
607
+ const close = head.search(/<\/head>/i);
608
+ return close === -1 ? `${head}${links}` : `${head.slice(0, close)}${links}${head.slice(close)}`;
609
+ }
610
+
611
+ /** A module's path as an error names it: project-relative when it can be. */
612
+ function projectPath(root, file) {
613
+ const relative = path.relative(root, file);
614
+ return relative.startsWith("..") || path.isAbsolute(relative)
615
+ ? file
616
+ : relative.split(path.sep).join("/");
617
+ }
618
+
619
+ function cleanId(id) {
620
+ const at = id.indexOf("?");
621
+ return at === -1 ? id : id.slice(0, at);
622
+ }
@@ -0,0 +1,33 @@
1
+ // @noflow
2
+ //
3
+ // A document's YAML front matter, as `export const frontmatter`.
4
+ //
5
+ // Plain JavaScript, for the reason `index.js` gives: Vite imports this before
6
+ // any transform runs.
7
+ //
8
+ // This is `remark-mdx-frontmatter` for the one format uf reads, and it replaces
9
+ // that package for one reason. The package imported `toml` 3.0.0 at the top of
10
+ // its module whether or not a document held any TOML, so every project uf
11
+ // scaffolded installed a parser with two high advisories and failed its first
12
+ // `uf audit` (#1009). The TOML half was never reachable from here either:
13
+ // `remark-frontmatter` is given its default, which recognises YAML and nothing
14
+ // else, so a `+++` block was never a front-matter node to parse.
15
+ //
16
+ // For YAML the output is the package's, through the same two helpers it used:
17
+ // the first `yaml` node is parsed and defined as `frontmatter`, and a document
18
+ // with none exports `undefined`.
19
+
20
+ import { valueToEstree } from "estree-util-value-to-estree";
21
+ import { define } from "unist-util-mdx-define";
22
+ import { parse } from "yaml";
23
+
24
+ /** The remark plugin: `export const frontmatter` from a document's YAML. */
25
+ export default function remarkFrontmatterExport() {
26
+ return (tree, file) => {
27
+ const node = tree.children.find((child) => child.type === "yaml");
28
+ const data = node == null ? undefined : parse(node.value);
29
+ define(tree, file, {
30
+ frontmatter: valueToEstree(data, { preserveReferences: true }),
31
+ });
32
+ };
33
+ }