Skip to content

fix: declare let: directives before {@const} on slotted elements#18271

Merged
dummdidumm merged 1 commit into
sveltejs:mainfrom
ATOM00blue:fix/let-directive-const-tag-slotted-element
May 22, 2026
Merged

fix: declare let: directives before {@const} on slotted elements#18271
dummdidumm merged 1 commit into
sveltejs:mainfrom
ATOM00blue:fix/let-directive-const-tag-slotted-element

Conversation

@ATOM00blue

@ATOM00blue ATOM00blue commented May 22, 2026

Copy link
Copy Markdown
Contributor

Fixes #18119 (missed a spot in #16985)

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time.
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

The bug

A {@const} that captures a let: slot prop throws ReferenceError: Cannot access '<name>' before initialization when the let: directive is on a slotted element (not on the component itself):

<!-- Parent.svelte -->
{#each data as row}
  <slot name="row" {row}></slot>
{/each}

<!-- Consumer.svelte -->
<Parent>
  <tr slot="row" let:row>
    {@const props = { row }}
    <td>{props.row.label}</td>
  </tr>
</Parent>

This is the same class of bug as #16585, but #16985 only fixed the variant where let: is on the component itself. The slotted-element variant still regresses.

Root cause

{@const} declarations are collected into state.consts, and Fragment emits them as [...state.let_directives, ...state.consts, ...state.init]. LetDirective declarations on a component are routed into let_directives (since #16985), so they correctly come first.

But in RegularElement, the let: declarations were pushed onto context.state.init:

// Let bindings first, they can be used on attributes
context.state.init.push(...lets);

Since init is emitted after consts, the generated code declared the {@const} derived before the let: derived it depends on:

const props = $.derived_safe_equal(() => ({ row: $.get(row) })); // ← references row
const row = $.derived_safe_equal(() => $$slotProps.row);          // ← declared after

In dev mode the {@const} derived is read eagerly ($.get(props)), so this hits the temporal dead zone and throws.

The fix

Push the element's let: declarations into let_directives instead of init, so they precede {@const} declarations, mirroring the component-level handling from #16985. They still come before the element's own init (and therefore before any attributes that use them), since Fragment emits let_directives before init.

Testing

Added runtime-legacy/samples/let-directive-and-const-tag-slotted (compiled with dev: true, which is where the eager read surfaces the error). It fails before this change with Cannot access 'thing' before initialization and passes after.

Full runtime-legacy, runtime-runes, server-side-rendering, hydration, snapshot, css, validator and print suites pass.

When a `let:` directive sits on a slotted element (e.g. `<tr slot="row" let:row>`),
its derived declaration was pushed onto the element's `init` statements, which the
Fragment emits after `{@const}` declarations. A sibling `{@const}` that captured the
slot prop therefore referenced it before initialization, throwing "Cannot access
'...' before initialization" in dev mode (where the const is read eagerly).

Route the `let:` declarations into `let_directives` instead so they precede the
`{@const}` declarations, matching how component-level `let:` directives are handled.
Copilot AI review requested due to automatic review settings May 22, 2026 01:32
@changeset-bot

changeset-bot Bot commented May 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e2fbe8e

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

This PR includes changesets to release 1 package
Name Type
svelte 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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR fixes a compiler ordering issue so let: directive bindings on slotted elements are declared before {@const} declarations that depend on them (notably in dev mode), and adds a legacy runtime regression test plus a changeset entry.

Changes:

  • Adjusts RegularElement transform to queue let: directive bindings via context.state.let_directives.
  • Adds a new runtime-legacy sample to reproduce/guard the slotted let: + {@const} ordering behavior in dev mode.
  • Adds a patch changeset describing the fix.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js Changes how let: directive bindings are queued so they can come before {@const} usage on slotted elements.
packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag-slotted/main.svelte New regression sample exercising let: on a slotted element with an inner {@const} that captures it.
packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag-slotted/Nested.svelte Provides the slot prop (thing) via an {#each} + <slot ... {thing} />.
packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag-slotted/_config.js Adds dev-mode legacy runtime test expectation for the new sample.
.changeset/quiet-rivers-melt.md Documents the patch-level fix in release notes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dummdidumm dummdidumm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you

@dummdidumm dummdidumm merged commit bb69f9c into sveltejs:main May 22, 2026
16 of 17 checks passed
@github-actions github-actions Bot mentioned this pull request May 21, 2026
Rich-Harris pushed a commit that referenced this pull request May 27, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## svelte@5.55.10

### Patch Changes

- fix: unlink errored and otherwise finished batch
([#18264](#18264))

- perf: walk composedPath() directly in delegated event propagation
([#18268](#18268))

- fix: transfer effects when merging batches
([#18254](#18254))

- fix: allow `$derived(await ...)` in disconnected effect roots
([#18273](#18273))

- fix: remove temporary raw-text hydration markers
([#18269](#18269))

- fix: propagate async `@const` blockers through closure references so
template expressions like `{(() => host)()}` correctly wait for the
awaited value ([#18309](#18309))

- fix: properly unlink batches
([#18298](#18298))

- fix: settle discarded batch
([#18290](#18290))

- fix: declare `let:` directives before `{@const}` declarations on
slotted elements
([#18271](#18271))

- fix: resume outro-ed branches if they were kept around
([#18291](#18291))

- fix: avoid waterfall-warning when async resolves to same value
([#18297](#18297))

- fix: correctly coordinate component-level effects inside async blocks
([#18260](#18260))

- fix: make unnecessary commit work less likely
([#18263](#18263))

- chore: add tag name to `a11y_click_events_have_key_events` warning
([#18272](#18272))

- fix: catch rejected promises while merging/committing
([#18266](#18266))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

{@const} referencing let: slot prop causes "Cannot access '...' before initialization" (regression of #16585)

3 participants