@uniflowed/vite 0.0.0-alpha.17 → 0.0.0-alpha.20
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 +356 -27
- package/index.js +102 -16
- package/internal/a11y.js +6 -4
- package/internal/devtools.js +2 -2
- package/internal/diagnostics.js +2 -2
- package/internal/flow-keywords.js +1 -1
- package/internal/routes.js +457 -54
- package/internal/rsc.js +38 -4
- package/internal/serve.js +158 -28
- package/package.json +5 -4
package/index.js
CHANGED
|
@@ -135,6 +135,20 @@ export default function uniflowed(options = {}) {
|
|
|
135
135
|
// no `uf.config.js` will mention. It only ever reaches the *development*
|
|
136
136
|
// client entry; see `flowPlugin`'s `load`. ubugeeei-prod/uf#516.
|
|
137
137
|
const strictMode = app.react?.strictMode !== false;
|
|
138
|
+
// What the browser does with a link, read here for the reason Strict Mode is
|
|
139
|
+
// and honoured in every command rather than in the build alone: it is the
|
|
140
|
+
// one setting whose whole effect is what happens on a click, so a dev server
|
|
141
|
+
// that disagreed with the deployment would be the wrong application to look
|
|
142
|
+
// at. Anything but `"document"` is the client router, which is what every
|
|
143
|
+
// project that has not heard of the key has.
|
|
144
|
+
const navigation = app.rendering?.navigation === "document" ? "document" : "client";
|
|
145
|
+
// Whether this application starts by attaching to markup or by rendering
|
|
146
|
+
// into an empty root. `["csr"]` is the only list that means the second, and
|
|
147
|
+
// `uf` refuses that value beside any other while the config is read — so the
|
|
148
|
+
// question here is "is it in the list", not "is it the only thing in it",
|
|
149
|
+
// and a driver started by hand on a config `uf` never validated gets the same
|
|
150
|
+
// answer for the same reason a project would want.
|
|
151
|
+
const mount = (app.rendering?.modes ?? []).includes("csr") ? "render" : "hydrate";
|
|
138
152
|
|
|
139
153
|
const accessibility = ufConfig.accessibility ?? {};
|
|
140
154
|
|
|
@@ -143,6 +157,8 @@ export default function uniflowed(options = {}) {
|
|
|
143
157
|
routerRoot,
|
|
144
158
|
appEntry,
|
|
145
159
|
strictMode,
|
|
160
|
+
navigation,
|
|
161
|
+
mount,
|
|
146
162
|
command: options.command,
|
|
147
163
|
accessibility,
|
|
148
164
|
}),
|
|
@@ -157,7 +173,15 @@ export default function uniflowed(options = {}) {
|
|
|
157
173
|
];
|
|
158
174
|
}
|
|
159
175
|
|
|
160
|
-
function flowPlugin({
|
|
176
|
+
function flowPlugin({
|
|
177
|
+
routerRoot,
|
|
178
|
+
appEntry,
|
|
179
|
+
strictMode,
|
|
180
|
+
navigation,
|
|
181
|
+
mount,
|
|
182
|
+
command,
|
|
183
|
+
accessibility,
|
|
184
|
+
}) {
|
|
161
185
|
let root = process.cwd();
|
|
162
186
|
let isProduction = false;
|
|
163
187
|
/**
|
|
@@ -331,7 +355,10 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
331
355
|
ensureService();
|
|
332
356
|
},
|
|
333
357
|
|
|
334
|
-
resolveId(id) {
|
|
358
|
+
resolveId(id, importer, resolveOptions) {
|
|
359
|
+
if (id === "@uniflowed/react" && !isSsr(this, resolveOptions)) {
|
|
360
|
+
return this.resolve("react", importer, { ...resolveOptions, skipSelf: true });
|
|
361
|
+
}
|
|
335
362
|
if (id === RUNTIME_PUBLIC_PATH) return RUNTIME_RESOLVED_ID;
|
|
336
363
|
if (id === AUDIT_PUBLIC_PATH) return AUDIT_RESOLVED_ID;
|
|
337
364
|
if (VIRTUAL_IDS.has(id)) return resolved(id);
|
|
@@ -356,8 +383,17 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
356
383
|
// Strict Mode belongs to the client entry and to development only: a
|
|
357
384
|
// build passes `false`, so the generated module is the one that existed
|
|
358
385
|
// before #516 and a visitor's browser renders once.
|
|
386
|
+
//
|
|
387
|
+
// `navigation` is in the same entry and has no `isProduction` beside it,
|
|
388
|
+
// deliberately: it is what a link does, and a dev server whose links
|
|
389
|
+
// behave differently from the deployment is the wrong thing to be
|
|
390
|
+
// looking at. See `clientModuleSource`.
|
|
359
391
|
if (id === resolved(VIRTUAL.client)) {
|
|
360
|
-
return clientModuleSource(entryPath, {
|
|
392
|
+
return clientModuleSource(entryPath, {
|
|
393
|
+
strictMode: strictMode && !isProduction,
|
|
394
|
+
navigation,
|
|
395
|
+
mount,
|
|
396
|
+
});
|
|
361
397
|
}
|
|
362
398
|
if (id === resolved(VIRTUAL.server)) return serverModuleSource(entryPath);
|
|
363
399
|
// Only `virtual:uf/server` imports this, so it is only ever asked for in
|
|
@@ -474,12 +510,13 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
474
510
|
const tags = [
|
|
475
511
|
{
|
|
476
512
|
tag: "script",
|
|
513
|
+
attrs: { "data-uf-dev-head-preamble": "react-devtools" },
|
|
477
514
|
children: devtoolsPreamble(),
|
|
478
515
|
injectTo: "head-prepend",
|
|
479
516
|
},
|
|
480
517
|
{
|
|
481
518
|
tag: "script",
|
|
482
|
-
attrs: { type: "module" },
|
|
519
|
+
attrs: { type: "module", "data-uf-dev-head-preamble": "react-refresh" },
|
|
483
520
|
children: preambleCode(base),
|
|
484
521
|
injectTo: "head-prepend",
|
|
485
522
|
},
|
|
@@ -509,9 +546,8 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
509
546
|
// missing `route` — so adding a route handler to a running dev server
|
|
510
547
|
// did not rebuild the table and the handler stayed invisible until a
|
|
511
548
|
// restart. A list that has to match another list has to be that list.
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
.join("|");
|
|
549
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
550
|
+
const stems = Object.values(RESERVED).map(escapeRegExp).join("|");
|
|
515
551
|
const reserved = new RegExp(`/(${stems})(\\.[a-z]+)?\\.(js|jsx|mdx)$`);
|
|
516
552
|
const onRouteFile = (file) => {
|
|
517
553
|
if (!reserved.test(file) || !file.startsWith(appRoot)) return;
|
|
@@ -562,7 +598,7 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
562
598
|
// claimed, this one decided, and the other was reached only for what
|
|
563
599
|
// this one declined. Route-handler dispatch was in the other. A
|
|
564
600
|
// `GET /feed` from a browser is `Accept: text/html` with no extension,
|
|
565
|
-
// so it looked like a document, so `app/feed
|
|
601
|
+
// so it looked like a document, so `app/feed/$route.js` was never
|
|
566
602
|
// asked and the reader got the route table's page — or the not-found
|
|
567
603
|
// page — for a path that had a handler. See ubugeeei-prod/uf#349.
|
|
568
604
|
//
|
|
@@ -598,7 +634,7 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
598
634
|
// list rather than something this middleware can decide on its own.
|
|
599
635
|
return () => {
|
|
600
636
|
// The browser's own reporting channel, mounted above the application
|
|
601
|
-
// so that a report never reaches a project's
|
|
637
|
+
// so that a report never reaches a project's `$middleware.js` or
|
|
602
638
|
// its route table. See `internal/diagnostics.js`.
|
|
603
639
|
devServer.middlewares.use(
|
|
604
640
|
createChannelMiddleware((diagnostic) => emit("diagnostic", diagnostic)),
|
|
@@ -687,6 +723,30 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
687
723
|
}
|
|
688
724
|
if (notDocument != null) return false;
|
|
689
725
|
|
|
726
|
+
// A single-page project's deployment answers every navigation
|
|
727
|
+
// with the same empty shell, so this does too. Rendering the
|
|
728
|
+
// route here instead would have been the better-looking dev
|
|
729
|
+
// server and the wrong one: a page that only works because the
|
|
730
|
+
// server rendered it would work all through development and be
|
|
731
|
+
// blank the day it shipped. It is the same argument
|
|
732
|
+
// `app.rendering.navigation` makes about a link, one level up.
|
|
733
|
+
//
|
|
734
|
+
// The three steps above still ran — the guard, the action, the
|
|
735
|
+
// handler — and each of them is something `uf build` refuses in
|
|
736
|
+
// a `["csr"]` project by name. A dev server that skipped them
|
|
737
|
+
// would hide the very thing the build is going to stop.
|
|
738
|
+
if (mount === "render") {
|
|
739
|
+
response.statusCode = 200;
|
|
740
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
741
|
+
const shell = entry.shellDocument({
|
|
742
|
+
scripts: [devUrlFor(VIRTUAL.client)],
|
|
743
|
+
styles: [],
|
|
744
|
+
preloads: [],
|
|
745
|
+
});
|
|
746
|
+
response.end(await devServer.transformIndexHtml(url, shell));
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
|
|
690
750
|
const result = await entry.render(
|
|
691
751
|
url,
|
|
692
752
|
{ scripts: [devUrlFor(VIRTUAL.client)], styles: [], preloads: [] },
|
|
@@ -696,15 +756,41 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
696
756
|
// result by then — so the terminal hears about it here or not
|
|
697
757
|
// at all.
|
|
698
758
|
onError: (error) => reportRenderError(devServer, url, error),
|
|
759
|
+
// Vite sees the head and only the head. That is what lets the
|
|
760
|
+
// development server stream like every other host — see below.
|
|
761
|
+
transformHead: (head) => devServer.transformIndexHtml(url, head),
|
|
762
|
+
// And what the streaming actually did, when it changed. The
|
|
763
|
+
// router has already decided there is something worth saying
|
|
764
|
+
// and written the words — see its `internal/inspector.js`,
|
|
765
|
+
// which cannot be imported from here because this file is
|
|
766
|
+
// plain JavaScript that Vite loads before any Flow transform
|
|
767
|
+
// exists. `info`, because a page that streamed is a
|
|
768
|
+
// measurement and not a problem; `origin`, because a report
|
|
769
|
+
// that does not say which page produced it is one somebody
|
|
770
|
+
// has to reproduce before they can act on it.
|
|
771
|
+
onStream: (diagnostic) =>
|
|
772
|
+
emit("diagnostic", {
|
|
773
|
+
severity: "info",
|
|
774
|
+
origin: url,
|
|
775
|
+
message: diagnostic.message,
|
|
776
|
+
detail: diagnostic.detail,
|
|
777
|
+
}),
|
|
699
778
|
},
|
|
700
779
|
);
|
|
701
780
|
if (result.error != null) reportRenderError(devServer, url, result.error);
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
|
|
781
|
+
// Piped, like every other host. This used to collect the whole
|
|
782
|
+
// document and transform it at the end, because
|
|
783
|
+
// `transformIndexHtml` is a *whole document* hook — which made the
|
|
784
|
+
// one place a developer would notice streaming the one place it
|
|
785
|
+
// did not happen: a slow page showed nothing until it was finished
|
|
786
|
+
// and `$loading.js` looked broken.
|
|
787
|
+
//
|
|
788
|
+
// `transformHead` above is the seam. `internal/stream.js` already
|
|
789
|
+
// held the opening chunk back until the head was complete and
|
|
790
|
+
// forwarded everything after it untouched, so the hook only ever
|
|
791
|
+
// sees the head — and Vite's dev hook handles a document that ends
|
|
792
|
+
// mid-`<body>` without complaint, which was the open question on
|
|
793
|
+
// ubugeeei-prod/uf#374.
|
|
708
794
|
response.statusCode = result.status ?? 200;
|
|
709
795
|
// The render's own headers, then the content type over the top:
|
|
710
796
|
// exactly the order `@uniflowed/server`'s `fetch.js` writes them
|
|
@@ -714,7 +800,7 @@ function flowPlugin({ routerRoot, appEntry, strictMode, command, accessibility }
|
|
|
714
800
|
response.setHeader(name, value);
|
|
715
801
|
}
|
|
716
802
|
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
717
|
-
|
|
803
|
+
await result.pipe(response);
|
|
718
804
|
return true;
|
|
719
805
|
});
|
|
720
806
|
|
package/internal/a11y.js
CHANGED
|
@@ -86,15 +86,17 @@ export function auditRuntimeSource(settings) {
|
|
|
86
86
|
/**
|
|
87
87
|
* The tag that loads it, or `null` when this project has no engine.
|
|
88
88
|
*
|
|
89
|
-
* `injectTo: "
|
|
90
|
-
*
|
|
91
|
-
*
|
|
89
|
+
* `injectTo: "head"` because `uf dev` streams the body. Vite's HTML hook only
|
|
90
|
+
* sees the opening chunk; a `body` injection can land at a React chunk boundary
|
|
91
|
+
* and split an attribute before the rest of the body arrives. A module in the
|
|
92
|
+
* head still waits for a settled DOM before auditing — see the runtime — while
|
|
93
|
+
* staying in markup the opening transform can safely rewrite.
|
|
92
94
|
*/
|
|
93
95
|
export function auditTag(base, available) {
|
|
94
96
|
if (!available) return null;
|
|
95
97
|
return {
|
|
96
98
|
tag: "script",
|
|
97
99
|
attrs: { type: "module", src: `${base}${AUDIT_PUBLIC_PATH.slice(1)}` },
|
|
98
|
-
injectTo: "
|
|
100
|
+
injectTo: "head",
|
|
99
101
|
};
|
|
100
102
|
}
|
package/internal/devtools.js
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
// how the preamble is injected, could have taken it away in a diff nobody would
|
|
24
24
|
// read as being about DevTools. See ubugeeei-prod/uf#503.
|
|
25
25
|
//
|
|
26
|
-
// So the hook is installed here, first, deliberately, and `
|
|
27
|
-
//
|
|
26
|
+
// So the hook is installed here, first, deliberately, and `devtools.test.js`
|
|
27
|
+
// beside this package runs this script's own text against a fake window.
|
|
28
28
|
//
|
|
29
29
|
// # Three things DevTools needs, and what carries each
|
|
30
30
|
//
|
package/internal/diagnostics.js
CHANGED
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
// `@uniflowed/router`'s `internal/diagnostics.js` are the same two strings, and
|
|
58
58
|
// they are the contract. They cannot be *imported* here: this module is loaded
|
|
59
59
|
// by Vite before any Flow transform exists, and both of those are Flow. So they
|
|
60
|
-
// are written out, and `
|
|
60
|
+
// are written out, and `packages/vite/dev-channel.test.js` asserts that all
|
|
61
61
|
// four spellings agree — a duplicated constant with a test on it is honest, and
|
|
62
62
|
// one without is how the browser ends up posting to a path nothing serves.
|
|
63
63
|
//
|
|
@@ -105,7 +105,7 @@ const SEVERITIES = new Set(["error", "warn", "info"]);
|
|
|
105
105
|
* The connect middleware that answers the channel.
|
|
106
106
|
*
|
|
107
107
|
* Mounted **before** the application middleware, so a request under `/__uf/`
|
|
108
|
-
* never reaches a project's
|
|
108
|
+
* never reaches a project's `$middleware.js` or its route table. A guard
|
|
109
109
|
* that ran for a page's own telemetry would be a guard asked a question the
|
|
110
110
|
* application never asks, and one that redirected it would turn a report into
|
|
111
111
|
* a login page.
|
|
@@ -435,7 +435,7 @@ export function markLines(lines) {
|
|
|
435
435
|
*
|
|
436
436
|
* Exported because it is where the decision is visible without starting Shiki:
|
|
437
437
|
* give it the token split a grammar would produce and it says which words it
|
|
438
|
-
* marked. `
|
|
438
|
+
* marked. `packages/vite/highlight.test.js` uses exactly that, and the splits
|
|
439
439
|
* in it are the ones that had bugs.
|
|
440
440
|
*/
|
|
441
441
|
export function markLine(line) {
|