Fix crash in bundler related to onLoad plugins that return file loader for HTML imports (#20849)

This commit is contained in:
Zack Radisic
2025-07-07 01:07:03 -07:00
committed by GitHub
parent c370645afc
commit dacb75dc1f
3 changed files with 78 additions and 8 deletions

View File

@@ -34,10 +34,6 @@ emit_decorator_metadata: bool = false,
ctx: *BundleV2,
package_version: string = "",
is_entry_point: bool = false,
/// This is set when the file is an entrypoint, and it has an onLoad plugin.
/// In this case we want to defer adding this to additional_files until after
/// the onLoad plugin has finished.
defer_copy_for_bundling: bool = false,
const ParseTaskStage = union(enum) {
needs_source_code: void,

View File

@@ -1964,7 +1964,8 @@ pub const BundleV2 = struct {
this.decrementScanCounter();
},
.success => |code| {
const should_copy_for_bundling = load.parse_task.defer_copy_for_bundling and code.loader.shouldCopyForBundling();
// When a plugin returns a file loader, we always need to populate additional_files
const should_copy_for_bundling = code.loader.shouldCopyForBundling();
if (should_copy_for_bundling) {
const source_index = load.source_index;
var additional_files: *BabyList(AdditionalFile) = &this.graph.input_files.items(.additional_files)[source_index.get()];
@@ -2619,9 +2620,6 @@ pub const BundleV2 = struct {
pub fn enqueueOnLoadPluginIfNeededImpl(this: *BundleV2, parse: *ParseTask) bool {
if (this.plugins) |plugins| {
if (plugins.hasAnyMatches(&parse.path, true)) {
if (parse.is_entry_point and parse.loader != null and parse.loader.?.shouldCopyForBundling()) {
parse.defer_copy_for_bundling = true;
}
// This is where onLoad plugins are enqueued
debug("enqueue onLoad: {s}:{s}", .{
parse.path.namespace,

View File

@@ -820,4 +820,80 @@ describe("bundler", () => {
},
};
});
itBundled("plugin/FileLoaderWithCustomContents", {
files: {
"index.html": /* html */ `
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<img src="./image.jpeg" />
<script src="./script.js"></script>
</body>
</html>
`,
"script.js": /* js */ `
console.log("Script loaded");
`,
"image.jpeg": "actual image data would be here",
},
entryPoints: ["./index.html"],
outdir: "/out",
plugins(build) {
// This plugin intercepts .jpeg files and returns them with custom contents
// This previously caused a crash because additional_files wasn't populated
build.onLoad({ filter: /\.jpe?g$/ }, async args => {
return {
loader: "file",
contents: "custom image contents",
};
});
},
onAfterBundle(api) {
// Verify the build succeeded and files were created
api.assertFileExists("index.html");
// The image should be copied with a hashed name
const html = api.readFile("index.html");
expect(html).toContain('src="');
expect(html).toContain('.jpeg"');
},
});
itBundled("plugin/FileLoaderMultipleAssets", {
files: {
"index.js": /* js */ `
import imgUrl from "./image.png";
import wasmUrl from "./module.wasm";
console.log(imgUrl, wasmUrl);
`,
"image.png": "png data",
"module.wasm": "wasm data",
},
entryPoints: ["./index.js"],
outdir: "/out",
plugins(build) {
// Test multiple file types with custom contents
build.onLoad({ filter: /\.(png|wasm)$/ }, async args => {
const ext = args.path.split(".").pop();
return {
loader: "file",
contents: `custom ${ext} contents`,
};
});
},
run: {
stdout: /\.(png|wasm)/,
},
onAfterBundle(api) {
// Verify the build succeeded and files were created
api.assertFileExists("index.js");
const js = api.readFile("index.js");
// Should contain references to the copied files
expect(js).toContain('.png"');
expect(js).toContain('.wasm"');
},
});
});