Skip to content

fix(compiler): extract "use server" function declarations nested in a function - #3369

Merged
ryansolid merged 1 commit into
nextfrom
fix/nested-server-function-declarations
Sep 11, 2026
Merged

fix(compiler): extract "use server" function declarations nested in a function#3369
ryansolid merged 1 commit into
nextfrom
fix/nested-server-function-declarations

Conversation

@ryansolid

Copy link
Copy Markdown
Member

A "use server" directive on a function declaration nested inside another function did nothing. The bubbling pre-pass turns declarations into const name = function name() {} so the transform only handles expression forms, but (matching Babel's tmp.skip()) it never descended into a bubbled body, so a nested declaration never became an expression and was never extracted. The body shipped to the client and ran there.

Meanwhile the capture validator walks every function, so the same declaration did get its captures checked:

export function outer(db) {
  async function inner() { "use server"; return db.x; }  // compile error: captures `db`
  async function other() { "use server"; return 1; }     // compiled, silently NOT extracted
}

The bubbler now descends into each bubbled body, so nested declarations are extracted like any other marked function, at any depth and inside nested blocks. The const lands at the top of its block, so a use above the declaration still resolves. Ids follow the binding path (outer.inner, outer.mid.inner).

  • Only files that extract at least one function keep the pass's output (valid), so the extra bubbling never reaches a file without server functions.
  • No existing fixture contains a nested declaration, so the frozen Babel references are unchanged. Babel never produced output for this case, so rather than author a fictional reference the behaviour is pinned in directives-nested-declarations.test.js (server and client shape, hoisting, depth, block nesting, capture rejection, unmarked declarations untouched).
  • One existing id-scheme test now compares order-insensitively: nested declarations hoist in reverse source order like top-level ones, which moves the reporting order without moving an id.

Surfaced while reviewing #3363/#3365.

… function

The bubbling pre-pass turns every function declaration into
`const name = function name() {}` at the top of its block so the
transform only handles expression forms. Babel skipped the body of each
bubbled declaration (`tmp.skip()`), so a declaration nested inside
another function was never bubbled, never became an expression, and a
directive on it was silently ignored: the body shipped to the client
and ran there. The capture validator walks every function, so the same
declaration still had its captures rejected. One spelling produced a
compile error, the other a client-side body.

The bubbler now descends into each bubbled body, so nested declarations
are extracted like any other marked function, at any depth and inside
nested blocks. The const lands at the top of its block, so a use above
the declaration still resolves. Ids follow the binding path
(`outer.inner`). Only files that extract at least one function keep the
pass's output, so the extra bubbling never reaches a file without server
functions. No existing fixture contains a nested declaration, so the
frozen references are unchanged.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 882ad7f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/compiler Patch
@solidjs/babel-plugin Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
@solidjs/signals Patch
solid-js Patch
@solidjs/universal Patch
@solidjs/web Patch
test-integration Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ryansolid
ryansolid merged commit 8366e09 into next Sep 11, 2026
6 checks passed
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34577398077

Coverage remained the same at 71.842%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1007
Covered Lines: 772
Line Coverage: 76.66%
Relevant Branches: 790
Covered Branches: 519
Branch Coverage: 65.7%
Branches in Coverage %: Yes
Coverage Strength: 15.01 hits per line

💛 - Coveralls

@ryansolid
ryansolid deleted the fix/nested-server-function-declarations branch September 11, 2026 08:10
@codspeed-hq

codspeed-hq Bot commented Sep 11, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 160 untouched benchmarks


Comparing fix/nested-server-function-declarations (882ad7f) with next (4935c7d)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants