@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/driver.js
CHANGED
|
@@ -42,23 +42,31 @@
|
|
|
42
42
|
import { randomUUID } from "node:crypto";
|
|
43
43
|
import { createServer as createHttpServer } from "node:http";
|
|
44
44
|
import { builtinModules, register } from "node:module";
|
|
45
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
45
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
46
46
|
import path from "node:path";
|
|
47
47
|
import { pathToFileURL } from "node:url";
|
|
48
48
|
|
|
49
49
|
import { COMPILE_ASSETS_ID, compileAssetsPlugin } from "./internal/compile-assets.js";
|
|
50
|
+
import {
|
|
51
|
+
survivingImports,
|
|
52
|
+
unavailableOnWorkers,
|
|
53
|
+
workerBuiltinWarnings,
|
|
54
|
+
} from "./internal/worker-builtins.js";
|
|
50
55
|
import { emit, errorEvent, eventLogger } from "./internal/events.js";
|
|
51
56
|
import { loadUfConfig, projectConfig } from "./internal/config.js";
|
|
52
57
|
import { send, toRequest } from "./internal/http.js";
|
|
53
58
|
import { createOpenApiDocument } from "./internal/openapi.js";
|
|
54
59
|
import { withProjectConfig } from "./merge.js";
|
|
60
|
+
import { FLIGHT_VIRTUAL, RSC_ENVIRONMENT } from "./internal/flight.js";
|
|
55
61
|
import { VIRTUAL, resolveRouteTarget, scanRoutes } from "./internal/routes.js";
|
|
56
62
|
import {
|
|
57
63
|
BUILD_ID_FILE,
|
|
64
|
+
DOCUMENT_ASSETS_FILE,
|
|
58
65
|
assetsFromManifest,
|
|
59
66
|
buildIdentity,
|
|
60
67
|
createPrerenderGate,
|
|
61
68
|
createServeHandler,
|
|
69
|
+
documentAssetsFor,
|
|
62
70
|
loadBuild,
|
|
63
71
|
nodeListener,
|
|
64
72
|
providerSpecifier,
|
|
@@ -437,6 +445,17 @@ async function preview() {
|
|
|
437
445
|
const draftFirst = {
|
|
438
446
|
name: "uf:draft-before-files",
|
|
439
447
|
configurePreviewServer(previewServer) {
|
|
448
|
+
// A prerendered payload is a file whose extension Vite's static middleware
|
|
449
|
+
// knows no type for, and the router hands bytes to React only when they
|
|
450
|
+
// are answered as a payload — so a navigation on a preview would silently
|
|
451
|
+
// become a document load. Set here, in front of the file server, which
|
|
452
|
+
// keeps a type it did not choose.
|
|
453
|
+
previewServer.middlewares.use((request, response, next) => {
|
|
454
|
+
if ((request.url ?? "").split("?")[0].endsWith("/__uf.flight")) {
|
|
455
|
+
response.setHeader("content-type", "text/x-component");
|
|
456
|
+
}
|
|
457
|
+
next();
|
|
458
|
+
});
|
|
440
459
|
if (handle == null) return;
|
|
441
460
|
const run = answer(previewServer);
|
|
442
461
|
previewServer.middlewares.use((request, response, next) => {
|
|
@@ -589,17 +608,48 @@ async function build() {
|
|
|
589
608
|
const staticBuild = flag("--static-build");
|
|
590
609
|
const because = argument("--because") ?? "this build prerenders every route";
|
|
591
610
|
|
|
611
|
+
// 0. The rsc graph, for an application React Server Components render: the
|
|
612
|
+
// route table and every server component, resolved under `react-server`.
|
|
613
|
+
// First, because it is what finds the client modules the next pass has
|
|
614
|
+
// to build; `./internal/flight.js` has the order and the reason for it.
|
|
615
|
+
const flight = flightStateOf(inline);
|
|
616
|
+
const rscDir = path.join(root, ".uf", "build", "rsc");
|
|
617
|
+
if (flight != null) {
|
|
618
|
+
emit("phase", { name: "rsc" });
|
|
619
|
+
await buildRscGraph(vite, inline, flight, { outDir: rscDir, conditions: null });
|
|
620
|
+
}
|
|
621
|
+
|
|
592
622
|
// 1. The client: everything the browser loads, with a manifest so the
|
|
593
|
-
// server render knows which script and stylesheet tags to write.
|
|
623
|
+
// server render knows which script and stylesheet tags to write. Under
|
|
624
|
+
// React Server Components that is the entry and one entry per client
|
|
625
|
+
// module, each keeping its export names, because a payload asks for a
|
|
626
|
+
// chunk by its URL and for a component by its export.
|
|
594
627
|
emit("phase", { name: "client" });
|
|
628
|
+
const references = flight == null ? [] : [...flight.clientModules].sort();
|
|
629
|
+
const input = { client: VIRTUAL.client };
|
|
630
|
+
references.forEach((file, index) => {
|
|
631
|
+
input[`client-reference-${index}`] = file;
|
|
632
|
+
});
|
|
595
633
|
await vite.build({
|
|
596
634
|
...inline,
|
|
597
635
|
build: {
|
|
598
636
|
...inline.build,
|
|
599
|
-
rollupOptions:
|
|
637
|
+
rollupOptions:
|
|
638
|
+
flight == null ? { input } : { input, preserveEntrySignatures: "exports-only" },
|
|
600
639
|
},
|
|
601
640
|
});
|
|
602
641
|
const manifest = readManifest(outDir);
|
|
642
|
+
if (flight != null) {
|
|
643
|
+
recordClientChunks(flight, manifest, references, rscDir);
|
|
644
|
+
// What the summary's "pages in the client bundle" reads. None: a browser
|
|
645
|
+
// that hydrates a payload imports no page, whichever route it is on.
|
|
646
|
+
emit("rsc-split", {
|
|
647
|
+
pages: 0,
|
|
648
|
+
routes: scanRoutes(path.resolve(root, config.app?.router?.root ?? "app"), {
|
|
649
|
+
target: resolveRouteTarget(config, argument("--target")),
|
|
650
|
+
}).routes.length,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
603
653
|
|
|
604
654
|
// 2. The server entry, bundled for the host, outside `dist/` so it is never
|
|
605
655
|
// deployed by accident.
|
|
@@ -635,7 +685,14 @@ async function build() {
|
|
|
635
685
|
// arrives here as one word, and this is where it meets the route table.
|
|
636
686
|
emit("phase", { name: "prerender" });
|
|
637
687
|
const server = await import(pathToFileURL(path.join(serverDir, "server.js")).href);
|
|
638
|
-
const assets =
|
|
688
|
+
const assets =
|
|
689
|
+
flight == null
|
|
690
|
+
? assetsFromManifest(manifest)
|
|
691
|
+
: flightAssets(manifest, references, rscDir, outDir);
|
|
692
|
+
// Recorded beside the server bundle, because whatever serves this build
|
|
693
|
+
// later cannot recompute them from the client manifest alone; see
|
|
694
|
+
// `documentAssetsFor`.
|
|
695
|
+
writeFileSync(path.join(serverDir, DOCUMENT_ASSETS_FILE), `${JSON.stringify(assets, null, 2)}\n`);
|
|
639
696
|
const openapi = await createOpenApiDocument(server.handlers);
|
|
640
697
|
const openapiFile = path.join(root, ".uf", "build", "meta", "openapi.json");
|
|
641
698
|
mkdirSync(path.dirname(openapiFile), { recursive: true });
|
|
@@ -701,6 +758,11 @@ async function build() {
|
|
|
701
758
|
const file = htmlPathFor(outDir, url);
|
|
702
759
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
703
760
|
writeFileSync(file, result.html);
|
|
761
|
+
// The payload the document was rendered from, beside it: what a browser
|
|
762
|
+
// navigating to this route fetches, from whatever serves the files.
|
|
763
|
+
if (result.payload != null) {
|
|
764
|
+
writeFileSync(path.join(path.dirname(file), "__uf.flight"), result.payload);
|
|
765
|
+
}
|
|
704
766
|
emit("page", {
|
|
705
767
|
url,
|
|
706
768
|
file: path.relative(root, file),
|
|
@@ -1039,9 +1101,16 @@ async function compile() {
|
|
|
1039
1101
|
mkdirSync(bundleDir, { recursive: true });
|
|
1040
1102
|
writeFileSync(
|
|
1041
1103
|
entry,
|
|
1042
|
-
entrySource(
|
|
1104
|
+
entrySource(
|
|
1105
|
+
path.relative(root, assets),
|
|
1106
|
+
await documentAssetsFor(path.join(root, ".uf", "build", "server"), readManifest(outDir)),
|
|
1107
|
+
),
|
|
1043
1108
|
);
|
|
1044
1109
|
|
|
1110
|
+
// The rsc graph `uf build` built, and the client chunks its references name.
|
|
1111
|
+
const flight = flightStateOf(inline);
|
|
1112
|
+
if (flight != null) loadFlightBuild(flight, path.join(root, ".uf", "build", "rsc"));
|
|
1113
|
+
|
|
1045
1114
|
await vite.build({
|
|
1046
1115
|
...inline,
|
|
1047
1116
|
customLogger: eventLogger("warn"),
|
|
@@ -1141,6 +1210,9 @@ const ADAPTERS = {
|
|
|
1141
1210
|
// target which cannot provide a durable store says so, and this is the one
|
|
1142
1211
|
// target that cannot.
|
|
1143
1212
|
filesystem: false,
|
|
1213
|
+
// And the Node built-ins it has only as stubs, which the link reports by
|
|
1214
|
+
// module and importer. See `./internal/worker-builtins.js`.
|
|
1215
|
+
workerBuiltins: true,
|
|
1144
1216
|
},
|
|
1145
1217
|
serverless: {
|
|
1146
1218
|
entries: (document, cache, build) => ({
|
|
@@ -1267,7 +1339,10 @@ async function deploy() {
|
|
|
1267
1339
|
// file is the version a person can open when a deployed directory
|
|
1268
1340
|
// misbehaves.
|
|
1269
1341
|
mkdirSync(work, { recursive: true });
|
|
1270
|
-
const document =
|
|
1342
|
+
const document = await documentAssetsFor(
|
|
1343
|
+
path.join(root, ".uf", "build", "server"),
|
|
1344
|
+
readManifest(outDir),
|
|
1345
|
+
);
|
|
1271
1346
|
// Whatever `build` above minted, so a durable cache in the deployed artefact
|
|
1272
1347
|
// is keyed by the build that produced it and not by the moment it was
|
|
1273
1348
|
// packaged. Read rather than minted again for exactly that reason: a second
|
|
@@ -1290,6 +1365,21 @@ async function deploy() {
|
|
|
1290
1365
|
input[name] = path.join(work, `${name}.js`);
|
|
1291
1366
|
}
|
|
1292
1367
|
|
|
1368
|
+
// An application React Server Components render bundles the rsc graph into
|
|
1369
|
+
// its server, and a target with export conditions of its own needs that graph
|
|
1370
|
+
// resolved under them as well: React's Flight server has a Node build and a
|
|
1371
|
+
// worker build, exactly as its HTML renderer does.
|
|
1372
|
+
const flight = flightStateOf(inline);
|
|
1373
|
+
if (flight != null) {
|
|
1374
|
+
loadFlightBuild(flight, path.join(root, ".uf", "build", "rsc"));
|
|
1375
|
+
if (shape.conditions != null) {
|
|
1376
|
+
await buildRscGraph(vite, inline, flight, {
|
|
1377
|
+
outDir: path.join(work, "rsc"),
|
|
1378
|
+
conditions: shape.conditions,
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1293
1383
|
const ssr = { ...(inline.ssr ?? {}), noExternal: true };
|
|
1294
1384
|
if (shape.conditions != null) {
|
|
1295
1385
|
// Which build of a dependency this target gets, and it is the difference
|
|
@@ -1304,7 +1394,24 @@ async function deploy() {
|
|
|
1304
1394
|
await vite.build({
|
|
1305
1395
|
...inline,
|
|
1306
1396
|
customLogger: eventLogger("warn"),
|
|
1307
|
-
plugins: [
|
|
1397
|
+
plugins: [
|
|
1398
|
+
...inline.plugins,
|
|
1399
|
+
nativeAddonGuard(),
|
|
1400
|
+
...(shape.workerBuiltins === true ? [workerBuiltinGuard()] : []),
|
|
1401
|
+
],
|
|
1402
|
+
// Fixed, because this bundle inlines every dependency and so both of each
|
|
1403
|
+
// React package's builds, and a runtime lookup of `NODE_ENV` in a worker
|
|
1404
|
+
// finds nothing and picks the development one. React's Flight client's
|
|
1405
|
+
// development build constructs a `WeakRef` for every response, which
|
|
1406
|
+
// workerd does not have: every document the edge artefact rendered was a
|
|
1407
|
+
// `ReferenceError`. The production build has none, and is the one a
|
|
1408
|
+
// deployment means.
|
|
1409
|
+
define: {
|
|
1410
|
+
...(inline.define ?? {}),
|
|
1411
|
+
"process.env.NODE_ENV": JSON.stringify(
|
|
1412
|
+
inline.mode === "development" ? "development" : "production",
|
|
1413
|
+
),
|
|
1414
|
+
},
|
|
1308
1415
|
ssr,
|
|
1309
1416
|
build: {
|
|
1310
1417
|
...inline.build,
|
|
@@ -1356,9 +1463,9 @@ async function deploy() {
|
|
|
1356
1463
|
* out — `server.js` below does exactly that through
|
|
1357
1464
|
* `@uniflowed/server/node`, and a worker hands `settle` to `ctx.waitUntil`.
|
|
1358
1465
|
* It comes from the bundle rather than from the host's own
|
|
1359
|
-
* `@uniflowed/server`, because the request
|
|
1360
|
-
*
|
|
1361
|
-
*
|
|
1466
|
+
* `@uniflowed/server`, because the request store is shared only by copies of
|
|
1467
|
+
* one release of that package and the release the application reads is the one
|
|
1468
|
+
* inlined here. See ubugeeei-prod/uf#389.
|
|
1362
1469
|
*
|
|
1363
1470
|
* The document's script and stylesheet URLs are baked in here because they
|
|
1364
1471
|
* come from the client manifest, which exists at this moment and not in the
|
|
@@ -1671,10 +1778,14 @@ function workerEntrySource(handlerSpecifier, schedules) {
|
|
|
1671
1778
|
// would not be there.
|
|
1672
1779
|
if (declared.length === 0) {
|
|
1673
1780
|
return `// Generated by \`uf build --adapter edge\`. Not checked in, not edited.
|
|
1674
|
-
import { createWorkerFetch } from "@uniflowed/server/edge";
|
|
1781
|
+
import { createWorkerFetch, installWorkerLogger } from "@uniflowed/server/edge";
|
|
1675
1782
|
|
|
1676
1783
|
import { beginRequest, fetch as handle } from ${JSON.stringify(handlerSpecifier)};
|
|
1677
1784
|
|
|
1785
|
+
// After the imports, so a logger the application installed while it loaded is
|
|
1786
|
+
// the one that stays; see \`installWorkerLogger\`.
|
|
1787
|
+
installWorkerLogger();
|
|
1788
|
+
|
|
1678
1789
|
export default { fetch: createWorkerFetch({ handle, beginRequest }) };
|
|
1679
1790
|
`;
|
|
1680
1791
|
}
|
|
@@ -1684,10 +1795,14 @@ export default { fetch: createWorkerFetch({ handle, beginRequest }) };
|
|
|
1684
1795
|
// `uf` writes both from one list, so the two cannot disagree.
|
|
1685
1796
|
const routes = Object.fromEntries(declared.map((schedule) => [schedule.cron, schedule.path]));
|
|
1686
1797
|
return `// Generated by \`uf build --adapter edge\`. Not checked in, not edited.
|
|
1687
|
-
import { createWorkerFetch, createWorkerScheduled } from "@uniflowed/server/edge";
|
|
1798
|
+
import { createWorkerFetch, createWorkerScheduled, installWorkerLogger } from "@uniflowed/server/edge";
|
|
1688
1799
|
|
|
1689
1800
|
import { beginRequest, fetch as handle } from ${JSON.stringify(handlerSpecifier)};
|
|
1690
1801
|
|
|
1802
|
+
// After the imports, so a logger the application installed while it loaded is
|
|
1803
|
+
// the one that stays; see \`installWorkerLogger\`.
|
|
1804
|
+
installWorkerLogger();
|
|
1805
|
+
|
|
1691
1806
|
// \`triggers.crons\` in the wrangler.json beside this file names these same
|
|
1692
1807
|
// expressions. See ubugeeei-prod/uf#531.
|
|
1693
1808
|
const routes = ${JSON.stringify(routes, null, 2)};
|
|
@@ -1803,6 +1918,38 @@ function nativeAddonGuard() {
|
|
|
1803
1918
|
};
|
|
1804
1919
|
}
|
|
1805
1920
|
|
|
1921
|
+
/**
|
|
1922
|
+
* Say which Node built-ins the Worker being linked reaches and does not have.
|
|
1923
|
+
*
|
|
1924
|
+
* `--adapter edge` only. Every import the bundler resolves passes through here,
|
|
1925
|
+
* and one naming a module `./internal/worker-builtins.js` measured as a stub at
|
|
1926
|
+
* the compatibility date uf writes is kept with the file that imported it. Only
|
|
1927
|
+
* the ones still in the output are reported — see `survivingImports` for the
|
|
1928
|
+
* fixture that showed why — and as warnings rather than a refusal, for the
|
|
1929
|
+
* reason that module gives: a deployment at a newer date may have the module,
|
|
1930
|
+
* and uf cannot see that deployment. What it can do is say, before anything is
|
|
1931
|
+
* uploaded, which request is going to answer 500 and why.
|
|
1932
|
+
*/
|
|
1933
|
+
function workerBuiltinGuard() {
|
|
1934
|
+
const reached = [];
|
|
1935
|
+
return {
|
|
1936
|
+
name: "uf:worker-builtins",
|
|
1937
|
+
enforce: "pre",
|
|
1938
|
+
resolveId(source, importer) {
|
|
1939
|
+
if (importer != null && unavailableOnWorkers(source) != null) {
|
|
1940
|
+
reached.push({ specifier: source, importer });
|
|
1941
|
+
}
|
|
1942
|
+
return null;
|
|
1943
|
+
},
|
|
1944
|
+
generateBundle(_options, bundle) {
|
|
1945
|
+
const chunks = Object.values(bundle).filter((output) => output.type === "chunk");
|
|
1946
|
+
for (const warning of workerBuiltinWarnings(survivingImports(reached, chunks), root)) {
|
|
1947
|
+
this.warn(warning);
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1806
1953
|
/** `word`, pluralised for `count`. */
|
|
1807
1954
|
function plural(count, word) {
|
|
1808
1955
|
return count === 1 ? word : `${word}s`;
|
|
@@ -1814,6 +1961,135 @@ async function printConfig() {
|
|
|
1814
1961
|
process.exit(0);
|
|
1815
1962
|
}
|
|
1816
1963
|
|
|
1964
|
+
/**
|
|
1965
|
+
* The React Server Components state `@uniflowed/vite` shares with this driver,
|
|
1966
|
+
* or `null` for an application rendered from its modules.
|
|
1967
|
+
*
|
|
1968
|
+
* Read off the plugin rather than decided again here: `rendersFlight` in
|
|
1969
|
+
* `./internal/flight.js` decides from the same `uf.config.js`, and a second
|
|
1970
|
+
* reading of it would be the one that drifts.
|
|
1971
|
+
*/
|
|
1972
|
+
function flightStateOf(inline) {
|
|
1973
|
+
const plugins = (inline.plugins ?? []).flat(Number.POSITIVE_INFINITY);
|
|
1974
|
+
return plugins.find((plugin) => plugin?.name === "uf:flow")?.api?.flight ?? null;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/** What a later command needs of the rsc build, written beside its output. */
|
|
1978
|
+
const FLIGHT_BUILD_FILE = "uf-flight.json";
|
|
1979
|
+
|
|
1980
|
+
/**
|
|
1981
|
+
* Build the rsc graph into `outDir`.
|
|
1982
|
+
*
|
|
1983
|
+
* `conditions` are a deploy target's own, added to `react-server`; `null` is
|
|
1984
|
+
* the Node server `uf build` writes. Vite's builder rather than `vite.build`,
|
|
1985
|
+
* because the rsc graph is an environment of its own and `vite.build` builds
|
|
1986
|
+
* the two Vite always has.
|
|
1987
|
+
*/
|
|
1988
|
+
async function buildRscGraph(vite, inline, state, { outDir, conditions }) {
|
|
1989
|
+
const environment = {
|
|
1990
|
+
build: {
|
|
1991
|
+
outDir,
|
|
1992
|
+
emptyOutDir: true,
|
|
1993
|
+
rollupOptions: {
|
|
1994
|
+
input: { index: FLIGHT_VIRTUAL.entry },
|
|
1995
|
+
output: { entryFileNames: "[name].js", format: "es" },
|
|
1996
|
+
},
|
|
1997
|
+
},
|
|
1998
|
+
};
|
|
1999
|
+
if (conditions != null) {
|
|
2000
|
+
environment.resolve = {
|
|
2001
|
+
conditions: ["react-server", ...conditions],
|
|
2002
|
+
externalConditions: ["react-server", ...conditions],
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
const builder = await vite.createBuilder({
|
|
2006
|
+
...inline,
|
|
2007
|
+
customLogger: eventLogger("warn"),
|
|
2008
|
+
environments: { [RSC_ENVIRONMENT]: environment },
|
|
2009
|
+
});
|
|
2010
|
+
await builder.build(builder.environments[RSC_ENVIRONMENT]);
|
|
2011
|
+
state.rscOutput = path.join(outDir, "index.js");
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
/**
|
|
2015
|
+
* Record the chunk each client module was built into, and write it down.
|
|
2016
|
+
*
|
|
2017
|
+
* Written down because `uf build --adapter` and `uf build --compile` bundle
|
|
2018
|
+
* the server again in a process of their own, and a reference in the rsc
|
|
2019
|
+
* output names its module by path, which only this build's manifest turns
|
|
2020
|
+
* into a URL.
|
|
2021
|
+
*/
|
|
2022
|
+
function recordClientChunks(state, manifest, references, rscDir) {
|
|
2023
|
+
for (const file of references) {
|
|
2024
|
+
const key = path.relative(root, file).split(path.sep).join("/");
|
|
2025
|
+
const chunk = manifest[key];
|
|
2026
|
+
if (chunk == null) {
|
|
2027
|
+
throw new Error(
|
|
2028
|
+
`uf: the client build wrote no chunk for ${key}, which a server component renders as a ` +
|
|
2029
|
+
"client component",
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
state.chunkUrls.set(file, `/${chunk.file}`);
|
|
2033
|
+
}
|
|
2034
|
+
writeFileSync(
|
|
2035
|
+
path.join(rscDir, FLIGHT_BUILD_FILE),
|
|
2036
|
+
`${JSON.stringify({ chunkUrls: [...state.chunkUrls] }, null, 2)}\n`,
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/** What `recordClientChunks` wrote, for a command that runs after `uf build`. */
|
|
2041
|
+
function loadFlightBuild(state, rscDir) {
|
|
2042
|
+
const file = path.join(rscDir, FLIGHT_BUILD_FILE);
|
|
2043
|
+
if (!existsSync(file)) {
|
|
2044
|
+
throw new Error(
|
|
2045
|
+
`uf: ${path.relative(root, file)} is missing, so there is no rsc graph to render routes ` +
|
|
2046
|
+
"with; run `uf build` first",
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
state.chunkUrls = new Map(JSON.parse(readFileSync(file, "utf8")).chunkUrls);
|
|
2050
|
+
state.rscOutput = path.join(rscDir, "index.js");
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
/**
|
|
2054
|
+
* The tags a document React Server Components render needs.
|
|
2055
|
+
*
|
|
2056
|
+
* `assetsFromManifest`'s, with stylesheets from three places in the order they
|
|
2057
|
+
* cascade: the rsc graph's first — every layout's and every server component's
|
|
2058
|
+
* — then the client entry's, then each client module's own, which the client
|
|
2059
|
+
* build emits beside that module's chunk and no import from the entry reaches.
|
|
2060
|
+
*
|
|
2061
|
+
* The rsc build's emitted files are copied under `dist/` so those URLs resolve,
|
|
2062
|
+
* and only its assets: a server bundle's JavaScript is never a deployable file.
|
|
2063
|
+
*/
|
|
2064
|
+
function flightAssets(manifest, references, rscDir, outDir) {
|
|
2065
|
+
const assets = assetsFromManifest(manifest);
|
|
2066
|
+
const styles = new Set();
|
|
2067
|
+
const rscManifest = path.join(rscDir, ".vite", "manifest.json");
|
|
2068
|
+
if (existsSync(rscManifest)) {
|
|
2069
|
+
const rscStyles = assetsFromManifest(JSON.parse(readFileSync(rscManifest, "utf8"))).styles;
|
|
2070
|
+
for (const href of rscStyles) styles.add(href);
|
|
2071
|
+
}
|
|
2072
|
+
const rscAssets = path.join(rscDir, "assets");
|
|
2073
|
+
if (existsSync(rscAssets)) {
|
|
2074
|
+
cpSync(rscAssets, path.join(outDir, "assets"), {
|
|
2075
|
+
recursive: true,
|
|
2076
|
+
filter: (from) => !/\.(?:[cm]?js|map)$/.test(from),
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
for (const href of assets.styles) styles.add(href);
|
|
2080
|
+
const seen = new Set();
|
|
2081
|
+
const visit = (key) => {
|
|
2082
|
+
if (seen.has(key)) return;
|
|
2083
|
+
seen.add(key);
|
|
2084
|
+
const chunk = manifest[key];
|
|
2085
|
+
if (chunk == null) return;
|
|
2086
|
+
for (const css of chunk.css ?? []) styles.add(`/${css}`);
|
|
2087
|
+
for (const imported of chunk.imports ?? []) visit(imported);
|
|
2088
|
+
};
|
|
2089
|
+
for (const file of references) visit(path.relative(root, file).split(path.sep).join("/"));
|
|
2090
|
+
return { ...assets, styles: [...styles] };
|
|
2091
|
+
}
|
|
2092
|
+
|
|
1817
2093
|
function readManifest(outDir) {
|
|
1818
2094
|
const file = path.join(outDir, ".vite", "manifest.json");
|
|
1819
2095
|
if (!existsSync(file)) throw new Error(`uf: the client build wrote no manifest at ${file}`);
|