Skip to content
17 changes: 15 additions & 2 deletions docs/runtime/transpiler.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,23 @@ interface TranspilerOptions {
macro?: MacroMap,

// Specify a set of exports to eliminate
// Or rename certain exports
// Or replace certain exports
exports?: {
eliminate?: string[];
replace?: Record<string, string>;
// A string, number, boolean, null or undefined becomes the value of the export
// [name, value] replaces the export with an export called name, with that value
// For a pair or a map that you keep in a variable, write as const
// Write each key as an identifier or default, and each name as an identifier
// The constructor throws a TypeError for a key or a name such as a-b
replace?: Record<
string,
| string
| number
| boolean
| null
| undefined
| readonly [name: string, value: string | number | boolean | null | undefined]
>;
},

// Whether to remove unused imports from transpiled file
Expand Down
48 changes: 47 additions & 1 deletion packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3011,7 +3011,53 @@ declare module "bun" {
allowBunRuntime?: boolean;
exports?: {
eliminate?: string[];
replace?: Record<string, string>;
/**
* Replace exported declarations of the file. Each key is the name of an
* exported declaration, and its value is the replacement.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
*
* - A string, number, boolean, `null` or `undefined` becomes the value
* of the declaration. The declaration keeps its name.
Comment thread
robobun marked this conversation as resolved.
Resolved
* - `[name, value]` replaces the declaration with an export called
* `name`. The export has the value `value`, which is a string,
* number, boolean, `null` or `undefined`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Comment thread
robobun marked this conversation as resolved.
Resolved
*
* For a pair or a map that you keep in a variable, write `as const`:
* `const pair = ["__N_SSG", true] as const`. Without a type, TypeScript
* infers an array for the pair and reports an error.
*
* Write each key as an identifier or `default`, and each `name` as an
* identifier. The constructor throws a `TypeError` for a key or a
* `name` such as `a-b`.
*
* @example
* ```ts
* const transpiler = new Bun.Transpiler({
* loader: "ts",
* exports: {
* replace: {
* revalidate: 60,
* getStaticProps: ["__N_SSG", true],
* },
* },
* });
*
* transpiler.transformSync(`
* export const revalidate = readConfig().revalidate;
* export const getStaticProps = async () => ({ props: await load() });
* `);
* // export const revalidate = 60;
* // export const __N_SSG = true;
* ```
*/
replace?: Record<
string,
| string
| number
| boolean
| null
| undefined
| readonly [name: string, value: string | number | boolean | null | undefined]
Comment thread
robobun marked this conversation as resolved.
Resolved
>;
};
treeShaking?: boolean;
trimUnusedImports?: boolean;
Expand Down
149 changes: 149 additions & 0 deletions test/integration/bun-types/fixture/transpiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { expectType } from "./utilities";

type ExportsReplace = NonNullable<NonNullable<Bun.TranspilerOptions["exports"]>["replace"]>;

expectType<ExportsReplace[string]>().is<
| string
| number
| boolean
| null
| undefined
| readonly [name: string, value: string | number | boolean | null | undefined]
>();

// The example in the JSDoc of `exports.replace`.
{
const transpiler = new Bun.Transpiler({
loader: "ts",
exports: {
replace: {
revalidate: 60,
getStaticProps: ["__N_SSG", true],
},
},
});

expectType(
transpiler.transformSync(`
export const revalidate = readConfig().revalidate;
export const getStaticProps = async () => ({ props: await load() });
`),
).is<string>();
}

// A plain value becomes the value of the export.
new Bun.Transpiler({
exports: {
replace: {
aString: "bar",
aNumber: 60,
aBoolean: true,
aNull: null,
anUndefined: undefined,
},
},
});

// `[name, value]` replaces the export with an export called `name`.
new Bun.Transpiler({
exports: {
replace: {
withString: ["__NAME", "bar"],
withNumber: ["__NAME", 60],
withBoolean: ["__N_SSG", true],
withNull: ["__NAME", null],
withUndefined: ["__NAME", undefined],
},
},
});

// The runtime only reads the pair, so a readonly tuple is accepted.
{
const entry = ["__N_SSG", true] as const;
new Bun.Transpiler({ exports: { replace: { getStaticProps: entry, getStaticPaths: entry } } });
}

// A map that is built before the constructor call.
{
const replace: ExportsReplace = {};
for (const name of ["getStaticProps", "getStaticPaths"]) {
replace[name] = ["__N_SSG", true];
}
replace.revalidate = 60;
new Bun.Transpiler({ exports: { eliminate: ["loader"], replace } });
}

// Maps with a type of their own, such as the type of the declaration before this one.
{
const strings: Record<string, string> = { version: "1.0.0" };
new Bun.Transpiler({ exports: { replace: strings } });

const pairs: Record<string, number | [string, boolean]> = { revalidate: 60, getStaticProps: ["__N_SSG", true] };
new Bun.Transpiler({ exports: { replace: pairs } });
}

// A pair or a map in a variable. The call gives no type to a literal that is written before it.
{
const constMap = { revalidate: 60, getStaticProps: ["__N_SSG", true] } as const;
new Bun.Transpiler({ exports: { replace: constMap } });

const mapWithConstPair = { revalidate: 60, getStaticProps: ["__N_SSG", true] as const };
new Bun.Transpiler({ exports: { replace: mapWithConstPair } });

const checkedMap = { revalidate: 60, getStaticProps: ["__N_SSG", true] } satisfies ExportsReplace;
new Bun.Transpiler({ exports: { replace: checkedMap } });

const typedPair: [string, boolean] = ["__N_SSG", true];
new Bun.Transpiler({ exports: { replace: { getStaticProps: typedPair } } });

const options: Bun.TranspilerOptions = { exports: { replace: { getStaticProps: ["__N_SSG", true] } } };
new Bun.Transpiler(options);

const inferredPair = ["__N_SSG", true];
// @ts-expect-error - without a type, TypeScript infers (string | boolean)[] for the pair
new Bun.Transpiler({ exports: { replace: { getStaticProps: inferredPair } } });

const inferredMap = { revalidate: 60, getStaticProps: ["__N_SSG", true] };
// @ts-expect-error - without a type, TypeScript infers (string | boolean)[] for the pair
new Bun.Transpiler({ exports: { replace: inferredMap } });
}

// The `default` key takes a value or a pair.
{
new Bun.Transpiler({ exports: { replace: { default: 60 } } });
new Bun.Transpiler({ exports: { replace: { default: "bar", revalidate: 60 } } });
new Bun.Transpiler({ exports: { replace: { default: ["__N_SSG", true] } } });

const entry = ["__N_SSG", true] as const;
new Bun.Transpiler({ exports: { replace: { default: entry } } });

const replace: ExportsReplace = {};
replace.default = 60;
replace.default = ["__N_SSG", true];
}

// The constructor throws for each of these values.
new Bun.Transpiler({
exports: {
replace: {
// @ts-expect-error - bigint is not a replacement value
aBigInt: 1n,
// @ts-expect-error - symbol is not a replacement value
aSymbol: Symbol("s"),
// @ts-expect-error - function is not a replacement value
aFunction: () => 1,
// @ts-expect-error - object is not a replacement value
anObject: { value: 1 },
// @ts-expect-error - the pair needs a value
nameOnly: ["__NAME"],
// @ts-expect-error - the pair has two elements
threeElements: ["__NAME", true, 1],
// @ts-expect-error - the value of the pair is one of the plain values
objectInPair: ["__NAME", { value: 1 }],
// @ts-expect-error - the value of the pair cannot be another pair
pairInPair: ["__NAME", ["__OTHER", 1]],
// @ts-expect-error - the name of the pair is a string
numberAsName: [1, true],
},
},
});
Loading