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

fix(docs): extend vue 2 info regarding types #750

Merged
merged 3 commits into from Mar 6, 2022
Merged

fix(docs): extend vue 2 info regarding types #750

merged 3 commits into from Mar 6, 2022

Conversation

rchl
Copy link
Collaborator

@rchl rchl commented Nov 30, 2021

I know this puts the instructions upside-down since you've been suggesting to remove Vue.extend while I've changed it to recommend Vue.extend. The rationale is that, based on testing and as stated in the comments in #741, Vue.extend is really the way to go to fix the inevitable type issues in Vue 2 projects and I haven't really found any downsides to it.

If you have any specific examples where Vue.extend would makes things worse type-wise then I can tweak the wording or change it completely.

Note that when working with Vetur, I'm used to having to help the type system in many situations by adding manual JSDoc comments (when using JS) so I would expect this to be the case with Volar also. But that shouldn't change the fact that Vue.extend is required in the first place.

Related #741

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Dec 1, 2021

If you don't care about the component props / events type-check and auto-complete in template, Vue.extend is fine. Otherwise you need to migrate to defineComponent from @vue/composition-api to support these features.

Could you mention this?

@rchl
Copy link
Collaborator Author

rchl commented Dec 1, 2021

If you don't care about the component props / events type-check and auto-complete in template,

I'm trying to say that this already works. Check the simple https://github.com/rchl/volar-vue2-test project for example.

I know that using Composition API works better in terms of type support but I just can't understand why do you keep saying that Vue.extend breaks auto-complete in the template. This is simply not true. I'd need you to explain this clearly before I adjust the text.

EDIT: I've also added a simple method which requires annotation since the argument can't be inferred automatically.

@johnsoncodehk
Copy link
Member

Sorry for not making it clear, I am talking about cross-component props auto-complete and type-checking.

It should have auto-complete for :test.
螢幕截圖 2021-12-04 上午2 15 18

It should have type error for :test.
螢幕截圖 2021-12-04 上午2 16 05

@rchl
Copy link
Collaborator Author

rchl commented Dec 3, 2021

But that's not related to whether someone is using Vue.extend or not. It doesn't work even when not using Vue.extend on both index.vue and Tutorial.vue.
EDIT: edited as my initial comment wasn't entirely correct.

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Dec 3, 2021

Without Vue.extend it should works. But actually I planned to abandon support for cross-component props type-checking with export default { ... }, so I hope remove mentioned export default { ... } in this pr.

image

So user only have 3 option in future:

  • export default Vue.extend({ ... }): support type-check to <script> and <template>, but not support cross-component type-check.
  • export default defineComponent({ ... }): support type-check to <script> and <template> and cross-component
  • class component: support type-check to <script> and <template> and cross-component with additional code. (Methods not found when using mapGetters #21)

@rchl
Copy link
Collaborator Author

rchl commented Dec 3, 2021

Seems to be working with Vue.extend also. (I've pushed this change to the repo so you can try yourself)

Screenshot 2021-12-03 at 20 27 21

So to conclude:

  • cross-component props type-checking seems to be working with both Vue.extend and without
  • Type-checking within the component requires Vue.extend to work properly.

With that said, is there anything you want me to change in this PR?

@johnsoncodehk
Copy link
Member

@rchl emmmm it's vetur report. :S

@rchl
Copy link
Collaborator Author

rchl commented Dec 3, 2021

🤦 Sorry. That's my default setup and forgot to switch for testing.

But now that I've switched to Volar, it doesn't seem to work with either export default {} or export default Vue.extend({})`.

Screenshot 2021-12-03 at 20 33 34

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Dec 3, 2021

Maybe Tutorial.vue is still export default Vue.extend({ ... }). If you change Tutorial.vue from export default Vue.extend({ ... }) to export default { ... }

@rchl
Copy link
Collaborator Author

rchl commented Dec 3, 2021

No, you can try yourself on the project. Pushed Vue.extend removal

@johnsoncodehk
Copy link
Member

Sorry, also need lang="ts".

@rchl
Copy link
Collaborator Author

rchl commented Dec 3, 2021

OK, can reproduce that case.

That complicates things since now we have more variables to potentially document.

As far as using plain JS, Vue.extend still seems like a preferred option.

As far as TS, are you saying that it will work only with Vue.extend in the future (based on your comment)? In which case, doesn't my point still stands that using Vue.extend is recommended? Or do you mean that it won't be supported at all. And in that case, only for TS or also JS?

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Dec 5, 2021

I think we don’t need to advise users what to use, just state the facts, which methods are supported and which are not.

Component options type-checking in <script> Interpolation type-checking in <template> Cross-component props type-checking
export default { ... } with JS Not support Not support Not support
export default { ... } with TS Not support Support but planed to abandon in future Support but planed to abandon in future
export default Vue.extend({ ... }) with JS Not support Not support Not support
export default Vue.extend({ ... }) with TS Limited (Support data types but not support props types) Limited Not support
export default defineComponent({ ... }) Support Support Support
Class component Support Support with additional code (#21) Support with additional code

@rchl
Copy link
Collaborator Author

rchl commented Dec 5, 2021

Is this a typo?

Screenshot 2021-12-05 at 20 35 08

Vue.extend is exactly what is supposed to make those work.

@johnsoncodehk
Copy link
Member

Vue.extend only working with js but not working with ts on my tests.

You can try:

// test-js.js
import Vue from 'vue'

Vue.extend({
	data: () => ({
		foo: 123
	}),
	mounted() {
		this.foo // any
	},
})
// test-ts.ts
import Vue from 'vue'

Vue.extend({
	data: () => ({
		foo: 123
	}),
	mounted() {
		this.foo // number
	},
})

@rchl
Copy link
Collaborator Author

rchl commented Dec 6, 2021

I would swear that JS was working before but it doesn't seem like it. Maybe I was confused due to my Vetur running again...

In that case I would say that Options API Vue2 support is not in a state that is usable in a real world projects so maybe it shouldn't be supported/advertised at all?

Having to add Vue.extend everywhere is one major obstacle. I don't think that it's feasible to do that everywhere in an existing, potentially large projects.

The other issue is that type checking is spotty at best so many of the issues wouldn't be caught. Vetur is not ideal either, mind you, but certainly more complete.

@johnsoncodehk
Copy link
Member

I would swear that JS was working before but it doesn't seem like it. Maybe I was confused due to my Vetur running again...

I think this has not relate to vetur/volar because it is reproduced in vanilla js/ts.

In that case I would say that Options API Vue2 support is not in a state that is usable in a real world projects so maybe it shouldn't be supported/advertised at all?

With defineComponent from @vue/composition-api I think the support for vue2 is already quite good.

Having to add Vue.extend everywhere is one major obstacle. I don't think that it's feasible to do that everywhere in an existing, potentially large projects.

Users only need to continue to use vetur. If they don't want to modify the code, they don't have to switch to volar.
btw the method I recommend is defineComponent instead of Vue.extend.

The other issue is that type checking is spotty at best so many of the issues wouldn't be caught. Vetur is not ideal either, mind you, but certainly more complete.

Are you talking about using Vue.extend? If yes, it is because vetur has done some work outside of Vue.extend to support more types. But like I said, doing so destroys predictability, so Volar won't do it. The component type will be completely controlled by the vue library. If Vue.extend is missing the props type, the user should not get the props type in the template, otherwise it is a bug for volar.

@rchl
Copy link
Collaborator Author

rchl commented Dec 6, 2021

With defineComponent from @vue/composition-api I think the support for vue2 is already quite good.

But I'm talking about the Options API specifically. Can definedComponent be used with Options API? If so then I'm fine with just including that info the readme (the table you've posted).

@johnsoncodehk
Copy link
Member

Can definedComponent be used with Options API?

Yes, it is comprehensively better than Vue.extend.

@yangHeavy
Copy link

I think we don’t need to advise users what to use, just state the facts, which methods are supported and which are not.

Component options type-checking in <script> Interpolation type-checking in <template> Cross-component props type-checking
export default { ... } with JS Not support Not support Not support
export default { ... } with TS Not support Support but planed to abandon in future Support but planed to abandon in future
export default Vue.extend({ ... }) with JS Not support Not support Not support
export default Vue.extend({ ... }) with TS Limited (Support data types but not support props types) Limited Not support
export default defineComponent({ ... }) Support Support Support
Class component Support Support with additional code (#21) Support with additional code

hello, i have seen this property table for prop type check.
First, i am Very exciting volar supports the vue2.0 type check.
But what about vue-property-decorator use @prop ? Can it have the type check and auto-complete? It seems belong to class component type.
See the example in this repository: https://github.com/MAG-Christian-Birkl/volar-vue2-test-mag
Although I have add static components: { HelloWorld: typeof HelloWorld }, it still failed with type check.

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Jan 28, 2022

@yangHeavy it's a example what you can define in class component for volar.

export default class Foo extends Vue {
  static components: {
    MyComp: typeof MyComp
  }
  $props!: {
    optionalProp?: string;
    requiredProp: string;
  }
  $emit!: {
    (e: 'my-event-1'): void
    (e: 'my-event-2', arg1: string, arg2: number): void
  }
  $scopedSlots!: { // $scopedSlots for vue2, $slots for vue3
    'my-slot-1': () => VNode[]
    'my-slot-2': (bindings: { arg1: string, arg2: number }) => VNode[]
  }
}

@yangHeavy
Copy link

yangHeavy commented Jan 28, 2022

@johnsoncodehk Thanks for answer for how to use class component with volar. But there some problems too.

  1. I have to use declarations, otherwise I get an error
    image
  2. so vue-property-decorator use @prop can't check?

@johnsoncodehk
Copy link
Member

@yangHeavy yes you need to define it repeatedly. decorator == no TS support :)

@yangHeavy
Copy link

yangHeavy commented Jan 28, 2022

@johnsoncodehk I see. But there are some problems when I have defined the variable.
I define it string, use it with number, but volar doesn't get an error.
image

@johnsoncodehk
Copy link
Member

@yangHeavy in this case you need to define $props!: { what?: string } additionally.

@yangHeavy
Copy link

@johnsoncodehk Sorry, I found it. I try it success!! I will share Volar with my colleagues and weigh whether we need to use variable type checking in cases where we need to define it twice. Thank you very much.

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Jan 28, 2022

@yangHeavy as a workaround, you could shim the component type to auto define $props.

In this workaround, you need to write readonly additionally to distinguish this is a prop not component data.

@Component({})
class HelloWorld extends Vue {
  @Prop() readonly requiredProp!: string;
  @Prop() readonly optionalProp?: string;
  nonProp?: string;
}

export default HelloWorld as ShimComponentProps<typeof HelloWorld>;

/** equal to
@Component({})
class HelloWorld extends Vue {
  @Prop() readonly requiredProp!: string;
  @Prop() readonly optionalProp?: string;
  nonProp?: string;
  $props!: {
    requiredProp: string,
    optionalProp?: string
  }
}
 */

helper types:

type ShimComponentProps<T extends new (...args: any) => any> = new () => InstanceType<T> & {
  $props: ComponentProps<InstanceType<T>>
};

type ComponentProps<T> =
  { [K in keyof Pick<T, ReadonlyKeys<T> & OptionalKeys<T>>]?: T[K] }
  & { [K in keyof Pick<T, ReadonlyKeys<T> & RequiredKeys<T>>]: T[K] };

type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

type ReadonlyKeys<
  T,
  U extends Readonly<T> = Readonly<T>,
  K extends keyof T = keyof T
  > = K extends keyof T ? Equal<Pick<T, K>, Pick<U, K>> extends true ? K : never : never;

type OptionalKeys<T> = keyof {
  [K in keyof T as T[K] extends Required<T>[K] ? never : K]: T[K]
};

type RequiredKeys<T> = keyof {
  [K in keyof T as T[K] extends Required<T>[K] ? K : never]: T[K]
};

@yangHeavy
Copy link

yangHeavy commented Jan 29, 2022

@johnsoncodehk ShimComponentProps is a nice typescript coding example! But it still has some problems after i test it.
image
ShimComponentProps doesn't extends Vue! And this will cause @components() can't identify HelloWorld.
image

I think the reason is interface Vue has readonly attr also. And it is set to $props too.
image

rchl added 2 commits March 5, 2022 23:12
* master: (134 commits)
  Document recursive components (#1008)
  refactor: move common TS logic to new package `@volar/vue-typescript` (#1004)
  chore: update pnpm-lock.yaml
  v0.32.1
  chore: changelog
  feat: show vite app preview config file
  fix: vite icon do not show with first editor
  fix: document symbols confusion between `<script>` and `<script setup>`
  feat: support generic props events
  fix: pug last child tag mapping incorrect
  chore: set minimal vscode version to 1.61.0
  fix: auto-import cache logic break langauge server if TS version < 4.4
  fix: exclude recursive component type if duplicate name with import components
  chore: update pnpm-lock.yaml
  fix: `source.organizeImports` not working in `editor.codeActionsOnSave`
  fix: slot name mapping range incorrect
  v0.32.0
  chore: changelog
  feat: experimental webview features
  fix: don't pack extension with `--minify` flag which break scss formatting
  ...
@rchl
Copy link
Collaborator Author

rchl commented Mar 5, 2022

I've applied your suggestion with some minor changes. Let me know if it looks better now.

@johnsoncodehk
Copy link
Member

LGTM, thank you!

@hexf00
Copy link

hexf00 commented May 18, 2022

@yangHeavy it's a example what you can define in class component for volar.

export default class Foo extends Vue {
  static components: {
    MyComp: typeof MyComp
  }
  $props!: {
    optionalProp?: string;
    requiredProp: string;
  }
  $emit!: {
    (e: 'my-event-1'): void
    (e: 'my-event-2', arg1: string, arg2: number): void
  }
  $scopedSlots!: { // $scopedSlots for vue2, $slots for vue3
    'my-slot-1': () => VNode[]
    'my-slot-2': (bindings: { arg1: string, arg2: number }) => VNode[]
  }
}

In vue2 project, the type of scope parameter is deduced as vnode [] | undefined, I want to know what happened in the middle, or does it not have this feature?

@hexf00
Copy link

hexf00 commented May 18, 2022

@yangHeavy it's a example what you can define in class component for volar.

export default class Foo extends Vue {
  static components: {
    MyComp: typeof MyComp
  }
  $props!: {
    optionalProp?: string;
    requiredProp: string;
  }
  $emit!: {
    (e: 'my-event-1'): void
    (e: 'my-event-2', arg1: string, arg2: number): void
  }
  $scopedSlots!: { // $scopedSlots for vue2, $slots for vue3
    'my-slot-1': () => VNode[]
    'my-slot-2': (bindings: { arg1: string, arg2: number }) => VNode[]
  }
}

In vue2 project, the type of scope parameter is deduced as vnode [] | undefined, I want to know what happened in the middle, or does it not have this feature?

https://github.com/johnsoncodehk/volar/tree/master/extensions/vscode-vue-language-features

I followed the steps prompted in the document and found that the type overload of Vue needs to be deleted. The slot parameter type prompt of volar can take effect

@aziztitu
Copy link

aziztitu commented Oct 12, 2022

@johnsoncodehk I was able to follow your instructions and get the type checking for Props and ScopedSlots working, but for the Emit, it seems like there might be a type mismatch.

image

In the Vue types package, it looks like $emit expects a Function type.

image

Should we override a different variable other than $emit instead?

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Oct 12, 2022

@aziztitu Vue 2 do not support typed emit, if you do want to, you can try omit $emit from Vue interface.

@aziztitu
Copy link

aziztitu commented Oct 12, 2022

@johnsoncodehk Gotcha. I was able to at least get the Event suggestions working by hacking it like this:

image
image

This stops any errors from being thrown, but the only issue is there's no actual type checking happening. It seems to think that all events now take 2 args :D
image

But it probably is just due to the fact that it's using the args from the last match. I can just remove the args from the Emit type, and live with it for now :)

@johnsoncodehk
Copy link
Member

@aziztitu please track #1855

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants