diff --git a/src/bundler/ParseTask.zig b/src/bundler/ParseTask.zig index c42df528f3..b12b1c8ef4 100644 --- a/src/bundler/ParseTask.zig +++ b/src/bundler/ParseTask.zig @@ -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, diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index fb4bf0ad22..dd4d7fd766 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -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, diff --git a/test/bundler/bundler_plugin.test.ts b/test/bundler/bundler_plugin.test.ts index e2cdcbffa9..44188998ed 100644 --- a/test/bundler/bundler_plugin.test.ts +++ b/test/bundler/bundler_plugin.test.ts @@ -820,4 +820,80 @@ describe("bundler", () => { }, }; }); + + itBundled("plugin/FileLoaderWithCustomContents", { + files: { + "index.html": /* 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"');
+ },
+ });
});