@uniflowed/vite 0.0.0-alpha.34 → 0.0.0-alpha.37
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/driver.js +537 -48
- package/index.js +327 -24
- package/internal/barrel-imports.js +459 -0
- package/internal/flight.js +736 -0
- package/internal/frontmatter.js +33 -0
- package/internal/http.js +21 -2
- package/internal/module-graph.js +134 -0
- package/internal/routes.js +55 -2
- package/internal/serve.js +206 -14
- package/internal/server-components.js +109 -0
- package/internal/worker-builtins.js +170 -0
- package/package.json +9 -7
package/index.js
CHANGED
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
// markdown, front matter, heading ids and build-time syntax
|
|
29
29
|
// highlighting, so `.mdx` works with
|
|
30
30
|
// no configuration.
|
|
31
|
+
// * `uf:barrel-imports` — a named import from `@uniflowed/ui` becomes an import
|
|
32
|
+
// from the file that defines the name, in every environment,
|
|
33
|
+
// so a page using one component loads that component's module
|
|
34
|
+
// rather than every module the barrel re-exports. See
|
|
35
|
+
// `internal/barrel-imports.js`.
|
|
31
36
|
// * `uf:asset` — an imported image is decoded, resized to the widths the
|
|
32
37
|
// project declares and re-encoded by `uf assets`, and an
|
|
33
38
|
// imported font is self-hosted with the `@font-face` and the
|
|
@@ -53,11 +58,13 @@ import {
|
|
|
53
58
|
auditTag,
|
|
54
59
|
} from "./internal/a11y.js";
|
|
55
60
|
import { assetPlugin } from "./internal/assets.js";
|
|
61
|
+
import { barrelImportsPlugin, namespaceViewOf } from "./internal/barrel-imports.js";
|
|
56
62
|
import { emit, reportRenderError } from "./internal/events.js";
|
|
63
|
+
import remarkFrontmatterExport from "./internal/frontmatter.js";
|
|
57
64
|
import { highlightPlugin } from "./internal/highlight.js";
|
|
65
|
+
import { moduleId } from "./internal/module-graph.js";
|
|
58
66
|
import remarkFrontmatter from "remark-frontmatter";
|
|
59
67
|
import remarkGfm from "remark-gfm";
|
|
60
|
-
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
|
|
61
68
|
|
|
62
69
|
import {
|
|
63
70
|
RUNTIME_PUBLIC_PATH,
|
|
@@ -82,18 +89,49 @@ import {
|
|
|
82
89
|
clientModuleSource,
|
|
83
90
|
resolveRouteTarget,
|
|
84
91
|
routesModuleSource,
|
|
92
|
+
routingRulesOf,
|
|
85
93
|
scanRoutes,
|
|
86
94
|
serverModuleSource,
|
|
87
95
|
} from "./internal/routes.js";
|
|
88
96
|
import { TransformService, isFlowModule } from "@uniflowed/host/transform";
|
|
97
|
+
import {
|
|
98
|
+
DEV_RSC_HOOK,
|
|
99
|
+
FLIGHT_BROWSER_DEPENDENCIES,
|
|
100
|
+
FLIGHT_VIRTUAL,
|
|
101
|
+
RSC_ENVIRONMENT,
|
|
102
|
+
builtBridgeSource,
|
|
103
|
+
builtReferencesSource,
|
|
104
|
+
clientManifestSource,
|
|
105
|
+
clientModuleUrlPlugin,
|
|
106
|
+
clientReferencePlugin,
|
|
107
|
+
compilerRuntimeSource,
|
|
108
|
+
createFlightState,
|
|
109
|
+
devBridgeSource,
|
|
110
|
+
devReferencesSource,
|
|
111
|
+
devStylesheets,
|
|
112
|
+
flightClientSource,
|
|
113
|
+
flightDocumentPath,
|
|
114
|
+
flightServerSource,
|
|
115
|
+
linkStylesheets,
|
|
116
|
+
rendersFlight,
|
|
117
|
+
rscEntrySource,
|
|
118
|
+
rscEnvironment,
|
|
119
|
+
} from "./internal/flight.js";
|
|
89
120
|
import { createChannelMiddleware } from "./internal/diagnostics.js";
|
|
90
121
|
import { devtoolsPreamble } from "./internal/devtools.js";
|
|
91
|
-
import { send, toRequest } from "./internal/http.js";
|
|
92
|
-
import {
|
|
122
|
+
import { send, toAddressRequest, toRequest } from "./internal/http.js";
|
|
123
|
+
import {
|
|
124
|
+
answerRouting,
|
|
125
|
+
answersInFrontOfFiles,
|
|
126
|
+
beginRequest,
|
|
127
|
+
forViteBase,
|
|
128
|
+
rewriteRouting,
|
|
129
|
+
} from "./internal/serve.js";
|
|
130
|
+
import { serverComponentsProblem } from "./internal/server-components.js";
|
|
93
131
|
|
|
94
132
|
/** A resolved virtual id: Vite's convention is a leading NUL byte. */
|
|
95
133
|
const resolved = (id) => `\0${id}`;
|
|
96
|
-
const VIRTUAL_IDS = new Set(Object.values(VIRTUAL));
|
|
134
|
+
const VIRTUAL_IDS = new Set([...Object.values(VIRTUAL), ...Object.values(FLIGHT_VIRTUAL)]);
|
|
97
135
|
|
|
98
136
|
/**
|
|
99
137
|
* Prefix of the virtual module that carries one source module's StyleX rules.
|
|
@@ -152,8 +190,21 @@ export default function uniflowed(options = {}) {
|
|
|
152
190
|
// and a driver started by hand on a config `uf` never validated gets the same
|
|
153
191
|
// answer for the same reason a project would want.
|
|
154
192
|
const mount = (app.rendering?.modes ?? []).includes("csr") ? "render" : "hydrate";
|
|
193
|
+
// Whether routes render as React Server Components, which is the default: a
|
|
194
|
+
// second module graph resolved under `react-server`, a document carrying the
|
|
195
|
+
// Flight payload it was rendered from, and a browser that hydrates that
|
|
196
|
+
// payload rather than importing routes. `app.rsc: false` is the application
|
|
197
|
+
// rendered from its modules, as every uf application was before
|
|
198
|
+
// ubugeeei-prod/uf#519. See `./internal/flight.js`.
|
|
199
|
+
const flightState = rendersFlight(app, { mount, routeTarget })
|
|
200
|
+
? createFlightState({ root: options.root ?? process.cwd() })
|
|
201
|
+
: null;
|
|
155
202
|
|
|
156
203
|
const accessibility = ufConfig.accessibility ?? {};
|
|
204
|
+
// `app.router.redirects`, `rewrites` and `headers`: written into the server
|
|
205
|
+
// bundle for every host that serves a build, and asked by `uf dev` itself
|
|
206
|
+
// from the same object. See `@uniflowed/server`'s `internal/routing.js`.
|
|
207
|
+
const routing = routingRulesOf(app.router);
|
|
157
208
|
|
|
158
209
|
return [
|
|
159
210
|
flowPlugin({
|
|
@@ -163,9 +214,15 @@ export default function uniflowed(options = {}) {
|
|
|
163
214
|
strictMode,
|
|
164
215
|
navigation,
|
|
165
216
|
mount,
|
|
217
|
+
flightState,
|
|
218
|
+
routing,
|
|
166
219
|
command: options.command,
|
|
167
220
|
accessibility,
|
|
168
221
|
}),
|
|
222
|
+
...(flightState == null ? [] : [clientReferencePlugin(flightState), clientModuleUrlPlugin()]),
|
|
223
|
+
// After the references, so a client module the rsc graph has already
|
|
224
|
+
// replaced is not read for imports it no longer has; see the file.
|
|
225
|
+
barrelImportsPlugin(),
|
|
169
226
|
mdxPlugin(markdown),
|
|
170
227
|
assetPlugin({
|
|
171
228
|
images: builtins.images ?? {},
|
|
@@ -184,6 +241,8 @@ function flowPlugin({
|
|
|
184
241
|
strictMode,
|
|
185
242
|
navigation,
|
|
186
243
|
mount,
|
|
244
|
+
flightState,
|
|
245
|
+
routing,
|
|
187
246
|
command,
|
|
188
247
|
accessibility,
|
|
189
248
|
}) {
|
|
@@ -314,10 +373,26 @@ function flowPlugin({
|
|
|
314
373
|
return {
|
|
315
374
|
name: "uf:flow",
|
|
316
375
|
enforce: "pre",
|
|
376
|
+
// What `driver.js` runs the three builds with; `null` for an application
|
|
377
|
+
// rendered from its modules. See `./internal/flight.js`.
|
|
378
|
+
api: { flight: flightState },
|
|
317
379
|
|
|
318
380
|
config(userConfig, env) {
|
|
319
381
|
const projectRoot = path.resolve(userConfig.root ?? process.cwd());
|
|
382
|
+
// Before anything is resolved. Routes that render as React Server
|
|
383
|
+
// Components need `react-server-dom-parcel` and React 19.3, and the router
|
|
384
|
+
// installs without either (ubugeeei-prod/uf#992). Said here, once, rather
|
|
385
|
+
// than as an unresolved import deep in a build or a failure inside a render.
|
|
386
|
+
if (flightState != null) {
|
|
387
|
+
const problem = serverComponentsProblem(projectRoot);
|
|
388
|
+
if (problem != null) {
|
|
389
|
+
throw new Error(problem);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
320
392
|
isProduction = env.mode === "production" || env.command === "build";
|
|
393
|
+
// A reference names the client manifest only in a build, where a client
|
|
394
|
+
// build writes the chunks it names; a dev server names the URL it serves.
|
|
395
|
+
if (flightState != null) flightState.production = env.command === "build";
|
|
321
396
|
// Decided here rather than in `configResolved`, because the answer has to
|
|
322
397
|
// reach `optimizeDeps.include` below and that is written in this hook.
|
|
323
398
|
auditsPage =
|
|
@@ -336,6 +411,9 @@ function flowPlugin({
|
|
|
336
411
|
"react/compiler-runtime",
|
|
337
412
|
"react-dom",
|
|
338
413
|
"react-dom/client",
|
|
414
|
+
// React's Flight client, for an application whose routes render as
|
|
415
|
+
// Server Components; `FLIGHT_BROWSER_DEPENDENCIES` says why.
|
|
416
|
+
...(flightState == null ? [] : FLIGHT_BROWSER_DEPENDENCIES),
|
|
339
417
|
// The accessibility audit's engine, when this project has one.
|
|
340
418
|
//
|
|
341
419
|
// Named up front rather than left to be discovered. axe-core is
|
|
@@ -357,12 +435,29 @@ function flowPlugin({
|
|
|
357
435
|
// externalised.
|
|
358
436
|
noExternal: [/^@uniflowed\//],
|
|
359
437
|
},
|
|
438
|
+
// The graph React Server Components render in, beside the two Vite
|
|
439
|
+
// always has — declared only for an application that renders them, so
|
|
440
|
+
// `app.rsc: false` is a Vite configuration with nothing added.
|
|
441
|
+
...(flightState == null
|
|
442
|
+
? {}
|
|
443
|
+
: {
|
|
444
|
+
environments: {
|
|
445
|
+
[RSC_ENVIRONMENT]: rscEnvironment({
|
|
446
|
+
production: env.command === "build",
|
|
447
|
+
exclude: uniflowedPackages(projectRoot),
|
|
448
|
+
}),
|
|
449
|
+
},
|
|
450
|
+
}),
|
|
360
451
|
};
|
|
361
452
|
},
|
|
362
453
|
|
|
363
454
|
configResolved(config) {
|
|
364
455
|
root = config.root;
|
|
365
456
|
base = config.base;
|
|
457
|
+
if (flightState != null) {
|
|
458
|
+
flightState.root = config.root;
|
|
459
|
+
flightState.base = config.base;
|
|
460
|
+
}
|
|
366
461
|
appRoot = path.resolve(root, routerRoot);
|
|
367
462
|
entryPath = path.resolve(root, appEntry);
|
|
368
463
|
},
|
|
@@ -372,11 +467,42 @@ function flowPlugin({
|
|
|
372
467
|
},
|
|
373
468
|
|
|
374
469
|
resolveId(id, importer, resolveOptions) {
|
|
375
|
-
|
|
470
|
+
// The client's graph, and the rsc graph too. `@uniflowed/react` is
|
|
471
|
+
// `export * from "react"`, and the rsc graph pre-bundles React's CommonJS
|
|
472
|
+
// under `react-server`: a star re-export of that namespace names nothing
|
|
473
|
+
// Vite's module runner can forward, so under `uf dev` every hook a server
|
|
474
|
+
// component imported from `@uniflowed/react` was `undefined` — `use` first.
|
|
475
|
+
// Importing `react` itself is the module those names are on. The ssr graph
|
|
476
|
+
// keeps its own resolution, because React is external there and Node's
|
|
477
|
+
// interop forwards the names.
|
|
478
|
+
if (
|
|
479
|
+
id === "@uniflowed/react" &&
|
|
480
|
+
(this.environment?.name === RSC_ENVIRONMENT || !isSsr(this, resolveOptions))
|
|
481
|
+
) {
|
|
376
482
|
return this.resolve("react", importer, { ...resolveOptions, skipSelf: true });
|
|
377
483
|
}
|
|
378
484
|
if (id === RUNTIME_PUBLIC_PATH) return RUNTIME_RESOLVED_ID;
|
|
379
485
|
if (id === AUDIT_PUBLIC_PATH) return AUDIT_RESOLVED_ID;
|
|
486
|
+
// The rsc build cannot know a client chunk's URL, because the client
|
|
487
|
+
// build that writes the chunks runs after it. It leaves the manifest as an
|
|
488
|
+
// import, and the ssr build — which bundles the rsc output in — resolves it.
|
|
489
|
+
if (
|
|
490
|
+
id === FLIGHT_VIRTUAL.manifest &&
|
|
491
|
+
flightState?.production === true &&
|
|
492
|
+
this.environment?.name === RSC_ENVIRONMENT
|
|
493
|
+
) {
|
|
494
|
+
return { id, external: true };
|
|
495
|
+
}
|
|
496
|
+
// The React Compiler's runtime, which reads the client's internals and so
|
|
497
|
+
// cannot run where `react` resolved under `react-server`; see
|
|
498
|
+
// `compilerRuntimeSource`.
|
|
499
|
+
if (
|
|
500
|
+
id === "react/compiler-runtime" &&
|
|
501
|
+
flightState != null &&
|
|
502
|
+
this.environment?.name === RSC_ENVIRONMENT
|
|
503
|
+
) {
|
|
504
|
+
return resolved(FLIGHT_VIRTUAL.compilerRuntime);
|
|
505
|
+
}
|
|
380
506
|
if (VIRTUAL_IDS.has(id)) return resolved(id);
|
|
381
507
|
// A module's own stylesheet, which `transform` below asked for by
|
|
382
508
|
// importing this id. Returning it unchanged marks it resolved without
|
|
@@ -390,6 +516,18 @@ function flowPlugin({
|
|
|
390
516
|
if (id === AUDIT_RESOLVED_ID) return auditRuntimeSource(accessibility?.axe);
|
|
391
517
|
if (id === resolved(VIRTUAL.routes)) {
|
|
392
518
|
const table = scanRoutes(appRoot, { target: routeTarget });
|
|
519
|
+
// Under React Server Components the table is split by graph rather than
|
|
520
|
+
// filtered. The rsc graph renders routes, so it gets every route and
|
|
521
|
+
// boundary and no handler or middleware: those answer a request, and
|
|
522
|
+
// importing one here would resolve its dependencies under
|
|
523
|
+
// `react-server` for nothing. The ssr graph gets exactly those two,
|
|
524
|
+
// because every route it renders reaches it as a payload.
|
|
525
|
+
if (flightState != null && this.environment?.name === RSC_ENVIRONMENT) {
|
|
526
|
+
return routesModuleSource({ ...table, handlers: [], middleware: [] });
|
|
527
|
+
}
|
|
528
|
+
if (flightState != null && isSsr(this, loadOptions)) {
|
|
529
|
+
return routesModuleSource({ ...table, routes: [], notFound: [], errors: [] });
|
|
530
|
+
}
|
|
393
531
|
// The server renders every route, so the server's table is the whole
|
|
394
532
|
// one and is generated with no filter at all. Only the browser's copy
|
|
395
533
|
// is split.
|
|
@@ -404,14 +542,48 @@ function flowPlugin({
|
|
|
404
542
|
// deliberately: it is what a link does, and a dev server whose links
|
|
405
543
|
// behave differently from the deployment is the wrong thing to be
|
|
406
544
|
// looking at. See `clientModuleSource`.
|
|
545
|
+
if (id === resolved(VIRTUAL.client) && flightState != null) {
|
|
546
|
+
return flightClientSource(entryPath, {
|
|
547
|
+
strictMode: strictMode && !isProduction,
|
|
548
|
+
navigation,
|
|
549
|
+
routing,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
407
552
|
if (id === resolved(VIRTUAL.client)) {
|
|
408
553
|
return clientModuleSource(entryPath, {
|
|
409
554
|
strictMode: strictMode && !isProduction,
|
|
410
555
|
navigation,
|
|
411
556
|
mount,
|
|
557
|
+
routing,
|
|
412
558
|
});
|
|
413
559
|
}
|
|
414
|
-
if (id === resolved(VIRTUAL.server))
|
|
560
|
+
if (id === resolved(VIRTUAL.server)) {
|
|
561
|
+
return flightState == null
|
|
562
|
+
? serverModuleSource(entryPath, routing)
|
|
563
|
+
: flightServerSource(entryPath, VIRTUAL.routes, VIRTUAL.actions, routing);
|
|
564
|
+
}
|
|
565
|
+
if (flightState != null) {
|
|
566
|
+
if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes, routing);
|
|
567
|
+
if (id === resolved(FLIGHT_VIRTUAL.compilerRuntime)) return compilerRuntimeSource();
|
|
568
|
+
if (id === resolved(FLIGHT_VIRTUAL.bridge)) {
|
|
569
|
+
if (server != null) return devBridgeSource();
|
|
570
|
+
if (flightState.rscOutput == null) {
|
|
571
|
+
throw new Error(
|
|
572
|
+
"uf: the server bundle was asked to bundle the rsc graph before it was built. " +
|
|
573
|
+
"`uf build` builds `virtual:uf/rsc` first; a build started some other way has to as well.",
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return builtBridgeSource(flightState.rscOutput);
|
|
577
|
+
}
|
|
578
|
+
if (id === resolved(FLIGHT_VIRTUAL.manifest)) {
|
|
579
|
+
return clientManifestSource(flightState.chunkUrls);
|
|
580
|
+
}
|
|
581
|
+
if (id === resolved(FLIGHT_VIRTUAL.references)) {
|
|
582
|
+
return server != null
|
|
583
|
+
? devReferencesSource(root, base)
|
|
584
|
+
: builtReferencesSource(flightState.chunkUrls);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
415
587
|
// Only `virtual:uf/server` imports this, so it is only ever asked for in
|
|
416
588
|
// the server environment — but the table it carries is every callable
|
|
417
589
|
// endpoint of the build, so it is worth saying that a browser asking for
|
|
@@ -438,7 +610,10 @@ function flowPlugin({
|
|
|
438
610
|
// and an import of anything else is a build error rather than a silent
|
|
439
611
|
// `undefined`. `crates/uf_rsc/src/graph/build.rs` colours these modules
|
|
440
612
|
// server for the same reason, so the analysis and the bundle agree.
|
|
441
|
-
|
|
613
|
+
//
|
|
614
|
+
// Nor in the rsc graph: a server component that calls an action is calling
|
|
615
|
+
// a function on the server, and it gets the function.
|
|
616
|
+
if (!isSsr(this, loadOptions) && this.environment?.name !== RSC_ENVIRONMENT) {
|
|
442
617
|
const references = actionTables().modules.get(cleanId(id));
|
|
443
618
|
if (references != null) return actionReferenceSource(references);
|
|
444
619
|
}
|
|
@@ -446,8 +621,13 @@ function flowPlugin({
|
|
|
446
621
|
},
|
|
447
622
|
|
|
448
623
|
async transform(code, id, transformOptions) {
|
|
449
|
-
|
|
450
|
-
|
|
624
|
+
// A view of a barrel's namespace has the barrel's path and none of its
|
|
625
|
+
// source: `uf:barrel-imports` generates it as JavaScript.
|
|
626
|
+
if (!isFlowModule(id) || namespaceViewOf(id) != null) return null;
|
|
627
|
+
// Both server graphs: neither gets a refresh wrapper, and the rsc graph's
|
|
628
|
+
// findings are reported as that graph's.
|
|
629
|
+
const rsc = this.environment?.name === RSC_ENVIRONMENT;
|
|
630
|
+
const ssr = transformOptions?.ssr === true || this.environment?.name === "ssr" || rsc;
|
|
451
631
|
const refresh = !isProduction && !ssr && server != null;
|
|
452
632
|
const out = await ensureService().transform(cleanId(id), code, {
|
|
453
633
|
development: !isProduction,
|
|
@@ -459,7 +639,7 @@ function flowPlugin({
|
|
|
459
639
|
id: cleanId(id),
|
|
460
640
|
root,
|
|
461
641
|
diagnostics: out.diagnostics,
|
|
462
|
-
environment: ssr ? "ssr" : "client",
|
|
642
|
+
environment: rsc ? RSC_ENVIRONMENT : ssr ? "ssr" : "client",
|
|
463
643
|
reported,
|
|
464
644
|
suppressed,
|
|
465
645
|
});
|
|
@@ -473,10 +653,17 @@ function flowPlugin({
|
|
|
473
653
|
// business: Vite already injects a stylesheet in dev, extracts it in a
|
|
474
654
|
// build, code-splits it per chunk, and replaces it over HMR. A module
|
|
475
655
|
// whose styles are gone stops importing it, and Vite notices.
|
|
656
|
+
//
|
|
657
|
+
// The stylesheet is named by the module's path from the project root, the
|
|
658
|
+
// spelling `moduleId` gives the module graph report, and not by its
|
|
659
|
+
// absolute path. In a client chunk the stylesheet is one of the chunk's
|
|
660
|
+
// sources, so its name is written into the source map a site publishes,
|
|
661
|
+
// and an absolute name would publish where the machine that built it
|
|
662
|
+
// keeps its files. A module outside the root climbs out with `../`.
|
|
476
663
|
const styled = out.css != null && out.css !== "";
|
|
477
664
|
let output = out.code;
|
|
478
665
|
if (styled) {
|
|
479
|
-
const styleId = `${STYLE_PREFIX}${cleanId(id)}.css`;
|
|
666
|
+
const styleId = `${STYLE_PREFIX}${moduleId(root, cleanId(id))}.css`;
|
|
480
667
|
styles.set(styleId, out.css);
|
|
481
668
|
output = `import ${JSON.stringify(styleId)};\n${output}`;
|
|
482
669
|
}
|
|
@@ -546,13 +733,68 @@ function flowPlugin({
|
|
|
546
733
|
return tags;
|
|
547
734
|
},
|
|
548
735
|
|
|
736
|
+
// An edit to a server component changes what the rsc graph renders and no
|
|
737
|
+
// module the browser holds, so Vite has nothing to tell the browser. It is
|
|
738
|
+
// reloaded, which renders the edit; an edit to a client module — which the
|
|
739
|
+
// rsc graph only holds references to — is left to Fast Refresh.
|
|
740
|
+
hotUpdate({ modules }) {
|
|
741
|
+
if (flightState == null || server == null) return;
|
|
742
|
+
if (this.environment?.name !== RSC_ENVIRONMENT) return;
|
|
743
|
+
const serverSide = modules.some((module) => {
|
|
744
|
+
const file = module.file ?? cleanId(module.id ?? "");
|
|
745
|
+
return file !== "" && !flightState.clientModules.has(file);
|
|
746
|
+
});
|
|
747
|
+
if (serverSide) server.environments.client.hot.send({ type: "full-reload", path: "*" });
|
|
748
|
+
},
|
|
749
|
+
|
|
549
750
|
configureServer(devServer) {
|
|
550
751
|
server = devServer;
|
|
752
|
+
// The ssr graph's way into the rsc graph; see `devBridgeSource`.
|
|
753
|
+
if (flightState != null) {
|
|
754
|
+
globalThis[Symbol.for(DEV_RSC_HOOK)] = () =>
|
|
755
|
+
devServer.environments[RSC_ENVIRONMENT].runner.import(FLIGHT_VIRTUAL.entry);
|
|
756
|
+
}
|
|
551
757
|
devServer.httpServer?.once("close", () => {
|
|
552
758
|
service?.close();
|
|
553
759
|
service = null;
|
|
554
760
|
});
|
|
555
761
|
|
|
762
|
+
// `app.router.headers` and `redirects`, in front of Vite's own middleware
|
|
763
|
+
// — the hook's body runs before those are installed — so a redirect
|
|
764
|
+
// answers before `public/` is looked in and a header reaches a file Vite
|
|
765
|
+
// serves, as both do in front of the static half of every other door.
|
|
766
|
+
// Vite's module server is left alone: `/@vite/client` and `/@fs/…` are
|
|
767
|
+
// development plumbing no deployment has, and `/__uf/` is the browser's
|
|
768
|
+
// channel back. Not mounted at all for a project with neither list.
|
|
769
|
+
if (answersInFrontOfFiles(routing)) {
|
|
770
|
+
devServer.middlewares.use((request, response, next) => {
|
|
771
|
+
const url = request.url ?? "/";
|
|
772
|
+
// Vite serves its own paths under the base path too, so they are
|
|
773
|
+
// recognised once it is taken off.
|
|
774
|
+
const under =
|
|
775
|
+
routing.basePath !== "" && url.startsWith(`${routing.basePath}/`)
|
|
776
|
+
? url.slice(routing.basePath.length)
|
|
777
|
+
: url;
|
|
778
|
+
if (
|
|
779
|
+
under.startsWith("/@") ||
|
|
780
|
+
under.startsWith("/node_modules/") ||
|
|
781
|
+
under.startsWith("/__uf/")
|
|
782
|
+
) {
|
|
783
|
+
next();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
answerRouting(routing, toAddressRequest(request), response)
|
|
787
|
+
.then((answered) => {
|
|
788
|
+
if (answered) return;
|
|
789
|
+
// The bare base path is the root, which Vite only knows as
|
|
790
|
+
// `/docs/`; see `forViteBase`.
|
|
791
|
+
forViteBase(routing, request);
|
|
792
|
+
next();
|
|
793
|
+
})
|
|
794
|
+
.catch(next);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
556
798
|
// A reserved file appearing or disappearing changes the route table,
|
|
557
799
|
// which lives in a virtual module the watcher knows nothing about.
|
|
558
800
|
//
|
|
@@ -664,14 +906,26 @@ function flowPlugin({
|
|
|
664
906
|
// `request.url` and not `originalUrl`, which is the URL Vite's base
|
|
665
907
|
// middleware has already stripped the base from — and the route
|
|
666
908
|
// table's paths have no base in them either.
|
|
667
|
-
|
|
909
|
+
//
|
|
910
|
+
// `let`, because a rewrite moves it: from here on it is the address
|
|
911
|
+
// the application answers for, which is what the render, the payload
|
|
912
|
+
// and the document test below all have to agree on.
|
|
913
|
+
let url = request.url ?? "/";
|
|
668
914
|
// Declared out here so the catch below can still settle: a request
|
|
669
915
|
// that failed is a request that happened, and a middleware that
|
|
670
916
|
// logged its arrival is owed its callback either way.
|
|
671
917
|
let lifecycle = null;
|
|
672
918
|
try {
|
|
673
919
|
const entry = await importServerEntry(devServer);
|
|
674
|
-
|
|
920
|
+
// At `request.url`, which Vite's base middleware has already taken
|
|
921
|
+
// `app.router.basePath` off: the application path, as every other
|
|
922
|
+
// front door hands the application once it has admitted a request.
|
|
923
|
+
const arrived = await toRequest(request, devServer.config, url);
|
|
924
|
+
// `app.router.rewrites`, where `createFetchHandler` applies them for
|
|
925
|
+
// every other door: after the files Vite already served, before the
|
|
926
|
+
// guard — so the guard that runs is the destination's.
|
|
927
|
+
let asRequest = (await rewriteRouting(entry.routing, arrived)) ?? arrived;
|
|
928
|
+
if (asRequest !== arrived) url = addressOf(asRequest);
|
|
675
929
|
|
|
676
930
|
// The request begins here and ends when the response has been
|
|
677
931
|
// written, which is what `after()` promises and what `uf preview`,
|
|
@@ -679,7 +933,7 @@ function flowPlugin({
|
|
|
679
933
|
// logs a response's status has to mean the same thing in
|
|
680
934
|
// development as in production. See `internal/serve.js` and
|
|
681
935
|
// ubugeeei-prod/uf#389.
|
|
682
|
-
lifecycle = await beginRequest(entry,
|
|
936
|
+
lifecycle = await beginRequest(entry, arrived);
|
|
683
937
|
const answered = await lifecycle.run(async () => {
|
|
684
938
|
// Before anything answers: a middleware guards a subtree, so it
|
|
685
939
|
// has to run for a page, for a route handler, and for a path
|
|
@@ -687,8 +941,14 @@ function flowPlugin({
|
|
|
687
941
|
// dispatcher and again inside the renderer would have left
|
|
688
942
|
// `/dashboard/typo` unguarded and run it twice for a path that
|
|
689
943
|
// is both. See ubugeeei-prod/uf#260.
|
|
944
|
+
//
|
|
945
|
+
// A `Request` back is a middleware's `rewrite()`, already past
|
|
946
|
+
// the destination's own middleware.
|
|
690
947
|
const guarded = await entry.runMiddleware(asRequest);
|
|
691
|
-
if (guarded
|
|
948
|
+
if (guarded instanceof Request) {
|
|
949
|
+
asRequest = guarded;
|
|
950
|
+
url = addressOf(guarded);
|
|
951
|
+
} else if (guarded != null) {
|
|
692
952
|
await send(response, guarded);
|
|
693
953
|
return true;
|
|
694
954
|
}
|
|
@@ -713,6 +973,29 @@ function flowPlugin({
|
|
|
713
973
|
// and a `<form action>` navigation both send `Accept:
|
|
714
974
|
// text/html`, and both want the handler's answer — which is the
|
|
715
975
|
// whole of ubugeeei-prod/uf#349.
|
|
976
|
+
// A browser that is navigating, asking for the next route's
|
|
977
|
+
// payload: after the guard and before the handlers, for the
|
|
978
|
+
// reasons `@uniflowed/server`'s `internal/flight.js` gives.
|
|
979
|
+
const payloadFor =
|
|
980
|
+
entry.flight == null ? null : flightDocumentPath(url.split("?")[0]);
|
|
981
|
+
if (payloadFor != null && (request.method === "GET" || request.method === "HEAD")) {
|
|
982
|
+
const query = url.includes("?") ? url.slice(url.indexOf("?")) : "";
|
|
983
|
+
const target = payloadFor + query;
|
|
984
|
+
const answered = await entry.flight(target, {
|
|
985
|
+
onError: (error) => reportRenderError(devServer, target, error),
|
|
986
|
+
});
|
|
987
|
+
if (answered.error != null) reportRenderError(devServer, target, answered.error);
|
|
988
|
+
if (request.method === "HEAD") await answered.stream?.cancel();
|
|
989
|
+
await send(
|
|
990
|
+
response,
|
|
991
|
+
new Response(request.method === "HEAD" ? null : answered.stream, {
|
|
992
|
+
status: answered.status,
|
|
993
|
+
headers: answered.headers,
|
|
994
|
+
}),
|
|
995
|
+
);
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
|
|
716
999
|
const handled = await entry.dispatch(asRequest);
|
|
717
1000
|
if (handled != null) {
|
|
718
1001
|
await send(response, handled);
|
|
@@ -733,7 +1016,7 @@ function flowPlugin({
|
|
|
733
1016
|
// and the project's own not-found *page* under `uf preview` and
|
|
734
1017
|
// `uf start` — a difference in the body of a 404 for a path that
|
|
735
1018
|
// is an asset request in the first place.
|
|
736
|
-
const notDocument = notADocumentBecause(request);
|
|
1019
|
+
const notDocument = notADocumentBecause(request, url);
|
|
737
1020
|
if (notDocument === "accept") {
|
|
738
1021
|
// Everything about this is a navigation except the header, and
|
|
739
1022
|
// the path is one uf renders. Say so, rather than letting the
|
|
@@ -778,7 +1061,18 @@ function flowPlugin({
|
|
|
778
1061
|
onError: (error) => reportRenderError(devServer, url, error),
|
|
779
1062
|
// Vite sees the head and only the head. That is what lets the
|
|
780
1063
|
// development server stream like every other host — see below.
|
|
781
|
-
|
|
1064
|
+
//
|
|
1065
|
+
// Under React Server Components it is also where a server
|
|
1066
|
+
// component's stylesheets are linked in development: they are
|
|
1067
|
+
// in the rsc graph, which the browser never loads, and they
|
|
1068
|
+
// enter it when the route's modules are imported — after this
|
|
1069
|
+
// call and before the head is written, which is when this
|
|
1070
|
+
// runs. See `devStylesheets`.
|
|
1071
|
+
transformHead: (head) =>
|
|
1072
|
+
devServer.transformIndexHtml(
|
|
1073
|
+
url,
|
|
1074
|
+
flightState == null ? head : linkStylesheets(head, devStylesheets(devServer)),
|
|
1075
|
+
),
|
|
782
1076
|
// And what the streaming actually did, when it changed. The
|
|
783
1077
|
// router has already decided there is something worth saying
|
|
784
1078
|
// and written the words — see its `internal/inspector.js`,
|
|
@@ -862,11 +1156,7 @@ function mdxPlugin(markdown) {
|
|
|
862
1156
|
enforce: "pre",
|
|
863
1157
|
...mdx({
|
|
864
1158
|
jsxImportSource: "react",
|
|
865
|
-
remarkPlugins: [
|
|
866
|
-
remarkGfm,
|
|
867
|
-
remarkFrontmatter,
|
|
868
|
-
[remarkMdxFrontmatter, { name: "frontmatter" }],
|
|
869
|
-
],
|
|
1159
|
+
remarkPlugins: [remarkGfm, remarkFrontmatter, remarkFrontmatterExport],
|
|
870
1160
|
rehypePlugins,
|
|
871
1161
|
}),
|
|
872
1162
|
name: "uf:mdx",
|
|
@@ -891,9 +1181,23 @@ async function importServerEntry(devServer) {
|
|
|
891
1181
|
return devServer.ssrLoadModule(VIRTUAL.server);
|
|
892
1182
|
}
|
|
893
1183
|
|
|
1184
|
+
/**
|
|
1185
|
+
* The path and query a `Request` is for, which is what the dev server's
|
|
1186
|
+
* renderer and payload test are keyed on.
|
|
1187
|
+
*
|
|
1188
|
+
* @param {Request} request
|
|
1189
|
+
*/
|
|
1190
|
+
function addressOf(request) {
|
|
1191
|
+
const url = new URL(request.url);
|
|
1192
|
+
return url.pathname + url.search;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
894
1195
|
/**
|
|
895
1196
|
* Why `request` is not a document request, or `null` when it is one.
|
|
896
1197
|
*
|
|
1198
|
+
* `url` is the address the application answers for, which a rewrite may have
|
|
1199
|
+
* moved away from the one the request line named.
|
|
1200
|
+
*
|
|
897
1201
|
* A reason rather than a boolean because one of the four is worth saying out
|
|
898
1202
|
* loud. Three of them mean the request belongs to somebody else — Vite's module
|
|
899
1203
|
* server, a static file, or a method a page cannot answer — and handing it back
|
|
@@ -905,9 +1209,8 @@ async function importServerEntry(devServer) {
|
|
|
905
1209
|
* confused: `/favicon.svg` with `Accept: *\/*` is an asset, not a navigation
|
|
906
1210
|
* with the wrong header, and still goes back to Vite's chain untouched.
|
|
907
1211
|
*/
|
|
908
|
-
function notADocumentBecause(request) {
|
|
1212
|
+
function notADocumentBecause(request, url = request.url ?? "/") {
|
|
909
1213
|
if (request.method !== "GET" && request.method !== "HEAD") return "method";
|
|
910
|
-
const url = request.url ?? "/";
|
|
911
1214
|
if (url.startsWith("/@") || url.startsWith("/node_modules/")) return "module-server";
|
|
912
1215
|
const pathname = url.split("?")[0];
|
|
913
1216
|
// A request for a file — `/favicon.svg`, `/assets/x.js` — that no static
|