Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/runtime/bake/dev_server/incremental_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,19 @@ impl<const SIDE: bake::Side> IncrementalGraph<SIDE> {
);
}

// Nothing will `receive_chunk` this node again to clear a failure from
// its last bundle, so retract it here; `index_failures` publishes the
// removal at the end of this bundle.
if self.bundled_files.values()[index.get() as usize].failed {
self.bundled_files.values_mut()[index.get() as usize].failed = false;
let owner = serialized_failure::OwnerPacked::new(SIDE, index.get());
let kv = self.dev_bundling_failures().fetch_swap_remove(&owner);
let kv = kv.unwrap_or_else(|| {
bun_core::Output::panic(format_args!("Missing failure in IncrementalGraph"))
});
self.dev_incremental_result().failures_removed.push(kv.1);
}

// Bust the resolution cache of the dir containing this file.
let dirname = bun_paths::dirname(abs_path).unwrap_or(abs_path);
let _ = bv2.transpiler.resolver.bust_dir_cache(dirname);
Expand Down
95 changes: 95 additions & 0 deletions test/bake/dev/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,101 @@ devTest("deleting imported file shows error then recovers", {
});
},
});
// Deleting a file whose last bundle failed used to leave that failure in
// dev.bundling_failures: the overlay kept showing it next to the importer's new
// resolution error and every later "Build Failed" page listed it again.
devTest("deleting a file that failed to bundle retracts its failure", {
skip: [
"win32", // unlinkSync is having weird behavior
],
files: {
"index.html": emptyHtmlFile({
styles: [],
scripts: ["index.ts"],
}),
"index.ts": `
import { value } from "./other";
console.log(value);
`,
"other.ts": `
export const value = 123;
`,
},
async test(dev) {
await using c = await dev.client("/");
await c.expectMessage(123);
await dev.write("other.ts", `export const value = ;`, {
errors: ["other.ts:1:22: error: Unexpected ;"],
});
// The errors packet for this rebuild has to retract other.ts's failure
// along with adding index.ts's resolution failure.
await dev.delete("other.ts", {
errors: ['index.ts:1:23: error: Could not resolve: "./other"'],
});
// A fresh page load lists every failure the dev server still tracks.
await c.hardReload({
errors: ['index.ts:1:23: error: Could not resolve: "./other"'],
});
// Dropping the import fixes the last remaining failure, so the error page
// reloads into the working page. A stale other.ts entry would keep it
// stuck on the error page.
await c.expectReload(async () => {
await dev.write("index.ts", `console.log("without other");`);
});
await c.expectMessage("without other");
// Recreating the file reuses its node in the incremental graph, which no
// longer owns a failure.
await dev.write(
"index.ts",
`
import { value } from "./other";
console.log(value);
`,
{ errors: ['index.ts:1:23: error: Could not resolve: "./other"'] },
);
await c.expectReload(async () => {
await dev.write("other.ts", `export const value = 456;`);
});
await c.expectMessage(456);
},
});
// Same as above for a file that only the server graph knows about.
devTest("deleting a server file that failed to bundle retracts its failure", {
skip: [
"win32", // unlinkSync is having weird behavior
],
framework: minimalFramework,
files: {
"routes/index.ts": `
import { value } from '../db';
export default function (req, meta) {
return new Response('value: ' + value);
}
`,
"db.ts": `export const value = 123;`,
},
async test(dev) {
await dev.fetch("/").equals("value: 123");
await dev.write("db.ts", `export const value = ;`, { errors: null });
{
// This client sits on the "Build Failed" page, which only listens for
// failures being added and removed.
await using c = await dev.client("/", {
errors: ["db.ts:1:22: error: Unexpected ;"],
});
await dev.delete("db.ts", {
errors: [`routes/index.ts:1:23: error: Could not resolve: "../db"`],
});
await using fresh = await dev.client("/", {
errors: [`routes/index.ts:1:23: error: Could not resolve: "../db"`],
});
}
// Recreating the file reuses its node in the incremental graph, which no
// longer owns a failure.
await dev.write("db.ts", `export const value = 456;`);
await dev.fetch("/").equals("value: 456");
},
});
// Regression test: DirectoryWatchStore.Dep.source_file_path borrows the key
// string from IncrementalGraph.bundled_files. When a client-component boundary
// is demoted (its "use client" directive is removed) the server graph calls
Expand Down