Describe the bug
When a dependency ships .svelte.ts rune modules, dep prebundling in dev hands them to svelte.compileModule without preprocessing, so any TypeScript syntax in them is a parse error and vite dev fails.
The .svelte path and the .svelte.[jt]s path in setup-optimizer.js are asymmetric. compileSvelte preprocesses:
// src/plugins/setup-optimizer.js:157, 174-182 (v7.3.0)
async function compileSvelte(options, { filename, code }, generate, environment, statsCollection) {
…
if (options.preprocess) {
preprocessed = await svelte.preprocess(code, options.preprocess, { filename });
…
}
const finalCode = preprocessed ? preprocessed.code : code;
compileSvelteModule does not:
// src/plugins/setup-optimizer.js:228 (v7.3.0)
async function compileSvelteModule(options, { filename, code }, generate, statsCollection) {
const endStat = statsCollection?.start(filename);
const compiled = svelte.compileModule(code, { // <-- raw `code`, never preprocessed
dev: options.compilerOptions?.dev ?? true,
filename,
generate
});
svelte.compileModule is a JS parser, so a type annotation, an interface, or an import type in a dependency's .svelte.ts throws js_parse_error.
This looks like an oversight rather than a design choice — the two functions are siblings in the same file, invoked from the same load hook, and only one of them consults options.preprocess.
Reproduction
Minimal shape, no repo needed:
-
Publish (or npm link) a library that exports a .svelte.ts module containing any TypeScript — e.g.
// src/context.svelte.ts
export interface Ctx { width: number }
export const COMPACT_BELOW = 640;
export function plan(ctx: Ctx): 'strip' | 'bar' {
return ctx.width < COMPACT_BELOW ? 'bar' : 'strip';
}
-
Consume it from a fresh Vite + Svelte 5 app with no optimizeDeps block and no plugin option.
-
vite dev → js_parse_error from the prebundle step.
Measured on Vite 8.2.2 · Svelte 5.56.10 · @sveltejs/vite-plugin-svelte 7.3.0, against a published package (@nighthq/components@0.2.0) that ships three such modules: 3 errors. With the same package's three files' types stripped in node_modules and nothing else changed: starts clean, ready in 3249 ms, zero errors. That isolates preprocessing as the only variable.
Expected behaviour
.svelte.[jt]s modules from a prebundled dependency are preprocessed on the same terms as .svelte files, so a library may ship TypeScript rune modules without every consumer configuring around it.
Why the documented workarounds do not cover it
optimizeDeps.exclude: ['<lib>'] works, and is what we document today — but it is per-consumer config for a library-side property, and a consumer only discovers the need when their dev server dies with an error that names none of this.
- The
svelte export condition puts a library on the auto-exclude list, but prebundleSvelteLibraries defaults to true in dev and the list is discarded — so declaring the condition correctly does not help.
So there is currently no library-side fix: the only remedies are consumer config, or shipping compiled JS and giving up publishing source.
Suggested fix
Add the same options.preprocess branch to compileSvelteModule that compileSvelte already has, passing the preprocessed code to svelte.compileModule and carrying preprocessed.map into sourcemap.
Happy to open a PR if the direction is welcome — say the word and I will.
Versions
@sveltejs/vite-plugin-svelte 7.3.0
vite 8.2.2
svelte 5.56.10
Describe the bug
When a dependency ships
.svelte.tsrune modules, dep prebundling in dev hands them tosvelte.compileModulewithout preprocessing, so any TypeScript syntax in them is a parse error andvite devfails.The
.sveltepath and the.svelte.[jt]spath insetup-optimizer.jsare asymmetric.compileSveltepreprocesses:compileSvelteModuledoes not:svelte.compileModuleis a JS parser, so a type annotation, aninterface, or animport typein a dependency's.svelte.tsthrowsjs_parse_error.This looks like an oversight rather than a design choice — the two functions are siblings in the same file, invoked from the same
loadhook, and only one of them consultsoptions.preprocess.Reproduction
Minimal shape, no repo needed:
Publish (or
npm link) a library that exports a.svelte.tsmodule containing any TypeScript — e.g.Consume it from a fresh Vite + Svelte 5 app with no
optimizeDepsblock and no plugin option.vite dev→js_parse_errorfrom the prebundle step.Measured on Vite 8.2.2 · Svelte 5.56.10 · @sveltejs/vite-plugin-svelte 7.3.0, against a published package (
@nighthq/components@0.2.0) that ships three such modules: 3 errors. With the same package's three files' types stripped innode_modulesand nothing else changed: starts clean, ready in 3249 ms, zero errors. That isolates preprocessing as the only variable.Expected behaviour
.svelte.[jt]smodules from a prebundled dependency are preprocessed on the same terms as.sveltefiles, so a library may ship TypeScript rune modules without every consumer configuring around it.Why the documented workarounds do not cover it
optimizeDeps.exclude: ['<lib>']works, and is what we document today — but it is per-consumer config for a library-side property, and a consumer only discovers the need when their dev server dies with an error that names none of this.svelteexport condition puts a library on the auto-exclude list, butprebundleSvelteLibrariesdefaults totruein dev and the list is discarded — so declaring the condition correctly does not help.So there is currently no library-side fix: the only remedies are consumer config, or shipping compiled JS and giving up publishing source.
Suggested fix
Add the same
options.preprocessbranch tocompileSvelteModulethatcompileSveltealready has, passing the preprocessed code tosvelte.compileModuleand carryingpreprocessed.mapintosourcemap.Happy to open a PR if the direction is welcome — say the word and I will.
Versions