Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ impl IntoExprData for &E::EString {
impl Expr {
/// When the lifetime of an Expr.Data's pointer must exist longer than reset() is called, use this function.
/// Be careful to free the memory (or use an arena that does it for you)
/// Also, prefer Expr.init or Expr.alloc when possible. This will be slower.
/// Prefer `Expr::init` for nodes that only need to live until the current parse's reset().
#[inline]
pub fn allocate<T: IntoExprData>(bump: &Bump, st: T, loc: Loc) -> Expr {
data::Store::assert();
Expand Down
6 changes: 5 additions & 1 deletion src/runtime/api/JSTranspiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,9 @@ fn export_replacement_value(
global: &JSGlobalObject,
arena: &Arena,
) -> JsResult<Option<bun_ast::Expr>> {
// The result outlives every parse, so it must not be built in the thread-local store.
let _guard = bun_ast::expr::Disabler::scope();

if value.is_boolean() {
return Ok(Some(Expr {
data: bun_ast::ExprData::EBoolean(bun_ast::E::Boolean {
Expand Down Expand Up @@ -916,7 +919,8 @@ fn export_replacement_value(
// `E::EString::init` erases the borrow to `'static` per the AST
// crate's `Str` convention (see ast/E.rs).
let data = arena.alloc_slice_copy(utf8.slice());
return Ok(Some(Expr::init(
return Ok(Some(Expr::allocate(
arena,
bun_ast::E::EString::init(data),
bun_ast::Loc::EMPTY,
)));
Expand Down
58 changes: 58 additions & 0 deletions test/bundler/transpiler/transpiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2404,6 +2404,64 @@ export default class {
expect(output.includes("localVarToReplace")).toBe(true);
expect(output.includes("localVarToRemove")).toBe(false);
});

it("string replacement values survive other modules being loaded after the transpiler is created", async () => {
// Release builds only reuse the AST store slot after a few dozen string declarations.
const resetLines = [];
for (let i = 0; i < 300; i++) {
resetLines.push(`const s${i} = "other string ${i}";`);
}
resetLines.push("module.exports = { s0, s299 };");

using dir = tempDir("transpiler-replace-string-values", {
"reset.cjs": resetLines.join("\n"),
"reset.json": `{}`,
"entry.mjs": `
const transpiler = new Bun.Transpiler({
exports: {
replace: {
foo: "bar",
getStaticProps: ["__N_SSG", "ssg"],
default: "dflt",
},
},
});

require("./reset.cjs");
await import("./reset.json");

console.log(
JSON.stringify([
transpiler.transformSync("export const foo = 1;"),
transpiler.transformSync("export function getStaticProps() {}"),
transpiler.transformSync("export default 1;"),
await transpiler.transform("export const foo = 1;"),
await transpiler.transform("export function getStaticProps() {}"),
await transpiler.transform("export default 1;"),
]),
);
`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), "entry.mjs"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(JSON.parse(stdout)).toEqual([
'export const foo = "bar";\n',
'export var __N_SSG = "ssg";\n',
'export default "dflt";\n',
'export const foo = "bar";\n',
'export var __N_SSG = "ssg";\n',
'export default "dflt";\n',
]);
expect(exitCode).toBe(0);
});
});

const bunTranspiler = new Bun.Transpiler({
Expand Down
Loading