@uniflowed/vite 0.0.0-alpha.34 → 0.0.0-alpha.35
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 +285 -9
- package/index.js +199 -13
- package/internal/flight.js +622 -0
- package/internal/frontmatter.js +33 -0
- package/internal/serve.js +48 -2
- package/internal/worker-builtins.js +170 -0
- package/package.json +9 -7
package/internal/serve.js
CHANGED
|
@@ -213,7 +213,46 @@ export async function loadBuild({ root, outDir, serverDir }) {
|
|
|
213
213
|
const entry = await import(pathToFileURL(entryFile).href);
|
|
214
214
|
await deployment();
|
|
215
215
|
const build = await buildIdentity(root, serverDir);
|
|
216
|
-
return {
|
|
216
|
+
return {
|
|
217
|
+
entry,
|
|
218
|
+
assets: await documentAssetsFor(path.resolve(root, serverDir), manifest),
|
|
219
|
+
distDir,
|
|
220
|
+
root,
|
|
221
|
+
build,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** What `uf build` records a document's tags in, beside the server bundle. */
|
|
226
|
+
export const DOCUMENT_ASSETS_FILE = "uf-document-assets.json";
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The tags a served document needs: the ones `uf build` recorded, or — for a
|
|
230
|
+
* build from before it recorded them — the client manifest's.
|
|
231
|
+
*
|
|
232
|
+
* Recorded rather than recomputed, because the client manifest is no longer the
|
|
233
|
+
* whole answer. An application React Server Components render links the
|
|
234
|
+
* stylesheets its rsc graph emitted and the ones each client module's chunk
|
|
235
|
+
* carries, and neither is reachable from the client entry the manifest is walked
|
|
236
|
+
* from — so a server that recomputed the tags rendered every page without the
|
|
237
|
+
* stylesheets its prerendered pages had. `uf start`, `uf preview`, `--adapter`
|
|
238
|
+
* and `--compile` all read them from here.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} serverDir absolute path of the server bundle's directory
|
|
241
|
+
* @param {object} manifest the client build's Vite manifest
|
|
242
|
+
*/
|
|
243
|
+
export async function documentAssetsFor(serverDir, manifest) {
|
|
244
|
+
const file = path.join(serverDir, DOCUMENT_ASSETS_FILE);
|
|
245
|
+
let recorded;
|
|
246
|
+
try {
|
|
247
|
+
recorded = await readFile(file, "utf8");
|
|
248
|
+
} catch {
|
|
249
|
+
return assetsFromManifest(manifest);
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
return JSON.parse(recorded);
|
|
253
|
+
} catch {
|
|
254
|
+
throw new Error(`uf: ${file} is not the JSON \`uf build\` writes; run \`uf build\` again`);
|
|
255
|
+
}
|
|
217
256
|
}
|
|
218
257
|
|
|
219
258
|
/**
|
|
@@ -304,7 +343,14 @@ async function readable(file, message) {
|
|
|
304
343
|
* at build time, and bakes the answer into what it emits.
|
|
305
344
|
*/
|
|
306
345
|
export function assetsFromManifest(manifest) {
|
|
307
|
-
|
|
346
|
+
// `client` by name first. An application React Server Components render
|
|
347
|
+
// gives the client build one entry per client module as well, and the
|
|
348
|
+
// document's script is the application's entry, not whichever of those the
|
|
349
|
+
// manifest happens to list first.
|
|
350
|
+
const chunks = Object.values(manifest);
|
|
351
|
+
const entry =
|
|
352
|
+
chunks.find((chunk) => chunk.isEntry && chunk.name === "client") ??
|
|
353
|
+
chunks.find((chunk) => chunk.isEntry);
|
|
308
354
|
if (entry == null) throw new Error("uf: the client manifest has no entry chunk");
|
|
309
355
|
|
|
310
356
|
const styles = new Set(entry.css ?? []);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// @noflow
|
|
2
|
+
//
|
|
3
|
+
// Plain JavaScript: `../driver.js` imports this before any transform exists.
|
|
4
|
+
//
|
|
5
|
+
// The Node built-ins a Cloudflare Worker does not provide at the compatibility
|
|
6
|
+
// date `uf build --adapter edge` writes, and what the build says about a module
|
|
7
|
+
// that reaches one.
|
|
8
|
+
//
|
|
9
|
+
// # Where the list comes from
|
|
10
|
+
//
|
|
11
|
+
// A measurement rather than a summary of the documentation. Each module below
|
|
12
|
+
// was imported statically into a Worker — so Wrangler applied the polyfills it
|
|
13
|
+
// applies to uf's own bundle — served by `wrangler dev --local` at compatibility
|
|
14
|
+
// date 2024-09-23 with `nodejs_compat`, and the function beside it was called.
|
|
15
|
+
// Every one threw `[unenv] <function> is not implemented yet!`.
|
|
16
|
+
// `tools/ci/edge-worker-smoke.sh` builds that probe from this table on every run
|
|
17
|
+
// and fails when a module here stops throwing or stops importing, so the list
|
|
18
|
+
// cannot go on claiming something workerd no longer does.
|
|
19
|
+
//
|
|
20
|
+
// Whole modules only. `net` answers `connect` and refuses `createServer`, and
|
|
21
|
+
// `dgram`, `inspector` and `worker_threads` construct objects without throwing;
|
|
22
|
+
// a module that partly works is not one a build can name as absent. A call that
|
|
23
|
+
// fails in one of those fails at request time, where `@uniflowed/server/edge`
|
|
24
|
+
// names the function in the log line.
|
|
25
|
+
//
|
|
26
|
+
// # Reported, not refused
|
|
27
|
+
//
|
|
28
|
+
// The build goes on. The date is the one uf writes into `wrangler.json`, and a
|
|
29
|
+
// project that deploys with a `wrangler.json` of its own at a newer date may
|
|
30
|
+
// have the module — `node:fs` gained a virtual filesystem at a later one. A
|
|
31
|
+
// refusal here would stop a deployment uf cannot see from being built at all,
|
|
32
|
+
// so what uf owes is the sentence: which module, which file reached it, and what
|
|
33
|
+
// a request that reaches it answers on uf's date.
|
|
34
|
+
|
|
35
|
+
/** The compatibility date the table was measured at; `wrangler.json` pins it. */
|
|
36
|
+
export const WORKERS_COMPATIBILITY_DATE = "2024-09-23";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Every module a Worker provides only as a stub at that date, with the call
|
|
40
|
+
* that shows it.
|
|
41
|
+
*
|
|
42
|
+
* `construct` is for the one entry whose representative member is a class.
|
|
43
|
+
* The smoke builds `new wasi.WASI(...)` from it rather than a call.
|
|
44
|
+
*/
|
|
45
|
+
export const UNAVAILABLE_ON_WORKERS = Object.freeze([
|
|
46
|
+
Object.freeze({ module: "child_process", member: "execFileSync", args: ["true"] }),
|
|
47
|
+
Object.freeze({ module: "cluster", member: "fork", args: [] }),
|
|
48
|
+
Object.freeze({ module: "fs", member: "readFileSync", args: ["/tmp/uf-probe"] }),
|
|
49
|
+
Object.freeze({ module: "http", member: "createServer", args: [] }),
|
|
50
|
+
Object.freeze({ module: "http2", member: "createServer", args: [] }),
|
|
51
|
+
Object.freeze({ module: "https", member: "createServer", args: [] }),
|
|
52
|
+
Object.freeze({ module: "repl", member: "start", args: [] }),
|
|
53
|
+
Object.freeze({
|
|
54
|
+
module: "wasi",
|
|
55
|
+
member: "WASI",
|
|
56
|
+
construct: true,
|
|
57
|
+
args: [{ version: "preview1" }],
|
|
58
|
+
}),
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
/** How many reached modules one build names before it counts the rest. */
|
|
62
|
+
export const MAX_NAMED = 8;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The table entry `specifier` reaches, or `null` when it reaches none.
|
|
66
|
+
*
|
|
67
|
+
* `node:fs`, `fs` and `node:fs/promises` are all `fs`: the prefix is optional
|
|
68
|
+
* in an import and a subpath is the same module's functions under another name.
|
|
69
|
+
*/
|
|
70
|
+
export function unavailableOnWorkers(specifier) {
|
|
71
|
+
if (typeof specifier !== "string") return null;
|
|
72
|
+
const bare = specifier.startsWith("node:") ? specifier.slice("node:".length) : specifier;
|
|
73
|
+
const name = bare.split("/")[0];
|
|
74
|
+
return UNAVAILABLE_ON_WORKERS.find((entry) => entry.module === name) ?? null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The reached imports that are still in the output once the bundler has shaken
|
|
79
|
+
* the graph.
|
|
80
|
+
*
|
|
81
|
+
* Resolution sees every import of every module the graph names, including
|
|
82
|
+
* modules that contribute nothing to the bundle. Measured on the
|
|
83
|
+
* `rsc-split-app` fixture: a client component's top-level
|
|
84
|
+
* `import { expect, it } from "@uniflowed/test"`, kept for an in-source test
|
|
85
|
+
* that compiles to `void 0` in a build, reached `node:fs` and
|
|
86
|
+
* `node:child_process` through `@uniflowed/test` and `@uniflowed/host` — and not
|
|
87
|
+
* one byte of either was in the Worker. A warning about a module the Worker does
|
|
88
|
+
* not contain is a warning every edge project would learn to ignore.
|
|
89
|
+
*
|
|
90
|
+
* So an import counts only when a chunk of the output still imports that
|
|
91
|
+
* module *and* the importing module rendered code into that chunk. `chunks` is
|
|
92
|
+
* the bundle's chunk entries, as `generateBundle` receives them: `imports` is
|
|
93
|
+
* the chunk's external imports, `modules` its modules with `renderedLength`.
|
|
94
|
+
*/
|
|
95
|
+
export function survivingImports(reached, chunks) {
|
|
96
|
+
return reached.filter(({ specifier, importer }) => {
|
|
97
|
+
const entry = unavailableOnWorkers(specifier);
|
|
98
|
+
if (entry == null) return false;
|
|
99
|
+
return chunks.some(
|
|
100
|
+
(chunk) =>
|
|
101
|
+
(chunk.imports ?? []).some((name) => unavailableOnWorkers(name)?.module === entry.module) &&
|
|
102
|
+
(chunk.modules?.[importer]?.renderedLength ?? 0) > 0,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The warnings one edge build prints, one line per module and importer.
|
|
109
|
+
*
|
|
110
|
+
* `reached` is `{ specifier, importer }` in the order the bundler met them,
|
|
111
|
+
* `root` the project; an importer inside the project and outside
|
|
112
|
+
* `node_modules` is the project's own code and is named before any
|
|
113
|
+
* dependency, because it is the one a reader can change. Bounded by
|
|
114
|
+
* [`MAX_NAMED`], with the rest counted, so a dependency that reaches a stub
|
|
115
|
+
* from a hundred files cannot print a hundred lines.
|
|
116
|
+
*/
|
|
117
|
+
export function workerBuiltinWarnings(reached, root) {
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
const lines = [];
|
|
120
|
+
for (const { specifier, importer } of reached) {
|
|
121
|
+
const entry = unavailableOnWorkers(specifier);
|
|
122
|
+
if (entry == null || importer == null) continue;
|
|
123
|
+
const key = `${entry.module}\0${importer}`;
|
|
124
|
+
if (seen.has(key)) continue;
|
|
125
|
+
seen.add(key);
|
|
126
|
+
const inside = isProjectModule(importer, root);
|
|
127
|
+
const from = inside ? relative(importer, root) : dependencyName(importer);
|
|
128
|
+
lines.push({
|
|
129
|
+
inside,
|
|
130
|
+
text:
|
|
131
|
+
`${from} imports node:${entry.module}, which a Cloudflare Worker at compatibility date ` +
|
|
132
|
+
`${WORKERS_COMPATIBILITY_DATE} provides only as a stub: every function of it throws. ` +
|
|
133
|
+
(inside
|
|
134
|
+
? "A request that reaches it will answer 500 on this target."
|
|
135
|
+
: "The dependency may never call it here; if a request does, it answers 500."),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
lines.sort((a, b) => Number(b.inside) - Number(a.inside));
|
|
139
|
+
const named = lines.slice(0, MAX_NAMED).map((line) => `--adapter edge: ${line.text}`);
|
|
140
|
+
if (lines.length > MAX_NAMED) {
|
|
141
|
+
named.push(
|
|
142
|
+
`--adapter edge: and ${lines.length - MAX_NAMED} more importers of the same modules`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return named;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isProjectModule(importer, root) {
|
|
149
|
+
const normalised = importer.split("\\").join("/");
|
|
150
|
+
const base = root.split("\\").join("/").replace(/\/$/, "");
|
|
151
|
+
return normalised.startsWith(`${base}/`) && !normalised.includes("/node_modules/");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function relative(importer, root) {
|
|
155
|
+
const base = root.split("\\").join("/").replace(/\/$/, "");
|
|
156
|
+
return importer
|
|
157
|
+
.split("\\")
|
|
158
|
+
.join("/")
|
|
159
|
+
.slice(base.length + 1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** `a dependency (name)`, from the last `node_modules/` segment of a path. */
|
|
163
|
+
function dependencyName(importer) {
|
|
164
|
+
const normalised = importer.split("\\").join("/");
|
|
165
|
+
const at = normalised.lastIndexOf("/node_modules/");
|
|
166
|
+
if (at === -1) return `a module outside the project (${normalised})`;
|
|
167
|
+
const rest = normalised.slice(at + "/node_modules/".length).split("/");
|
|
168
|
+
const name = rest[0]?.startsWith("@") ? `${rest[0]}/${rest[1]}` : rest[0];
|
|
169
|
+
return `the dependency ${name}`;
|
|
170
|
+
}
|
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.35",
|
|
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",
|
|
@@ -34,16 +34,18 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@mdx-js/rollup": "^3.1.1",
|
|
36
36
|
"@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.
|
|
37
|
+
"@uniflowed/host": "0.0.0-alpha.35",
|
|
38
|
+
"@uniflowed/router": "0.0.0-alpha.35",
|
|
39
|
+
"@uniflowed/server": "0.0.0-alpha.35",
|
|
40
|
+
"@uniflowed/validator": "0.0.0-alpha.35",
|
|
41
|
+
"estree-util-value-to-estree": "^3.5.0",
|
|
41
42
|
"rehype-slug": "^6.0.0",
|
|
42
43
|
"remark-frontmatter": "^5.0.0",
|
|
43
44
|
"remark-gfm": "^4.0.1",
|
|
44
|
-
"remark-mdx-frontmatter": "^5.2.0",
|
|
45
45
|
"shiki": "^3.23.0",
|
|
46
|
-
"
|
|
46
|
+
"unist-util-mdx-define": "^1.1.2",
|
|
47
|
+
"vite": "^8.2.2",
|
|
48
|
+
"yaml": "^2.9.1"
|
|
47
49
|
},
|
|
48
50
|
"peerDependencies": {
|
|
49
51
|
"react": ">=19",
|