Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript: Exported members from Svelte modules cannot be detected from Typescript files #5817

Closed
bryanmylee opened this issue Dec 23, 2020 · 15 comments

Comments

@bryanmylee
Copy link

bryanmylee commented Dec 23, 2020

Describe the bug
When using Typescript, exported properties from a component's context="module" script can be imported in another .svelte file, but cannot be imported in a regular .ts file.

<!-- Page.svelte -->
<script lang="ts" context="module">
  export const version = '1.0';
</script>
<!-- App.svelte -->
<script lang="ts">
  import { version } from './Page.svelte'; // works fine
</script>
// index.ts
import { version } from './Page.svelte'; // [tsserver 2614] [E] Module '"*.svelte"' has no exported member 'version'. Did you mean to use 'import version from "*.svelte"' instead?

Logs

[tsserver 2614] [E] Module '"*.svelte"' has no exported member 'version'. Did you mean to use 'import version from "*.svelte"' instead?

To Reproduce
Issue does not exist with Javascript Svelte, and cannot be reproduced on https://svelte.dev/repl.

Issue can be reproduced in this repository.

https://github.com/bryanmylee/svelte-ts-module-export

It is still possible to compile the code with Rollup despite the Typescript errors, however this breaks the build step when using Sapper and Webpack with Typescript.

Expected behavior
Import should work even from .ts file.

Stacktraces
On a Sapper project with Webpack and Typescript:

✗ server /Users/bryan/Projects/Programs/bryanmylee.github.io/src/node_modules/@my/components/PageTransitions/index.ts ./src/node_modules/@my/components/PageTransitions/index.ts [tsl] ERROR in /Users/bryan/Projects/Programs/bryanmylee.github.io/src/node_modules/@my/components/PageTransitions/index.ts(3,10) TS2614: Module '"*.svelte"' has no exported member 'version'. Did you mean to use 'import version from "*.svelte"' instead? ✗ client /Users/bryan/Projects/Programs/bryanmylee.github.io/src/node_modules/@my/components/PageTransitions/index.ts ./src/node_modules/@my/components/PageTransitions/index.ts [tsl] ERROR in /Users/bryan/Projects/Programs/bryanmylee.github.io/src/node_modules/@my/components/PageTransitions/index.ts(3,10) TS2614: Module '"*.svelte"' has no exported member 'version'. Did you mean to use 'import version from "*.svelte"' instead?

Information about your Svelte project:

  System:
    OS: macOS 11.0.1
    CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
    Memory: 454.03 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 14.4.0 - ~/.nvm/versions/node/v14.4.0/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 6.14.9 - ~/.nvm/versions/node/v14.4.0/bin/npm
  Browsers:
    Chrome: 87.0.4280.88
    Edge: 87.0.664.66
    Firefox: 81.0.1
    Safari: 14.0.1
  npmPackages:
    svelte: ^3.17.3 => 3.31.0
    typescript: ^4.0.3 => 4.1.3
    webpack: ^4.7.0 => 4.44.2

Severity
Medium severity.

I can workaround this by moving context="module" exports to a Typescript file instead. However, it does break my design pattern of putting all sub-components of a component in a folder, and re-exporting the main component and its interfaces via a index.ts file.

@dummdidumm
Copy link
Member

It's only the typing that's wrong here, you are able to use the imports like you want.

Reason: Svelte files are declared like this in the Svelte type definitions:

declare module '*.svelte' {
    export { SvelteComponentDev as default } from 'svelte/internal';
}

This means "Dear TypeScript, if you see a file ending with .svelte in .ts or .js files, use that definition". That definition does not contain other exports, so TypeScript says "that is wrong", even if it's right in your case.
To fix it, you can provide your own declaration inside a .d.ts file:

declare module '*.svelte' {
    export { SvelteComponentDev as default } from 'svelte/internal';
    export const version: string;
    // ... other stuff
}

@Conduitry maybe we could relax the module declaration and just do declare module '*.svelte';. Everything is typed as any then - the disadvantage is that we no longer have a typed default export then. Maybe someone else knows how to get the best of both worlds.

@bryanmylee
Copy link
Author

That clears it up a lot, thank you.

I would love to contribute somehow to developing a Typescript version of the documentation, or a journal of best practices with Typescript + Svelte. I’m beginning to feel bad using the issue tracker so much to discover Typescript + Svelte features.

@bryanmylee
Copy link
Author

bryanmylee commented Dec 23, 2020

This actually reminds me of #442 on sveltejs/language-tools. Basically figuring out a better way to get support for advanced Component definitions such as generics.

@thebestgin
Copy link

thebestgin commented Mar 5, 2021

I have spent several hours on this problem.
It is a pity that there is no clean and simple solution for such a primitive problem.
I don't like the idea with an extra file (.d.ts), because I have to map my logic in it or lose type safety.

The following solution works in most of my use cases.
Maybe it also helps you.

  1. Create a file with extension '.svelte' like
    'some-file.ts.svelte'
  2. Wrap your js code like
<script context="module" lang="ts">
  export const foo:string = "bar";
</script>
  1. Import it in another js file or svelte file like
    import { foo } from "./js/some-file.ts.svelte";

@dummdidumm
Copy link
Member

As stated above, this is a "TypeScript interop with non-TypeScript-files" issue. Solving it for VS Code required writing a dedicated TypeScript plugin, which is now available. More info here: sveltejs/language-tools#580 (comment)
Closing in favor of that linked issue as this is not directly related to Svelte, rather the tooling around it.

@NickDarvey
Copy link

@bryanmylee, I don't suppose you have any insights as to why this works with rollup but not webpack?

@dummdidumm
Copy link
Member

When using webpack or rollup with Svelte it's best to turn off their type checker and use svelte-check instead, as it is has knowledge of the whole program, not just TypeScript files. The alternative is to use the workaround I described above #5817 (comment)

@NickDarvey
Copy link

NickDarvey commented Jun 30, 2021

Okay, I guess I misinterpreted the closing of this issue.

When using webpack or rollup with Svelte it's best to turn off their type checker and use svelte-check

I'll try this approach. Thanks @dummdidumm.

@Alexandre-Fernandez
Copy link

When using webpack or rollup with Svelte it's best to turn off their type checker and use svelte-check instead, as it is has knowledge of the whole program, not just TypeScript files. The alternative is to use the workaround I described above #5817 (comment)

How do you turn off type cheking from the rollup config ? if you remove @rollup/plugin-typescript the app will not build.

I use typescript-svelte-plugin (via Svelte VS Code extension) and it works well when coding.
However when building I get the following error (from @rollup/plugin-typescript in the terminal) when trying to export a class from a .svelte component module context into a .ts file (store) :

(!) Plugin typescript: @rollup/plugin-typescript TS2614: Module '"*.svelte"' has no exported member 'Product'. Did you mean to use 'import Product from "*.svelte"' instead?

src/stores/products.ts: (2:10)

2 import { Product } from "../components/Product.svelte"

@dummdidumm
Copy link
Member

You can make the build ignore errors (it will still show them but not abort) https://github.com/rollup/plugins/tree/master/packages/typescript/#noemitonerror

@Alexandre-Fernandez
Copy link

Alexandre-Fernandez commented Feb 22, 2022

Thanks, since typescript-svelte-plugin can resolve the modules correctly do you know if there is a way for rollup to do so ?

@dummdidumm
Copy link
Member

Unfortunately there's no way. A plugin like typescript-svelte-plugin only works for the IDE, the TS team explicitly disallows usage of plugins in the CLI, so you can't tell rollup to use it.

@Alexandre-Fernandez
Copy link

Alexandre-Fernandez commented Feb 22, 2022

So basically, as I understand it, the only way for now is to ignore warnings or to manually redeclare all types inside a .d.ts.
I hope svelte finds a solution to this problem, pretty annoying to have 100+ warnings every time you save a file (this also will make real errors harder to spot).
Maybe make svelte create the .d.ts type declaration files automatically for each svelte component that has exports in its module context ?
That doesn't look impossible to me, then again I don't know in detail how svelte works under the hood.
I don't understand why this issue is closed though... EDIT: that's why I opened another one #7304

pavish added a commit to mathesar-foundation/mathesar that referenced this issue Apr 27, 2022
… type defintions. Moves types from component module to ts files.

Reasoning: Unit tests fail when types are imported in a ts file from a component directly. See sveltejs/svelte#5817.
@achamas-playco
Copy link

For anyone still stuck with this problem, or Svelte modules and TypeScript in general, I use this approach which works fine.

For a given Svelte component which requires module context (eg. as a Singleton to share with other parts of the application), simply create a supplementary TypeScript file at the same file location. This is your module context, an actual TypeScript module. That way the Svelte component can import those shared values and read/write, just as any other part of the application can.

So for example for MyComponent.svelte, create a file at the same project location called MyComponent.ts and export any shared const or types/interfaces from there. Your structure would look something like:

/src
   /components
      MyComponent.svelte
      MyComponent.ts     <--- module context

This is essentially what the context="module" script directive is trying to achieve, so no harm in doing it with plain old TypeScript.

Yes, it adds extra files, however if you keep the filenames the same and keep them both in the same project location it isn't really a big problem. Considering that most Svelte components don't need module context, and for the ones which do it's technically easier for other devs to know that the .ts companion file is going to provide shared types or objects.

christianhugoch added a commit to christianhugoch/ANovokmet-svelte-gantt that referenced this issue Jul 18, 2023
- buildfix
- updated 'rollup-plugin-typescript2' for: 'Unknown object type "asyncfunction"
- added type file for: has no exported member
  sveltejs/svelte#5817 (comment)
@curiousdannii
Copy link

curiousdannii commented Oct 10, 2024

@Rich-Harris commented #7304 (comment)

I'm going to close this as there's really nothing we can do about it. The solution is to avoid importing named bindings from .svelte files into .js and .ts modules — in general this will mean putting those exports in .js/.ts files in the first place.

<script context="module"> should be considered an escape hatch, not a place to put a meaningful amount of logic that other parts of your app depend on.

Unfortunately nothing in the docs suggests that there are any gotchas involved with context=module, or that it should be avoided if possible.

Can we at the very least treat this as a documentation issue? It seems that there is some important info that is not clearly stated anywhere:

  1. Whether context=module exports can be used outside of Svelte? (Depends on your build bundler, and not, for example, in bare tsc.)
  2. Whether svelte-check checks all files or just .svelte files?
  3. What svelte-preprocess actually enables? For example, it seems to allow use of Svelte with tsc without any other bundler, but it doesn't provide full type exports for everything, so you may need to run tsc --noCheck

I've hobbled together something that works mostly, but it's hard to know what I've missed.

For my case at least, there's the simple solution of importing constants into Svelte, rather than exporting them to TS.

Edit: I've made a PR suggesting a minimal docs change for 1.

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

No branches or pull requests

7 participants