In vue 3 composition API im trying to do the following:
<script setup lang="ts">
import { computed } from "vue";
// vue doesnt like this line (the export seems to be the issue)
export interface ButtonItemProp extends ButtonData, Styling {}
const props = withDefaults(defineProps<ButtonItemProp>(), {
type: "button",
...
});
...
But i keep getting the following error:
Unexpected "}"
28 | expose();
29 |
30 | const props = __props as };
| ^
31 |
32 |
As soon as i add a fake property to ButtonItemProp as such:
export interface ButtonItemProp extends ButtonData, Styling {
fake_property: boolean;
}
Then it works fine...
Interestingly I have just found that that without the export, it works fine so I have had to do this instead further down:
export type { ButtonItemProp };
Wondering if someone could explain whats going on with Vue when I am exporting it...?
The entire <script setup lang="ts">
will be processed by vue sfc compiler first into a typescript module, and afterwards this module is forwarded to typescript compiler for compilation. The error you saw is reported by typescript compiler, implying that vue sfc compiler generated typescript module has syntax errors.
Long story short, the error is related to how defineProps
is handled, and the minimal setup to reproduce the exact syntax error is (i.e. empty interface body):
<script setup lang="ts">
interface ButtonItemProp {}
const props = withDefaults(defineProps<ButtonItemProp>(), {});
</script>
SFC compiler yields:
import { defineComponent as _defineComponent } from 'vue'
interface ButtonItemProp {}
export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose }) {
expose();
const props = __props as };
return { props }
}
})
In contrast:
<script setup lang="ts">
interface ButtonItemProp { type: string }
const props = withDefaults(defineProps<ButtonItemProp>(), {});
</script>
yields:
import { defineComponent as _defineComponent } from 'vue'
interface ButtonItemProp { type : string }
export default /*#__PURE__*/_defineComponent({
props: {
type: { type: String, required: true }
},
setup(__props: any, { expose }) {
expose();
const props = __props as { type : string };
return { props }
}
})
Note that how const props = __props as { type : string };
always cast props to a type literal.
This will also work:
interface ButtonData { type: string }
interface ButtonItemProp extends ButtonData {}
But the below wouldn't and it is the bug:
interface ButtonData { type: string }
export interface ButtonItemProp extends ButtonData {}
Basically Vue believes ButtonItemProp now has an empty body.
Some Details
The transformation into typescript module does not involve typescript compiler, and is done completely by vue with the help of babel-parser. Basically vue invokes babel-parser to produce an AST of the <script setup>
body, and then vue walks the AST to generate the desired ts module content.
defineProps
is a special construct that participates in this stage - in other words, it is almost like a callback to be invoked by the compiler in order to generate a better transpiled result. In particular, defineProps
gives the compiler hints to perform ts type inference so that the generated ts module can have the props
variable properly typed. This process of inferring the props type is also done by vue (not by typescript compiler) based on the babel-parser generated AST.
It is easy to imagine how the process roughly works:
defineProps
is presented with a
TSTypeParameterInstantiation (<ButtonItemProp>
) being a TSTypeReference (ButtonItemProp
)ButtonItemProp
ButtonItemProp
(through its TSIntefaceDeclaration) extends ButtonData
ButtonData
props
.Now, the difference that export
makes is that it changes the AST node type of export interface ButtonItemProp extends ...
: instead of TSIntefaceDeclaration, it will emit node ExportNamedDeclaration (the actual TSIntefaceDeclaration is its child node).
Vue compiler has a bug here that it only performs the extends
resolution for TSIntefaceDeclaration, not for ExportNamedDeclaration.