Valid typescript when using defineEmits() results in bad code transformation during compile
Version
3.2.30
Reproduction link
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\esbuild@0.13.15\node_modules\esbuild\lib\main.js:1493:15)
at C:\Work\scolasta2\node_modules\.pnpm\esbuild@0.13.15\node_modules\esbuild\lib\main.js:1282:29
at C:\Work\scolasta2\node_modules\.pnpm\esbuild@0.13.15\node_modules\esbuild\lib\main.js:629:9
at handleIncomingPacket (C:\Work\scolasta2\node_modules\.pnpm\esbuild@0.13.15\node_modules\esbuild\lib\main.js:726:9)
at Socket.readFromStdout (C:\Work\scolasta2\node_modules\.pnpm\esbuild@0.13.15\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.
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
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
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"
@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.
@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?https://github.com/vuejs/core/issues/5393#issuecomment-1034335331
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