@uniflowed/vite 0.0.0-alpha.33 → 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.
- package/driver.js +288 -12
- package/index.js +199 -13
- package/internal/flight.js +622 -0
- package/internal/frontmatter.js +33 -0
- package/internal/routes.js +343 -76
- package/internal/rsc.js +7 -1
- package/internal/serve.js +62 -16
- package/internal/worker-builtins.js +170 -0
- package/package.json +9 -7
package/index.js
CHANGED
|
@@ -54,10 +54,10 @@ import {
|
|
|
54
54
|
} from "./internal/a11y.js";
|
|
55
55
|
import { assetPlugin } from "./internal/assets.js";
|
|
56
56
|
import { emit, reportRenderError } from "./internal/events.js";
|
|
57
|
+
import remarkFrontmatterExport from "./internal/frontmatter.js";
|
|
57
58
|
import { highlightPlugin } from "./internal/highlight.js";
|
|
58
59
|
import remarkFrontmatter from "remark-frontmatter";
|
|
59
60
|
import remarkGfm from "remark-gfm";
|
|
60
|
-
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
|
|
61
61
|
|
|
62
62
|
import {
|
|
63
63
|
RUNTIME_PUBLIC_PATH,
|
|
@@ -86,6 +86,27 @@ import {
|
|
|
86
86
|
serverModuleSource,
|
|
87
87
|
} from "./internal/routes.js";
|
|
88
88
|
import { TransformService, isFlowModule } from "@uniflowed/host/transform";
|
|
89
|
+
import {
|
|
90
|
+
DEV_RSC_HOOK,
|
|
91
|
+
FLIGHT_VIRTUAL,
|
|
92
|
+
RSC_ENVIRONMENT,
|
|
93
|
+
builtBridgeSource,
|
|
94
|
+
builtReferencesSource,
|
|
95
|
+
clientManifestSource,
|
|
96
|
+
clientReferencePlugin,
|
|
97
|
+
compilerRuntimeSource,
|
|
98
|
+
createFlightState,
|
|
99
|
+
devBridgeSource,
|
|
100
|
+
devReferencesSource,
|
|
101
|
+
devStylesheets,
|
|
102
|
+
flightClientSource,
|
|
103
|
+
flightDocumentPath,
|
|
104
|
+
flightServerSource,
|
|
105
|
+
linkStylesheets,
|
|
106
|
+
rendersFlight,
|
|
107
|
+
rscEntrySource,
|
|
108
|
+
rscEnvironment,
|
|
109
|
+
} from "./internal/flight.js";
|
|
89
110
|
import { createChannelMiddleware } from "./internal/diagnostics.js";
|
|
90
111
|
import { devtoolsPreamble } from "./internal/devtools.js";
|
|
91
112
|
import { send, toRequest } from "./internal/http.js";
|
|
@@ -93,7 +114,7 @@ import { beginRequest } from "./internal/serve.js";
|
|
|
93
114
|
|
|
94
115
|
/** A resolved virtual id: Vite's convention is a leading NUL byte. */
|
|
95
116
|
const resolved = (id) => `\0${id}`;
|
|
96
|
-
const VIRTUAL_IDS = new Set(Object.values(VIRTUAL));
|
|
117
|
+
const VIRTUAL_IDS = new Set([...Object.values(VIRTUAL), ...Object.values(FLIGHT_VIRTUAL)]);
|
|
97
118
|
|
|
98
119
|
/**
|
|
99
120
|
* Prefix of the virtual module that carries one source module's StyleX rules.
|
|
@@ -152,6 +173,15 @@ export default function uniflowed(options = {}) {
|
|
|
152
173
|
// and a driver started by hand on a config `uf` never validated gets the same
|
|
153
174
|
// answer for the same reason a project would want.
|
|
154
175
|
const mount = (app.rendering?.modes ?? []).includes("csr") ? "render" : "hydrate";
|
|
176
|
+
// Whether routes render as React Server Components, which is the default: a
|
|
177
|
+
// second module graph resolved under `react-server`, a document carrying the
|
|
178
|
+
// Flight payload it was rendered from, and a browser that hydrates that
|
|
179
|
+
// payload rather than importing routes. `app.rsc: false` is the application
|
|
180
|
+
// rendered from its modules, as every uf application was before
|
|
181
|
+
// ubugeeei-prod/uf#519. See `./internal/flight.js`.
|
|
182
|
+
const flightState = rendersFlight(app, { mount, routeTarget })
|
|
183
|
+
? createFlightState({ root: options.root ?? process.cwd() })
|
|
184
|
+
: null;
|
|
155
185
|
|
|
156
186
|
const accessibility = ufConfig.accessibility ?? {};
|
|
157
187
|
|
|
@@ -163,9 +193,11 @@ export default function uniflowed(options = {}) {
|
|
|
163
193
|
strictMode,
|
|
164
194
|
navigation,
|
|
165
195
|
mount,
|
|
196
|
+
flightState,
|
|
166
197
|
command: options.command,
|
|
167
198
|
accessibility,
|
|
168
199
|
}),
|
|
200
|
+
...(flightState == null ? [] : [clientReferencePlugin(flightState)]),
|
|
169
201
|
mdxPlugin(markdown),
|
|
170
202
|
assetPlugin({
|
|
171
203
|
images: builtins.images ?? {},
|
|
@@ -184,6 +216,7 @@ function flowPlugin({
|
|
|
184
216
|
strictMode,
|
|
185
217
|
navigation,
|
|
186
218
|
mount,
|
|
219
|
+
flightState,
|
|
187
220
|
command,
|
|
188
221
|
accessibility,
|
|
189
222
|
}) {
|
|
@@ -314,10 +347,16 @@ function flowPlugin({
|
|
|
314
347
|
return {
|
|
315
348
|
name: "uf:flow",
|
|
316
349
|
enforce: "pre",
|
|
350
|
+
// What `driver.js` runs the three builds with; `null` for an application
|
|
351
|
+
// rendered from its modules. See `./internal/flight.js`.
|
|
352
|
+
api: { flight: flightState },
|
|
317
353
|
|
|
318
354
|
config(userConfig, env) {
|
|
319
355
|
const projectRoot = path.resolve(userConfig.root ?? process.cwd());
|
|
320
356
|
isProduction = env.mode === "production" || env.command === "build";
|
|
357
|
+
// A reference names the client manifest only in a build, where a client
|
|
358
|
+
// build writes the chunks it names; a dev server names the URL it serves.
|
|
359
|
+
if (flightState != null) flightState.production = env.command === "build";
|
|
321
360
|
// Decided here rather than in `configResolved`, because the answer has to
|
|
322
361
|
// reach `optimizeDeps.include` below and that is written in this hook.
|
|
323
362
|
auditsPage =
|
|
@@ -357,12 +396,29 @@ function flowPlugin({
|
|
|
357
396
|
// externalised.
|
|
358
397
|
noExternal: [/^@uniflowed\//],
|
|
359
398
|
},
|
|
399
|
+
// The graph React Server Components render in, beside the two Vite
|
|
400
|
+
// always has — declared only for an application that renders them, so
|
|
401
|
+
// `app.rsc: false` is a Vite configuration with nothing added.
|
|
402
|
+
...(flightState == null
|
|
403
|
+
? {}
|
|
404
|
+
: {
|
|
405
|
+
environments: {
|
|
406
|
+
[RSC_ENVIRONMENT]: rscEnvironment({
|
|
407
|
+
production: env.command === "build",
|
|
408
|
+
exclude: uniflowedPackages(projectRoot),
|
|
409
|
+
}),
|
|
410
|
+
},
|
|
411
|
+
}),
|
|
360
412
|
};
|
|
361
413
|
},
|
|
362
414
|
|
|
363
415
|
configResolved(config) {
|
|
364
416
|
root = config.root;
|
|
365
417
|
base = config.base;
|
|
418
|
+
if (flightState != null) {
|
|
419
|
+
flightState.root = config.root;
|
|
420
|
+
flightState.base = config.base;
|
|
421
|
+
}
|
|
366
422
|
appRoot = path.resolve(root, routerRoot);
|
|
367
423
|
entryPath = path.resolve(root, appEntry);
|
|
368
424
|
},
|
|
@@ -372,11 +428,42 @@ function flowPlugin({
|
|
|
372
428
|
},
|
|
373
429
|
|
|
374
430
|
resolveId(id, importer, resolveOptions) {
|
|
375
|
-
|
|
431
|
+
// The client's graph, and the rsc graph too. `@uniflowed/react` is
|
|
432
|
+
// `export * from "react"`, and the rsc graph pre-bundles React's CommonJS
|
|
433
|
+
// under `react-server`: a star re-export of that namespace names nothing
|
|
434
|
+
// Vite's module runner can forward, so under `uf dev` every hook a server
|
|
435
|
+
// component imported from `@uniflowed/react` was `undefined` — `use` first.
|
|
436
|
+
// Importing `react` itself is the module those names are on. The ssr graph
|
|
437
|
+
// keeps its own resolution, because React is external there and Node's
|
|
438
|
+
// interop forwards the names.
|
|
439
|
+
if (
|
|
440
|
+
id === "@uniflowed/react" &&
|
|
441
|
+
(this.environment?.name === RSC_ENVIRONMENT || !isSsr(this, resolveOptions))
|
|
442
|
+
) {
|
|
376
443
|
return this.resolve("react", importer, { ...resolveOptions, skipSelf: true });
|
|
377
444
|
}
|
|
378
445
|
if (id === RUNTIME_PUBLIC_PATH) return RUNTIME_RESOLVED_ID;
|
|
379
446
|
if (id === AUDIT_PUBLIC_PATH) return AUDIT_RESOLVED_ID;
|
|
447
|
+
// The rsc build cannot know a client chunk's URL, because the client
|
|
448
|
+
// build that writes the chunks runs after it. It leaves the manifest as an
|
|
449
|
+
// import, and the ssr build — which bundles the rsc output in — resolves it.
|
|
450
|
+
if (
|
|
451
|
+
id === FLIGHT_VIRTUAL.manifest &&
|
|
452
|
+
flightState?.production === true &&
|
|
453
|
+
this.environment?.name === RSC_ENVIRONMENT
|
|
454
|
+
) {
|
|
455
|
+
return { id, external: true };
|
|
456
|
+
}
|
|
457
|
+
// The React Compiler's runtime, which reads the client's internals and so
|
|
458
|
+
// cannot run where `react` resolved under `react-server`; see
|
|
459
|
+
// `compilerRuntimeSource`.
|
|
460
|
+
if (
|
|
461
|
+
id === "react/compiler-runtime" &&
|
|
462
|
+
flightState != null &&
|
|
463
|
+
this.environment?.name === RSC_ENVIRONMENT
|
|
464
|
+
) {
|
|
465
|
+
return resolved(FLIGHT_VIRTUAL.compilerRuntime);
|
|
466
|
+
}
|
|
380
467
|
if (VIRTUAL_IDS.has(id)) return resolved(id);
|
|
381
468
|
// A module's own stylesheet, which `transform` below asked for by
|
|
382
469
|
// importing this id. Returning it unchanged marks it resolved without
|
|
@@ -390,6 +477,18 @@ function flowPlugin({
|
|
|
390
477
|
if (id === AUDIT_RESOLVED_ID) return auditRuntimeSource(accessibility?.axe);
|
|
391
478
|
if (id === resolved(VIRTUAL.routes)) {
|
|
392
479
|
const table = scanRoutes(appRoot, { target: routeTarget });
|
|
480
|
+
// Under React Server Components the table is split by graph rather than
|
|
481
|
+
// filtered. The rsc graph renders routes, so it gets every route and
|
|
482
|
+
// boundary and no handler or middleware: those answer a request, and
|
|
483
|
+
// importing one here would resolve its dependencies under
|
|
484
|
+
// `react-server` for nothing. The ssr graph gets exactly those two,
|
|
485
|
+
// because every route it renders reaches it as a payload.
|
|
486
|
+
if (flightState != null && this.environment?.name === RSC_ENVIRONMENT) {
|
|
487
|
+
return routesModuleSource({ ...table, handlers: [], middleware: [] });
|
|
488
|
+
}
|
|
489
|
+
if (flightState != null && isSsr(this, loadOptions)) {
|
|
490
|
+
return routesModuleSource({ ...table, routes: [], notFound: [], errors: [] });
|
|
491
|
+
}
|
|
393
492
|
// The server renders every route, so the server's table is the whole
|
|
394
493
|
// one and is generated with no filter at all. Only the browser's copy
|
|
395
494
|
// is split.
|
|
@@ -404,6 +503,12 @@ function flowPlugin({
|
|
|
404
503
|
// deliberately: it is what a link does, and a dev server whose links
|
|
405
504
|
// behave differently from the deployment is the wrong thing to be
|
|
406
505
|
// looking at. See `clientModuleSource`.
|
|
506
|
+
if (id === resolved(VIRTUAL.client) && flightState != null) {
|
|
507
|
+
return flightClientSource(entryPath, {
|
|
508
|
+
strictMode: strictMode && !isProduction,
|
|
509
|
+
navigation,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
407
512
|
if (id === resolved(VIRTUAL.client)) {
|
|
408
513
|
return clientModuleSource(entryPath, {
|
|
409
514
|
strictMode: strictMode && !isProduction,
|
|
@@ -411,7 +516,33 @@ function flowPlugin({
|
|
|
411
516
|
mount,
|
|
412
517
|
});
|
|
413
518
|
}
|
|
414
|
-
if (id === resolved(VIRTUAL.server))
|
|
519
|
+
if (id === resolved(VIRTUAL.server)) {
|
|
520
|
+
return flightState == null
|
|
521
|
+
? serverModuleSource(entryPath)
|
|
522
|
+
: flightServerSource(entryPath, VIRTUAL.routes, VIRTUAL.actions);
|
|
523
|
+
}
|
|
524
|
+
if (flightState != null) {
|
|
525
|
+
if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes);
|
|
526
|
+
if (id === resolved(FLIGHT_VIRTUAL.compilerRuntime)) return compilerRuntimeSource();
|
|
527
|
+
if (id === resolved(FLIGHT_VIRTUAL.bridge)) {
|
|
528
|
+
if (server != null) return devBridgeSource();
|
|
529
|
+
if (flightState.rscOutput == null) {
|
|
530
|
+
throw new Error(
|
|
531
|
+
"uf: the server bundle was asked to bundle the rsc graph before it was built. " +
|
|
532
|
+
"`uf build` builds `virtual:uf/rsc` first; a build started some other way has to as well.",
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return builtBridgeSource(flightState.rscOutput);
|
|
536
|
+
}
|
|
537
|
+
if (id === resolved(FLIGHT_VIRTUAL.manifest)) {
|
|
538
|
+
return clientManifestSource(flightState.chunkUrls);
|
|
539
|
+
}
|
|
540
|
+
if (id === resolved(FLIGHT_VIRTUAL.references)) {
|
|
541
|
+
return server != null
|
|
542
|
+
? devReferencesSource(root, base)
|
|
543
|
+
: builtReferencesSource(flightState.chunkUrls);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
415
546
|
// Only `virtual:uf/server` imports this, so it is only ever asked for in
|
|
416
547
|
// the server environment — but the table it carries is every callable
|
|
417
548
|
// endpoint of the build, so it is worth saying that a browser asking for
|
|
@@ -438,7 +569,10 @@ function flowPlugin({
|
|
|
438
569
|
// and an import of anything else is a build error rather than a silent
|
|
439
570
|
// `undefined`. `crates/uf_rsc/src/graph/build.rs` colours these modules
|
|
440
571
|
// server for the same reason, so the analysis and the bundle agree.
|
|
441
|
-
|
|
572
|
+
//
|
|
573
|
+
// Nor in the rsc graph: a server component that calls an action is calling
|
|
574
|
+
// a function on the server, and it gets the function.
|
|
575
|
+
if (!isSsr(this, loadOptions) && this.environment?.name !== RSC_ENVIRONMENT) {
|
|
442
576
|
const references = actionTables().modules.get(cleanId(id));
|
|
443
577
|
if (references != null) return actionReferenceSource(references);
|
|
444
578
|
}
|
|
@@ -447,7 +581,10 @@ function flowPlugin({
|
|
|
447
581
|
|
|
448
582
|
async transform(code, id, transformOptions) {
|
|
449
583
|
if (!isFlowModule(id)) return null;
|
|
450
|
-
|
|
584
|
+
// Both server graphs: neither gets a refresh wrapper, and the rsc graph's
|
|
585
|
+
// findings are reported as that graph's.
|
|
586
|
+
const rsc = this.environment?.name === RSC_ENVIRONMENT;
|
|
587
|
+
const ssr = transformOptions?.ssr === true || this.environment?.name === "ssr" || rsc;
|
|
451
588
|
const refresh = !isProduction && !ssr && server != null;
|
|
452
589
|
const out = await ensureService().transform(cleanId(id), code, {
|
|
453
590
|
development: !isProduction,
|
|
@@ -459,7 +596,7 @@ function flowPlugin({
|
|
|
459
596
|
id: cleanId(id),
|
|
460
597
|
root,
|
|
461
598
|
diagnostics: out.diagnostics,
|
|
462
|
-
environment: ssr ? "ssr" : "client",
|
|
599
|
+
environment: rsc ? RSC_ENVIRONMENT : ssr ? "ssr" : "client",
|
|
463
600
|
reported,
|
|
464
601
|
suppressed,
|
|
465
602
|
});
|
|
@@ -546,8 +683,27 @@ function flowPlugin({
|
|
|
546
683
|
return tags;
|
|
547
684
|
},
|
|
548
685
|
|
|
686
|
+
// An edit to a server component changes what the rsc graph renders and no
|
|
687
|
+
// module the browser holds, so Vite has nothing to tell the browser. It is
|
|
688
|
+
// reloaded, which renders the edit; an edit to a client module — which the
|
|
689
|
+
// rsc graph only holds references to — is left to Fast Refresh.
|
|
690
|
+
hotUpdate({ modules }) {
|
|
691
|
+
if (flightState == null || server == null) return;
|
|
692
|
+
if (this.environment?.name !== RSC_ENVIRONMENT) return;
|
|
693
|
+
const serverSide = modules.some((module) => {
|
|
694
|
+
const file = module.file ?? cleanId(module.id ?? "");
|
|
695
|
+
return file !== "" && !flightState.clientModules.has(file);
|
|
696
|
+
});
|
|
697
|
+
if (serverSide) server.environments.client.hot.send({ type: "full-reload", path: "*" });
|
|
698
|
+
},
|
|
699
|
+
|
|
549
700
|
configureServer(devServer) {
|
|
550
701
|
server = devServer;
|
|
702
|
+
// The ssr graph's way into the rsc graph; see `devBridgeSource`.
|
|
703
|
+
if (flightState != null) {
|
|
704
|
+
globalThis[Symbol.for(DEV_RSC_HOOK)] = () =>
|
|
705
|
+
devServer.environments[RSC_ENVIRONMENT].runner.import(FLIGHT_VIRTUAL.entry);
|
|
706
|
+
}
|
|
551
707
|
devServer.httpServer?.once("close", () => {
|
|
552
708
|
service?.close();
|
|
553
709
|
service = null;
|
|
@@ -713,6 +869,29 @@ function flowPlugin({
|
|
|
713
869
|
// and a `<form action>` navigation both send `Accept:
|
|
714
870
|
// text/html`, and both want the handler's answer — which is the
|
|
715
871
|
// whole of ubugeeei-prod/uf#349.
|
|
872
|
+
// A browser that is navigating, asking for the next route's
|
|
873
|
+
// payload: after the guard and before the handlers, for the
|
|
874
|
+
// reasons `@uniflowed/server`'s `internal/flight.js` gives.
|
|
875
|
+
const payloadFor =
|
|
876
|
+
entry.flight == null ? null : flightDocumentPath(url.split("?")[0]);
|
|
877
|
+
if (payloadFor != null && (request.method === "GET" || request.method === "HEAD")) {
|
|
878
|
+
const query = url.includes("?") ? url.slice(url.indexOf("?")) : "";
|
|
879
|
+
const target = payloadFor + query;
|
|
880
|
+
const answered = await entry.flight(target, {
|
|
881
|
+
onError: (error) => reportRenderError(devServer, target, error),
|
|
882
|
+
});
|
|
883
|
+
if (answered.error != null) reportRenderError(devServer, target, answered.error);
|
|
884
|
+
if (request.method === "HEAD") await answered.stream?.cancel();
|
|
885
|
+
await send(
|
|
886
|
+
response,
|
|
887
|
+
new Response(request.method === "HEAD" ? null : answered.stream, {
|
|
888
|
+
status: answered.status,
|
|
889
|
+
headers: answered.headers,
|
|
890
|
+
}),
|
|
891
|
+
);
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
|
|
716
895
|
const handled = await entry.dispatch(asRequest);
|
|
717
896
|
if (handled != null) {
|
|
718
897
|
await send(response, handled);
|
|
@@ -778,7 +957,18 @@ function flowPlugin({
|
|
|
778
957
|
onError: (error) => reportRenderError(devServer, url, error),
|
|
779
958
|
// Vite sees the head and only the head. That is what lets the
|
|
780
959
|
// development server stream like every other host — see below.
|
|
781
|
-
|
|
960
|
+
//
|
|
961
|
+
// Under React Server Components it is also where a server
|
|
962
|
+
// component's stylesheets are linked in development: they are
|
|
963
|
+
// in the rsc graph, which the browser never loads, and they
|
|
964
|
+
// enter it when the route's modules are imported — after this
|
|
965
|
+
// call and before the head is written, which is when this
|
|
966
|
+
// runs. See `devStylesheets`.
|
|
967
|
+
transformHead: (head) =>
|
|
968
|
+
devServer.transformIndexHtml(
|
|
969
|
+
url,
|
|
970
|
+
flightState == null ? head : linkStylesheets(head, devStylesheets(devServer)),
|
|
971
|
+
),
|
|
782
972
|
// And what the streaming actually did, when it changed. The
|
|
783
973
|
// router has already decided there is something worth saying
|
|
784
974
|
// and written the words — see its `internal/inspector.js`,
|
|
@@ -862,11 +1052,7 @@ function mdxPlugin(markdown) {
|
|
|
862
1052
|
enforce: "pre",
|
|
863
1053
|
...mdx({
|
|
864
1054
|
jsxImportSource: "react",
|
|
865
|
-
remarkPlugins: [
|
|
866
|
-
remarkGfm,
|
|
867
|
-
remarkFrontmatter,
|
|
868
|
-
[remarkMdxFrontmatter, { name: "frontmatter" }],
|
|
869
|
-
],
|
|
1055
|
+
remarkPlugins: [remarkGfm, remarkFrontmatter, remarkFrontmatterExport],
|
|
870
1056
|
rehypePlugins,
|
|
871
1057
|
}),
|
|
872
1058
|
name: "uf:mdx",
|