@uniflowed/vite 0.0.0-alpha.40 → 0.0.0-alpha.41
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 +6 -2
- package/index.js +61 -33
- package/internal/dev-state.js +161 -0
- package/internal/events.js +3 -0
- package/internal/flight.js +7 -2
- package/internal/native-web.js +26 -0
- package/internal/relay.js +74 -0
- package/internal/serve.js +4 -3
- package/package.json +9 -7
package/driver.js
CHANGED
|
@@ -524,6 +524,9 @@ async function preview() {
|
|
|
524
524
|
if ((request.url ?? "").split("?")[0].endsWith("/__uf.flight")) {
|
|
525
525
|
response.setHeader("content-type", "text/x-component");
|
|
526
526
|
}
|
|
527
|
+
if ((request.url ?? "").split("?")[0] === "/.well-known/apple-app-site-association") {
|
|
528
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
529
|
+
}
|
|
527
530
|
next();
|
|
528
531
|
});
|
|
529
532
|
if (handle == null) return;
|
|
@@ -1677,11 +1680,12 @@ async function deploy() {
|
|
|
1677
1680
|
function handlerEntrySource(document, cache, capabilities, build, regeneration) {
|
|
1678
1681
|
const route = cache?.route === true;
|
|
1679
1682
|
const fetchCache = cache?.fetch === true;
|
|
1683
|
+
const dataCache = cache?.data === true;
|
|
1680
1684
|
// Nothing at all when both switches are off, so a default project's
|
|
1681
1685
|
// `handler.js` is the file it has always been. A cache that appears in
|
|
1682
1686
|
// generated output nobody asked for is the second half of the complaint
|
|
1683
1687
|
// #277 makes about the first half.
|
|
1684
|
-
const store = route || fetchCache;
|
|
1688
|
+
const store = route || fetchCache || dataCache;
|
|
1685
1689
|
const durable = store ? durableStoreSource(root, cache, build) : null;
|
|
1686
1690
|
const options = [
|
|
1687
1691
|
"app",
|
|
@@ -1716,7 +1720,7 @@ ${
|
|
|
1716
1720
|
} See ubugeeei-prod/uf#277.
|
|
1717
1721
|
const cache = { store: createCacheStore(${
|
|
1718
1722
|
durable == null ? "" : `{ provider: ${durable.provider}, build: ${JSON.stringify(build)} }`
|
|
1719
|
-
}), route: ${String(route)}, fetch: ${String(fetchCache)} };
|
|
1723
|
+
}), route: ${String(route)}, fetch: ${String(fetchCache)}, data: ${String(dataCache)} };
|
|
1720
1724
|
|
|
1721
1725
|
`
|
|
1722
1726
|
: ""
|
package/index.js
CHANGED
|
@@ -44,11 +44,12 @@
|
|
|
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 { readdirSync } from "node:fs";
|
|
48
47
|
import path from "node:path";
|
|
48
|
+
import { nativeWebPlugin } from "./internal/native-web.js";
|
|
49
49
|
|
|
50
50
|
import mdx from "@mdx-js/rollup";
|
|
51
51
|
import rehypeSlug from "rehype-slug";
|
|
52
|
+
import { relayConfig, relayDependencies, transformRelay } from "./internal/relay.js";
|
|
52
53
|
|
|
53
54
|
import {
|
|
54
55
|
AUDIT_PUBLIC_PATH,
|
|
@@ -59,7 +60,7 @@ import {
|
|
|
59
60
|
} from "./internal/a11y.js";
|
|
60
61
|
import { assetPlugin } from "./internal/assets.js";
|
|
61
62
|
import { barrelImportsPlugin, namespaceViewOf } from "./internal/barrel-imports.js";
|
|
62
|
-
import { emit, reportRenderError } from "./internal/events.js";
|
|
63
|
+
import { emit, reportRenderError, errorEvent } from "./internal/events.js";
|
|
63
64
|
import remarkFrontmatterExport from "./internal/frontmatter.js";
|
|
64
65
|
import { highlightPlugin } from "./internal/highlight.js";
|
|
65
66
|
import { moduleId } from "./internal/module-graph.js";
|
|
@@ -93,11 +94,12 @@ import {
|
|
|
93
94
|
scanRoutes,
|
|
94
95
|
serverModuleSource,
|
|
95
96
|
} from "./internal/routes.js";
|
|
96
|
-
import { TransformService, isFlowModule } from "@uniflowed/host/transform";
|
|
97
|
+
import { TransformService, isFlowModule, uniflowedPackages } from "@uniflowed/host/transform";
|
|
97
98
|
import {
|
|
98
99
|
DEV_RSC_HOOK,
|
|
99
100
|
FLIGHT_BROWSER_DEPENDENCIES,
|
|
100
101
|
FLIGHT_VIRTUAL,
|
|
102
|
+
INTERCEPTED_FROM_HEADER,
|
|
101
103
|
RSC_ENVIRONMENT,
|
|
102
104
|
builtBridgeSource,
|
|
103
105
|
builtReferencesSource,
|
|
@@ -118,6 +120,7 @@ import {
|
|
|
118
120
|
rscEnvironment,
|
|
119
121
|
} from "./internal/flight.js";
|
|
120
122
|
import { createChannelMiddleware } from "./internal/diagnostics.js";
|
|
123
|
+
import { startDevState } from "./internal/dev-state.js";
|
|
121
124
|
import { devtoolsPreamble } from "./internal/devtools.js";
|
|
122
125
|
import { send, toAddressRequest, toRequest } from "./internal/http.js";
|
|
123
126
|
import {
|
|
@@ -133,6 +136,25 @@ import { serverComponentsProblem } from "./internal/server-components.js";
|
|
|
133
136
|
const resolved = (id) => `\0${id}`;
|
|
134
137
|
const VIRTUAL_IDS = new Set([...Object.values(VIRTUAL), ...Object.values(FLIGHT_VIRTUAL)]);
|
|
135
138
|
|
|
139
|
+
/**
|
|
140
|
+
* CommonJS packages the browser can reach only through an installed
|
|
141
|
+
* `@uniflowed/*` package.
|
|
142
|
+
*
|
|
143
|
+
* uf excludes its own packages from Vite's dependency optimizer because they
|
|
144
|
+
* ship Flow, so Vite will not discover a CommonJS peer through them once they
|
|
145
|
+
* live under a project's `node_modules`. React Testing's `happy-dom` edge is
|
|
146
|
+
* ESM and browser-mapped to a local stub, and the optional `axe-core` edge is
|
|
147
|
+
* named separately below only when the project actually installed it.
|
|
148
|
+
*/
|
|
149
|
+
const CLIENT_COMMONJS_DEPENDENCIES = Object.freeze([
|
|
150
|
+
"react",
|
|
151
|
+
"react/jsx-runtime",
|
|
152
|
+
"react/jsx-dev-runtime",
|
|
153
|
+
"react/compiler-runtime",
|
|
154
|
+
"react-dom",
|
|
155
|
+
"react-dom/client",
|
|
156
|
+
]);
|
|
157
|
+
|
|
136
158
|
/**
|
|
137
159
|
* Prefix of the virtual module that carries one source module's StyleX rules.
|
|
138
160
|
*
|
|
@@ -229,6 +251,7 @@ export default function uniflowed(options = {}) {
|
|
|
229
251
|
routing,
|
|
230
252
|
command: options.command,
|
|
231
253
|
accessibility,
|
|
254
|
+
relayEnabled: builtins.relay !== false,
|
|
232
255
|
}),
|
|
233
256
|
...(flightState == null ? [] : [clientReferencePlugin(flightState), clientModuleUrlPlugin()]),
|
|
234
257
|
// After the references, so a client module the rsc graph has already
|
|
@@ -242,6 +265,7 @@ export default function uniflowed(options = {}) {
|
|
|
242
265
|
og: builtins.og ?? {},
|
|
243
266
|
command: options.command,
|
|
244
267
|
}),
|
|
268
|
+
nativeWebPlugin(routeTarget),
|
|
245
269
|
];
|
|
246
270
|
}
|
|
247
271
|
|
|
@@ -257,6 +281,7 @@ function flowPlugin({
|
|
|
257
281
|
routing,
|
|
258
282
|
command,
|
|
259
283
|
accessibility,
|
|
284
|
+
relayEnabled,
|
|
260
285
|
}) {
|
|
261
286
|
let root = process.cwd();
|
|
262
287
|
let isProduction = false;
|
|
@@ -276,6 +301,7 @@ function flowPlugin({
|
|
|
276
301
|
let server = null;
|
|
277
302
|
/** @type {TransformService | null} */
|
|
278
303
|
let service = null;
|
|
304
|
+
let relayOptions = null;
|
|
279
305
|
/**
|
|
280
306
|
* Each module's compiled stylesheet, keyed by the virtual id serving it.
|
|
281
307
|
*
|
|
@@ -413,16 +439,12 @@ function flowPlugin({
|
|
|
413
439
|
// uf serves HTML itself; there is no index.html to fall back to.
|
|
414
440
|
appType: "custom",
|
|
415
441
|
resolve: {
|
|
416
|
-
dedupe: ["react", "react-dom"],
|
|
442
|
+
dedupe: ["react", "react-dom", "react-relay", "relay-runtime"],
|
|
417
443
|
},
|
|
418
444
|
optimizeDeps: {
|
|
419
445
|
include: [
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
"react/jsx-dev-runtime",
|
|
423
|
-
"react/compiler-runtime",
|
|
424
|
-
"react-dom",
|
|
425
|
-
"react-dom/client",
|
|
446
|
+
...CLIENT_COMMONJS_DEPENDENCIES,
|
|
447
|
+
...relayDependencies(projectRoot),
|
|
426
448
|
// React's Flight client, for an application whose routes render as
|
|
427
449
|
// Server Components; `FLIGHT_BROWSER_DEPENDENCIES` says why.
|
|
428
450
|
...(flightState == null ? [] : FLIGHT_BROWSER_DEPENDENCIES),
|
|
@@ -455,6 +477,7 @@ function flowPlugin({
|
|
|
455
477
|
: {
|
|
456
478
|
environments: {
|
|
457
479
|
[RSC_ENVIRONMENT]: rscEnvironment({
|
|
480
|
+
root: projectRoot,
|
|
458
481
|
production: env.command === "build",
|
|
459
482
|
exclude: uniflowedPackages(projectRoot),
|
|
460
483
|
}),
|
|
@@ -657,7 +680,16 @@ function flowPlugin({
|
|
|
657
680
|
reported,
|
|
658
681
|
suppressed,
|
|
659
682
|
});
|
|
660
|
-
|
|
683
|
+
let map = out.map == null ? null : JSON.parse(out.map);
|
|
684
|
+
let output = out.code;
|
|
685
|
+
if (relayEnabled && output.includes("graphql")) {
|
|
686
|
+
relayOptions ??= relayConfig(root);
|
|
687
|
+
const transformed = await transformRelay(output, cleanId(id), map, await relayOptions);
|
|
688
|
+
if (transformed != null) {
|
|
689
|
+
output = transformed.code;
|
|
690
|
+
map = transformed.map;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
661
693
|
// StyleX. `uf transform` compiled the module's `stylex.create` calls into
|
|
662
694
|
// class names and handed back the rules they declared; the rules become a
|
|
663
695
|
// module of their own that this one imports.
|
|
@@ -675,7 +707,6 @@ function flowPlugin({
|
|
|
675
707
|
// and an absolute name would publish where the machine that built it
|
|
676
708
|
// keeps its files. A module outside the root climbs out with `../`.
|
|
677
709
|
const styled = out.css != null && out.css !== "";
|
|
678
|
-
let output = out.code;
|
|
679
710
|
if (styled) {
|
|
680
711
|
const styleId = `${STYLE_PREFIX}${moduleId(root, cleanId(id))}.css`;
|
|
681
712
|
styles.set(styleId, out.css);
|
|
@@ -763,6 +794,16 @@ function flowPlugin({
|
|
|
763
794
|
|
|
764
795
|
configureServer(devServer) {
|
|
765
796
|
server = devServer;
|
|
797
|
+
const diagnostics = startDevState(root, () => ({
|
|
798
|
+
routes: scanRoutes(appRoot, { target: routeTarget }).routes.map((route) => ({
|
|
799
|
+
path: route.path,
|
|
800
|
+
page: route.page,
|
|
801
|
+
layouts: route.layouts,
|
|
802
|
+
})),
|
|
803
|
+
actions: actionTables().table,
|
|
804
|
+
}));
|
|
805
|
+
devServer.middlewares.use(diagnostics.middleware);
|
|
806
|
+
devServer.httpServer?.once("close", diagnostics.close);
|
|
766
807
|
// The ssr graph's way into the rsc graph; see `devBridgeSource`.
|
|
767
808
|
if (flightState != null) {
|
|
768
809
|
globalThis[Symbol.for(DEV_RSC_HOOK)] = () =>
|
|
@@ -995,8 +1036,10 @@ function flowPlugin({
|
|
|
995
1036
|
if (payloadFor != null && (request.method === "GET" || request.method === "HEAD")) {
|
|
996
1037
|
const query = url.includes("?") ? url.slice(url.indexOf("?")) : "";
|
|
997
1038
|
const target = payloadFor + query;
|
|
1039
|
+
const fromHeader = request.headers[INTERCEPTED_FROM_HEADER];
|
|
998
1040
|
const answered = await entry.flight(target, {
|
|
999
1041
|
onError: (error) => reportRenderError(devServer, target, error),
|
|
1042
|
+
interceptedFrom: Array.isArray(fromHeader) ? fromHeader[0] : fromHeader,
|
|
1000
1043
|
});
|
|
1001
1044
|
if (answered.error != null) reportRenderError(devServer, target, answered.error);
|
|
1002
1045
|
if (request.method === "HEAD") await answered.stream?.cancel();
|
|
@@ -1148,6 +1191,12 @@ function flowPlugin({
|
|
|
1148
1191
|
// Map the stack back onto the Flow source before it reaches the
|
|
1149
1192
|
// overlay.
|
|
1150
1193
|
if (error instanceof Error) devServer.ssrFixStacktrace(error);
|
|
1194
|
+
emit("diagnostic", {
|
|
1195
|
+
severity: "error",
|
|
1196
|
+
kind: "runtime",
|
|
1197
|
+
origin: url,
|
|
1198
|
+
...errorEvent(error),
|
|
1199
|
+
});
|
|
1151
1200
|
next(error);
|
|
1152
1201
|
}
|
|
1153
1202
|
});
|
|
@@ -1420,24 +1469,3 @@ function cleanId(id) {
|
|
|
1420
1469
|
const at = id.indexOf("?");
|
|
1421
1470
|
return at === -1 ? id : id.slice(0, at);
|
|
1422
1471
|
}
|
|
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
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
// route's modules have been imported.
|
|
51
51
|
|
|
52
52
|
import path from "node:path";
|
|
53
|
+
import { relayDependencies } from "./relay.js";
|
|
53
54
|
|
|
54
55
|
import {
|
|
55
56
|
createRunnableDevEnvironment,
|
|
@@ -120,6 +121,9 @@ export const DEV_RSC_HOOK = "uf.dev.rsc";
|
|
|
120
121
|
*/
|
|
121
122
|
export const FLIGHT_SEGMENT = "__uf.flight";
|
|
122
123
|
|
|
124
|
+
/** The header a browser sends when a Flight payload should render an interception. */
|
|
125
|
+
export const INTERCEPTED_FROM_HEADER = "uf-intercepted-from";
|
|
126
|
+
|
|
123
127
|
/** The document a payload path is for, or `null` for any other path. */
|
|
124
128
|
export function flightDocumentPath(pathname) {
|
|
125
129
|
const suffix = `/${FLIGHT_SEGMENT}`;
|
|
@@ -166,7 +170,7 @@ export function rendersFlight(app, { mount, routeTarget }) {
|
|
|
166
170
|
*
|
|
167
171
|
* @param {{ production: boolean, exclude: Array<string> }} options
|
|
168
172
|
*/
|
|
169
|
-
export function rscEnvironment({ production, exclude }) {
|
|
173
|
+
export function rscEnvironment({ production, exclude, root = process.cwd() }) {
|
|
170
174
|
return {
|
|
171
175
|
consumer: "server",
|
|
172
176
|
resolve: {
|
|
@@ -180,6 +184,7 @@ export function rscEnvironment({ production, exclude }) {
|
|
|
180
184
|
"react/jsx-runtime",
|
|
181
185
|
"react/jsx-dev-runtime",
|
|
182
186
|
"react-server-dom-parcel/server",
|
|
187
|
+
...relayDependencies(root, true),
|
|
183
188
|
],
|
|
184
189
|
exclude,
|
|
185
190
|
},
|
|
@@ -731,7 +736,7 @@ export function devStylesheets(server) {
|
|
|
731
736
|
// package's, which Vite serves under `/@fs/` — and the same rule a client
|
|
732
737
|
// reference's URL follows. A stylesheet with no file is a virtual one.
|
|
733
738
|
urls.push(
|
|
734
|
-
typeof module.file === "string" && module.file
|
|
739
|
+
typeof module.file === "string" && path.isAbsolute(module.file)
|
|
735
740
|
? devUrlOf(root, base, module.file)
|
|
736
741
|
: `${base.replace(/\/$/, "")}/@id/${id.replace(/\0/g, "__x00__")}`,
|
|
737
742
|
);
|
|
@@ -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/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.41",
|
|
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.41",
|
|
39
|
+
"@uniflowed/router": "0.0.0-alpha.41",
|
|
40
|
+
"@uniflowed/server": "0.0.0-alpha.41",
|
|
41
|
+
"@uniflowed/validator": "0.0.0-alpha.41",
|
|
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",
|
|
@@ -48,9 +50,9 @@
|
|
|
48
50
|
"yaml": "^2.9.1"
|
|
49
51
|
},
|
|
50
52
|
"peerDependencies": {
|
|
53
|
+
"axe-core": ">=4",
|
|
51
54
|
"react": ">=19",
|
|
52
|
-
"react-dom": ">=19"
|
|
53
|
-
"axe-core": ">=4"
|
|
55
|
+
"react-dom": ">=19"
|
|
54
56
|
},
|
|
55
57
|
"peerDependenciesMeta": {
|
|
56
58
|
"axe-core": {
|