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

Valid typescript when using defineEmits() results in bad code transformation during compile #5393

Closed
nborko opened this issue Feb 9, 2022 · 8 comments · Fixed by #5394
Closed
Labels
🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. scope: compiler scope: script-setup

Comments

@nborko
Copy link

nborko commented Feb 9, 2022

Version

3.2.30

Reproduction link

sfc.vuejs.org/

Steps to reproduce

In a <script setup lang="ts"> block, this causes an error during compilation:

<script setup lang="ts">
import { defineEmits } from 'vue'

interface Emits {
  (e: 'foo'): void
}
 
const emit: Emits = defineEmits(['foo'])
</script>

From vite:

  Expected "}" but found ":"
  9  |  const _sfc_main = /*#__PURE__*/_defineComponent({
  10 |    emits: ['foo'],
  11 |    setup(__props, { expose, emit: emit: Emits }) {
     |                                       ^
  12 |    expose();
  13 |  
  
      at failureErrorWithLog (C:\Work\scolasta2\node_modules\.pnpm\[email protected]\node_modules\esbuild\lib\main.js:1493:15)
      at C:\Work\scolasta2\node_modules\.pnpm\[email protected]\node_modules\esbuild\lib\main.js:1282:29
      at C:\Work\scolasta2\node_modules\.pnpm\[email protected]\node_modules\esbuild\lib\main.js:629:9
      at handleIncomingPacket (C:\Work\scolasta2\node_modules\.pnpm\[email protected]\node_modules\esbuild\lib\main.js:726:9)
      at Socket.readFromStdout (C:\Work\scolasta2\node_modules\.pnpm\[email protected]\node_modules\esbuild\lib\main.js:596:7)
      at Socket.emit (node:events:390:28)
      at addChunk (node:internal/streams/readable:315:12)
      at readableAddChunk (node:internal/streams/readable:289:9)
      at Socket.Readable.push (node:internal/streams/readable:228:10)
      at Pipe.onStreamRead (node:internal/stream_base_commons:199:23)

What is expected?

It should compile

What is actually happening?

Doesn't compile


This looks like a transformation error in the vue compiler, since the following works fine as a workaround:

const emit = defineEmits(['foo']) as Emits

But the above code is valid typescript and shouldn't result in what is being generated.

@nborko
Copy link
Author

nborko commented Feb 10, 2022

const emit = defineEmits(['foo']) as Emits

Ah, the above doesn't work either, it results in a runtime error, "ReferenceError: defineEmits is not defined"

I can leave it untyped as a workaround, but since in my actual implementation the Emits interface is NOT contained in the SFC file, I need a way to type check the usage of the emit function.

EDIT:
The following suboptimal method is my current workaround that seems to work:

const rawEmit = defineEmits(['foo'])
const emit: Emits = rawEmit

@edison1105
Copy link
Member

you should use type declaration

const emit = defineEmits<Emits>()

@nborko
Copy link
Author

nborko commented Feb 10, 2022

you should use type declaration

const emit = defineEmits<Emits>()

Ideally, but

in My actual implementation the Emits interface is NOT contained in the SFC file

@nborko nborko closed this as completed Feb 10, 2022
@nborko nborko reopened this Feb 10, 2022
@nborko
Copy link
Author

nborko commented Feb 10, 2022

I hate using GitHub on mobile... Didn't mean to close

@posva posva added the 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. label Feb 16, 2022
@NickBelhomme
Copy link

I have this too in version 3.2.31

No interface available. I do not agree that it is a "feature" or a "nice to have"

@NickBelhomme
Copy link

@nborko how could you work around the problem with
const emit: Emits = rawEmit

as that still needs the Emits type which does not exist...
Thanks in advance.

@ygj6
Copy link
Member

ygj6 commented Feb 21, 2022

@nborko how could you work around the problem with const emit: Emits = rawEmit

as that still needs the Emits type which does not exist... Thanks in advance.

You're looking for this?#5393 (comment)

@nborko
Copy link
Author

nborko commented Feb 21, 2022

As a quick explanation of my setup, I have a subdirectory for each component with the following structure:

// types.ts

// heavily documented with tsdoc
interface MyComponentProps {
    ...
}

// heavily documented with tsdoc
interface MyComponentEmits {
    ...
}

// synthetic type for api-extractor
// tsdoc style documentation for the actual component
interface MyComponent {
    props: MyComponentProps
    emits: MyComponentEmits
}
<comment><!-- MyComponent.vue --></comment>

<script setup lang="ts">
import { MyComponentEmits, MyComponentProps } from './types'

const props: MyComponentProps = defineProps({
    ...
})

// This is broken
const emit: MyComponentEmits = defineEmits([ ... ])

// some functionality
</script>
// index.ts

export { default as MyComponent } from './MyComponent.vue'

This helps keep the documentation and types separated from the SFC to keep the code clean and easier to maintain, and I point api-extractor to a top level types.ts that imports and re-exports the types from each of the components, for further processing into API documentation.

EDIT: Updated the sample synthetic interface for clarity

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. scope: compiler scope: script-setup
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants