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 <script setup /> in vue 3 #676

Merged
merged 3 commits into from Nov 23, 2021
Merged

Conversation

piotr-oles
Copy link
Collaborator

@piotr-oles piotr-oles commented Nov 21, 2021

In Vue 3, when <script> tag has "setup" attribute, SFC parser assignes script block to a separate field called "scriptSetup"

It's a second attempt to handle this syntax, the previous one was reverted due to incomplete feature support (#668).

See #668, #665

In Vue 3, when <script> tag has "setup" attribute, SFC parser assigned
script block to a separate field called "scriptSetup"

✅ Closes: #668
@filimon-danopoulos-stratsys
Copy link

I am not an expert but I need these updates so I gave it a shot at testing it locally. It seems that it does not recognize that I have @vue/compiler-sfc installed and throws. To get around this i just add @vue/compiler-sfc locally in fork-ts-checker-webpack-plugin (make sure the version matches the one in you project). I think this issue has to do with how yarn link works.

The good news is that it now checks <script setup lang="ts">. I have tested the following two components:

Test1.vue

<script lang="ts">
import { defineComponent } from 'vue'
const test: boolean = "test"
export default defineComponent({
  data() {
    return {
      test
    }
  }
})
</script>
<template>
  <div>
    {{ test }}
  </div>
</template>

Test2.vue

<script setup lang="ts">
const test: boolean = "true"
const props = defineProps({
  prop1: String,
})
</script>

<template>
  <div>
    {{ test }}
  </div>
</template>

The bad news is that regular components are no longer handled correctly. Test2.vue is clearly typechecked but defineComponent seems to not handle data() correctly but it did so previously.

ERROR in src/Test1.vue:5:7
TS2322: Type '"test"' is not assignable to type 'boolean'.
   3 | <script lang="ts">
   4 | import { defineComponent } from 'vue'
 > 5 | const test: boolean = "test"
     |       ^^^^
   6 | export default defineComponent({
   7 |   data() {
   8 |     return {

ERROR in src/Test1.vue:7:3
TS2769: No overload matches this call.
 The last overload gave the following error.
   Argument of type '{ data(): { test: boolean; }; }' is not assignable to parameter ofown, unknown, {}, {}, ComponentOptionsMixin, ... 4 more ..., any>'.
     Object literal may only specify known properties, and 'data' does not exist in typ unknown, {}, {}, ComponentOptionsMixin, ... 4 more ..., any>'.
    5 | const test: boolean = "test"
    6 | export default defineComponent({
 >  7 |   data() {
      |   ^^^^
    8 |     return {
    9 |       test
   10 |     }

ERROR in src/Test2.vue:3:7
TS2322: Type '"true"' is not assignable to type 'boolean'.
   1 | 
   2 | <script setup lang="ts">
 > 3 | const test: boolean = "true"
     |       ^^^^
   4 | const props = defineProps({
   5 |   prop1: String,
   6 | })

webpack 5.64.1 compiled with 3 errors in 1249 ms

Compared to the output of 6.4.0 where Test2.vue is absent and defineComponent is handled correctly.

ERROR in src/Test1.vue:5:7
    3 | <script lang="ts">
    4 | import { defineComponent } from 'vue'
  > 5 | const test: boolean = "test"
      |       ^^^^
    6 | export default defineComponent({
    7 |   data() {
    8 |     return {

webpack 5.64.1 compiled with 1 error in 1460 ms

Edit
For the sake of completeness I removed const test: boolean = "test" for const test: boolean = false in Test1.vue and still get the same error

ERROR in src/Test1.vue:7:3
TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type '{ data(): { test: boolean; }; }' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<readonly string[] | Readonly<ComponentObjectPropsOptions<Record<string, unknown>>>, unknown, unknown, {}, {}, ComponentOptionsMixin, ... 4 more ..., any>'.
      Object literal may only specify known properties, and 'data' does not exist in type 'ComponentOptionsWithObjectProps<readonly string[] | Readonly<ComponentObjectPropsOptions<Record<string, unknown>>>, unknown, unknown, {}, {}, ComponentOptionsMixin, ... 4 more ..., any>'.
     5 | const test: boolean =  false
     6 | export default defineComponent({
  >  7 |   data() {
       |   ^^^^
     8 |     return {
     9 |       test
    10 |     }

ERROR in src/Test2.vue:3:7
TS2322: Type '"true"' is not assignable to type 'boolean'.
    1 | 
    2 | <script setup lang="ts">
  > 3 | const test: boolean = "true"
      |       ^^^^
    4 | const props = defineProps({
    5 |   prop1: String,
    6 | })

webpack 5.64.1 compiled with 2 errors in 1269 ms

@piotr-oles
Copy link
Collaborator Author

@filimon-danopoulos-stratsys thanks for testing!

I will add a test-case with defineComponent. You can try to run npm pack instead of yarn link in the plugin directory, and then yarn add <path-to-plugin.tgz-file> in the project directory - this should fix the issue with dependencies that you have with yarn link (and maybe it will fix the issue altogether - maybe there is some typings versions conflict 🤔 )

@bodograumann
Copy link

I had to use nlc from npm-link-better to see some reasonable results. yarn pack and npm install ….tgz didn’t work, because it is actually installed as a sub-dependency via @vue/cli-plugin-typescript.

Luckily I don’t have any legacy component, but I do have the situation where I have both a <script setup lang="ts"> as well as a regular <script lang="ts"> block. E.g. besides the component there is:

<script lang="ts">
export interface ScrollAlignOptions {
…
}
</script>

Now in another component I try to import that interface, but get the error:

TS2614: Module '"@/components/generic/ScrollContainer.vue"' has no exported member 'ScrollAlignOptions'. Did you mean to use 'import ScrollAlignOptions from "@/components/generic/ScrollContainer.vue"' instead?

@filimon-danopoulos-stratsys

@piotr-oles Packing and installing locally fixes the issue I encountered. Just another case of yarn link not really doing what you expect. Great work!

@filimon-danopoulos-stratsys
Copy link

@bodograumann I can verify that this is indeed an issue. In addition, no type checking is performed in <script lang="ts"> if a .vue file includes <script setup lang="ts">. I expanded my little test suite with

Test3.vue

<script setup lang="ts">
const test: boolean = 'test'
</script>

<script lang="ts">
const test: boolean = 'test'
export interface TestInterface {
  prop1: string
}
</script>

<template>
  <div>
    {{ test }}
  </div>
</template>

I simply import it and use it for defineProps<TestInterface>() in Test2.vue and I get the following output.

ERROR in src/Test1.vue:5:7
TS2322: Type 'string' is not assignable to type 'boolean'.
    3 | <script lang="ts">
    4 | import { defineComponent } from 'vue'
  > 5 | const test: boolean = "test"
      |       ^^^^
    6 | export default defineComponent({
    7 |   data() {
    8 |     return {

ERROR in src/Test2.vue:3:10
TS2614: Module '"./Test3.vue"' has no exported member 'TestInterface'. Did you mean to use 'import TestInterface from "./Test3.vue"' instead?
    1 |
    2 | <script setup lang="ts">
  > 3 | import { TestInterface } from "./Test3.vue"
      |          ^^^^^^^^^^^^^
    4 |
    5 | const test: boolean = "true"
    6 | const props = defineProps<TestInterface>()

ERROR in src/Test2.vue:5:7
TS2322: Type 'string' is not assignable to type 'boolean'.
    3 | import { TestInterface } from "./Test3.vue"
    4 |
  > 5 | const test: boolean = "true"
      |       ^^^^
    6 | const props = defineProps<TestInterface>()
    7 | </script>
    8 |

ERROR in src/Test3.vue:12:7
TS2322: Type 'string' is not assignable to type 'boolean'.
    10 |
    11 | <script setup lang="ts">
  > 12 | const test: boolean = 'test'
       |       ^^^^
    13 | </script>
    14 |
    15 | <template>

webpack 5.64.1 compiled with 4 errors in 1569 ms

The <script lang="ts"> block in Test3.vue clearly contains a typing error that is not caught.

Note that I am not using it through @vue/cli-service but rather a custom webpack setup.

@piotr-oles
Copy link
Collaborator Author

Is it correct to have <script /> and <script setup /> in the same .vue file? Currently, we support only single <script /> tag

@filimon-danopoulos-stratsys

@piotr-oles
Copy link
Collaborator Author

piotr-oles commented Nov 22, 2021

Thanks, I will try to support for this case :)

@piotr-oles
Copy link
Collaborator Author

I pushed a new commit with support for <script /> and <script setup /> in the same file. Let me know if it works for you!

@bodograumann
Copy link

Works nicely, thank you @piotr-oles. Hopefully we can get this into vue-cli soon.

@piotr-oles piotr-oles merged commit 40e4ecf into main Nov 23, 2021
@piotr-oles piotr-oles deleted the fix/support-vue-script-setup branch November 23, 2021 07:39
@piotr-oles
Copy link
Collaborator Author

🎉 This PR is included in version 6.4.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@filimon-danopoulos-stratsys

Unfortunately it does not work 100%. The following does not typescheck <script setup lang="ts"> properly again

<script setup lang="ts">
const test1: boolean = 'test1'
</script>

<script lang="ts">
const test2: boolean = 'test2'
export interface TestInterface {
  prop1: string
}
</script>

<template>
  <div>
    {{ test }}
  </div>
</template>

Result

ERROR in src/Test2.vue:5:7
TS2322: Type 'string' is not assignable to type 'boolean'.
   3 | import { TestInterface } from "./Test3.vue"
   4 |
 > 5 | const test: boolean = "true"
     |       ^^^^
   6 | const props = defineProps<TestInterface>()
   7 | </script>
   8 |

ERROR in src/Test3.vue:7:7
TS2322: Type 'string' is not assignable to type 'boolean'.
    5 |
    6 | <script lang="ts">
 >  7 | const test2: boolean = 'test2'
      |       ^^^^^
    8 | export interface TestInterface {
    9 |   prop1: string
   10 | }

webpack 5.64.2 compiled with 3 errors in 1546 ms

I would have expected to see an error for test1 in the first <script setup lang="ts"> . I think the issue is found on this line
I tried to debug it a little but I don't know the code enough to give very meaningful feedback. What I saw is that the lines are not empty but contain the string //. I logged the internal state to file and it looks like this:

{
"script": [
  "//",
  "//",
  "//",
  "//",
  "//",
  "",
  "const test: boolean = 'test2'",
  "export interface TestInterface {",
  "  prop1: string",
  "}",
  ""
],
"setup": [
  "//",
  "",
  "const test: boolean = 'test1'",
  ""
],
"merge": [
  "//",
  "//",
  "//",
  "//",
  "//",
  null,
  "const test: boolean = 'test2'",
  "export interface TestInterface {",
  "  prop1: string",
  "}",
  null
]
}

This means that you would never get the contents of the <script setup> block since the <script> block contains // and thus scriptLines[line] || scriptSetupLines[line] would always return the contents of scriptLines[line].

Furthermore I am not certain that this approach will result in correct behavior. Type checking both these things together might produce type warnings that are not valid. <script setup> in essence means that you omit some code that is automatically generated.

<script setup>
  const test = "test"
</script>

Is in practice something like this (the actually generated code is different, this just illustrates the point)

import { defineComponent } from 'vue'
export default defineComponent({
  setup() {
    const test = "test"
    return {
      test
    }
  }
})

Expanding that example with a regular <script> this code

<script>
const test = "test"
</script>
<script setup>
  const test = "test"
</script>

Would generate something like this

import { defineComponent } from 'vue'
const test = "test"
export default defineComponent({
  setup() {
    const test = "test"
    return {
      test
    }
  }
})

Where the two constants both named test are defined in different closures and would not throw any type errors. However the merge strategy introduced here would generate (only considering the merge step)

const test = "test"
const test = "test"

The two test constants are in the same block and this will and should result in TS2451: Cannot redeclare block-scoped variable 'test'.

@screetBloom
Copy link

screetBloom commented Nov 23, 2021

Has anyone encountered this error?

"vue": "2.6.11",
"vue-template-compiler": "2.6.11"
"@vue/composition-api": "1.2.4",
<template>
  <div id="app">
    hello world
  </div>
</template>

<script setup lang="ts">
  const x: string = 42;
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
}
</style>

Error

 File '/Users/xxx/cli5-test/src/App.vue.ts' is not a module.
    1 | import Vue from 'vue'
  > 2 | import App from './App.vue'

my shims-vue.d.ts 👇

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

@bodograumann
Copy link

@screetBloom You cannot use <script setup> with vue 2. It only works in vue 3.

@filimon-danopoulos-stratsys

I would suggest you use the provided API from @johnsoncodehk that he mentioned here as he seems to have solved these issues already.

@sodatea
Copy link

sodatea commented Nov 23, 2021

@screetBloom You cannot use <script setup> with vue 2. It only works in vue 3.

Actually, you can, with https://github.com/antfu/unplugin-vue2-script-setup
But maybe not many people have been using it yet.

@piotr-oles
Copy link
Collaborator Author

It's not that easy to integrate as it seems :) I will have to add source-map support to the plugin to properly map diagnostic positions.

@johnsoncodehk
Copy link

It's not that easy to integrate as it seems :) I will have to add source-map support to the plugin to properly map diagnostic positions.

If you need, I can expose volar internal source map api. It can transform between source range and generated range.

@filimon-danopoulos-stratsys

Sorry I don't think that I can be of much assistance in the implementation. I don't know the codebase well enough to provide meaningful feedback. Am happy to test it when there is something to look at again.

@piotr-oles
Copy link
Collaborator Author

🎉 This PR is included in version 7.0.0-alpha.15 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Successfully merging this pull request may close these issues.

None yet

6 participants