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 */ ` + + + + Test + + + + + + + `, + "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"'); + }, + }); });