mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Fix bundler crash with onLoad plugins on copy-file loaders used on entrypoints (#15231)
This commit is contained in:
@@ -1816,11 +1816,19 @@ pub const BundleV2 = struct {
|
||||
_ = @atomicRmw(usize, &this.graph.parse_pending, .Sub, 1, .monotonic);
|
||||
},
|
||||
.success => |code| {
|
||||
const should_copy_for_bundling = load.parse_task.defer_copy_for_bundling and code.loader.shouldCopyForBundling(this.bundler.options.experimental_css);
|
||||
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()];
|
||||
additional_files.push(this.graph.allocator, .{ .source_index = source_index.get() }) catch unreachable;
|
||||
this.graph.input_files.items(.side_effects)[source_index.get()] = _resolver.SideEffects.no_side_effects__pure_data;
|
||||
this.graph.estimated_file_loader_count += 1;
|
||||
}
|
||||
this.graph.input_files.items(.loader)[load.source_index.get()] = code.loader;
|
||||
this.graph.input_files.items(.source)[load.source_index.get()].contents = code.source_code;
|
||||
var parse_task = load.parse_task;
|
||||
parse_task.loader = code.loader;
|
||||
this.free_list.append(code.source_code) catch unreachable;
|
||||
if (!should_copy_for_bundling) this.free_list.append(code.source_code) catch unreachable;
|
||||
parse_task.contents_or_fd = .{
|
||||
.contents = code.source_code,
|
||||
};
|
||||
@@ -2259,6 +2267,9 @@ pub const BundleV2 = struct {
|
||||
pub fn enqueueOnLoadPluginIfNeeded(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(this.bundler.options.experimental_css)) {
|
||||
parse.defer_copy_for_bundling = true;
|
||||
}
|
||||
// This is where onLoad plugins are enqueued
|
||||
debug("enqueue onLoad: {s}:{s}", .{
|
||||
parse.path.namespace,
|
||||
@@ -3201,6 +3212,10 @@ pub const ParseTask = struct {
|
||||
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,
|
||||
|
||||
/// The information returned to the Bundler thread when a parse finishes.
|
||||
pub const Result = struct {
|
||||
@@ -3874,7 +3889,7 @@ pub const ParseTask = struct {
|
||||
var ast: JSAst = if (!is_empty)
|
||||
try getAST(log, bundler, opts, allocator, resolver, source, loader, task.ctx.unique_key, &unique_key_for_additional_file)
|
||||
else switch (opts.module_type == .esm) {
|
||||
inline else => |as_undefined| if (loader == .css) try getEmptyCSSAST(
|
||||
inline else => |as_undefined| if (loader == .css and this.ctx.bundler.options.experimental_css) try getEmptyCSSAST(
|
||||
log,
|
||||
bundler,
|
||||
opts,
|
||||
@@ -12302,7 +12317,8 @@ pub const LinkerContext = struct {
|
||||
if (strings.eqlComptime(from_chunk_dir, "."))
|
||||
from_chunk_dir = "";
|
||||
|
||||
const additional_files: []AdditionalFile = c.parse_graph.input_files.items(.additional_files)[piece.query.index].slice();
|
||||
const source_index = piece.query.index;
|
||||
const additional_files: []AdditionalFile = c.parse_graph.input_files.items(.additional_files)[source_index].slice();
|
||||
bun.assert(additional_files.len > 0);
|
||||
switch (additional_files[0]) {
|
||||
.output_file => |output_file_id| {
|
||||
|
||||
@@ -656,7 +656,6 @@ pub const Loader = enum(u8) {
|
||||
if (experimental_css) {
|
||||
return switch (this) {
|
||||
.file,
|
||||
.css,
|
||||
.napi,
|
||||
.sqlite,
|
||||
.sqlite_embedded,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { fileURLToPath } from "bun";
|
||||
import { describe } from "bun:test";
|
||||
import fs from "node:fs";
|
||||
import { fileURLToPath, Loader } from "bun";
|
||||
import { describe, expect } from "bun:test";
|
||||
import fs, { readdirSync } from "node:fs";
|
||||
import { join } from "path";
|
||||
import { itBundled } from "./expectBundled";
|
||||
|
||||
@@ -56,6 +56,7 @@ describe("bundler", async () => {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
itBundled("bun/loader-text-file", {
|
||||
target: "bun",
|
||||
outfile: "",
|
||||
@@ -114,4 +115,65 @@ describe("bundler", async () => {
|
||||
stdout: moon,
|
||||
},
|
||||
});
|
||||
|
||||
const loaders: Loader[] = ["wasm", "css", "json", "file" /* "napi" */, "text"];
|
||||
const exts = ["wasm", "css", "json", ".lmao" /* ".node" */, ".txt"];
|
||||
for (let i = 0; i < loaders.length; i++) {
|
||||
const loader = loaders[i];
|
||||
const ext = exts[i];
|
||||
itBundled(`bun/loader-copy-file-entry-point-with-onLoad-${loader}`, {
|
||||
target: "bun",
|
||||
outdir: "/out",
|
||||
experimentalCss: false,
|
||||
files: {
|
||||
[`/entry.${ext}`]: /* js */ `{ "hello": "friends" }`,
|
||||
},
|
||||
entryNaming: "[dir]/[name]-[hash].[ext]",
|
||||
plugins(builder) {
|
||||
builder.onLoad({ filter: new RegExp(`.${loader}$`) }, async ({ path }) => {
|
||||
const result = await Bun.file(path).text();
|
||||
return { contents: result, loader };
|
||||
});
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const jsFile = readdirSync(api.outdir).find(x => x.endsWith(".js"))!;
|
||||
const module = require(join(api.outdir, jsFile));
|
||||
|
||||
if (loader === "json") {
|
||||
expect(module.default).toStrictEqual({ hello: "friends" });
|
||||
} else if (loader === "text") {
|
||||
expect(module.default).toStrictEqual('{ "hello": "friends" }');
|
||||
} else {
|
||||
api.assertFileExists(join("out", module.default));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < loaders.length; i++) {
|
||||
const loader = loaders[i];
|
||||
const ext = exts[i];
|
||||
itBundled(`bun/loader-copy-file-entry-point-${loader}`, {
|
||||
target: "bun",
|
||||
outfile: "",
|
||||
outdir: "/out",
|
||||
experimentalCss: false,
|
||||
files: {
|
||||
[`/entry.${ext}`]: /* js */ `{ "hello": "friends" }`,
|
||||
},
|
||||
entryNaming: "[dir]/[name]-[hash].[ext]",
|
||||
onAfterBundle(api) {
|
||||
const jsFile = readdirSync(api.outdir).find(x => x.endsWith(".js"))!;
|
||||
const module = require(join(api.outdir, jsFile));
|
||||
|
||||
if (loader === "json") {
|
||||
expect(module.default).toStrictEqual({ hello: "friends" });
|
||||
} else if (loader === "text") {
|
||||
expect(module.default).toStrictEqual('{ "hello": "friends" }');
|
||||
} else {
|
||||
api.assertFileExists(join("out", module.default));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user