Closed Otto-J closed 1 year ago
any news..?
Is this issue still resolved?
+1 for needing this also. I try to wrap all components, especially third-party libraries so I need to be able to export their interfaces into the wrapped components.
Any news?
@jnarowski here is a little workaround which might work for you if you want to get Intellisense for props from an imported interface on your wrapper. Tested in VsCode with Volar.
Vue will not create props for properties on the imported interface but we still can pass them through with $attrs and then provide Intellisense through our own interface which extends the imported one.
<template>
<LibraryComponent v-bind="$attrs" />
</template>
<script setup lang="ts">
...
// has to be inside the wrapper component
// vue will not know about the properties inside LibraryProps that's why we have to pass them by $attrs
interface MyProps extends LibraryProps {
// a prop that we add on top of LibraryProps
myProp?: string
}
const props = defineProps<MyProps>()
...
If we want to add a default value for props from the imported interface we can add only those to our interface. Props for those will be created as usually and we can supply default values.
One downside at the moment is, that Volar will complain about the props on our interface not being defined in the template section of the wrapper but it's just an IDE issue.
<template>
<LibraryComponent
v-bind="$attrs"
:aLibraryProp="aLibraryProp" <-- our default value
/>
...
<script setup lang="ts">
...
// has to be inside the wrapper component
// vue will not know about the properties on LibraryProps that's why we have to pass them with $attrs
interface MyProps extends LibraryProps {
// a prop that we want to give a default value
aLibraryProp: string
// a prop that we add on top of LibraryProps
myProp?: string
}
const props = withDefaults(defineProps<MyProps>(), {
aLibraryProp: 'defaultValue'
}
...
It's a messy workaround but for wrapping library components which come with many props it's still useful.
hi, any news about this? people with vite has a temporary solution with the plugin, but what about vue-cli?
@TuringJest thank you for the explanation!
I basically did the same thing in my project with Quasar UI but had to learn it the hard way. I also use inheritAttrs: false
to make sure that $attrs
will be passed to the right child component and bind props
to the child component as well because binding only $attrs
is not enough sometimes.
For e.g.
<script setup lang="ts">
import { QBtnProps } from 'quasar';
// ✨ Types
export interface LBtnProps extends QBtnProps {
someNewProp?: string
}
// 🚧 Props
const props = withDefaults(
defineProps<LBtnProps>(), { someNewProp: 'someDefaultValue' }
);
...
</script>
<script lang="ts">
export default {
inheritAttrs: false
};
</script>
<template>
...
<QBtn
v-bind="{ ...$attrs, ...props }"
...
/>
...
</template>
Any news?
It's painful to reuse the props interface :weary: any satisfied solution?
Any news?
Note: Please stop posting messages like "any news?" If there is an update, you will see it here. No updates means no updates. Spamming the same message over and over again will not help it get resolved earlier.
Question: Is it possible to force webpack to import and then paste the type before compiling?
For example:
code
<script lang="ts" setup>
import { SharedProps } from "./sharedProps.ts";
interface Props extends SharedProps {}
const props = defineProps<Props>(); // The type props.foo can be inferred to be a string
</script>
compile before SFC (it is like webpack.DefinePlugin):
<script lang="ts" setup>
// import { SharedProps } from "./sharedProps.ts";
// auto copy and paste:
interface SharedProps {
foo: string;
bar?: number;
}
interface Props extends SharedProps {}
const props = defineProps<Props>(); // The type props.foo can be inferred to be a string
</script>
Maybe that will be better. vscode plugin or vite plugin ?
<script lang="ts" setup>
import { SharedProps } from "./sharedProps.ts";
interface Props extends SharedProps {
// auto copy and paste:
foo: string;
bar?: number;
}
const props = defineProps<Props>(); // The type props.foo can be inferred to be a string
</script>
Component:
Types:
All working:
Add custom loader
{
test: /\.vue$/,
use: {
loader: path.resolve(__dirname, './WebpackLoaderVueSFCTypes.js'),
},
},
And write loader (WebpackLoaderVueSFCTypes.js):
const fs = require('fs');
const path = require('path');
const regExpDefineProps = /defineProps<(\w+)>\(\)/;
function getRegExpImportDefault(typeName) {
return new RegExp(`import type ${typeName} from ['"](.*?)['"]`);
}
function getTypeName(source) {
return source.match(regExpDefineProps)?.[1];
}
function getTypePath(typeName, source, vueFilePath) {
if (!typeName) {
return null;
}
// todo need to support `import { Type } from` and multilines
const filePath = source.match(getRegExpImportDefault(typeName))?.[1];
// todo need to support aliases like `@/`
const fullPath = path.resolve(
path.dirname(vueFilePath),
filePath.endsWith('.ts') ? filePath : `${filePath}.ts`,
);
if (!fs.existsSync(fullPath)) {
console.warn(new Error(`Not found file ${fullPath}`));
return null;
}
return fullPath;
}
function getTypesFromFile(typeName, typePath) {
// todo better will analysis ts file, without regexp or replace
return fs.readFileSync(typePath)
.toString()
.replace(/import.*?;.*/g, '')
.replace(/export.*/g, '');
}
function WebpackLoaderVueSFCTypes(source) {
const typeName = getTypeName(source);
const typePath = getTypePath(typeName, source, this.resource);
if (!typeName || !typePath) {
return source;
}
return source.replace(
getRegExpImportDefault(typeName),
getTypesFromFile(typeName, typePath),
);
}
module.exports = WebpackLoaderVueSFCTypes;
Notice: I stopped developing the loader because the types in the component are comfortable. I need the ability to export from component
Ha! I found a better solution! And Webstorm is understand it!
function WebpackLoaderVueSFCTypes(source) {
if (!source.includes('export type')) {
return source;
}
return source.replaceAll(
'export type',
'type',
);
}
module.exports = WebpackLoaderVueSFCTypes;
Component:
Storybook (can import styles from vue component):
Import component to other component is supported types
@AndreiSoroka what if step type is a complex object instead a Number ?
@aislanmaia
hi
for SFC comiler it is not important (link)
P.s. I provided two solutions
I only use the second option. The first option needs to be improved. The first option is more of a working concept (I don't want to support it).
P.s.s. Will there be any feedback from the core team? Thanks
when will it be supported ??😂
based on above comments, I got it working by extending the imported interface in the setup script (typescript)
import { PaginationOptions } from '~~types`
interface PaginationProps extends PaginationOptions { }
defineProps<PaginationProps>() // OR with default values, use below
withDefaults(defineProps<PaginationProps>(), { [YOUR DEFAULT VALUES] })
I just observed this does not work, it only takes away the error, and the props don't get passed down using withDefaults
Because I ran into this problem* today, but with defineEmits
, I am boosting the search-engine signal for defineEmits as this seems to be the overarching issue tracking a single solution for both.
*to clarify, like others I've tried to extract Type literals for use in defineProps
and defineEmits
from a set of related components and put it along other composables.
As a workaround, the value can be used instead of the type definition
❌As follows I encapsulated an input component with a label
<script setup lang="ts">
import { ElInput } from "element-plus";
import type { InputEmits, InputProps } from "element-plus";
defineProps<
InputProps & {
label?: string;
}
>();
defineEmits<InputEmits>();
</script>
<template>
<div class="w-input">
{{ label }}
<el-input v-bind="$attrs">
<template #[slotName]="slotProps" v-for="(slot, slotName) in $slots">
<slot :name="slotName" v-bind="slotProps" />
</template>
</el-input>
</div>
</template>
The following type error was thrown while referencing
Type '{}' is not assignable to type 'IntrinsicAttributes & Partial<{}> & Omit<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ readonly type: string; readonly disabled: boolean; readonly modelValue: EpPropMergeType<(new (...args: any[]) => (string | number | null | undefined) & {}) | (() => string | ... 2 more ... | undefined) | ((new (...args...'.
Type '{}' is missing the following properties from type 'Omit<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ readonly type: string; readonly disabled: boolean; readonly modelValue: EpPropMergeType<(new (...args: any[]) => (string | number | null | undefined) & {}) | (() => string | number | null | undefined) | ((new (...args: any[]) => (string | ... 2 more ... ...': type, disabled, modelValue, autosize, and 11 more.
✅Below is no error and has correct type hints
<script setup lang="ts">
import { ElInput, inputEmits, inputProps } from "element-plus";
defineProps({
...inputProps,
label: String,
});
defineEmits(inputEmits);
</script>
<template>
<div class="w-input">
{{ label }}
<el-input v-bind="$attrs">
<template #[slotName]="slotProps" v-for="(slot, slotName) in $slots">
<slot :name="slotName" v-bind="slotProps" />
</template>
</el-input>
</div>
</template>
OR
<script lang="ts">
import { defineComponent } from "vue";
import { inputProps, inputEmits } from "element-plus";
export default defineComponent({
props: {
...inputProps,
label: String,
},
emits: inputEmits,
});
</script>
<template>
<div class="w-input">
{{ label }}
<el-input v-bind="$attrs">
<template #[slotName]="slotProps" v-for="(slot, slotName) in $slots">
<slot :name="slotName" v-bind="slotProps" />
</template>
</el-input>
</div>
</template>
If you are using Vite, you can use this plugin https://github.com/wheatjs/vite-plugin-vue-type-imports as a patch until this is officially supported.
This is great, thank you!!
When wrapping a third party component, I want to expose its props.
import { Modal } from "@arco-design/web-vue";
// does not work
// defineProps<InstanceType<typeof Modal>["$props"]>();
// works
export type ModalProps = InstanceType<typeof Modal>["$props"];
export interface Props extends ModalProps {}
defineProps<Props>();
If you are using Vite, you can use this plugin https://github.com/wheatjs/vite-plugin-vue-type-imports as a patch until this is officially supported.
That's all for now🫡
As someone coming from a strong React + Typescript team I must saying not being able to have shareable reusable types across components is quite a let down. Especially when it comes to things like being able to directly use swagger schema generated Typescript definitions for which I've done a lot of in the past. Is there anyway to help contribute to resolve this?
Just for the record, Evan You told it would be addressed in Vue 3.3.
Do you have a release plan?
I found some hack. Currently, it works for me. Just wrap imported type with Omit
.
<script setup lang="ts">
import { Button as AButton, ButtonProps } from "ant-design-vue";
interface Props extends Omit<ButtonProps, ""> {}
const props = defineProps<Props>();
</script>
<template>
<AButton v-bind="props">
<slot />
</AButton>
</template>
I found some hack. Currently, it works for me. Just wrap imported type with
Omit
.<script setup lang="ts"> import { Button as AButton, ButtonProps } from "ant-design-vue"; interface Props extends Omit<ButtonProps, ""> {} const props = defineProps<Props>(); </script> <template> <AButton v-bind="props"> <slot /> </AButton> </template>
I can approve that this works 👀 dafuq But it makes my code 1 line longer in comparison to the prev workaround + I get a eslint warning for an empty interface 🙈 I would still prefer an official solution
I found some hack. Currently, it works for me. Just wrap imported type with
Omit
.<script setup lang="ts"> import { Button as AButton, ButtonProps } from "ant-design-vue"; interface Props extends Omit<ButtonProps, ""> {} const props = defineProps<Props>(); </script> <template> <AButton v-bind="props"> <slot /> </AButton> </template>
I can approve that this works 👀 dafuq But it makes my code 1 line longer in comparison to the prev workaround + I get a eslint warning for an empty interface 🙈 I would still prefer an official solution
Yes! I agree with you. It's more like a temporary solution...
I found some hack. Currently, it works for me. Just wrap imported type with
Omit
.<script setup lang="ts"> import { Button as AButton, ButtonProps } from "ant-design-vue"; interface Props extends Omit<ButtonProps, ""> {} const props = defineProps<Props>(); </script> <template> <AButton v-bind="props"> <slot /> </AButton> </template>
If I use this solution and withDefaults, I get a bizarre error:
Unexpected "}"
13 | setup(__props: any, { emit: emits }) {
14 |
15 | const props = __props as }
| ^
16 |
17 | ;
Many thanks to Evan You and the rest of the community for developing and maintaining Vue, but I'm sorry I have to comment because this issue has been around for a year and I don't mean to rush you to fix it, I just want to know if there's a temporary solution, thanks again for your efforts.
Many thanks to Evan You and the rest of the community for developing and maintaining Vue, but I'm sorry I have to comment because this issue has been around for a year and I don't mean to rush you to fix it, I just want to know if there's a temporary solution, thanks again for your efforts.
Have you seen this https://github.com/vuejs/core/issues/4294#issuecomment-984033739 ?
Many thanks to Evan You and the rest of the community for developing and maintaining Vue, but I'm sorry I have to comment because this issue has been around for a year and I don't mean to rush you to fix it, I just want to know if there's a temporary solution, thanks again for your efforts.
Have you seen this #4294 (comment) ?
@soerenmartius Thank you very much for your reply! I really didn't notice this comment because there were so many comments in this issue that I didn't read them carefully, this looks like a viable solution, thank you again!😊
i tried most of these workarounds, how it's not supported yet? the vite plugin works but it's a little bit frustrating when facing this issue
I found that we can used it this way:
<script
setup
lang = "ts">
import type {Foo} from 'bar'
const props = withDefaults(defineProps<{
foo : Foo
}>(), {
foo: () => {
return {
fooProperty1: default1
}
}
})
</script>
Basically, by declaring a property on the prop which can have the required Typing.
Maybe it's silly solution, but adding not fully working plugins to vite is not an option for me. So in meantime until Vue 3.3 will be ready I just define types used in props in component and export them. Then this type is imported in types.ts
and reexported. This way I still import all types elsewhere from 'types.ts', so when vue 3.3 will be ready, I just move definitions to that file and the rest of the code stays untouched.
In FooField.vue
:
<script setup lang="ts">
export type FooFieldProps = {
foo: string
bar?: string
}
defineProps<FooFieldProps>()
</script>
then in types.ts
:
export type { FooFieldProps } from './FooField.vue'
👍Good wit
None of the proposed workarounds really worked for all uses cases but it really makes things easier to pass around the prop types to call some shared functions. I don't have a ton of props so I just ended up declaring them twice, once as an interface and once as a PropType. To keep the maintenance of this redundant code easier I came up with these helpers that keep the two in sync:
import {PropType} from "vue";
// copied and slightly modified from vue core
declare type Data = Record<string, unknown>;
declare type DefaultFactory<T> = (props: Data) => T | null | undefined;
type OptionalRequired<T> = undefined extends T ? { required?: false } : { required: true };
declare interface PropOptionsWorkaroundRequired<T = any, D = T> {
type?: PropType<T> | true | null;
default?: D | DefaultFactory<D> | null | undefined | object;
validator?(value: unknown): boolean;
}
// convert interface properties to property options
type PropsWorkaround<T> = {
[Key in keyof T]-?: PropOptionsWorkaroundRequired<T[Key]> & OptionalRequired<T[Key]>
}
to be used like this
interface InterfaceType {
foo: string
}
export interface PropsInterface {
text: string,
textOptional?: string,
someNumber?: number,
interface: InterfaceType
}
export const props_from_interface: PropsWorkaround<PropsInterface> = {
text: {type: String, required: true},
textOptional: {type: String},
someNumber: {type: Number},
interface: {type: Object as PropType<InterfaceType>, required: true}
}
// and somewhere else
import {props_from_interface} from "file"
const props = defineProps(props_from_interface);
Of course it is less than ideal to have redundant code but at least this way props_from_interface will not compile if any properties from PropsInterface are not implemented. And it is easy enough to add a new property in two places instead of just one until a fix lands in vue.
when this type will be used in other files.
// types.ts
interface Test {
a: string;
}
// component.vue
<script lang="ts" setup>
import type { Test } from 'types';
type Props<T extends unkown> = {
[P in keyof T]: T[P];
}
defineProps<Props<Test>>()
</script>
// other.ts
import type { Test } from 'types';
// ...
Any progress on a real fix for this issue?
Any progress on a real fix for this issue?
AFAIK this is on the roadmap for the vue 3.3 release
2022.09.21 这个问题依然存在🙈
Will the fix for this get back ported to 2.7?
We keep our composables and components separate (makes testing VERY easy). It would make for much cleaner code if the prop type and emit types were declared in the composable file.
Example of what we do:
So, the typescript in Vue3.x is a fake concept;
I first found the props
concept can work in Vue3.x along with ts
that way, which make me feel like home at first glance;
For example, in foo.vue;
import { defineProps } from 'vue'
interface Props {
foo: string;
bar?: number;
}
const props = defineProps<Props>();
However; while I am trying make a little step over, like called my own interface existed in another Props.ts file.
export interface Props {
foo: string;
bar?: number;
}
Then, import it to make everything more clear;
import { defineProps } from 'vue'
import Props from '@/utils/Props'
const props = defineProps<Props>();
Vue3.x shows everything just fucks up here:
type argument passed to defineProps() must be a literal type, or a reference to an interface or literal type.
@Mon-ius The entire discussion is about just that.
Interfaces work until you put them in a different file ant try to import... Tried with type import regular import with export default with export nothing helps always:
[vite] Internal server error: [@vue/compiler-sfc] type argument passed to defineProps() must be a literal type, or a reference to an interface or literal type. src/components/TheContent.vue 2 | import type WizardSteps from "@/interfaces/WizardSteps"; 3 |
4 | defineProps(); | ^^^^^^^^^^^ 5 | 6 |
Plugin: vite:vue src/components/TheContent.vue at error (node_modules/.pnpm/@vue+compiler-sfc@3.2.40/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js:3589:15) at processDefineProps (node_modules/.pnpm/@vue+compiler-sfc@3.2.40/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js:3631:17) at Object.compileScript (node_modules/.pnpm/@vue+compiler-sfc@3.2.40/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js:4105:17) at resolveScript (file://node_modules/.pnpm/@vitejs+plugin-vue@3.1.0_vite@3.1.4+vue@3.2.40/node_modules/@vitejs/plugin-vue/dist/index.mjs:266:31) at genScriptCode (file:///node_modules/.pnpm/@vitejs+plugin-vue@3.1.0_vite@3.1.4+vue@3.2.40/node_modules/@vitejs/plugin-vue/dist/index.mjs:2346:18) at transformMain (file:///node_modules/.pnpm/@vitejs+plugin-vue@3.1.0_vite@3.1.4+vue@3.2.40/node_modules/@vitejs/plugin-vue/dist/index.mjs:2167:54) at TransformContext.transform (file:///node_modules/.pnpm/@vitejs+plugin-vue@3.1.0_vite@3.1.4+vue@3.2.40/node_modules/@vitejs/plugin-vue/dist/index.mjs:2642:16) at Object.transform (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:41103:44) at async loadAndTransform (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:37365:29)
Maybe it's silly solution, but adding not fully working plugins to vite is not an option for me. So in meantime until Vue 3.3 will be ready I just define types used in props in component and export them. Then this type is imported in
types.ts
and reexported. This way I still import all types elsewhere from 'types.ts', so when vue 3.3 will be ready, I just move definitions to that file and the rest of the code stays untouched.In
FooField.vue
:<script setup lang="ts"> export type FooFieldProps = { foo: string bar?: string } defineProps<FooFieldProps>() </script>
then in
types.ts
:
export type { FooFieldProps } from './FooField.vue'
I get for export
modifier, when it is using setup attribute in script:
TS1184: Modifiers cannot appear here.
I guess I'll stick with duplication of interfaces for time being, and later on will move them into their files. 🙂
Also there is problem with alias in vite.config.ts
and tsconfig.json
, it does not work:
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
import { VitePluginFonts } from 'vite-plugin-fonts'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@/': path.resolve(__dirname, './src')
},
},
plugins: [
VitePluginFonts({
google: {
preconnect: true,
display: 'swap',
injectTo: 'head-prepend',
families: [
{
name: 'Montserrat',
styles: 'ital,wght@0,400;0,800;1,200',
defer: true,
},
],
},
}),
vue()
]
})
and tsconfig.json
:
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"baseUrl": "src",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
Output:
Failed to resolve import "@/components/TheHeader.vue" from "src/App.vue". Does the file exist?
4:54:40 PM [vite] Internal server error: Failed to resolve import "@/components/TheHeader.vue" from "src/App.vue". Does the file exist?
Plugin: vite:import-analysis
File: src/App.vue
1 | import { defineComponent as _defineComponent } from "vue";
2 | import TheHeader from "@/components/TheHeader.vue";
| ^
3 | import TheContent from "@/components/TheContent.vue";
4 | const _sfc_main = /* @__PURE__ */ _defineComponent({
at formatError (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:40854:46)
at TransformContext.error (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:40850:19)
at normalizeUrl (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:37587:33)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async TransformContext.transform (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:37720:47)
at async Object.transform (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:41103:30)
at async loadAndTransform (file:///node_modules/.pnpm/vite@3.1.4/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:37365:29)
update: please see:
7394
https://github.com/vuejs/core/issues/4294#issuecomment-1316097560
Version
3.2.1
Reproduction link
https://github.com/Otto-J/vue3-setup-interface-errors/blob/master/src/components/HelloWorld.vue
Steps to reproduce
clone: git clone start: yarn && yarn dev open: master/src/components/HelloWorld.vue modify: import interface from './types'
What is expected?
no error
What is actually happening?
[@vue/compiler-sfc] type argument passed to defineProps() must be a literal type, or a reference to an interface or literal type.
in chinese: 我想把 props的interface抽出去,但是会报错,如果interface组件里定义的就正常渲染 in english: I want to extract the interface of props, but an error will be reported. If the interface component is defined, it will render normally