@uniflowed/vite 0.0.0-alpha.35 → 0.0.0-alpha.39
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 +281 -50
- package/index.js +146 -15
- package/internal/barrel-imports.js +459 -0
- package/internal/flight.js +128 -11
- package/internal/http.js +21 -2
- package/internal/module-graph.js +134 -0
- package/internal/routes.js +58 -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}`;
|
|
@@ -166,6 +183,16 @@ export default function uniflowed(options = {}) {
|
|
|
166
183
|
// at. Anything but `"document"` is the client router, which is what every
|
|
167
184
|
// project that has not heard of the key has.
|
|
168
185
|
const navigation = app.rendering?.navigation === "document" ? "document" : "client";
|
|
186
|
+
// How long, in seconds, the client router shows a route it already fetched
|
|
187
|
+
// without asking the server again, written into the client entry beside
|
|
188
|
+
// `navigation` and honoured by every command for the same reason. Anything
|
|
189
|
+
// but a positive number is `0`, which keeps nothing.
|
|
190
|
+
const staleTime =
|
|
191
|
+
typeof app.rendering?.staleTime === "number" &&
|
|
192
|
+
Number.isFinite(app.rendering.staleTime) &&
|
|
193
|
+
app.rendering.staleTime > 0
|
|
194
|
+
? app.rendering.staleTime
|
|
195
|
+
: 0;
|
|
169
196
|
// Whether this application starts by attaching to markup or by rendering
|
|
170
197
|
// into an empty root. `["csr"]` is the only list that means the second, and
|
|
171
198
|
// `uf` refuses that value beside any other while the config is read — so the
|
|
@@ -184,6 +211,10 @@ export default function uniflowed(options = {}) {
|
|
|
184
211
|
: null;
|
|
185
212
|
|
|
186
213
|
const accessibility = ufConfig.accessibility ?? {};
|
|
214
|
+
// `app.router.redirects`, `rewrites` and `headers`: written into the server
|
|
215
|
+
// bundle for every host that serves a build, and asked by `uf dev` itself
|
|
216
|
+
// from the same object. See `@uniflowed/server`'s `internal/routing.js`.
|
|
217
|
+
const routing = routingRulesOf(app.router);
|
|
187
218
|
|
|
188
219
|
return [
|
|
189
220
|
flowPlugin({
|
|
@@ -192,12 +223,17 @@ export default function uniflowed(options = {}) {
|
|
|
192
223
|
routeTarget,
|
|
193
224
|
strictMode,
|
|
194
225
|
navigation,
|
|
226
|
+
staleTime,
|
|
195
227
|
mount,
|
|
196
228
|
flightState,
|
|
229
|
+
routing,
|
|
197
230
|
command: options.command,
|
|
198
231
|
accessibility,
|
|
199
232
|
}),
|
|
200
|
-
...(flightState == null ? [] : [clientReferencePlugin(flightState)]),
|
|
233
|
+
...(flightState == null ? [] : [clientReferencePlugin(flightState), clientModuleUrlPlugin()]),
|
|
234
|
+
// After the references, so a client module the rsc graph has already
|
|
235
|
+
// replaced is not read for imports it no longer has; see the file.
|
|
236
|
+
barrelImportsPlugin(),
|
|
201
237
|
mdxPlugin(markdown),
|
|
202
238
|
assetPlugin({
|
|
203
239
|
images: builtins.images ?? {},
|
|
@@ -215,8 +251,10 @@ function flowPlugin({
|
|
|
215
251
|
routeTarget,
|
|
216
252
|
strictMode,
|
|
217
253
|
navigation,
|
|
254
|
+
staleTime,
|
|
218
255
|
mount,
|
|
219
256
|
flightState,
|
|
257
|
+
routing,
|
|
220
258
|
command,
|
|
221
259
|
accessibility,
|
|
222
260
|
}) {
|
|
@@ -353,6 +391,16 @@ function flowPlugin({
|
|
|
353
391
|
|
|
354
392
|
config(userConfig, env) {
|
|
355
393
|
const projectRoot = path.resolve(userConfig.root ?? process.cwd());
|
|
394
|
+
// Before anything is resolved. Routes that render as React Server
|
|
395
|
+
// Components need `react-server-dom-parcel` and React 19.3, and the router
|
|
396
|
+
// installs without either (ubugeeei-prod/uf#992). Said here, once, rather
|
|
397
|
+
// than as an unresolved import deep in a build or a failure inside a render.
|
|
398
|
+
if (flightState != null) {
|
|
399
|
+
const problem = serverComponentsProblem(projectRoot);
|
|
400
|
+
if (problem != null) {
|
|
401
|
+
throw new Error(problem);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
356
404
|
isProduction = env.mode === "production" || env.command === "build";
|
|
357
405
|
// A reference names the client manifest only in a build, where a client
|
|
358
406
|
// build writes the chunks it names; a dev server names the URL it serves.
|
|
@@ -375,6 +423,9 @@ function flowPlugin({
|
|
|
375
423
|
"react/compiler-runtime",
|
|
376
424
|
"react-dom",
|
|
377
425
|
"react-dom/client",
|
|
426
|
+
// React's Flight client, for an application whose routes render as
|
|
427
|
+
// Server Components; `FLIGHT_BROWSER_DEPENDENCIES` says why.
|
|
428
|
+
...(flightState == null ? [] : FLIGHT_BROWSER_DEPENDENCIES),
|
|
378
429
|
// The accessibility audit's engine, when this project has one.
|
|
379
430
|
//
|
|
380
431
|
// Named up front rather than left to be discovered. axe-core is
|
|
@@ -507,22 +558,26 @@ function flowPlugin({
|
|
|
507
558
|
return flightClientSource(entryPath, {
|
|
508
559
|
strictMode: strictMode && !isProduction,
|
|
509
560
|
navigation,
|
|
561
|
+
staleTime,
|
|
562
|
+
routing,
|
|
510
563
|
});
|
|
511
564
|
}
|
|
512
565
|
if (id === resolved(VIRTUAL.client)) {
|
|
513
566
|
return clientModuleSource(entryPath, {
|
|
514
567
|
strictMode: strictMode && !isProduction,
|
|
515
568
|
navigation,
|
|
569
|
+
staleTime,
|
|
516
570
|
mount,
|
|
571
|
+
routing,
|
|
517
572
|
});
|
|
518
573
|
}
|
|
519
574
|
if (id === resolved(VIRTUAL.server)) {
|
|
520
575
|
return flightState == null
|
|
521
|
-
? serverModuleSource(entryPath)
|
|
522
|
-
: flightServerSource(entryPath, VIRTUAL.routes, VIRTUAL.actions);
|
|
576
|
+
? serverModuleSource(entryPath, routing)
|
|
577
|
+
: flightServerSource(entryPath, VIRTUAL.routes, VIRTUAL.actions, routing);
|
|
523
578
|
}
|
|
524
579
|
if (flightState != null) {
|
|
525
|
-
if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes);
|
|
580
|
+
if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes, routing);
|
|
526
581
|
if (id === resolved(FLIGHT_VIRTUAL.compilerRuntime)) return compilerRuntimeSource();
|
|
527
582
|
if (id === resolved(FLIGHT_VIRTUAL.bridge)) {
|
|
528
583
|
if (server != null) return devBridgeSource();
|
|
@@ -580,7 +635,9 @@ function flowPlugin({
|
|
|
580
635
|
},
|
|
581
636
|
|
|
582
637
|
async transform(code, id, transformOptions) {
|
|
583
|
-
|
|
638
|
+
// A view of a barrel's namespace has the barrel's path and none of its
|
|
639
|
+
// source: `uf:barrel-imports` generates it as JavaScript.
|
|
640
|
+
if (!isFlowModule(id) || namespaceViewOf(id) != null) return null;
|
|
584
641
|
// Both server graphs: neither gets a refresh wrapper, and the rsc graph's
|
|
585
642
|
// findings are reported as that graph's.
|
|
586
643
|
const rsc = this.environment?.name === RSC_ENVIRONMENT;
|
|
@@ -610,10 +667,17 @@ function flowPlugin({
|
|
|
610
667
|
// business: Vite already injects a stylesheet in dev, extracts it in a
|
|
611
668
|
// build, code-splits it per chunk, and replaces it over HMR. A module
|
|
612
669
|
// whose styles are gone stops importing it, and Vite notices.
|
|
670
|
+
//
|
|
671
|
+
// The stylesheet is named by the module's path from the project root, the
|
|
672
|
+
// spelling `moduleId` gives the module graph report, and not by its
|
|
673
|
+
// absolute path. In a client chunk the stylesheet is one of the chunk's
|
|
674
|
+
// sources, so its name is written into the source map a site publishes,
|
|
675
|
+
// and an absolute name would publish where the machine that built it
|
|
676
|
+
// keeps its files. A module outside the root climbs out with `../`.
|
|
613
677
|
const styled = out.css != null && out.css !== "";
|
|
614
678
|
let output = out.code;
|
|
615
679
|
if (styled) {
|
|
616
|
-
const styleId = `${STYLE_PREFIX}${cleanId(id)}.css`;
|
|
680
|
+
const styleId = `${STYLE_PREFIX}${moduleId(root, cleanId(id))}.css`;
|
|
617
681
|
styles.set(styleId, out.css);
|
|
618
682
|
output = `import ${JSON.stringify(styleId)};\n${output}`;
|
|
619
683
|
}
|
|
@@ -709,6 +773,42 @@ function flowPlugin({
|
|
|
709
773
|
service = null;
|
|
710
774
|
});
|
|
711
775
|
|
|
776
|
+
// `app.router.headers` and `redirects`, in front of Vite's own middleware
|
|
777
|
+
// — the hook's body runs before those are installed — so a redirect
|
|
778
|
+
// answers before `public/` is looked in and a header reaches a file Vite
|
|
779
|
+
// serves, as both do in front of the static half of every other door.
|
|
780
|
+
// Vite's module server is left alone: `/@vite/client` and `/@fs/…` are
|
|
781
|
+
// development plumbing no deployment has, and `/__uf/` is the browser's
|
|
782
|
+
// channel back. Not mounted at all for a project with neither list.
|
|
783
|
+
if (answersInFrontOfFiles(routing)) {
|
|
784
|
+
devServer.middlewares.use((request, response, next) => {
|
|
785
|
+
const url = request.url ?? "/";
|
|
786
|
+
// Vite serves its own paths under the base path too, so they are
|
|
787
|
+
// recognised once it is taken off.
|
|
788
|
+
const under =
|
|
789
|
+
routing.basePath !== "" && url.startsWith(`${routing.basePath}/`)
|
|
790
|
+
? url.slice(routing.basePath.length)
|
|
791
|
+
: url;
|
|
792
|
+
if (
|
|
793
|
+
under.startsWith("/@") ||
|
|
794
|
+
under.startsWith("/node_modules/") ||
|
|
795
|
+
under.startsWith("/__uf/")
|
|
796
|
+
) {
|
|
797
|
+
next();
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
answerRouting(routing, toAddressRequest(request), response)
|
|
801
|
+
.then((answered) => {
|
|
802
|
+
if (answered) return;
|
|
803
|
+
// The bare base path is the root, which Vite only knows as
|
|
804
|
+
// `/docs/`; see `forViteBase`.
|
|
805
|
+
forViteBase(routing, request);
|
|
806
|
+
next();
|
|
807
|
+
})
|
|
808
|
+
.catch(next);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
712
812
|
// A reserved file appearing or disappearing changes the route table,
|
|
713
813
|
// which lives in a virtual module the watcher knows nothing about.
|
|
714
814
|
//
|
|
@@ -820,14 +920,26 @@ function flowPlugin({
|
|
|
820
920
|
// `request.url` and not `originalUrl`, which is the URL Vite's base
|
|
821
921
|
// middleware has already stripped the base from — and the route
|
|
822
922
|
// table's paths have no base in them either.
|
|
823
|
-
|
|
923
|
+
//
|
|
924
|
+
// `let`, because a rewrite moves it: from here on it is the address
|
|
925
|
+
// the application answers for, which is what the render, the payload
|
|
926
|
+
// and the document test below all have to agree on.
|
|
927
|
+
let url = request.url ?? "/";
|
|
824
928
|
// Declared out here so the catch below can still settle: a request
|
|
825
929
|
// that failed is a request that happened, and a middleware that
|
|
826
930
|
// logged its arrival is owed its callback either way.
|
|
827
931
|
let lifecycle = null;
|
|
828
932
|
try {
|
|
829
933
|
const entry = await importServerEntry(devServer);
|
|
830
|
-
|
|
934
|
+
// At `request.url`, which Vite's base middleware has already taken
|
|
935
|
+
// `app.router.basePath` off: the application path, as every other
|
|
936
|
+
// front door hands the application once it has admitted a request.
|
|
937
|
+
const arrived = await toRequest(request, devServer.config, url);
|
|
938
|
+
// `app.router.rewrites`, where `createFetchHandler` applies them for
|
|
939
|
+
// every other door: after the files Vite already served, before the
|
|
940
|
+
// guard — so the guard that runs is the destination's.
|
|
941
|
+
let asRequest = (await rewriteRouting(entry.routing, arrived)) ?? arrived;
|
|
942
|
+
if (asRequest !== arrived) url = addressOf(asRequest);
|
|
831
943
|
|
|
832
944
|
// The request begins here and ends when the response has been
|
|
833
945
|
// written, which is what `after()` promises and what `uf preview`,
|
|
@@ -835,7 +947,7 @@ function flowPlugin({
|
|
|
835
947
|
// logs a response's status has to mean the same thing in
|
|
836
948
|
// development as in production. See `internal/serve.js` and
|
|
837
949
|
// ubugeeei-prod/uf#389.
|
|
838
|
-
lifecycle = await beginRequest(entry,
|
|
950
|
+
lifecycle = await beginRequest(entry, arrived);
|
|
839
951
|
const answered = await lifecycle.run(async () => {
|
|
840
952
|
// Before anything answers: a middleware guards a subtree, so it
|
|
841
953
|
// has to run for a page, for a route handler, and for a path
|
|
@@ -843,8 +955,14 @@ function flowPlugin({
|
|
|
843
955
|
// dispatcher and again inside the renderer would have left
|
|
844
956
|
// `/dashboard/typo` unguarded and run it twice for a path that
|
|
845
957
|
// is both. See ubugeeei-prod/uf#260.
|
|
958
|
+
//
|
|
959
|
+
// A `Request` back is a middleware's `rewrite()`, already past
|
|
960
|
+
// the destination's own middleware.
|
|
846
961
|
const guarded = await entry.runMiddleware(asRequest);
|
|
847
|
-
if (guarded
|
|
962
|
+
if (guarded instanceof Request) {
|
|
963
|
+
asRequest = guarded;
|
|
964
|
+
url = addressOf(guarded);
|
|
965
|
+
} else if (guarded != null) {
|
|
848
966
|
await send(response, guarded);
|
|
849
967
|
return true;
|
|
850
968
|
}
|
|
@@ -912,7 +1030,7 @@ function flowPlugin({
|
|
|
912
1030
|
// and the project's own not-found *page* under `uf preview` and
|
|
913
1031
|
// `uf start` — a difference in the body of a 404 for a path that
|
|
914
1032
|
// is an asset request in the first place.
|
|
915
|
-
const notDocument = notADocumentBecause(request);
|
|
1033
|
+
const notDocument = notADocumentBecause(request, url);
|
|
916
1034
|
if (notDocument === "accept") {
|
|
917
1035
|
// Everything about this is a navigation except the header, and
|
|
918
1036
|
// the path is one uf renders. Say so, rather than letting the
|
|
@@ -1077,9 +1195,23 @@ async function importServerEntry(devServer) {
|
|
|
1077
1195
|
return devServer.ssrLoadModule(VIRTUAL.server);
|
|
1078
1196
|
}
|
|
1079
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* The path and query a `Request` is for, which is what the dev server's
|
|
1200
|
+
* renderer and payload test are keyed on.
|
|
1201
|
+
*
|
|
1202
|
+
* @param {Request} request
|
|
1203
|
+
*/
|
|
1204
|
+
function addressOf(request) {
|
|
1205
|
+
const url = new URL(request.url);
|
|
1206
|
+
return url.pathname + url.search;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1080
1209
|
/**
|
|
1081
1210
|
* Why `request` is not a document request, or `null` when it is one.
|
|
1082
1211
|
*
|
|
1212
|
+
* `url` is the address the application answers for, which a rewrite may have
|
|
1213
|
+
* moved away from the one the request line named.
|
|
1214
|
+
*
|
|
1083
1215
|
* A reason rather than a boolean because one of the four is worth saying out
|
|
1084
1216
|
* loud. Three of them mean the request belongs to somebody else — Vite's module
|
|
1085
1217
|
* server, a static file, or a method a page cannot answer — and handing it back
|
|
@@ -1091,9 +1223,8 @@ async function importServerEntry(devServer) {
|
|
|
1091
1223
|
* confused: `/favicon.svg` with `Accept: *\/*` is an asset, not a navigation
|
|
1092
1224
|
* with the wrong header, and still goes back to Vite's chain untouched.
|
|
1093
1225
|
*/
|
|
1094
|
-
function notADocumentBecause(request) {
|
|
1226
|
+
function notADocumentBecause(request, url = request.url ?? "/") {
|
|
1095
1227
|
if (request.method !== "GET" && request.method !== "HEAD") return "method";
|
|
1096
|
-
const url = request.url ?? "/";
|
|
1097
1228
|
if (url.startsWith("/@") || url.startsWith("/node_modules/")) return "module-server";
|
|
1098
1229
|
const pathname = url.split("?")[0];
|
|
1099
1230
|
// A request for a file — `/favicon.svg`, `/assets/x.js` — that no static
|