@uniflowed/vite 0.0.0-alpha.35 → 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 +263 -50
- package/index.js +132 -15
- package/internal/barrel-imports.js +459 -0
- package/internal/flight.js +125 -11
- package/internal/http.js +21 -2
- package/internal/module-graph.js +134 -0
- package/internal/routes.js +55 -2
- package/internal/serve.js +158 -12
- package/internal/server-components.js +109 -0
- package/package.json +5 -5
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,9 +58,11 @@ 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";
|
|
57
63
|
import remarkFrontmatterExport from "./internal/frontmatter.js";
|
|
58
64
|
import { highlightPlugin } from "./internal/highlight.js";
|
|
65
|
+
import { moduleId } from "./internal/module-graph.js";
|
|
59
66
|
import remarkFrontmatter from "remark-frontmatter";
|
|
60
67
|
import remarkGfm from "remark-gfm";
|
|
61
68
|
|
|
@@ -82,17 +89,20 @@ 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";
|
|
89
97
|
import {
|
|
90
98
|
DEV_RSC_HOOK,
|
|
99
|
+
FLIGHT_BROWSER_DEPENDENCIES,
|
|
91
100
|
FLIGHT_VIRTUAL,
|
|
92
101
|
RSC_ENVIRONMENT,
|
|
93
102
|
builtBridgeSource,
|
|
94
103
|
builtReferencesSource,
|
|
95
104
|
clientManifestSource,
|
|
105
|
+
clientModuleUrlPlugin,
|
|
96
106
|
clientReferencePlugin,
|
|
97
107
|
compilerRuntimeSource,
|
|
98
108
|
createFlightState,
|
|
@@ -109,8 +119,15 @@ import {
|
|
|
109
119
|
} from "./internal/flight.js";
|
|
110
120
|
import { createChannelMiddleware } from "./internal/diagnostics.js";
|
|
111
121
|
import { devtoolsPreamble } from "./internal/devtools.js";
|
|
112
|
-
import { send, toRequest } from "./internal/http.js";
|
|
113
|
-
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";
|
|
114
131
|
|
|
115
132
|
/** A resolved virtual id: Vite's convention is a leading NUL byte. */
|
|
116
133
|
const resolved = (id) => `\0${id}`;
|
|
@@ -184,6 +201,10 @@ export default function uniflowed(options = {}) {
|
|
|
184
201
|
: null;
|
|
185
202
|
|
|
186
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);
|
|
187
208
|
|
|
188
209
|
return [
|
|
189
210
|
flowPlugin({
|
|
@@ -194,10 +215,14 @@ export default function uniflowed(options = {}) {
|
|
|
194
215
|
navigation,
|
|
195
216
|
mount,
|
|
196
217
|
flightState,
|
|
218
|
+
routing,
|
|
197
219
|
command: options.command,
|
|
198
220
|
accessibility,
|
|
199
221
|
}),
|
|
200
|
-
...(flightState == null ? [] : [clientReferencePlugin(flightState)]),
|
|
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(),
|
|
201
226
|
mdxPlugin(markdown),
|
|
202
227
|
assetPlugin({
|
|
203
228
|
images: builtins.images ?? {},
|
|
@@ -217,6 +242,7 @@ function flowPlugin({
|
|
|
217
242
|
navigation,
|
|
218
243
|
mount,
|
|
219
244
|
flightState,
|
|
245
|
+
routing,
|
|
220
246
|
command,
|
|
221
247
|
accessibility,
|
|
222
248
|
}) {
|
|
@@ -353,6 +379,16 @@ function flowPlugin({
|
|
|
353
379
|
|
|
354
380
|
config(userConfig, env) {
|
|
355
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
|
+
}
|
|
356
392
|
isProduction = env.mode === "production" || env.command === "build";
|
|
357
393
|
// A reference names the client manifest only in a build, where a client
|
|
358
394
|
// build writes the chunks it names; a dev server names the URL it serves.
|
|
@@ -375,6 +411,9 @@ function flowPlugin({
|
|
|
375
411
|
"react/compiler-runtime",
|
|
376
412
|
"react-dom",
|
|
377
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),
|
|
378
417
|
// The accessibility audit's engine, when this project has one.
|
|
379
418
|
//
|
|
380
419
|
// Named up front rather than left to be discovered. axe-core is
|
|
@@ -507,6 +546,7 @@ function flowPlugin({
|
|
|
507
546
|
return flightClientSource(entryPath, {
|
|
508
547
|
strictMode: strictMode && !isProduction,
|
|
509
548
|
navigation,
|
|
549
|
+
routing,
|
|
510
550
|
});
|
|
511
551
|
}
|
|
512
552
|
if (id === resolved(VIRTUAL.client)) {
|
|
@@ -514,15 +554,16 @@ function flowPlugin({
|
|
|
514
554
|
strictMode: strictMode && !isProduction,
|
|
515
555
|
navigation,
|
|
516
556
|
mount,
|
|
557
|
+
routing,
|
|
517
558
|
});
|
|
518
559
|
}
|
|
519
560
|
if (id === resolved(VIRTUAL.server)) {
|
|
520
561
|
return flightState == null
|
|
521
|
-
? serverModuleSource(entryPath)
|
|
522
|
-
: flightServerSource(entryPath, VIRTUAL.routes, VIRTUAL.actions);
|
|
562
|
+
? serverModuleSource(entryPath, routing)
|
|
563
|
+
: flightServerSource(entryPath, VIRTUAL.routes, VIRTUAL.actions, routing);
|
|
523
564
|
}
|
|
524
565
|
if (flightState != null) {
|
|
525
|
-
if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes);
|
|
566
|
+
if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes, routing);
|
|
526
567
|
if (id === resolved(FLIGHT_VIRTUAL.compilerRuntime)) return compilerRuntimeSource();
|
|
527
568
|
if (id === resolved(FLIGHT_VIRTUAL.bridge)) {
|
|
528
569
|
if (server != null) return devBridgeSource();
|
|
@@ -580,7 +621,9 @@ function flowPlugin({
|
|
|
580
621
|
},
|
|
581
622
|
|
|
582
623
|
async transform(code, id, transformOptions) {
|
|
583
|
-
|
|
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;
|
|
584
627
|
// Both server graphs: neither gets a refresh wrapper, and the rsc graph's
|
|
585
628
|
// findings are reported as that graph's.
|
|
586
629
|
const rsc = this.environment?.name === RSC_ENVIRONMENT;
|
|
@@ -610,10 +653,17 @@ function flowPlugin({
|
|
|
610
653
|
// business: Vite already injects a stylesheet in dev, extracts it in a
|
|
611
654
|
// build, code-splits it per chunk, and replaces it over HMR. A module
|
|
612
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 `../`.
|
|
613
663
|
const styled = out.css != null && out.css !== "";
|
|
614
664
|
let output = out.code;
|
|
615
665
|
if (styled) {
|
|
616
|
-
const styleId = `${STYLE_PREFIX}${cleanId(id)}.css`;
|
|
666
|
+
const styleId = `${STYLE_PREFIX}${moduleId(root, cleanId(id))}.css`;
|
|
617
667
|
styles.set(styleId, out.css);
|
|
618
668
|
output = `import ${JSON.stringify(styleId)};\n${output}`;
|
|
619
669
|
}
|
|
@@ -709,6 +759,42 @@ function flowPlugin({
|
|
|
709
759
|
service = null;
|
|
710
760
|
});
|
|
711
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
|
+
|
|
712
798
|
// A reserved file appearing or disappearing changes the route table,
|
|
713
799
|
// which lives in a virtual module the watcher knows nothing about.
|
|
714
800
|
//
|
|
@@ -820,14 +906,26 @@ function flowPlugin({
|
|
|
820
906
|
// `request.url` and not `originalUrl`, which is the URL Vite's base
|
|
821
907
|
// middleware has already stripped the base from — and the route
|
|
822
908
|
// table's paths have no base in them either.
|
|
823
|
-
|
|
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 ?? "/";
|
|
824
914
|
// Declared out here so the catch below can still settle: a request
|
|
825
915
|
// that failed is a request that happened, and a middleware that
|
|
826
916
|
// logged its arrival is owed its callback either way.
|
|
827
917
|
let lifecycle = null;
|
|
828
918
|
try {
|
|
829
919
|
const entry = await importServerEntry(devServer);
|
|
830
|
-
|
|
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);
|
|
831
929
|
|
|
832
930
|
// The request begins here and ends when the response has been
|
|
833
931
|
// written, which is what `after()` promises and what `uf preview`,
|
|
@@ -835,7 +933,7 @@ function flowPlugin({
|
|
|
835
933
|
// logs a response's status has to mean the same thing in
|
|
836
934
|
// development as in production. See `internal/serve.js` and
|
|
837
935
|
// ubugeeei-prod/uf#389.
|
|
838
|
-
lifecycle = await beginRequest(entry,
|
|
936
|
+
lifecycle = await beginRequest(entry, arrived);
|
|
839
937
|
const answered = await lifecycle.run(async () => {
|
|
840
938
|
// Before anything answers: a middleware guards a subtree, so it
|
|
841
939
|
// has to run for a page, for a route handler, and for a path
|
|
@@ -843,8 +941,14 @@ function flowPlugin({
|
|
|
843
941
|
// dispatcher and again inside the renderer would have left
|
|
844
942
|
// `/dashboard/typo` unguarded and run it twice for a path that
|
|
845
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.
|
|
846
947
|
const guarded = await entry.runMiddleware(asRequest);
|
|
847
|
-
if (guarded
|
|
948
|
+
if (guarded instanceof Request) {
|
|
949
|
+
asRequest = guarded;
|
|
950
|
+
url = addressOf(guarded);
|
|
951
|
+
} else if (guarded != null) {
|
|
848
952
|
await send(response, guarded);
|
|
849
953
|
return true;
|
|
850
954
|
}
|
|
@@ -912,7 +1016,7 @@ function flowPlugin({
|
|
|
912
1016
|
// and the project's own not-found *page* under `uf preview` and
|
|
913
1017
|
// `uf start` — a difference in the body of a 404 for a path that
|
|
914
1018
|
// is an asset request in the first place.
|
|
915
|
-
const notDocument = notADocumentBecause(request);
|
|
1019
|
+
const notDocument = notADocumentBecause(request, url);
|
|
916
1020
|
if (notDocument === "accept") {
|
|
917
1021
|
// Everything about this is a navigation except the header, and
|
|
918
1022
|
// the path is one uf renders. Say so, rather than letting the
|
|
@@ -1077,9 +1181,23 @@ async function importServerEntry(devServer) {
|
|
|
1077
1181
|
return devServer.ssrLoadModule(VIRTUAL.server);
|
|
1078
1182
|
}
|
|
1079
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
|
+
|
|
1080
1195
|
/**
|
|
1081
1196
|
* Why `request` is not a document request, or `null` when it is one.
|
|
1082
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
|
+
*
|
|
1083
1201
|
* A reason rather than a boolean because one of the four is worth saying out
|
|
1084
1202
|
* loud. Three of them mean the request belongs to somebody else — Vite's module
|
|
1085
1203
|
* server, a static file, or a method a page cannot answer — and handing it back
|
|
@@ -1091,9 +1209,8 @@ async function importServerEntry(devServer) {
|
|
|
1091
1209
|
* confused: `/favicon.svg` with `Accept: *\/*` is an asset, not a navigation
|
|
1092
1210
|
* with the wrong header, and still goes back to Vite's chain untouched.
|
|
1093
1211
|
*/
|
|
1094
|
-
function notADocumentBecause(request) {
|
|
1212
|
+
function notADocumentBecause(request, url = request.url ?? "/") {
|
|
1095
1213
|
if (request.method !== "GET" && request.method !== "HEAD") return "method";
|
|
1096
|
-
const url = request.url ?? "/";
|
|
1097
1214
|
if (url.startsWith("/@") || url.startsWith("/node_modules/")) return "module-server";
|
|
1098
1215
|
const pathname = url.split("?")[0];
|
|
1099
1216
|
// A request for a file — `/favicon.svg`, `/assets/x.js` — that no static
|