Skip to content

fix(emitDts): drop declarations emitted outside declarationDir#2965

Open
joeinnes wants to merge 3 commits intosveltejs:masterfrom
joeinnes:fix-emit-dts-src-pollution
Open

fix(emitDts): drop declarations emitted outside declarationDir#2965
joeinnes wants to merge 3 commits intosveltejs:masterfrom
joeinnes:fix-emit-dts-src-pollution

Conversation

@joeinnes
Copy link

@joeinnes joeinnes commented Mar 2, 2026

Fixes #2182

Problem

When a Svelte component imports a TypeScript module from outside the configured libRoot, TypeScript follows the import and attempts to emit a declaration file for that module. Because emitDts forces rootDir: config.libRoot, the imported file is outside rootDir, and TypeScript falls back to emitting its declaration using the file's absolute path as the output location — rather than placing it inside declarationDir. This pollutes the source tree with unwanted .d.ts files.

A second related bug compounds this: the writeFile hook unconditionally prepended pathPrefix (a relative path) to TypeScript's output filename using path.join. When TypeScript generated an absolute output path for the out-of-rootDir file, path.join appended the relative prefix to it, doubling the path and writing files deep inside a fake directory tree within the source tree (as reported in #2182).

Reproduce

A project where:

  • libRoot (or -i) points to a subdirectory (e.g. src/)
  • A component in that directory imports a .ts file from outside it (e.g. ../utils/helper.ts)
  • emitDts (or svelte-package) is run

Result: a declaration file for the imported module appears outside declarationDir, either next to the source or at a doubled absolute path inside the source tree.

Fix

Two changes to the writeFile hook in createTsCompilerHost:

  1. Guard pathPrefix with !path.isAbsolute(fileName) — only prepend the relative prefix when TypeScript has produced a relative output path. Fixes the doubled-path write (Emit dts not respecting abs path #2182).

  2. Drop .d.ts/.d.ts.map writes outside declarationDir — after resolving the final output path, silently skip any declaration file whose destination is not within declarationDir. This prevents out-of-rootDir imports from emitting declarations into the source tree.

Tests

Added a new emitDts sample (typescript-imports-outside-lib-root) where a Svelte component imports from outside libRoot. The test runner was extended to assert that no .d.ts files appear outside declarationDir after emitDts completes, and to clean up any such files (including newly-created directory trees) if the assertion fails.

joeinnes added 2 commits March 2, 2026 12:02
…arationDir

Adds a sample where a Svelte component imports a TypeScript module from
outside libRoot, triggering TypeScript to emit declarations outside the
configured declarationDir.

Also extends the test runner to detect and clean up any .d.ts files
written outside declarationDir, asserting that none are produced.
When a Svelte component imports TypeScript files from outside the
configured libRoot, TypeScript follows those imports and tries to emit
declarations for them. Because those files are outside the forced
rootDir, TypeScript falls back to emitting declarations using their
absolute path as the output location rather than placing them inside
declarationDir.

Two related issues are fixed in the writeFile hook:

1. Only prepend pathPrefix when fileName is not already absolute. The
   previous unconditional path.join caused the absolute output path to
   be appended to pathPrefix, doubling the path and writing files deep
   inside the source tree.

2. Silently drop any .d.ts or .d.ts.map write whose resolved destination
   falls outside declarationDir. This prevents declarations for
   out-of-rootDir imports from polluting the source directory.

Resolves: declarations written outside declarationDir when source files
are imported from outside libRoot.
@changeset-bot
Copy link

changeset-bot bot commented Mar 2, 2026

⚠️ No Changeset found

Latest commit: 22444fc

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

Can you provide a reproduction repository? I don't quite understand the problem you're trying to fix.

joeinnes added a commit to joeinnes/emitdts-repro that referenced this pull request Mar 11, 2026
Demonstrates sveltejs/language-tools#2182,
fixed by sveltejs/language-tools#2965.

Library maintainers using svelte-package with Svelte components that
import TypeScript from outside libRoot find stray .d.ts files written
into their source tree. Includes the cleanup workaround script that
projects like jazz-tools are currently forced to maintain.
@joeinnes
Copy link
Author

Sure, so for context, I'm a maintainer over at garden-co/jazz, where we're building a database product with a bunch of different capabilities. We offer Svelte bindings to allow users to build their Jazz apps with Svelte. We're running into an annoying issue where we need to run a clean-up step every time we build our repo, because the .d.ts files emitted during the Svelte bindings build get spat out into the source directory.

This repro should demo it: https://github.com/joeinnes/emitdts-repro

Clone, install deps, and run the build script (pnpm or npm run build). You'll see the .d.ts file appear at src/runtime/db.d.ts, not in dist/lib where you'd expect/want it to be.

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.

Emit dts not respecting abs path

2 participants