Skip to content

--tsgo: .svelte files imported through package.json imports (e.g. Kit 3 #lib/*) resolve to the ambient *.svelte shim #3142

Open

Description

@Bewinxed

Describe the bug

With --tsgo, a .svelte file imported through a package.json imports (subpath import) alias, such as SvelteKit 3's #lib/*, resolves to the ambient declare module "*.svelte" shim instead of the emitted .svelte.ts overlay. Named exports from <script module> then fail with TS2614, and component props become any, so every callback parameter reports TS7006. The same component imported by a relative path in the same file is typed correctly, and the default mode (no --tsgo) reports nothing.

This looks like the package.json#imports counterpart of #2952 (fixed for tsconfig paths in #2955). SvelteKit 3 replaced $lib (a paths alias) with #lib (a subpath import), so every Kit 3 app that imports components as #lib/…​.svelte hits it. In our app (556 .svelte files, Kit 3.0.0) --tsgo reports 103 false errors and the default mode reports 0.

Reproduction

package.json:

{
  "name": "svelte-check-tsgo-subpath-imports",
  "private": true,
  "type": "module",
  "imports": { "#lib/*": "./src/lib/*" },
  "devDependencies": {
    "svelte": "5.57.1",
    "svelte-check": "4.7.6",
    "typescript": "npm:@typescript/typescript6@6.0.2",
    "@typescript/native": "npm:typescript@7.0.2"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true,
    "types": ["svelte"]
  },
  "include": ["src/**/*.ts", "src/**/*.svelte"]
}

src/lib/Counter.svelte:

<script module lang="ts">
  export const STEP = 1;
</script>

<script lang="ts">
  let { label, onpick }: { label: string; onpick: (value: number) => void } = $props();
</script>

<button onclick={() => onpick(STEP)}>{label}</button>

src/App.svelte:

<script lang="ts">
  import Counter, { STEP } from "#lib/Counter.svelte";
  import Relative, { STEP as RELATIVE_STEP } from "./lib/Counter.svelte";
  let total = $state(STEP + RELATIVE_STEP);
</script>

<Counter label="via #lib" onpick={(value) => (total += value)} />
<Relative label="relative" onpick={(value) => (total += value)} />
<p>{total}</p>

Then:

$ npx svelte-check --tsconfig ./tsconfig.json
svelte-check found 0 errors and 0 warnings

$ npx svelte-check --tsconfig ./tsconfig.json --tsgo
src/App.svelte:2:21
Error: Module '"*.svelte"' has no exported member 'STEP'. Did you mean to use 'import STEP from "*.svelte"' instead? (ts)

src/App.svelte:7:36
Error: Parameter 'value' implicitly has an 'any' type. (ts)

svelte-check found 2 errors and 0 warnings in 1 file

Both errors are on the #lib import and its usage (lines 2 and 7). The relative import of the same file (lines 3 and 8) is clean.

--tsgo-experimental-api handles this case correctly (0 errors), so only the overlay-tsconfig --tsgo / --incremental path is affected.

Expected behaviour

The same result as the default mode: subpath-import specifiers that map to a .svelte file resolve to its emitted overlay, the way relative and paths-alias specifiers do after #2955.

System Info

  • svelte-check 4.7.6, svelte 5.57.1
  • typescript 6.0.2 (via @typescript/typescript6), @typescript/native = typescript 7.0.2
  • Node 26.10.0, Linux x64

Which package is the issue about?

svelte-check

Additional Information, eg. Screenshots

From reading incremental.ts, it looks like the overlay tsconfig only rebases compilerOptions.paths (rebasePathsConfig). A package.json imports target such as ./src/lib/* is resolved by TypeScript straight to the source .svelte file, which never matches an emitted .svelte.ts, so resolution falls through to the ambient module.

Activity

  1. jasonlyu123 commented on Oct 15, 2026

    @jasonlyu123
    Member

    I think we won't fix this. Once content mapper support is out with TypeScript 7.1. The overlay way won't be needed anymore. So it doesn't really make sense to spend the time fixing this at this point.

  2. TheEisbaer commented on Oct 16, 2026

    @TheEisbaer

    Another data point, same symptoms with --incremental (no --tsgo):

    • svelte-check 4.7.6, TypeScript 6.0.3, @sveltejs/kit 3.1.0, svelte 5.57.2, moduleResolution: "bundler"
    • package.json: "imports": { "#lib/*": "./src/lib/*" }, no kit.alias
    • Script: svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --incremental

    Without a tsconfig paths entry: 18 errors in 9 files. All of them are .svelte files imported through #lib/* falling back to the ambient *.svelte shim:

    • TS2614 Module '"*.svelte"' has no exported member 'PromptFormResult'
    • TS7006 Parameter 'e' implicitly has an 'any' type on component callback props
    • an untyped SvelteComponent

    No Cannot find module errors. Adding "paths": { "#lib/*": ["./src/lib/*"] } to tsconfig.json brings it to 0 errors, so the overlay picks up paths but not imports.

    Related: Kit's get_paths (packages/kit/src/core/sync/write_tsconfig/index.js) and the $app/tsconfig docs say the generated paths include package.json imports, but the code reads only kit.alias. If it did what the docs say, this workaround wouldn't be needed with --incremental either.

    Since #3142 is marked won't-fix pending TS 7.1, the tsconfig paths entry is a working stopgap for anyone else on Kit 3 with --incremental.

  3. jasonlyu123 commented on Oct 16, 2026

    @jasonlyu123
    Member

    I can take a look If anyone can find a fix. Just don't give me 500+ lines of AI generated test script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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