Skip to content

esrap 2.3.3: server codegen emits invalid JS when @type JSDoc on export let meets a $: declaration #18643

Description

@Ivaneyko

Correction (edited shortly after filing): the trigger is esrap 2.3.3, not a Svelte version. The original text bisected over Svelte releases, which was confounded — each install resolved a different transitive esrap. Details below.

Describe the bug

With esrap 2.3.3, generate: 'server' emits syntactically invalid JavaScript when a /** @type {...} */ comment sits on an export let prop and the component also has a $: declaration.

The hoisted declaration for the reactive variable now has its declarator wrapped in parentheses, which is not legal after let:

// esrap 2.3.2 — valid
let /** @type {string} */
c;

// esrap 2.3.3 — invalid
let /** @type {string} */ (
c);

V8 rejects it with SyntaxError: Unexpected strict mode reserved word, oxc/rolldown with The keyword 'let' is reserved — since let cannot start a declaration here, it parses as an identifier reference, which is reserved in strict mode/modules.

generate: 'client' is unaffected, so this only surfaces in SSR builds.

Because svelte depends on esrap: ^2.2.9, a fresh npm install resolves 2.3.3 and any project of this shape starts failing to build with no change on its own side. sveltekit-superforms is one concrete path in: its package entry re-exports dist/client/SuperDebug.svelte as the default export, so the component enters the module graph from every import { superForm } from 'sveltekit-superforms' and fails to parse even though it is never rendered.

Reproduction

MinRepro.svelte:

<script>
	/** @type {string} */
	export let a = 'x';
	$: c = a;
</script>

{c}
import fs from 'node:fs';
import { compile } from 'svelte/compiler';

fs.writeFileSync(
	'out.mjs',
	compile(fs.readFileSync('MinRepro.svelte', 'utf8'), {
		generate: 'server',
		filename: 'MinRepro.svelte'
	}).js.code
);
// node --check out.mjs  ->  SyntaxError: Unexpected strict mode reserved word

Both ingredients are required, and the trigger is specifically @type. Removing the $: line, or using /** Docs. */, @param, @deprecated, @typedef, @returns, a non-JSDoc /* … */ block, or // comments, all produce valid output.

Version boundary

Pinning esrap through overrides with svelte held fixed, on a clean node_modules each time:

esrap result
2.2.10 … 2.3.2 ok
2.3.3 (latest, published 2026-08-13) broken

Independent of the Svelte version — 5.56.1 and 5.56.9 are both ok on esrap 2.3.2 and both broken on 2.3.3.

This may belong in sveltejs/esrap rather than here; filing where it surfaces.

Stack

svelte:                     5.56.9 (also reproduced on 5.56.1)
esrap:                      2.3.3  (2.3.2 and earlier are fine)
sveltekit-superforms:       2.30.2
@sveltejs/kit:              2.70.2
@sveltejs/vite-plugin-svelte: 7.3.0
vite:                       8.2.1 (rolldown 1.2.4)
typescript:                 6.0.2
node:                       22.18.0
npm:                        10.9.3
OS:                         macOS 26.5.2

Severity

annoyance — the compiler produces invalid output silently rather than erroring, so it surfaces as a bundler parse error in an unrelated node_modules file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions