mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
306 lines
9.4 KiB
TypeScript
306 lines
9.4 KiB
TypeScript
import { describe, expect } from "bun:test";
|
|
import path from "node:path";
|
|
import { itBundled } from "./expectBundled";
|
|
|
|
describe("bundler", () => {
|
|
describe("plugin chain behavior", () => {
|
|
// Test that returning undefined/null/{} continues to next plugin
|
|
itBundled("plugin/ResolveChainContinues", ({ root }) => {
|
|
const callOrder: string[] = [];
|
|
|
|
return {
|
|
files: {
|
|
"index.ts": /* ts */ `
|
|
import { foo } from "./test.magic";
|
|
console.log(foo);
|
|
`,
|
|
"test.ts": /* ts */ `
|
|
export const foo = "resolved by plugin3";
|
|
`,
|
|
},
|
|
plugins: [
|
|
{
|
|
name: "plugin1",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.magic$/ }, args => {
|
|
callOrder.push("plugin1-resolve");
|
|
// Return undefined - should continue to next plugin
|
|
return undefined;
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin2",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.magic$/ }, args => {
|
|
callOrder.push("plugin2-resolve");
|
|
// Return null - should continue to next plugin
|
|
return null;
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin3",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.magic$/ }, args => {
|
|
callOrder.push("plugin3-resolve");
|
|
// Return empty object - should continue to next plugin
|
|
return {};
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin4",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.magic$/ }, args => {
|
|
callOrder.push("plugin4-resolve");
|
|
// Actually resolve it
|
|
return {
|
|
path: path.resolve(path.dirname(args.importer), "test.ts"),
|
|
};
|
|
});
|
|
},
|
|
},
|
|
],
|
|
run: {
|
|
stdout: "resolved by plugin3",
|
|
},
|
|
onAfterBundle() {
|
|
// All plugins should have been called in order
|
|
expect(callOrder).toEqual(["plugin1-resolve", "plugin2-resolve", "plugin3-resolve", "plugin4-resolve"]);
|
|
},
|
|
};
|
|
});
|
|
|
|
// Test that returning a path stops the chain
|
|
itBundled("plugin/ResolveChainStops", ({ root }) => {
|
|
const callOrder: string[] = [];
|
|
|
|
return {
|
|
files: {
|
|
"index.ts": /* ts */ `
|
|
import { foo } from "./test.magic";
|
|
console.log(foo);
|
|
`,
|
|
"resolved-by-plugin2.ts": /* ts */ `
|
|
export const foo = "plugin2 resolved";
|
|
`,
|
|
"resolved-by-plugin4.ts": /* ts */ `
|
|
export const foo = "plugin4 resolved";
|
|
`,
|
|
},
|
|
plugins: [
|
|
{
|
|
name: "plugin1",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.magic$/ }, args => {
|
|
callOrder.push("plugin1-resolve");
|
|
// Return undefined - continue to next
|
|
return undefined;
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin2",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.magic$/ }, args => {
|
|
callOrder.push("plugin2-resolve");
|
|
// Return a path - should stop chain here
|
|
return {
|
|
path: path.resolve(path.dirname(args.importer), "resolved-by-plugin2.ts"),
|
|
};
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin3",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.magic$/ }, args => {
|
|
callOrder.push("plugin3-resolve");
|
|
// This should NOT be called
|
|
return {
|
|
path: path.resolve(path.dirname(args.importer), "resolved-by-plugin4.ts"),
|
|
};
|
|
});
|
|
},
|
|
},
|
|
],
|
|
run: {
|
|
stdout: "plugin2 resolved",
|
|
},
|
|
onAfterBundle() {
|
|
// Only first two plugins should have been called
|
|
expect(callOrder).toEqual(["plugin1-resolve", "plugin2-resolve"]);
|
|
},
|
|
};
|
|
});
|
|
|
|
// Test entry point plugin chain behavior
|
|
itBundled("plugin/EntryPointResolveChain", ({ root }) => {
|
|
const callOrder: string[] = [];
|
|
|
|
return {
|
|
files: {
|
|
"actual-entry.ts": /* ts */ `
|
|
console.log("correct entry point");
|
|
`,
|
|
},
|
|
entryPointsRaw: ["virtual-entry.ts"],
|
|
plugins: [
|
|
{
|
|
name: "plugin1",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /virtual-entry\.ts$/ }, args => {
|
|
expect(args.kind).toBe("entry-point-build");
|
|
callOrder.push("plugin1-entry");
|
|
// Return null - continue to next
|
|
return null;
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin2",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /virtual-entry\.ts$/ }, args => {
|
|
expect(args.kind).toBe("entry-point-build");
|
|
callOrder.push("plugin2-entry");
|
|
// Return empty object - continue to next
|
|
return {};
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin3",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /virtual-entry\.ts$/ }, args => {
|
|
expect(args.kind).toBe("entry-point-build");
|
|
callOrder.push("plugin3-entry");
|
|
// Resolve to actual file
|
|
return {
|
|
path: path.join(root, "actual-entry.ts"),
|
|
};
|
|
});
|
|
},
|
|
},
|
|
],
|
|
run: {
|
|
file: "/out/virtual-entry.js",
|
|
stdout: "correct entry point",
|
|
},
|
|
onAfterBundle(api) {
|
|
// All plugins should have been called
|
|
expect(callOrder).toEqual(["plugin1-entry", "plugin2-entry", "plugin3-entry"]);
|
|
|
|
// Check what file was actually created
|
|
try {
|
|
api.readFile("/out/actual-entry.js");
|
|
console.log("Found /out/actual-entry.js");
|
|
} catch {}
|
|
try {
|
|
api.readFile("/out/virtual-entry.js");
|
|
console.log("Found /out/virtual-entry.js");
|
|
} catch {}
|
|
},
|
|
};
|
|
});
|
|
|
|
// Test with various return values that should continue chain
|
|
for (const returnValue of [undefined, null, {}, { external: undefined }, { namespace: undefined }]) {
|
|
const valueName = require("util").inspect(returnValue);
|
|
|
|
itBundled(`plugin/ResolveChainContinuesWith\`${valueName}\``, ({ root }) => {
|
|
let plugin2Called = false;
|
|
|
|
return {
|
|
files: {
|
|
"index.ts": /* ts */ `
|
|
import { value } from "./test.special";
|
|
console.log(value);
|
|
`,
|
|
"test.ts": /* ts */ `
|
|
export const value = "success";
|
|
`,
|
|
},
|
|
plugins: [
|
|
{
|
|
name: "plugin1",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.special$/ }, args => {
|
|
// Return the test value - should continue to next plugin
|
|
return returnValue as any;
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin2",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.special$/ }, args => {
|
|
plugin2Called = true;
|
|
return {
|
|
path: path.resolve(path.dirname(args.importer), "test.ts"),
|
|
};
|
|
});
|
|
},
|
|
},
|
|
],
|
|
run: {
|
|
stdout: "success",
|
|
},
|
|
onAfterBundle() {
|
|
expect(plugin2Called).toBe(true);
|
|
},
|
|
};
|
|
});
|
|
}
|
|
|
|
// Test that primitives other than null/undefined should continue chain
|
|
for (const value of [false, true, 0, 1, "string"]) {
|
|
const valueName = JSON.stringify(value);
|
|
|
|
itBundled(`plugin/ResolveChainContinuesWithPrimitive${valueName.replace(/[^a-zA-Z0-9]/g, "")}`, ({ root }) => {
|
|
let plugin2Called = false;
|
|
|
|
return {
|
|
files: {
|
|
"index.ts": /* ts */ `
|
|
import { value } from "./test.primitive";
|
|
console.log(value);
|
|
`,
|
|
"test.ts": /* ts */ `
|
|
export const value = "handled";
|
|
`,
|
|
},
|
|
plugins: [
|
|
{
|
|
name: "plugin1",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.primitive$/ }, args => {
|
|
// Return a primitive - should be treated as "not handled"
|
|
return value as any;
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: "plugin2",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /\.primitive$/ }, args => {
|
|
plugin2Called = true;
|
|
return {
|
|
path: path.resolve(path.dirname(args.importer), "test.ts"),
|
|
};
|
|
});
|
|
},
|
|
},
|
|
],
|
|
run: {
|
|
stdout: "handled",
|
|
},
|
|
onAfterBundle() {
|
|
expect(plugin2Called).toBe(true);
|
|
},
|
|
};
|
|
});
|
|
}
|
|
});
|
|
});
|