@uniflowed/vite 0.0.0-alpha.40 → 0.0.0-alpha.42
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 +17 -3
- package/index.js +77 -37
- package/internal/dev-state.js +161 -0
- package/internal/events.js +3 -0
- package/internal/flight.js +26 -8
- package/internal/instrumentation.js +23 -0
- package/internal/native-web.js +26 -0
- package/internal/relay.js +74 -0
- package/internal/routes.js +26 -6
- package/internal/serve.js +4 -3
- package/package.json +11 -8
package/driver.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
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 { esmExternalRequirePlugin } from "rolldown/plugins";
|
|
45
46
|
import { installFlowHooks } from "@uniflowed/host/internal/sync-hooks.js";
|
|
46
47
|
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
47
48
|
import path from "node:path";
|
|
@@ -524,6 +525,9 @@ async function preview() {
|
|
|
524
525
|
if ((request.url ?? "").split("?")[0].endsWith("/__uf.flight")) {
|
|
525
526
|
response.setHeader("content-type", "text/x-component");
|
|
526
527
|
}
|
|
528
|
+
if ((request.url ?? "").split("?")[0] === "/.well-known/apple-app-site-association") {
|
|
529
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
530
|
+
}
|
|
527
531
|
next();
|
|
528
532
|
});
|
|
529
533
|
if (handle == null) return;
|
|
@@ -1576,7 +1580,12 @@ async function deploy() {
|
|
|
1576
1580
|
plugins: [
|
|
1577
1581
|
...inline.plugins,
|
|
1578
1582
|
nativeAddonGuard(),
|
|
1579
|
-
...(shape.workerBuiltins === true
|
|
1583
|
+
...(shape.workerBuiltins === true
|
|
1584
|
+
? [
|
|
1585
|
+
workerBuiltinGuard(),
|
|
1586
|
+
esmExternalRequirePlugin({ external: [...builtinModules, /^node:/] }),
|
|
1587
|
+
]
|
|
1588
|
+
: []),
|
|
1580
1589
|
],
|
|
1581
1590
|
// Fixed, because this bundle inlines every dependency and so both of each
|
|
1582
1591
|
// React package's builds, and a runtime lookup of `NODE_ENV` in a worker
|
|
@@ -1607,6 +1616,10 @@ async function deploy() {
|
|
|
1607
1616
|
emptyOutDir: false,
|
|
1608
1617
|
rollupOptions: {
|
|
1609
1618
|
input,
|
|
1619
|
+
// Workers provide the selected built-ins, but cannot use Node's
|
|
1620
|
+
// createRequire(import.meta.url) runtime helper. External requires
|
|
1621
|
+
// above become ESM imports; the remaining helpers are platform neutral.
|
|
1622
|
+
...(shape.workerBuiltins === true ? { platform: "neutral" } : {}),
|
|
1610
1623
|
output: {
|
|
1611
1624
|
entryFileNames: "[name].js",
|
|
1612
1625
|
// Route modules are lazy `import()`s, so the server bundle splits
|
|
@@ -1677,11 +1690,12 @@ async function deploy() {
|
|
|
1677
1690
|
function handlerEntrySource(document, cache, capabilities, build, regeneration) {
|
|
1678
1691
|
const route = cache?.route === true;
|
|
1679
1692
|
const fetchCache = cache?.fetch === true;
|
|
1693
|
+
const dataCache = cache?.data === true;
|
|
1680
1694
|
// Nothing at all when both switches are off, so a default project's
|
|
1681
1695
|
// `handler.js` is the file it has always been. A cache that appears in
|
|
1682
1696
|
// generated output nobody asked for is the second half of the complaint
|
|
1683
1697
|
// #277 makes about the first half.
|
|
1684
|
-
const store = route || fetchCache;
|
|
1698
|
+
const store = route || fetchCache || dataCache;
|
|
1685
1699
|
const durable = store ? durableStoreSource(root, cache, build) : null;
|
|
1686
1700
|
const options = [
|
|
1687
1701
|
"app",
|
|
@@ -1716,7 +1730,7 @@ ${
|
|
|
1716
1730
|
} See ubugeeei-prod/uf#277.
|
|
1717
1731
|
const cache = { store: createCacheStore(${
|
|
1718
1732
|
durable == null ? "" : `{ provider: ${durable.provider}, build: ${JSON.stringify(build)} }`
|
|
1719
|
-
}), route: ${String(route)}, fetch: ${String(fetchCache)} };
|
|
1733
|
+
}), route: ${String(route)}, fetch: ${String(fetchCache)}, data: ${String(dataCache)} };
|
|
1720
1734
|
|
|
1721
1735
|
`
|
|
1722
1736
|
: ""
|
package/index.js
CHANGED
|
@@ -44,11 +44,14 @@
|
|
|
44
44
|
// `uniflowed(options)` returns the array; a project that wants to add a plugin
|
|
45
45
|
// declares it in `uf.config.js` and the driver appends it after these.
|
|
46
46
|
|
|
47
|
-
import {
|
|
47
|
+
import { instrumentationFile } from "./internal/instrumentation.js";
|
|
48
|
+
|
|
48
49
|
import path from "node:path";
|
|
50
|
+
import { nativeWebPlugin } from "./internal/native-web.js";
|
|
49
51
|
|
|
50
52
|
import mdx from "@mdx-js/rollup";
|
|
51
53
|
import rehypeSlug from "rehype-slug";
|
|
54
|
+
import { relayConfig, relayDependencies, transformRelay } from "./internal/relay.js";
|
|
52
55
|
|
|
53
56
|
import {
|
|
54
57
|
AUDIT_PUBLIC_PATH,
|
|
@@ -59,7 +62,7 @@ import {
|
|
|
59
62
|
} from "./internal/a11y.js";
|
|
60
63
|
import { assetPlugin } from "./internal/assets.js";
|
|
61
64
|
import { barrelImportsPlugin, namespaceViewOf } from "./internal/barrel-imports.js";
|
|
62
|
-
import { emit, reportRenderError } from "./internal/events.js";
|
|
65
|
+
import { emit, reportRenderError, errorEvent } from "./internal/events.js";
|
|
63
66
|
import remarkFrontmatterExport from "./internal/frontmatter.js";
|
|
64
67
|
import { highlightPlugin } from "./internal/highlight.js";
|
|
65
68
|
import { moduleId } from "./internal/module-graph.js";
|
|
@@ -93,11 +96,12 @@ import {
|
|
|
93
96
|
scanRoutes,
|
|
94
97
|
serverModuleSource,
|
|
95
98
|
} from "./internal/routes.js";
|
|
96
|
-
import { TransformService, isFlowModule } from "@uniflowed/host/transform";
|
|
99
|
+
import { TransformService, isFlowModule, uniflowedPackages } from "@uniflowed/host/transform";
|
|
97
100
|
import {
|
|
98
101
|
DEV_RSC_HOOK,
|
|
99
102
|
FLIGHT_BROWSER_DEPENDENCIES,
|
|
100
103
|
FLIGHT_VIRTUAL,
|
|
104
|
+
INTERCEPTED_FROM_HEADER,
|
|
101
105
|
RSC_ENVIRONMENT,
|
|
102
106
|
builtBridgeSource,
|
|
103
107
|
builtReferencesSource,
|
|
@@ -118,6 +122,7 @@ import {
|
|
|
118
122
|
rscEnvironment,
|
|
119
123
|
} from "./internal/flight.js";
|
|
120
124
|
import { createChannelMiddleware } from "./internal/diagnostics.js";
|
|
125
|
+
import { startDevState } from "./internal/dev-state.js";
|
|
121
126
|
import { devtoolsPreamble } from "./internal/devtools.js";
|
|
122
127
|
import { send, toAddressRequest, toRequest } from "./internal/http.js";
|
|
123
128
|
import {
|
|
@@ -133,6 +138,25 @@ import { serverComponentsProblem } from "./internal/server-components.js";
|
|
|
133
138
|
const resolved = (id) => `\0${id}`;
|
|
134
139
|
const VIRTUAL_IDS = new Set([...Object.values(VIRTUAL), ...Object.values(FLIGHT_VIRTUAL)]);
|
|
135
140
|
|
|
141
|
+
/**
|
|
142
|
+
* CommonJS packages the browser can reach only through an installed
|
|
143
|
+
* `@uniflowed/*` package.
|
|
144
|
+
*
|
|
145
|
+
* uf excludes its own packages from Vite's dependency optimizer because they
|
|
146
|
+
* ship Flow, so Vite will not discover a CommonJS peer through them once they
|
|
147
|
+
* live under a project's `node_modules`. React Testing's `happy-dom` edge is
|
|
148
|
+
* ESM and browser-mapped to a local stub, and the optional `axe-core` edge is
|
|
149
|
+
* named separately below only when the project actually installed it.
|
|
150
|
+
*/
|
|
151
|
+
const CLIENT_COMMONJS_DEPENDENCIES = Object.freeze([
|
|
152
|
+
"react",
|
|
153
|
+
"react/jsx-runtime",
|
|
154
|
+
"react/jsx-dev-runtime",
|
|
155
|
+
"react/compiler-runtime",
|
|
156
|
+
"react-dom",
|
|
157
|
+
"react-dom/client",
|
|
158
|
+
]);
|
|
159
|
+
|
|
136
160
|
/**
|
|
137
161
|
* Prefix of the virtual module that carries one source module's StyleX rules.
|
|
138
162
|
*
|
|
@@ -229,6 +253,7 @@ export default function uniflowed(options = {}) {
|
|
|
229
253
|
routing,
|
|
230
254
|
command: options.command,
|
|
231
255
|
accessibility,
|
|
256
|
+
relayEnabled: builtins.relay !== false,
|
|
232
257
|
}),
|
|
233
258
|
...(flightState == null ? [] : [clientReferencePlugin(flightState), clientModuleUrlPlugin()]),
|
|
234
259
|
// After the references, so a client module the rsc graph has already
|
|
@@ -242,6 +267,7 @@ export default function uniflowed(options = {}) {
|
|
|
242
267
|
og: builtins.og ?? {},
|
|
243
268
|
command: options.command,
|
|
244
269
|
}),
|
|
270
|
+
nativeWebPlugin(routeTarget),
|
|
245
271
|
];
|
|
246
272
|
}
|
|
247
273
|
|
|
@@ -257,6 +283,7 @@ function flowPlugin({
|
|
|
257
283
|
routing,
|
|
258
284
|
command,
|
|
259
285
|
accessibility,
|
|
286
|
+
relayEnabled,
|
|
260
287
|
}) {
|
|
261
288
|
let root = process.cwd();
|
|
262
289
|
let isProduction = false;
|
|
@@ -276,6 +303,7 @@ function flowPlugin({
|
|
|
276
303
|
let server = null;
|
|
277
304
|
/** @type {TransformService | null} */
|
|
278
305
|
let service = null;
|
|
306
|
+
let relayOptions = null;
|
|
279
307
|
/**
|
|
280
308
|
* Each module's compiled stylesheet, keyed by the virtual id serving it.
|
|
281
309
|
*
|
|
@@ -413,16 +441,12 @@ function flowPlugin({
|
|
|
413
441
|
// uf serves HTML itself; there is no index.html to fall back to.
|
|
414
442
|
appType: "custom",
|
|
415
443
|
resolve: {
|
|
416
|
-
dedupe: ["react", "react-dom"],
|
|
444
|
+
dedupe: ["react", "react-dom", "react-relay", "relay-runtime"],
|
|
417
445
|
},
|
|
418
446
|
optimizeDeps: {
|
|
419
447
|
include: [
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
"react/jsx-dev-runtime",
|
|
423
|
-
"react/compiler-runtime",
|
|
424
|
-
"react-dom",
|
|
425
|
-
"react-dom/client",
|
|
448
|
+
...CLIENT_COMMONJS_DEPENDENCIES,
|
|
449
|
+
...relayDependencies(projectRoot),
|
|
426
450
|
// React's Flight client, for an application whose routes render as
|
|
427
451
|
// Server Components; `FLIGHT_BROWSER_DEPENDENCIES` says why.
|
|
428
452
|
...(flightState == null ? [] : FLIGHT_BROWSER_DEPENDENCIES),
|
|
@@ -455,6 +479,7 @@ function flowPlugin({
|
|
|
455
479
|
: {
|
|
456
480
|
environments: {
|
|
457
481
|
[RSC_ENVIRONMENT]: rscEnvironment({
|
|
482
|
+
root: projectRoot,
|
|
458
483
|
production: env.command === "build",
|
|
459
484
|
exclude: uniflowedPackages(projectRoot),
|
|
460
485
|
}),
|
|
@@ -556,6 +581,7 @@ function flowPlugin({
|
|
|
556
581
|
// looking at. See `clientModuleSource`.
|
|
557
582
|
if (id === resolved(VIRTUAL.client) && flightState != null) {
|
|
558
583
|
return flightClientSource(entryPath, {
|
|
584
|
+
instrumentation: instrumentationFile(appRoot, true),
|
|
559
585
|
strictMode: strictMode && !isProduction,
|
|
560
586
|
navigation,
|
|
561
587
|
staleTime,
|
|
@@ -564,6 +590,7 @@ function flowPlugin({
|
|
|
564
590
|
}
|
|
565
591
|
if (id === resolved(VIRTUAL.client)) {
|
|
566
592
|
return clientModuleSource(entryPath, {
|
|
593
|
+
instrumentation: instrumentationFile(appRoot, true),
|
|
567
594
|
strictMode: strictMode && !isProduction,
|
|
568
595
|
navigation,
|
|
569
596
|
staleTime,
|
|
@@ -573,8 +600,14 @@ function flowPlugin({
|
|
|
573
600
|
}
|
|
574
601
|
if (id === resolved(VIRTUAL.server)) {
|
|
575
602
|
return flightState == null
|
|
576
|
-
? serverModuleSource(entryPath, routing)
|
|
577
|
-
: flightServerSource(
|
|
603
|
+
? serverModuleSource(entryPath, routing, instrumentationFile(appRoot))
|
|
604
|
+
: flightServerSource(
|
|
605
|
+
entryPath,
|
|
606
|
+
VIRTUAL.routes,
|
|
607
|
+
VIRTUAL.actions,
|
|
608
|
+
routing,
|
|
609
|
+
instrumentationFile(appRoot),
|
|
610
|
+
);
|
|
578
611
|
}
|
|
579
612
|
if (flightState != null) {
|
|
580
613
|
if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes, routing);
|
|
@@ -657,7 +690,16 @@ function flowPlugin({
|
|
|
657
690
|
reported,
|
|
658
691
|
suppressed,
|
|
659
692
|
});
|
|
660
|
-
|
|
693
|
+
let map = out.map == null ? null : JSON.parse(out.map);
|
|
694
|
+
let output = out.code;
|
|
695
|
+
if (relayEnabled && output.includes("graphql")) {
|
|
696
|
+
relayOptions ??= relayConfig(root);
|
|
697
|
+
const transformed = await transformRelay(output, cleanId(id), map, await relayOptions);
|
|
698
|
+
if (transformed != null) {
|
|
699
|
+
output = transformed.code;
|
|
700
|
+
map = transformed.map;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
661
703
|
// StyleX. `uf transform` compiled the module's `stylex.create` calls into
|
|
662
704
|
// class names and handed back the rules they declared; the rules become a
|
|
663
705
|
// module of their own that this one imports.
|
|
@@ -675,7 +717,6 @@ function flowPlugin({
|
|
|
675
717
|
// and an absolute name would publish where the machine that built it
|
|
676
718
|
// keeps its files. A module outside the root climbs out with `../`.
|
|
677
719
|
const styled = out.css != null && out.css !== "";
|
|
678
|
-
let output = out.code;
|
|
679
720
|
if (styled) {
|
|
680
721
|
const styleId = `${STYLE_PREFIX}${moduleId(root, cleanId(id))}.css`;
|
|
681
722
|
styles.set(styleId, out.css);
|
|
@@ -763,6 +804,16 @@ function flowPlugin({
|
|
|
763
804
|
|
|
764
805
|
configureServer(devServer) {
|
|
765
806
|
server = devServer;
|
|
807
|
+
const diagnostics = startDevState(root, () => ({
|
|
808
|
+
routes: scanRoutes(appRoot, { target: routeTarget }).routes.map((route) => ({
|
|
809
|
+
path: route.path,
|
|
810
|
+
page: route.page,
|
|
811
|
+
layouts: route.layouts,
|
|
812
|
+
})),
|
|
813
|
+
actions: actionTables().table,
|
|
814
|
+
}));
|
|
815
|
+
devServer.middlewares.use(diagnostics.middleware);
|
|
816
|
+
devServer.httpServer?.once("close", diagnostics.close);
|
|
766
817
|
// The ssr graph's way into the rsc graph; see `devBridgeSource`.
|
|
767
818
|
if (flightState != null) {
|
|
768
819
|
globalThis[Symbol.for(DEV_RSC_HOOK)] = () =>
|
|
@@ -823,8 +874,10 @@ function flowPlugin({
|
|
|
823
874
|
const reserved = new RegExp(`/(${stems})(\\.[a-z]+)?\\.(js|jsx|mdx)$`);
|
|
824
875
|
const onRouteFile = (file) => {
|
|
825
876
|
if (!reserved.test(file) || !file.startsWith(appRoot)) return;
|
|
826
|
-
const
|
|
827
|
-
|
|
877
|
+
for (const id of [VIRTUAL.routes, VIRTUAL.server, VIRTUAL.client]) {
|
|
878
|
+
const module = devServer.moduleGraph.getModuleById(resolved(id));
|
|
879
|
+
if (module) devServer.moduleGraph.invalidateModule(module);
|
|
880
|
+
}
|
|
828
881
|
devServer.ws.send({ type: "full-reload", path: "*" });
|
|
829
882
|
};
|
|
830
883
|
devServer.watcher.on("add", onRouteFile);
|
|
@@ -995,8 +1048,10 @@ function flowPlugin({
|
|
|
995
1048
|
if (payloadFor != null && (request.method === "GET" || request.method === "HEAD")) {
|
|
996
1049
|
const query = url.includes("?") ? url.slice(url.indexOf("?")) : "";
|
|
997
1050
|
const target = payloadFor + query;
|
|
1051
|
+
const fromHeader = request.headers[INTERCEPTED_FROM_HEADER];
|
|
998
1052
|
const answered = await entry.flight(target, {
|
|
999
1053
|
onError: (error) => reportRenderError(devServer, target, error),
|
|
1054
|
+
interceptedFrom: Array.isArray(fromHeader) ? fromHeader[0] : fromHeader,
|
|
1000
1055
|
});
|
|
1001
1056
|
if (answered.error != null) reportRenderError(devServer, target, answered.error);
|
|
1002
1057
|
if (request.method === "HEAD") await answered.stream?.cancel();
|
|
@@ -1148,6 +1203,12 @@ function flowPlugin({
|
|
|
1148
1203
|
// Map the stack back onto the Flow source before it reaches the
|
|
1149
1204
|
// overlay.
|
|
1150
1205
|
if (error instanceof Error) devServer.ssrFixStacktrace(error);
|
|
1206
|
+
emit("diagnostic", {
|
|
1207
|
+
severity: "error",
|
|
1208
|
+
kind: "runtime",
|
|
1209
|
+
origin: url,
|
|
1210
|
+
...errorEvent(error),
|
|
1211
|
+
});
|
|
1151
1212
|
next(error);
|
|
1152
1213
|
}
|
|
1153
1214
|
});
|
|
@@ -1420,24 +1481,3 @@ function cleanId(id) {
|
|
|
1420
1481
|
const at = id.indexOf("?");
|
|
1421
1482
|
return at === -1 ? id : id.slice(0, at);
|
|
1422
1483
|
}
|
|
1423
|
-
|
|
1424
|
-
/**
|
|
1425
|
-
* Every `@uniflowed/*` package the project can resolve, for
|
|
1426
|
-
* `optimizeDeps.exclude`, which takes names rather than patterns.
|
|
1427
|
-
*/
|
|
1428
|
-
function uniflowedPackages(root) {
|
|
1429
|
-
const names = new Set();
|
|
1430
|
-
let directory = root;
|
|
1431
|
-
for (let depth = 0; depth < 16; depth += 1) {
|
|
1432
|
-
const scope = path.join(directory, "node_modules", "@uniflowed");
|
|
1433
|
-
try {
|
|
1434
|
-
for (const entry of readdirSync(scope)) names.add(`@uniflowed/${entry}`);
|
|
1435
|
-
} catch {
|
|
1436
|
-
// no packages at this level
|
|
1437
|
-
}
|
|
1438
|
-
const parent = path.dirname(directory);
|
|
1439
|
-
if (parent === directory) break;
|
|
1440
|
-
directory = parent;
|
|
1441
|
-
}
|
|
1442
|
-
return [...names].sort();
|
|
1443
|
-
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// @noflow
|
|
2
|
+
// A local, bounded snapshot of the same diagnostic channel the terminal reads.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
+
import { format } from "node:util";
|
|
8
|
+
|
|
9
|
+
const requests = new AsyncLocalStorage();
|
|
10
|
+
let receive = null;
|
|
11
|
+
|
|
12
|
+
export function recordDevEvent(event) {
|
|
13
|
+
const request = requests.getStore();
|
|
14
|
+
receive?.({ ...event, ...(request == null ? {} : request), time: Date.now() });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function startDevState(root, metadata) {
|
|
18
|
+
const directory = path.join(root, ".uf");
|
|
19
|
+
if (fs.existsSync(directory) && fs.lstatSync(directory).isSymbolicLink())
|
|
20
|
+
throw new Error("uf dev: .uf must not be a symlink for diagnostics");
|
|
21
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
22
|
+
const file = path.join(directory, "dev-state.json");
|
|
23
|
+
if (fs.existsSync(file) && fs.lstatSync(file).isSymbolicLink())
|
|
24
|
+
throw new Error("uf dev: diagnostic state must not be a symlink");
|
|
25
|
+
const session = randomUUID();
|
|
26
|
+
const temporary = path.join(directory, "dev-state-" + session + ".tmp");
|
|
27
|
+
const state = {
|
|
28
|
+
schema: 1,
|
|
29
|
+
session,
|
|
30
|
+
pid: process.pid,
|
|
31
|
+
startedAt: Date.now(),
|
|
32
|
+
updatedAt: 0,
|
|
33
|
+
generation: 0,
|
|
34
|
+
errors: [],
|
|
35
|
+
logs: [],
|
|
36
|
+
routes: [],
|
|
37
|
+
actions: [],
|
|
38
|
+
};
|
|
39
|
+
let closed = false;
|
|
40
|
+
const persist = (refresh = false) => {
|
|
41
|
+
if (closed) return;
|
|
42
|
+
if (refresh || state.updatedAt === 0) {
|
|
43
|
+
let current;
|
|
44
|
+
try {
|
|
45
|
+
current = metadata();
|
|
46
|
+
state.metadataError = null;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
state.metadataError = String(error.message ?? error).slice(0, 4000);
|
|
49
|
+
current = { routes: state.routes, actions: state.actions };
|
|
50
|
+
}
|
|
51
|
+
state.routes = current.routes.slice(0, 1000);
|
|
52
|
+
state.actions = current.actions.slice(0, 1000);
|
|
53
|
+
state.truncated = current.routes.length > 1000 || current.actions.length > 1000;
|
|
54
|
+
}
|
|
55
|
+
state.updatedAt = Date.now();
|
|
56
|
+
let serialized = JSON.stringify(state);
|
|
57
|
+
while (Buffer.byteLength(serialized) > 3 * 1024 * 1024) {
|
|
58
|
+
const rows = state.logs.length
|
|
59
|
+
? state.logs
|
|
60
|
+
: state.errors.length
|
|
61
|
+
? state.errors
|
|
62
|
+
: state.routes.length
|
|
63
|
+
? state.routes
|
|
64
|
+
: state.actions;
|
|
65
|
+
if (rows.length === 0) break;
|
|
66
|
+
rows.shift();
|
|
67
|
+
state.truncated = true;
|
|
68
|
+
serialized = JSON.stringify(state);
|
|
69
|
+
}
|
|
70
|
+
fs.writeFileSync(temporary, serialized, { mode: 0o600 });
|
|
71
|
+
fs.renameSync(temporary, file);
|
|
72
|
+
};
|
|
73
|
+
const observer = (event) => {
|
|
74
|
+
const safe = bound(event);
|
|
75
|
+
if (event.event === "source-changed") {
|
|
76
|
+
state.generation += 1;
|
|
77
|
+
state.errors = [];
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
event.event === "error" ||
|
|
81
|
+
(event.event === "log" && event.level === "error") ||
|
|
82
|
+
(event.event === "diagnostic" && event.severity === "error")
|
|
83
|
+
) {
|
|
84
|
+
safe.kind ??= /hydrat/i.test(event.message ?? "")
|
|
85
|
+
? "hydration"
|
|
86
|
+
: event.event === "diagnostic"
|
|
87
|
+
? "runtime"
|
|
88
|
+
: "build";
|
|
89
|
+
state.errors.push(safe);
|
|
90
|
+
state.errors = state.errors.slice(-100);
|
|
91
|
+
}
|
|
92
|
+
if (["log", "diagnostic", "error", "request"].includes(event.event)) {
|
|
93
|
+
state.logs.push(safe);
|
|
94
|
+
state.logs = state.logs.slice(-150);
|
|
95
|
+
}
|
|
96
|
+
persist(event.event === "source-changed");
|
|
97
|
+
};
|
|
98
|
+
receive = observer;
|
|
99
|
+
persist();
|
|
100
|
+
// The terminal sees application console output too; keep its original behavior.
|
|
101
|
+
const originals = new Map();
|
|
102
|
+
for (const level of ["log", "info", "warn", "error", "debug"]) {
|
|
103
|
+
const original = console[level];
|
|
104
|
+
const wrapper = (...args) => {
|
|
105
|
+
recordDevEvent({ event: "log", level, kind: "runtime", message: format(...args) });
|
|
106
|
+
original.apply(console, args);
|
|
107
|
+
};
|
|
108
|
+
originals.set(level, { original, wrapper });
|
|
109
|
+
console[level] = wrapper;
|
|
110
|
+
}
|
|
111
|
+
const timer = setInterval(() => {
|
|
112
|
+
try {
|
|
113
|
+
persist(true);
|
|
114
|
+
} catch {
|
|
115
|
+
close();
|
|
116
|
+
}
|
|
117
|
+
}, 5000);
|
|
118
|
+
timer.unref?.();
|
|
119
|
+
const close = () => {
|
|
120
|
+
if (closed) return;
|
|
121
|
+
closed = true;
|
|
122
|
+
clearInterval(timer);
|
|
123
|
+
if (receive === observer) receive = null;
|
|
124
|
+
for (const [level, { original, wrapper }] of originals) {
|
|
125
|
+
if (console[level] === wrapper) console[level] = original;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
if (JSON.parse(fs.readFileSync(file, "utf8")).session === session) fs.unlinkSync(file);
|
|
129
|
+
} catch {
|
|
130
|
+
/* A missing or unreadable channel is reported by the MCP reader. */
|
|
131
|
+
}
|
|
132
|
+
process.off("exit", close);
|
|
133
|
+
};
|
|
134
|
+
process.once("exit", close);
|
|
135
|
+
return {
|
|
136
|
+
close,
|
|
137
|
+
middleware(request, response, next) {
|
|
138
|
+
const requestId = randomUUID();
|
|
139
|
+
const url = String(request.url ?? "/")
|
|
140
|
+
.split("?")[0]
|
|
141
|
+
.slice(0, 1000);
|
|
142
|
+
response.setHeader("x-uf-request-id", requestId);
|
|
143
|
+
requests.run({ requestId, url }, () => {
|
|
144
|
+
observer({ event: "request", requestId, url, method: request.method, time: Date.now() });
|
|
145
|
+
next();
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function bound(value, depth = 0) {
|
|
152
|
+
if (typeof value === "string") return value.slice(0, 4000);
|
|
153
|
+
if (value == null || typeof value !== "object") return value;
|
|
154
|
+
if (depth > 4) return "[truncated]";
|
|
155
|
+
if (Array.isArray(value)) return value.slice(0, 40).map((v) => bound(v, depth + 1));
|
|
156
|
+
return Object.fromEntries(
|
|
157
|
+
Object.entries(value)
|
|
158
|
+
.slice(0, 32)
|
|
159
|
+
.map(([key, v]) => [key, bound(v, depth + 1)]),
|
|
160
|
+
);
|
|
161
|
+
}
|
package/internal/events.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// logger, which is redirected here as well so nothing bypasses the channel.
|
|
12
12
|
|
|
13
13
|
import { createLogger } from "vite";
|
|
14
|
+
import { recordDevEvent } from "./dev-state.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Emit one event.
|
|
@@ -19,6 +20,7 @@ import { createLogger } from "vite";
|
|
|
19
20
|
* `done` is on the pipe before the process exits.
|
|
20
21
|
*/
|
|
21
22
|
export function emit(event, fields = {}) {
|
|
23
|
+
recordDevEvent({ event, ...fields });
|
|
22
24
|
process.stdout.write(`${JSON.stringify({ event, ...fields })}\n`);
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -67,6 +69,7 @@ export function reportRenderError(server, url, error) {
|
|
|
67
69
|
if (error instanceof Error) {
|
|
68
70
|
server.ssrFixStacktrace(error);
|
|
69
71
|
}
|
|
72
|
+
emit("diagnostic", { severity: "error", kind: "runtime", origin: url, ...errorEvent(error) });
|
|
70
73
|
const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
71
74
|
server.config.logger.error(`${url} rendered its error boundary\n${stripAnsi(detail)}`);
|
|
72
75
|
}
|
package/internal/flight.js
CHANGED
|
@@ -49,7 +49,10 @@
|
|
|
49
49
|
// In development the rsc environment's module graph is read instead, once the
|
|
50
50
|
// route's modules have been imported.
|
|
51
51
|
|
|
52
|
+
import { clientInstrumentationSource } from "./instrumentation.js";
|
|
53
|
+
|
|
52
54
|
import path from "node:path";
|
|
55
|
+
import { relayDependencies } from "./relay.js";
|
|
53
56
|
|
|
54
57
|
import {
|
|
55
58
|
createRunnableDevEnvironment,
|
|
@@ -120,6 +123,9 @@ export const DEV_RSC_HOOK = "uf.dev.rsc";
|
|
|
120
123
|
*/
|
|
121
124
|
export const FLIGHT_SEGMENT = "__uf.flight";
|
|
122
125
|
|
|
126
|
+
/** The header a browser sends when a Flight payload should render an interception. */
|
|
127
|
+
export const INTERCEPTED_FROM_HEADER = "uf-intercepted-from";
|
|
128
|
+
|
|
123
129
|
/** The document a payload path is for, or `null` for any other path. */
|
|
124
130
|
export function flightDocumentPath(pathname) {
|
|
125
131
|
const suffix = `/${FLIGHT_SEGMENT}`;
|
|
@@ -166,7 +172,7 @@ export function rendersFlight(app, { mount, routeTarget }) {
|
|
|
166
172
|
*
|
|
167
173
|
* @param {{ production: boolean, exclude: Array<string> }} options
|
|
168
174
|
*/
|
|
169
|
-
export function rscEnvironment({ production, exclude }) {
|
|
175
|
+
export function rscEnvironment({ production, exclude, root = process.cwd() }) {
|
|
170
176
|
return {
|
|
171
177
|
consumer: "server",
|
|
172
178
|
resolve: {
|
|
@@ -180,6 +186,7 @@ export function rscEnvironment({ production, exclude }) {
|
|
|
180
186
|
"react/jsx-runtime",
|
|
181
187
|
"react/jsx-dev-runtime",
|
|
182
188
|
"react-server-dom-parcel/server",
|
|
189
|
+
...relayDependencies(root, true),
|
|
183
190
|
],
|
|
184
191
|
exclude,
|
|
185
192
|
},
|
|
@@ -661,7 +668,7 @@ export function flightClientSource(appEntry, options = {}) {
|
|
|
661
668
|
options.staleTime > 0 ? `, staleTime: ${JSON.stringify(options.staleTime)}` : "";
|
|
662
669
|
return `import { hydrateFlight } from "@uniflowed/router/rsc/client";
|
|
663
670
|
import App from ${JSON.stringify(appEntry)};
|
|
664
|
-
hydrateFlight({ App${strictMode}${navigation}${staleTime}${routing} });
|
|
671
|
+
${clientInstrumentationSource(options.instrumentation)}hydrateFlight({ App${strictMode}${navigation}${staleTime}${routing} });
|
|
665
672
|
`;
|
|
666
673
|
}
|
|
667
674
|
|
|
@@ -682,9 +689,13 @@ export function flightServerSource(
|
|
|
682
689
|
routesId,
|
|
683
690
|
actionsId,
|
|
684
691
|
routing = { redirects: [], rewrites: [], headers: [], basePath: "", trailingSlash: "ignore" },
|
|
692
|
+
instrumentation = null,
|
|
685
693
|
) {
|
|
686
694
|
return `import {
|
|
687
695
|
createActionDispatcher,
|
|
696
|
+
createInstrumentation,
|
|
697
|
+
instrumentRender,
|
|
698
|
+
traceRequestPhase,
|
|
688
699
|
createDispatcher,
|
|
689
700
|
createMiddlewareRunner,
|
|
690
701
|
installRouting,
|
|
@@ -698,15 +709,22 @@ import App from ${JSON.stringify(appEntry)};
|
|
|
698
709
|
export const routing = ${JSON.stringify(routing)};
|
|
699
710
|
installRouting(routing);
|
|
700
711
|
export { routes, handlers, middleware, notFound, errors };
|
|
701
|
-
|
|
712
|
+
${instrumentation == null ? "" : `import * as hooks from ${JSON.stringify(instrumentation)};`}
|
|
713
|
+
export const { beginRequest } = createInstrumentation(${instrumentation == null ? "" : "hooks"});
|
|
702
714
|
const renderer = createDocumentRenderer({ App, renderFlight, loadClientModule });
|
|
703
|
-
export const render =
|
|
715
|
+
export const render = (url, assets, options = {}) => instrumentRender(
|
|
716
|
+
(onError) => renderer.render(url, assets, { ...options, onError }), options.onError,
|
|
717
|
+
);
|
|
704
718
|
export const prerender = renderer.prerender;
|
|
705
|
-
export const flight =
|
|
719
|
+
export const flight = (url, options = {}) => instrumentRender(
|
|
720
|
+
(onError) => renderer.flight(url, { ...options, onError }), options.onError,
|
|
721
|
+
);
|
|
706
722
|
export { shellDocument } from "@uniflowed/router/server";
|
|
707
|
-
|
|
723
|
+
const dispatchRoute = createDispatcher({ handlers });
|
|
724
|
+
export const dispatch = (request) => traceRequestPhase("route", () => dispatchRoute(request));
|
|
708
725
|
export const callAction = createActionDispatcher({ actions });
|
|
709
|
-
|
|
726
|
+
const guard = createMiddlewareRunner({ middleware });
|
|
727
|
+
export const runMiddleware = (request) => traceRequestPhase("middleware", () => guard(request));
|
|
710
728
|
`;
|
|
711
729
|
}
|
|
712
730
|
|
|
@@ -731,7 +749,7 @@ export function devStylesheets(server) {
|
|
|
731
749
|
// package's, which Vite serves under `/@fs/` — and the same rule a client
|
|
732
750
|
// reference's URL follows. A stylesheet with no file is a virtual one.
|
|
733
751
|
urls.push(
|
|
734
|
-
typeof module.file === "string" && module.file
|
|
752
|
+
typeof module.file === "string" && path.isAbsolute(module.file)
|
|
735
753
|
? devUrlOf(root, base, module.file)
|
|
736
754
|
: `${base.replace(/\/$/, "")}/@id/${id.replace(/\0/g, "__x00__")}`,
|
|
737
755
|
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @noflow
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
export function instrumentationFile(root, client = false) {
|
|
7
|
+
const stem = client ? "$instrumentation.client" : "$instrumentation";
|
|
8
|
+
const files = [".js", ".jsx"]
|
|
9
|
+
.map((extension) => path.join(root, stem + extension))
|
|
10
|
+
.filter(existsSync);
|
|
11
|
+
if (files.length > 1)
|
|
12
|
+
throw new Error(`uf: choose one instrumentation module: ${files.join(", ")}`);
|
|
13
|
+
return files[0] ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function clientInstrumentationSource(file) {
|
|
17
|
+
if (file == null) return "";
|
|
18
|
+
return `import * as instrumentation from ${JSON.stringify(file)};
|
|
19
|
+
import { installClientInstrumentation } from "@uniflowed/router/instrumentation";
|
|
20
|
+
const disposeInstrumentation = await installClientInstrumentation(instrumentation);
|
|
21
|
+
if (import.meta.hot) import.meta.hot.dispose(disposeInstrumentation);
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @noflow
|
|
2
|
+
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
/** React Native Web is supplied by the application, including its version. */
|
|
7
|
+
export function nativeWebPlugin(target) {
|
|
8
|
+
let root = process.cwd();
|
|
9
|
+
return {
|
|
10
|
+
name: "uf:react-native-web",
|
|
11
|
+
enforce: "pre",
|
|
12
|
+
configResolved(config) {
|
|
13
|
+
root = config.root;
|
|
14
|
+
},
|
|
15
|
+
resolveId(source) {
|
|
16
|
+
if (target !== "web" || source !== "react-native") return null;
|
|
17
|
+
try {
|
|
18
|
+
return createRequire(path.join(root, "package.json")).resolve("react-native-web");
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"A shared React Native page needs the application's react-native-web dependency: run `uf add react-native-web`.",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @noflow
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
/** CommonJS hidden behind uf's excluded Flow packages still needs prebundling. */
|
|
8
|
+
export function relayDependencies(root, server = false) {
|
|
9
|
+
const require = createRequire(path.join(root, "package.json"));
|
|
10
|
+
const entries = server
|
|
11
|
+
? ["relay-runtime", "relay-runtime/experimental", "react-relay/rsc_EXPERIMENTAL.js"]
|
|
12
|
+
: ["relay-runtime", "react-relay", "react-relay/rsc-client_EXPERIMENTAL.js"];
|
|
13
|
+
return entries.filter((entry) => {
|
|
14
|
+
try {
|
|
15
|
+
require.resolve(entry);
|
|
16
|
+
return true;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED")
|
|
19
|
+
return false;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve config from the application, independently of the process's cwd. */
|
|
26
|
+
export async function relayConfig(root) {
|
|
27
|
+
for (const name of ["relay.config.json", "relay.config.js", "relay.config.mjs"]) {
|
|
28
|
+
const filename = path.join(root, name);
|
|
29
|
+
if (!fs.existsSync(filename)) continue;
|
|
30
|
+
const config = name.endsWith(".json")
|
|
31
|
+
? JSON.parse(fs.readFileSync(filename, "utf8"))
|
|
32
|
+
: (await import(pathToFileURL(filename).href)).default;
|
|
33
|
+
return artifactOptions(root, config);
|
|
34
|
+
}
|
|
35
|
+
const filename = path.join(root, "package.json");
|
|
36
|
+
const config = fs.existsSync(filename)
|
|
37
|
+
? JSON.parse(fs.readFileSync(filename, "utf8")).relay
|
|
38
|
+
: null;
|
|
39
|
+
return artifactOptions(root, config);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function artifactOptions(root, config) {
|
|
43
|
+
if (config?.projects != null) {
|
|
44
|
+
throw new Error("uf's Relay integration currently needs a single-project Relay config.");
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
artifactDirectory:
|
|
48
|
+
config?.artifactDirectory == null ? null : path.resolve(root, config.artifactDirectory),
|
|
49
|
+
eagerEsModules: true,
|
|
50
|
+
jsModuleFormat: "commonjs",
|
|
51
|
+
codegenCommand: "uf run relay",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Flow has already lowered components/hooks; Relay owns the GraphQL transform. */
|
|
56
|
+
export async function transformRelay(code, filename, map, options) {
|
|
57
|
+
if (
|
|
58
|
+
!code.includes("graphql") ||
|
|
59
|
+
!/["'](?:@uniflowed\/relay|react-relay|relay-runtime)["']/.test(code)
|
|
60
|
+
)
|
|
61
|
+
return null;
|
|
62
|
+
const [{ transformAsync }, { default: relay }] = await Promise.all([
|
|
63
|
+
import("@babel/core"),
|
|
64
|
+
import("babel-plugin-relay"),
|
|
65
|
+
]);
|
|
66
|
+
return transformAsync(code, {
|
|
67
|
+
filename,
|
|
68
|
+
babelrc: false,
|
|
69
|
+
configFile: false,
|
|
70
|
+
sourceMaps: true,
|
|
71
|
+
inputSourceMap: map ?? undefined,
|
|
72
|
+
plugins: [[relay, options]],
|
|
73
|
+
});
|
|
74
|
+
}
|
package/internal/routes.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// route table imports every page and layout lazily, so a route is a chunk of
|
|
15
15
|
// its own and the client only downloads what it navigates to.
|
|
16
16
|
|
|
17
|
+
import { clientInstrumentationSource } from "./instrumentation.js";
|
|
18
|
+
|
|
17
19
|
import { readdirSync, statSync } from "node:fs";
|
|
18
20
|
import path from "node:path";
|
|
19
21
|
|
|
@@ -28,6 +30,7 @@ export const RESERVED = Object.freeze({
|
|
|
28
30
|
error: "$error",
|
|
29
31
|
loading: "$loading",
|
|
30
32
|
route: "$route",
|
|
33
|
+
instrumentation: "$instrumentation",
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
/**
|
|
@@ -381,6 +384,15 @@ export function scanRoutes(appRoot, options = {}) {
|
|
|
381
384
|
);
|
|
382
385
|
|
|
383
386
|
refuseUnsupportedTemplateFiles(directory, entries);
|
|
387
|
+
for (const entry of entries) {
|
|
388
|
+
if (entry.name.startsWith("$instrumentation") && !/\.test\.jsx?$/.test(entry.name)) {
|
|
389
|
+
if (depth !== 0 || !/^\$instrumentation(?:\.client)?\.jsx?$/.test(entry.name)) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
`uf: ${path.join(directory, entry.name)} must be $instrumentation.js or $instrumentation.client.js at the router root`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
384
396
|
|
|
385
397
|
// A `$default.js` answers one question — what a slot renders when the
|
|
386
398
|
// URL says nothing about it — and this walk is everywhere a slot is not,
|
|
@@ -1660,7 +1672,7 @@ export function clientModuleSource(appEntry, options = {}) {
|
|
|
1660
1672
|
return `import { ${mount} } from "@uniflowed/router/client";
|
|
1661
1673
|
import { routes, notFound, errors } from ${JSON.stringify(VIRTUAL.routes)};
|
|
1662
1674
|
import App from ${JSON.stringify(appEntry)};
|
|
1663
|
-
${mount}({ App, routes, notFound, errors${strictMode}${navigation}${staleTime}${routing} });
|
|
1675
|
+
${clientInstrumentationSource(options.instrumentation)}${mount}({ App, routes, notFound, errors${strictMode}${navigation}${staleTime}${routing} });
|
|
1664
1676
|
`;
|
|
1665
1677
|
}
|
|
1666
1678
|
|
|
@@ -1718,9 +1730,12 @@ ${mount}({ App, routes, notFound, errors${strictMode}${navigation}${staleTime}${
|
|
|
1718
1730
|
* shorter proof of the paragraph above: the copy the router dispatches and
|
|
1719
1731
|
* renders with is by construction the copy the host is handed.
|
|
1720
1732
|
*/
|
|
1721
|
-
export function serverModuleSource(appEntry, routing = routingRulesOf({})) {
|
|
1733
|
+
export function serverModuleSource(appEntry, routing = routingRulesOf({}), instrumentation = null) {
|
|
1722
1734
|
return `import {
|
|
1723
1735
|
createActionDispatcher,
|
|
1736
|
+
createInstrumentation,
|
|
1737
|
+
instrumentRender,
|
|
1738
|
+
traceRequestPhase,
|
|
1724
1739
|
createDispatcher,
|
|
1725
1740
|
createMiddlewareRunner,
|
|
1726
1741
|
createRenderer,
|
|
@@ -1731,14 +1746,19 @@ import { actions } from ${JSON.stringify(VIRTUAL.actions)};
|
|
|
1731
1746
|
import App from ${JSON.stringify(appEntry)};
|
|
1732
1747
|
${routingExportSource(routing)}installRouting(routing);
|
|
1733
1748
|
export { routes, handlers, middleware, notFound, errors };
|
|
1734
|
-
|
|
1749
|
+
${instrumentation == null ? "" : `import * as hooks from ${JSON.stringify(instrumentation)};`}
|
|
1750
|
+
export const { beginRequest } = createInstrumentation(${instrumentation == null ? "" : "hooks"});
|
|
1735
1751
|
const renderer = createRenderer({ App, routes, notFound, errors });
|
|
1736
|
-
export const render =
|
|
1752
|
+
export const render = (url, assets, options = {}) => instrumentRender(
|
|
1753
|
+
(onError) => renderer.render(url, assets, { ...options, onError }), options.onError,
|
|
1754
|
+
);
|
|
1737
1755
|
export const prerender = renderer.prerender;
|
|
1738
1756
|
export { shellDocument } from "@uniflowed/router/server";
|
|
1739
|
-
|
|
1757
|
+
const dispatchRoute = createDispatcher({ handlers });
|
|
1758
|
+
export const dispatch = (request) => traceRequestPhase("route", () => dispatchRoute(request));
|
|
1740
1759
|
export const callAction = createActionDispatcher({ actions });
|
|
1741
|
-
|
|
1760
|
+
const guard = createMiddlewareRunner({ middleware });
|
|
1761
|
+
export const runMiddleware = (request) => traceRequestPhase("middleware", () => guard(request));
|
|
1742
1762
|
`;
|
|
1743
1763
|
}
|
|
1744
1764
|
|
package/internal/serve.js
CHANGED
|
@@ -112,18 +112,19 @@ function deployment() {
|
|
|
112
112
|
* a setting nobody can set would be the shape of configurability with none of
|
|
113
113
|
* the substance.
|
|
114
114
|
*
|
|
115
|
-
* @param {{route?: boolean, fetch?: boolean, store?: string, storeDir?: string} | undefined} declared
|
|
115
|
+
* @param {{route?: boolean, fetch?: boolean, data?: boolean, store?: string, storeDir?: string} | undefined} declared
|
|
116
116
|
* @param {(options?: object) => object} createCacheStore
|
|
117
117
|
* @param {{root: string, build: string | null}} where
|
|
118
118
|
*/
|
|
119
119
|
async function cacheFor(declared, createCacheStore, where) {
|
|
120
120
|
const route = declared?.route === true;
|
|
121
121
|
const fetchCache = declared?.fetch === true;
|
|
122
|
-
|
|
122
|
+
const data = declared?.data === true;
|
|
123
|
+
if (!route && !fetchCache && !data) return undefined;
|
|
123
124
|
const provider = await providerFor(declared, where);
|
|
124
125
|
const store =
|
|
125
126
|
provider == null ? createCacheStore() : createCacheStore({ provider, build: where.build });
|
|
126
|
-
return { store, route, fetch: fetchCache };
|
|
127
|
+
return { store, route, fetch: fetchCache, data };
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniflowed/vite",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.42",
|
|
4
4
|
"description": "Vite, driven by uf.config.js: every Flow module through `uf transform`, MDX, the file-system router and static rendering as Vite plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,12 +32,14 @@
|
|
|
32
32
|
"!*.test.js"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@babel/core": "^7.29.7",
|
|
35
36
|
"@mdx-js/rollup": "^3.1.1",
|
|
36
37
|
"@shikijs/rehype": "^3.23.0",
|
|
37
|
-
"@uniflowed/host": "0.0.0-alpha.
|
|
38
|
-
"@uniflowed/router": "0.0.0-alpha.
|
|
39
|
-
"@uniflowed/server": "0.0.0-alpha.
|
|
40
|
-
"@uniflowed/validator": "0.0.0-alpha.
|
|
38
|
+
"@uniflowed/host": "0.0.0-alpha.42",
|
|
39
|
+
"@uniflowed/router": "0.0.0-alpha.42",
|
|
40
|
+
"@uniflowed/server": "0.0.0-alpha.42",
|
|
41
|
+
"@uniflowed/validator": "0.0.0-alpha.42",
|
|
42
|
+
"babel-plugin-relay": "^21.0.1",
|
|
41
43
|
"estree-util-value-to-estree": "^3.5.0",
|
|
42
44
|
"rehype-slug": "^6.0.0",
|
|
43
45
|
"remark-frontmatter": "^5.0.0",
|
|
@@ -45,12 +47,13 @@
|
|
|
45
47
|
"shiki": "^3.23.0",
|
|
46
48
|
"unist-util-mdx-define": "^1.1.2",
|
|
47
49
|
"vite": "^8.2.2",
|
|
48
|
-
"yaml": "^2.9.1"
|
|
50
|
+
"yaml": "^2.9.1",
|
|
51
|
+
"rolldown": "^1.2.7"
|
|
49
52
|
},
|
|
50
53
|
"peerDependencies": {
|
|
54
|
+
"axe-core": ">=4",
|
|
51
55
|
"react": ">=19",
|
|
52
|
-
"react-dom": ">=19"
|
|
53
|
-
"axe-core": ">=4"
|
|
56
|
+
"react-dom": ">=19"
|
|
54
57
|
},
|
|
55
58
|
"peerDependenciesMeta": {
|
|
56
59
|
"axe-core": {
|