feat(bake): handle bundle errors, re-assemble full client payloads, initial error modal (#14504)

This commit is contained in:
dave caruso
2024-10-14 16:49:38 -07:00
committed by GitHub
parent 29d287261b
commit d2fe1ce1c8
27 changed files with 2596 additions and 1317 deletions

View File

@@ -1,5 +1,6 @@
import assert from "node:assert";
import { existsSync, writeFileSync, rmSync } from "node:fs";
import { existsSync, writeFileSync, rmSync } from "node:fs";
import { watch } from "node:fs/promises";
import { basename, join } from "node:path";
// arg parsing
@@ -14,7 +15,7 @@ for (const arg of process.argv.slice(2)) {
options[split[0].slice(2)] = value;
}
let { codegen_root, debug } = options as any;
let { codegen_root, debug, live } = options as any;
if (!codegen_root) {
console.error("Missing --codegen_root=...");
process.exit(1);
@@ -24,10 +25,13 @@ if (debug === "false" || debug === "0" || debug == "OFF") debug = false;
const base_dir = join(import.meta.dirname, "../bake");
process.chdir(base_dir); // to make bun build predictable in development
async function run(){
const results = await Promise.allSettled(
["client", "server"].map(async side => {
["client", "server", "error"].map(async file => {
const side = file === 'error' ? 'client' : file;
let result = await Bun.build({
entrypoints: [join(base_dir, `hmr-runtime-${side}.ts`)],
entrypoints: [join(base_dir, `hmr-runtime-${file}.ts`)],
define: {
side: JSON.stringify(side),
IS_BUN_DEVELOPMENT: String(!!debug),
@@ -44,22 +48,19 @@ const results = await Promise.allSettled(
// A second pass is used to convert global variables into parameters, while
// allowing for renaming to properly function when minification is enabled.
const in_names = [
'input_graph',
'config',
side === 'server' && 'server_exports'
file !== 'error' && 'input_graph',
file !== 'error' && 'config',
file === 'server' && 'server_exports'
].filter(Boolean);
const combined_source = `
const combined_source = file === 'error' ? code : `
__marker__;
let ${in_names.join(",")};
${in_names.length > 0 ? 'let' : ''} ${in_names.join(",")};
__marker__(${in_names.join(",")});
${code};
`;
const generated_entrypoint = join(base_dir, `.runtime-${side}.generated.ts`);
const generated_entrypoint = join(base_dir, `.runtime-${file}.generated.ts`);
writeFileSync(generated_entrypoint, combined_source);
using _ = { [Symbol.dispose] : () => {
rmSync(generated_entrypoint);
}};
result = await Bun.build({
entrypoints: [generated_entrypoint],
@@ -71,48 +72,51 @@ const results = await Promise.allSettled(
});
if (!result.success) throw new AggregateError(result.logs);
assert(result.outputs.length === 1, "must bundle to a single file");
// @ts-ignore
code = await result.outputs[0].text();
code = (await result.outputs[0].text()).replace(`// ${basename(generated_entrypoint)}`, "").trim();
let names: string = "";
code = code
.replace(/(\n?)\s*__marker__.*__marker__\((.+?)\);\s*/s, (_, n, captured) => {
names = captured;
return n;
})
.replace(`// ${basename(generated_entrypoint)}`, "")
.trim();
assert(names, "missing name");
rmSync(generated_entrypoint);
if (debug) {
code = "\n " + code.replace(/\n/g, "\n ") + "\n";
if(file !== 'error') {
let names: string = "";
code = code
.replace(/(\n?)\s*__marker__.*__marker__\((.+?)\);\s*/s, (_, n, captured) => {
names = captured;
return n;
})
.trim();
assert(names, "missing name");
if (debug) {
code = "\n " + code.replace(/\n/g, "\n ") + "\n";
}
if (code[code.length - 1] === ";") code = code.slice(0, -1);
if (side === "server") {
const server_fetch_function = names.split(",")[2].trim();
code = debug ? `${code} return ${server_fetch_function};\n` : `${code};return ${server_fetch_function};`;
}
code = debug ? `((${names}) => {${code}})({\n` : `((${names})=>{${code}})({`;
if (side === "server") {
code = `export default await ${code}`;
}
}
if (code[code.length - 1] === ";") code = code.slice(0, -1);
if (side === "server") {
const server_fetch_function = names.split(",")[2].trim();
code = debug ? `${code} return ${server_fetch_function};\n` : `${code};return ${server_fetch_function};`;
}
code = debug ? `((${names}) => {${code}})({\n` : `((${names})=>{${code}})({`;
if (side === "server") {
code = `export default await ${code}`;
}
writeFileSync(join(codegen_root, `bake.${side}.js`), code);
writeFileSync(join(codegen_root, `bake.${file}.js`), code);
}),
);
// print failures in a de-duplicated fashion.
interface Err {
kind: "client" | "server" | "both";
kind: ("client" | "server" | "error")[];
err: any;
}
const failed = [
{ kind: "client", result: results[0] },
{ kind: "server", result: results[1] },
{ kind: ["client"], result: results[0] },
{ kind: ["server"], result: results[1] },
{ kind: ["error"], result: results[2] },
]
.filter(x => x.result.status === "rejected")
.map(x => ({ kind: x.kind, err: x.result.reason })) as Err[];
@@ -129,25 +133,39 @@ if (failed.length > 0) {
if (!x.err?.message) continue;
for (const other of flattened_errors.slice(0, i)) {
if (other.err?.message === x.err.message || other.err.stack === x.err.stack) {
other.kind = "both";
other.kind = [...x.kind, ...other.kind];
flattened_errors.splice(i, 1);
i -= 1;
continue;
}
}
}
let current = "";
for (const { kind, err } of flattened_errors) {
if (kind !== current) {
const map = { both: "runtime", client: "client runtime", server: "server runtime" };
console.error(`Errors while bundling HMR ${map[kind]}:`);
}
const map = { error: "error runtime", client: "client runtime", server: "server runtime" };
console.error(`Errors while bundling Bake ${kind.map(x=>map[x]).join(' and ')}:`);
console.error(err);
}
process.exit(1);
if(!live)
process.exit(1);
} else {
console.log("-> bake.client.js, bake.server.js");
console.log("-> bake.client.js, bake.server.js, bake.error.js");
const empty_file = join(codegen_root, "bake_empty_file");
if (!existsSync(empty_file)) writeFileSync(empty_file, "this is used to fulfill a cmake dependency");
}
}
await run();
if (live) {
const watcher = watch(base_dir, { recursive: true }) as any;
for await (const event of watcher) {
if(event.filename.endsWith('.zig')) continue;
if(event.filename.startsWith('.')) continue;
try {
await run();
}catch(e) {
console.log(e);
}
}
}