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

Support TypeScript in vue template #1359

Closed
xiaoxiangmoe opened this issue Jun 14, 2020 · 35 comments
Closed

Support TypeScript in vue template #1359

xiaoxiangmoe opened this issue Jun 14, 2020 · 35 comments

Comments

@xiaoxiangmoe
Copy link
Contributor

What problem does this feature solve?

Support TypeScript expression in template

What does the proposed API look like?

<template>
  <h1>{{ msg }}</h1>
  <button @click="(count as number)++">count is: {{ (count as number) +  1 }}</button>
  <p>
    Edit <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'HelloWorld',
  setup: (props: { readonly msg: string }) => {
    const { msg } = props;
    const count = ref(0 as unknown);
    return { msg, count };
  },
});
</script>
@pikax
Copy link
Member

pikax commented Jun 16, 2020

This will add quite more complexity to the template code, the solution for your problem would be better type inference in the template, with tools such as vetur and others.

@xiaoxiangmoe
Copy link
Contributor Author

xiaoxiangmoe commented Jun 16, 2020

@pikax
I guess we are not talking about the same thing. I am using @vuedx, which has better vue 3 support and type inference than vetur. But I found that if we need to write more complex types, we need support ts expression in template.

@Eldar-X
Copy link

Eldar-X commented Jun 17, 2020

I am also dreaming about ts support in templates. For example it's my favorite part of angular especially when you have large and complex project and need to change something, you mostly don't look into templates, you just change script part and never know if your template part is broken :(

@CyberAP
Copy link
Contributor

CyberAP commented Jun 18, 2020

Vue supports runtime template compilation. Bringing support for TypeScript in templates would mean that you'll get different levels of template support for pre-compiled and post-compiled applications, which in result would complicate things for developers and maintainers. In the example above you could provide better type safety by using methods or computeds instead of inline expressions.

@yyx990803
Copy link
Member

This is technically doable:

  1. We use @babel/parser to parse inline expressions, which already comes with TypeScript support for free. I tested by adding typescript to the parser plugin list and your example compiles just fine.

  2. We need to add a pass to strip type annotations, which can be done with esbuild so that will still be reasonably fast.

Obviously this won't work for the in-browser build, but runtime and build-time compilations already have different levels of support.

Still, I'll need to play with this and @vuedx to see whether the benefit justifies the extra cost.

@limingzhiguang
Copy link

I think use computed or methods

@phroggyy
Copy link

I think there is an alternate use case here which is usage of syntax that is in TS but not necessarily in the ES standard yet. Null coalescing and the elvis operator are both things I feel are missing.

However, I don't think a blanket "support TS in templates" might be the best solution. The ideal solution would be if we could pass expressions through the same build system as the script section, which would ensure you can use all the same features in templates as in the script. I'm not familiar with the template compiler and it seems like this might be problematic though.

@johnsoncodehk
Copy link
Member

Recent I got some issues direct or indirect point to there, I'm really look forward to see the implement, and this is a great DX improvement.

@jods4
Copy link
Contributor

jods4 commented Jun 21, 2021

TS support in views would be nice if not almost mandatory when you try to have fully type-checked templates in a complex project.

We started using Volar in one of our projects and we have a few errors that need a cast or a null-forgiving ! to fix.

Sometimes such casts can be moved into the script block by some refactorings, but at other times it's not really practical, e.g. when a v-for variable is at play.

@jods4
Copy link
Contributor

jods4 commented Jun 29, 2021

I would like to contribute a sample use case that I encountered today.

I was rendering a list of heterogeneous items.
Let's say your list has Link and Group in there (e.g. a navigation menu). You want to render them differently and when you click them different stuff happens.

<template v-for="i of items">
  <a v-if="i.isLink" @click="goToLink(i)">{{ i.linkText }}</a>
  <div v-else @click="openGroup(i)>{{ i.groupHeader }}</div>
</template>

Given items: (Link | Group)[] then everything works fine but you get tons of Volar errors:

  • goToLink take a Link but i is Link | Group
  • i.linkText doesn't exist on Link | Group
  • same for openGroup(i) and i.groupHeader

In pure TS this would be nicely fixed with if (i.isLink) being a type guard but I don't think this will ever be possible in the template.

The next best thing is probably to simply add a cast: goToLink(i as Link).

I don't want to go crazy with TS inside templates, but for examples such as this one, I don't see a better solution.

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Sep 9, 2021

@jods4 you can do this:

<template v-for="i of items">
  <a
    v-if="'linkText' in i"
    @click="'linkText' in i ? goToLink(i) : undefined"
  >{{ i.linkText }}</a>
  <div
    v-else
    @click="'linkText' in i ? undefined : openGroup(i)"
  >{{ i.groupHeader }}</div>
</template>

Type narrowing not working in event expression is expected, see vuejs/language-tools#146.

@Spittal
Copy link

Spittal commented Sep 14, 2021

Is there anyway to disable type checking within the template with Volar until this feature is implemented? It's certainly a pain not being able to address certain type issues without tools like the non-null assertion operator.

@Shinigami92
Copy link
Contributor

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  setup() {
    return {
      asAny: (v: any) => v
    }
  }
});
</script>

<template v-for="i of items">
  <a v-if="asAny(i).isLink" @click="goToLink(asAny(i))">{{ asAny(i).linkText }}</a>
  <div v-else @click="openGroup(asAny(i))>{{ asAny(i).groupHeader }}</div>
</template>

👀

@jods4
Copy link
Contributor

jods4 commented Sep 21, 2021

@jods4 you can do this:

<template v-for="i of items">
  <a
    v-if="'linkText' in i"
    @click="'linkText' in i ? goToLink(i) : undefined"
  >{{ i.linkText }}</a>
  <div
    v-else
    @click="'linkText' in i ? undefined : openGroup(i)"
  >{{ i.groupHeader }}</div>
</template>

Type narrowing not working in event expression is expected, see johnsoncodehk/volar#146.

Thank you for the work-around suggestion.
It makes the error go away but I don't find the code in events very nice, so I wouldn't apply this at large scale.
I just read that Evan has recently enabled TS in templates, so 🎉

@keithbrink
Copy link

@jods4 Can you like to where you read that? Curious to know more.

@chojnicki
Copy link

I just read that Evan has recently enabled TS in templates, so 🎉

@jods4 where did you read this? I'm really waiting for TS in templates...

@Shinigami92
Copy link
Contributor

https://twitter.com/youyuxi/status/1439304854028029953

@chojnicki
Copy link

I guess this does not supported v-slot anyway? v-slot="{ item as SomeType }"

[plugin:vite:vue] Error parsing JavaScript expression: Unexpected type cast in parameter position. (1:1)

@johnsoncodehk
Copy link
Member

I guess this does not supported v-slot anyway? v-slot="{ item as SomeType }"

[plugin:vite:vue] Error parsing JavaScript expression: Unexpected type cast in parameter position. (1:1)

I am not sure if this is supported. But if it is, the syntax should be: v-slot="{ item }: { item: SomeType }.

@Spittal
Copy link

Spittal commented Sep 22, 2021

I can confirm that syntax like this does work:

image

@chojnicki
Copy link

I guess this does not supported v-slot anyway? v-slot="{ item as SomeType }"
[plugin:vite:vue] Error parsing JavaScript expression: Unexpected type cast in parameter position. (1:1)

I am not sure if this is supported. But if it is, the syntax should be: v-slot="{ item }: { item: SomeType }.

Dumb mistake 🤦 yes it does work, thanks!

@yyx990803
Copy link
Member

I just realized I can close this :D

@Spittal
Copy link

Spittal commented Sep 27, 2021

Hey @yyx990803 is there a way to annotate a prop with a generic so that the types get carried over through scoped slots?

CompA

<script lang ="ts">
import { defineComponent, PropType } from 'vue';

defineComponent({
  props: {
    items: {
      type: Array as PropType<T[]>,
      required: true,
  },
})
</script>

<template>
  <li v-for="item of items>
    <slot v-bind="{ item }"></slot>
  </li>
</template>

CompB

// ...

<template>
  <CompA>
    <template #default="{ item }">
      {{ item.with.proper.types }} // <-- Looking to get proper types here in scoped slot
    </template>
  </CompA>
</template>

@Spittal
Copy link

Spittal commented Sep 27, 2021

Oh just saw this issue: #3102

@chojnicki
Copy link

Hey @yyx990803 is there a way to annotate a prop with a generic so that the types get carried over through scoped slots?

CompA

<script lang ="ts">
import { defineComponent, PropType } from 'vue';

defineComponent({
  props: {
    items: {
      type: Array as PropType<T[]>,
      required: true,
  },
})
</script>

<template>
  <li v-for="item of items>
    <slot v-bind="{ item }"></slot>
  </li>
</template>

CompB

// ...

<template>
  <CompA>
    <template #default="{ item }">
      {{ item.with.proper.types }} // <-- Looking to get proper types here in scoped slot
    </template>
  </CompA>
</template>

Volar yes? Similar question vuejs/language-tools#439

@Spittal
Copy link

Spittal commented Sep 28, 2021

There seems to be an issue with v-model whenever there is a type annotation in the template.

v-model value must be a valid JavaScript member expression.vue(42)

This can be reproduced with something as simple as:

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

const hi = ref('hi');
</script>

<template>
  <input v-model="hi!">
</template>

(Notice the ! on the v-model)

image

@avenmore
Copy link

@Spittal The solution for me was to delete package-lock.json and node_modules and npm install. I see that ncu doesn't update "vue":"3.2.4" so that's why a re-install worked. A solution may be to ensure that it is at least "3.2.19" and then install.

@Spittal
Copy link

Spittal commented Sep 29, 2021

Hey @avenmore Thanks for the tip unfortunately I can still replicate the issue.

image

I have double and triple checked that I am on 3.2.19

@henribru
Copy link

henribru commented Sep 29, 2021

This is presumably an issue on eslint-plugin-vue's side, but just as a heads-up to people, it seems ESLint can't format expressions in the template if they contain Typescript syntax

@Spittal
Copy link

Spittal commented Sep 29, 2021

In an environment without ESLint this still throws. I'm using the yarn create vite vue-ts. I updated to vue version 3.2.19, added the following to App.vue

//...
const hi = ref('hi');
</script>

<template>
  <input type="text" v-model="hi!">
  //...

and I still get the error. Even on a vite build of the app I get the error and it prevents the build from running.

image

@henribru
Copy link

Sorry for the confusion, my comment wasn't related to yours

@Spittal
Copy link

Spittal commented Sep 29, 2021

No problem, for anyone else looking for the recreation, here is a repo.

https://github.com/Spittal/v-model-repo

@avenmore
Copy link

This works fine for me now that didn't work before v-model="employeeEditModel.editingEmployeeDTO.value!.cLastName" (or else Volar complained that it may be undefined), but hi! also does give me the error.

@Spittal
Copy link

Spittal commented Sep 29, 2021

Hm, yeah it seems that 3.2.19 did fix most syntax, but not the non-null assertion operator at the end of a name.

@yyx990803
Copy link
Member

Please report bugs for TS in templates as separate issues with reproductions. Do not reuse closed issues.

@vuejs vuejs locked as resolved and limited conversation to collaborators Sep 29, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests