Skip to content

feat: auto-generate .svelte.d.ts for dependency packages in incremental/tsgo mode#2967

Closed
datstarkey wants to merge 2 commits intosveltejs:masterfrom
datstarkey:feat/dependency-svelte-dts-generation
Closed

feat: auto-generate .svelte.d.ts for dependency packages in incremental/tsgo mode#2967
datstarkey wants to merge 2 commits intosveltejs:masterfrom
datstarkey:feat/dependency-svelte-dts-generation

Conversation

@datstarkey
Copy link
Contributor

Problem

The svelte-check docs for --incremental and --tsgo state:

Specifically, anything that is not in the root dir of your tsconfig.json and is a Svelte file will not be properly loaded and type-checked.

In a monorepo where workspace packages export .svelte components (e.g. via pnpm workspace links), this means svelte-check --tsgo or --incremental cannot type-check those components — all props resolve to any. The only workaround today is pre-building every workspace package with svelte-package to generate .d.ts files, adding an unnecessary build step to the type-checking workflow.

Solution

This PR addresses the limitation by automatically detecting dependency packages that contain .svelte files without companion .d.ts declarations and generating virtual declarations for them using svelte2tsx — the same mechanism already used for the app's own .svelte files.

How it works

  1. Detection: Scans direct dependencies (via package.json + node_modules) for .svelte files that lack a companion .svelte.d.ts or .d.svelte.ts
  2. Emission: Runs svelte2tsx on each to emit virtual .ts/.d.ts into the existing .svelte-check cache directory, mirroring the package's internal directory structure
  3. Resolution: Adds rootDirs entries pairing each package's real path with its emit directory, so tsc/tsgo can find the virtual declarations when resolving .svelte imports

Resolution flow

App imports @shared/components/ui
  → TypeScript follows exports → finds barrel index.ts
  → Barrel: export { default as Button } from './Button.svelte'
  → TypeScript resolves ./Button.svelte in package dir
  → No .svelte.d.ts next to original file
  → rootDirs: checks emit dir at same relative path → finds Button.svelte.d.ts ✓

Known limitations / areas for improvement

  • Currently processes all .svelte files in each dependency package, not just the ones actually imported by the app. A smarter approach would trace the import graph to only process referenced files.
  • This is our first approach at solving this and may benefit from refinement based on feedback from different monorepo setups.
  • Dependency .svelte files are excluded from Svelte compiler diagnostics reporting (only TS diagnostics from tsc/tsgo are reported for them).

Test plan

  • All 6 existing sanity tests pass (no regressions)
  • 2 new monorepo sanity tests added (cold cache + warm cache) — test fixture with a shared workspace package exporting a typed Svelte component, consumed by an app that intentionally passes a wrong prop type
  • Tested against a real-world monorepo with 264 shared .svelte components: 0 errors, 3587 files checked (without any svelte-package pre-build step)

…al/tsgo mode

The svelte-check docs for --incremental and --tsgo state:

  "Specifically, anything that is not in the root dir of your
  tsconfig.json and is a Svelte file will not be properly loaded
  and type-checked."

In a monorepo where workspace packages export .svelte components
(e.g. via pnpm workspace links), this means svelte-check --tsgo
or --incremental cannot type-check those components - all props
resolve to `any`. The only workaround is pre-building every
workspace package with `svelte-package` to generate .d.ts files,
adding an unnecessary build step to the type-checking workflow.

This commit addresses the limitation by automatically detecting
dependency packages that contain .svelte files without companion
.d.ts declarations and generating virtual declarations for them
using svelte2tsx, the same way the app's own .svelte files are
already handled.

How it works:
- Scans direct dependencies (via package.json + node_modules) for
  .svelte files that lack a companion .svelte.d.ts
- Runs svelte2tsx on each to emit virtual .ts/.d.ts into the
  existing .svelte-check cache directory
- Adds rootDirs entries pairing each package's real path with its
  emit directory, so tsc/tsgo can find the declarations

Known limitations / areas for improvement:
- Currently processes ALL .svelte files in each dependency package,
  not just the ones actually imported by the app. A smarter approach
  would trace the import graph to only process referenced files.
- This is an initial approach and may benefit from refinement based
  on real-world feedback from different monorepo setups.
@changeset-bot
Copy link

changeset-bot bot commented Mar 2, 2026

⚠️ No Changeset found

Latest commit: 75ae82c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@jasonlyu123
Copy link
Member

Thanks for the contribution. Have you tried this, or is this all AI-generated? I don't think your solution works. Adding the generated directory to rootDir doesn't change how TypeScript resolve the package. You'll have to add it to the paths config as well. Another problem is that because these Svelte files are no longer under the same directory, TypeScript can't resolve the imports to node_modules in these files.

@datstarkey
Copy link
Contributor Author

So i did test this on my particular use case and it did solve the problem, but not the core underlying problem.

In my particular case I have a monorepo with a couple of frontend sveltekte projects, some in static adatper, 2 with node adapter etc.

All these share 2 projects in a worksace repo, for api and shared.

so the project structure is like

packages

  • app1 (static),
  • app2 (static),
  • app3 (node),
  • api (pure typescript no svelte)
  • shared (shared svelte component library)

Each has its own tsconfig and svelte.config.js

then at root i have a pnpm-workspace.yaml with a catalogue for all the same package versions across them.
The main issue i was trying to get around was longer svelte check times, my shared library has 20 different export sub paths and 400ish components.

the shared project is also dependant on the api project bla bla bla. so i had a turbo repo setup with a svelte package build step for the shared package, whcih worked but was a a bit slow, about 6-8 minutes for the whole build.
The pipeline did have check on shared, and each individual app, and using the new --tsgo and --incremental options shaved of a considerable amount of time.

I then conisidered taking out the build step entirely, which shaved it down to less than 2 minutes for the entire build (no cache) stack, using barreled sub path exports directly.

	"exports": {
		"./components": {
			"types": "./src/lib/components/index.ts",
			"svelte": "./src/lib/components/index.ts"
		},
	}

this resulted in the builds working fine, but check with --incremental or --tsgo would fail on the any types and lots of Error: Module '"*.svelte"' has no exported member 'blablabla'.

this resolves the typing issue by pulling it and storing the .d.ts, however it doesnt give errors on the shared files, it jsut allows us to keep the types for use in our frontend app. a check still needs to be run on the shared package for proper svelte diagnostics.

I have a replication here: https://github.com/datstarkey/svelte-check-monorepo-repro
This shows that the types are resolved to any with the current --tsgo/--incremental svelte check issue, and this fix here gives the correct typings (and errors)

tbh its wholey possible im using a monorepo wrong in this case, I did try with project references and paths aswell but couldn't get anything to work so gave this a bash instead.

@jasonlyu123
Copy link
Member

jasonlyu123 commented Mar 3, 2026

Sorry, but I didn't see any difference. Both the current 4.4.4 and your fix reports 0 errors when there should be errors.

@datstarkey
Copy link
Contributor Author

Did you build the svelte check in the cloned repo?

reporting 0 errors is the bug as its parsing it as any.

when i use the file version i get the actual error:

image

running on macOS pnpm version 10.28.2

@datstarkey
Copy link
Contributor Author

Actually, i just deleted my node modules and .svelte-kit folders and tried again and now it saying 0 errors, will have a look at this and why it worked the first time for me

@datstarkey
Copy link
Contributor Author

datstarkey commented Mar 3, 2026

OKAY, you need to run svelte-kit sync so the .svelte-check folder is inside .svelte-kit

thats a bug ill fix in the PR, but ive updated the repro to run sync first then check which shows it working atleast for now, may neeed to delete the .svelte-check folder first

EDIT:
doesn't need to be fixed, it's because the sync creates the tsconfig so without it the type checking doesnt work anyways. the correction solution is to sync before checking in the repro case

@jasonlyu123
Copy link
Member

Yeah, I did build svelte-check in the submodule and have run svelte-kit sync. It still shows 0 errors. I am currently testing it on Windows. I could try it on Linux later. However, judging from the code change, I just don't get why it would work.

@datstarkey
Copy link
Contributor Author

Ill check on my windows machien later aswell.

I updaetd the repro to check (no --tsgo) and check:go (using --tsgo) in app one (using the fix) and app two (using old).

for me in app two, there are 3 errors with no tsgo and no errors with tsgo
and app one, there is 1 error with both correctly

@jasonlyu123
Copy link
Member

jasonlyu123 commented Mar 3, 2026

Ok. It does work on Linux, so there's probably a bug on Windows. I think the reason it works is:

  1. typescript resolve @repro/shared/components to packages/shared/src/lib/components/index.ts
  2. resolves ./Button.svelte using rootDirs to packages/shared/src/lib/components/Button.svelte

This means if you have a ahared package that also has lib/components/Button.svelte. The second step will instead resolve to packages/ahared/src/lib/components/Button.svelte. Not really ideal. It can be solved by rerouting it through the paths config. But now that I think about it, the paths config menas typescript will ignore the export map. So it might not be ideal either. There is also the problem I mentioned earlier: packages/shared/src/lib/components/Button.svelte importing some-other-library.

I am not really sure if we should dig more into this rabbit hole 😅 I think if there isn't a good enough solution, I would prefer not do this and wait for TypeScript team to come up with a proper API.

@datstarkey
Copy link
Contributor Author

yeah in perspective this is a super hacky workaround, and if its not working on windows. I'll close this in favour of an offical ts api, the real approach here is to have a build step with svelte pacakge, as thats still faster than running the non incremental/tsgo svelte check.

@datstarkey datstarkey closed this Mar 3, 2026
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