core icon indicating copy to clipboard operation
core copied to clipboard

Adding `generic=` to `<script setup>` makes it impossible to export types from SFC

Open alexchexes opened this issue 7 months ago • 3 comments

Vue version

3.5.16

Link to minimal reproduction

https://github.com/alexchexes/vue-test-generic-export/blob/master/src/App.vue

Steps to reproduce

  1. Add generic="T" attribute to the <script setup> (<script setup lang="ts" generic="T">)
  2. Add a type export, e.g. export type MyType = 123, within that script setup scope
  3. Observe the Modifiers cannot appear here. ts(1184) TypeScript error in VSCode
  4. Remove generic="T" and the error disappears, confirming the issue is related to the generic="T" attribute

Reproduction:

<script setup lang="ts" generic="T">
defineProps<{
  items: string[]
  selected: string
}>()
export type MyType = 123 // ← TS Error: Modifiers cannot appear here. ts(1184)
</script>

What is expected?

No error should occur when exporting types after adding generic="T" to <script setup>

What is actually happening?

A Modifiers cannot appear here. ts(1184) TypeScript error appears as soon as generic="T" is added to <script setup>, but no error occurs when it is removed

System Info

System:
    OS: Windows 11 10.0.26100
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12700H
    Memory: 32.97 GB / 63.67 GB
  Binaries:
    Node: 22.11.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.22 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 10.9.0 - C:\Program Files\nodejs\npm.CMD
    pnpm: 9.12.3 - ~\AppData\Roaming\npm\pnpm.CMD
  Browsers:
    Edge: Chromium (130.0.2849.80)
  npmPackages:
    vue: ^3.5.13 => 3.5.16

Any additional comments?

If this is a restriction of generic components, we should mention it in the generics section of the <script setup> docs

alexchexes avatar May 29 '25 20:05 alexchexes

When we use generic, we should assume that all the code in the script block is within the scope of the function.

You can move the type declarations with export modifier to the top to avoid this situation.

KazariEX avatar Jun 02 '25 14:06 KazariEX

You can move the type declarations with export modifier to the top to avoid this situation

Unlike the code in the example, in the real code the use case is to export the type of whatever emit() returns. I mean something like this (real code):

const emit = defineEmits<{
  /** emitted in case of any error (usually only network errors occurs) */
  'error': [error: unknown];
  /** emitted after suggestion is selected, whether by clicking on a suggestion in the dropdown, by pressing "Enter" or by auto-selecting when `selectOnBlur=true` */
  'select': [suggestion: DadataSuggestion, selectType: SelectType];
  /** emitted after selected suggestion was enriched in case `enrichOnSelect` props is `true` */
  'enriched': [suggestion: DadataSuggestion, diff: DeepPartial<DadataSuggestion> | null];
  /** emitted if attemp to enrich selected suggestion failed (in case `enrichOnSelect` props is `true`) */
  'enrichFail': [unrestricted_value: string];
  /** emitted whenever input is focused */
  'focus': [event: FocusEvent];
  /** emitted whenever input looses focus */
  'blur': [event: FocusEvent];
}>();
export type VueDadataEmits = typeof emit;

So we can pass emit to the composable and emit from there without using any any. What is the recommended approach in this case?

alexchexes avatar Jun 02 '25 14:06 alexchexes

Firstly, ensure that the type of the emit is independent of generic type parameters; Then it is best to use some type helpers to explicitly infer the type of emit in a non setup script block and export it.

KazariEX avatar Jun 02 '25 15:06 KazariEX