From 14f1fe97f07d9cab4823dc8cc92614d23aedaa56 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 01:20:56 -0700 Subject: [PATCH] Keep a hoisted class's next piece when the initializer ends with it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In minified output the class closes right where the initializer body does (`…Counter=class{…}}})`), so the empty insertion that opens the next run-once piece shared its position with the body's closing edit and was dropped: the initializer then called a piece that did not exist. The opener now rides on the class's own closing brace. Co-Authored-By: Claude Opus 5.5 --- .../frontend/npm-static-bundled-esm.test.ts | 7 +++++++ .../src/frontend/npm-static-bundled-esm.ts | 21 ++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts index ac3811594..ea1c22ce5 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts @@ -256,3 +256,10 @@ console.log(typeof make().on); `); expect(lowered).toContain('new (require("events")).EventEmitter()'); }); + +test("a minified initializer that ends with its class still hoists it", () => { + const lowered = expectSameBehavior( + `${helpers}var Counter;var init_counter=__esm({"src/counter.ts"(){console.log("counter init"),Counter=class{n=0;next(){return++this.n}}}});function run(){init_counter();const c=new Counter;c.next();console.log(c.next())}run();`, + ); + expect(lowered).toContain("class Counter{"); +}); diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index e4beb240c..f38d4117d 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -984,11 +984,22 @@ function hoistInitClasses( hoistedEls.add(el); edits.push({ start: from, end: cls.getStart(sf), text: `${sep} ${rest}(); ${close} ` }); const next = els[i + 1]; - edits.push({ - start: cls.getEnd(), - end: next === undefined ? cls.getEnd() : next.getStart(sf), - text: ` function ${rest}() { ${strict}`, - }); + // The class's own closing brace carries the next piece's opener: an + // empty insertion at its end would share its position with the + // initializer body's closing edit in minified text (`…}}})`). + edits.push( + next === undefined + ? { + start: cls.getEnd() - 1, + end: cls.getEnd(), + text: `} function ${rest}() { ${strict}`, + } + : { + start: cls.getEnd(), + end: next.getStart(sf), + text: ` function ${rest}() { ${strict}`, + }, + ); if (cls.name === undefined) { const keyword = cls.getStart(sf); edits.push({ start: keyword, end: keyword + "class".length, text: `class ${name}` });