Skip to content

Unable to use TypeScript type exports from vue component file #1281

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

Open
d-akara opened this issue May 2, 2018 · 25 comments
Open

Unable to use TypeScript type exports from vue component file #1281

d-akara opened this issue May 2, 2018 · 25 comments

Comments

@d-akara
Copy link

d-akara commented May 2, 2018

Version

15.0.0

Reproduction link

https://github.com/dakaraphi/vue-loader-bug-sample

Steps to reproduce

  1. clone referenced github repo.
  2. npm install
  3. npm run build

What is expected?

Should build without error

What is actually happening?

16:77-84 "export 'Message' was not found in './InlineMessage.vue'


This seems related to #1234

@yyx990803
Copy link
Member

yyx990803 commented May 2, 2018

Vue files currently does not support type exports, because that's processed at the TypeScript checker level, and all *.vue modules are simply shimmed to be of type Vue (see your vue-shims.d.ts). I don't think this can be fixed in vue-loader unless we have first-class *.vue support in TS itself.

/cc @octref

@yyx990803 yyx990803 changed the title Unable to use named exports from vue component file Unable to use TypeScript type exports from vue component file May 2, 2018
@d-akara
Copy link
Author

d-akara commented May 2, 2018

@yyx990803 I forgot to mention, webpack will build properly on incremental build. So after the error is shown on initial build, if you then just touch the file InlineMessage.vue it will then build without error.

I don't understand enough of the internal build process to know if this is useful information or not, but just wanted to point it out in case.

@codetalks-new
Copy link

An unrelated opinion: I think we should put the below code in a separate ts file. because it's purely model mode.

export type MessageType = 'none' | 'info' | 'warning' | 'error' | 'success' | 'loading'
export interface Message {
     type: MessageType
     dismissable: boolean
     content: string
}

and I prefer declare a enum type for MessageType, such as:

export enum MessageType{
    none = 'none', 
    info = 'info' ,
    warning = 'warning',
    error = 'success' ,
    loading = 'loading',
}

@octref
Copy link
Member

octref commented May 14, 2018

As @yyx990803 said there is no existing way to do this. Your best bet right now would be having a type.ts file where you define the types and import them from the Vue files.

@DanielRosenwasser Do you know what would it take to have this supported in TS? microsoft/TypeScript#12846 doesn't seem to cover it.

@DanielRosenwasser
Copy link

Really, you need something special to resolve these types correctly within

  1. the language service
  2. the Webpack loaders themselves

There's always been a problem with importing from .vue files from a .ts file in the language service (i.e. in the editor)

I've forgotten whether the Webpack loaders are capable of doing "the right thing" without the vue-shims.d.ts file.

@nailfar
Copy link

nailfar commented May 16, 2018

Could this way help you ?

//index.ts

import Test from "./Test.vue";
export default Test;

You can just write your template and styles in .vue file (and it is surport scoped scss less) ,and script in ts files;

//Template.vue

<template>
  <div>Test</div>
</template>
<style lang="scss" scoped>
</style>

//test.ts

import { Vue, Component, Prop } from "vue-property-decorator";
import _Template from "./Template.vue";
@Component({
  mixins: [_Template],
})
export default class Test extends Vue {
  public id: string = "Test";
}

//in ohter .ts to import

import Test from "@/Test"
expport default TestChild extend Test{
console.info(this.id);
}

You can use ts type now;
You can also use tsx like react; It is surport ts type;

@yyx990803 @dakaraphi

@d-akara
Copy link
Author

d-akara commented May 16, 2018

@nailfar yes, it has been worked around using .ts files. It is just not as convenient.

@nailfar
Copy link

nailfar commented May 17, 2018

We can write a bash or node script to create module files.
run command such as npm run generate app/views/test.
then create the Test.ts , Index.ts and Template.vue files with templates in app/views/test;
// ts Template

import { Vue, Component, Prop } from "vue-property-decorator";
( other common import  you want here);
import _Template from "./Template.vue";
@Component({
  mixins: [_Template],
})
export default class {{FILE_NAME}} extends Vue {
  public id: string = "{{FILE_NAME}}";
  (do some thing others  you want...)
}

// vue Template

<template>
  <div class="module-{{FILE_NAME}}">{{FILE_NAME}}</div>
</template>
<style lang="scss" scoped>
</style>

// barrel Template

import {{FILE_NAME}} from "./{{FILE_NAME}}.vue";
export default {{FILE_NAME}};

{{FILE_NAME}} will be replaced by the script;
one command many code;

@ZSkycat
Copy link

ZSkycat commented Aug 27, 2018

@nailfar This will make the hot update not work

@nailfar
Copy link

nailfar commented Aug 27, 2018

@ZSkycat
Copy link

ZSkycat commented Aug 28, 2018

This is the two methods I tried, but there are still problems.
TypeStrong/ts-loader#826

And a another way:
Type conversion of vue components with additional ts files.

file list

Hello.vue // <script src="./Hello.script.ts"></script>
Hello.script.ts // <script> content
Hello.ts // magic wrap

Hello.ts content

import components from './Hello.vue';
import componentsClass from './Hello.script';
class Magic extends componentsClass {}
(<any>Magic) = components;
export default Magic;

This is written using vue-class-component.

@ZSkycat
Copy link

ZSkycat commented Aug 28, 2018

@yyx990803 It is probably a good idea to allow vue-loader to use the ts file as an entry point.

about webpack.module.rules
We can use double extensions to avoid affecting normal ts files. For example *.vue.ts

(I have been exploring vue + typescript and if there is a chance I hope we can talk about it.)

@ZSkycat
Copy link

ZSkycat commented Aug 29, 2018

I found a more appropriate method. (TypeStrong/ts-loader#826 (comment))

We only need to spoof the IDE's language service to achieve the goal. For example:

Demonstrates Repository: https://github.com/ZSkycat/issue-ts-loader-20180829/tree/better

Hello.vue

<template>
    <div><h1>Hello</h1></div>
</template>

<script src="./Hello.vue.ts"></script>

Hello.vue.ts

import { Component, Vue } from 'vue-property-decorator';

@Component<Hello>({
    name: 'Hello',
})
export default class Hello extends Vue {
    hello() {
        console.log('hello!');
    }
}

Used in ts file

import Hello from './Hello.vue';

The most important thing is the naming of the ts file.

  • For vscode, './Hello.vue' points to './Hello.vue.ts'
  • For webpack, './Hello.vue' points to './Hello.vue' and does not trigger type checking errors

@HerringtonDarkholme
Copy link
Member

HerringtonDarkholme commented Aug 29, 2018

@dakaraphi

The real problem is that you should not reexport type here. Exporting type in vue file is fine.

More technical detail: Vue loader depends on ts-loader to transpile script. And ts-loader in turn requires vue-loader to extract script from vue file. Communication between two loaders are done via webpack: vue-loader will generate script files like app.vue.ts in webpack and ts-loader will read those files.

At first build, vue-loader hasn't generated script. To kick off the generation, vue-loader has to first ask ts-loader for script content to find out all components in the build. Thus, ts-loader transpiles script independently without other files' info in the first run because vue files aren't processed yet. But in later build type checking works because script generation has done before.

However, re-exporting type requires type info and cannot be transpiled solely by one single file.

My recommendation is to turn on --isolateModules flag in tsconfig, which bans re-exporting type and guarantees successful transpilation. Transpilation itself is essential in large app since it speeds up build, and it is also assumed by lots of other tools. For example, babel, thread-loader and fork-ts-checker-plugin all exploit TS's transpilation feature to some extent. isolateModules is also recommended officially. https://blogs.msdn.microsoft.com/typescript/2018/08/27/typescript-and-babel-7/

As @yyx990803 said, vue-loader cannot do much here. So this issue can be safely closed since exporting type actually works . Supporting re-exporting type isn't widely supported in JS community and I suspect its value.

@reshma-menon
Copy link

There is a workaround with default objects.
// types.ts

// Type intended to share between vue components
type MyCommonType = {
    id?:string,
    name:string,
}

export const MyDefaultProp:MyCommonType = {
    id:'',
    name:'',
}

// component1.vue

. . .
import {MyDefaultProp} from './types.ts'

type MyPropType = typeof MyDefaultProp // Better than defining whole type/interface

export default class Component1 extends Vue {
    @Prop({ default: () => Object.assign({}, MyDefaultProp), type: Object }) myProp!: MyPropType
. . . 

// component2.vue

. . .
import {MyDefaultProp} from './types.ts'

type MyPropType = typeof MyDefaultProp // Better than defining whole type/interface

export default class Component2 extends Vue {
    @Prop({ default: () => Object.assign({}, MyDefaultProp), type: Object }) myProp!: MyPropType
. . . 

Typescript compiler will be happy without any warning...

@marvelperseus
Copy link

I am gonna import ts file in vue component.
But the following error happens.

<script lang="ts"> import '../apiclient/client.generated.ts' ----------------------------------------------------------------------- Module parse failed: Unexpected token (10:12) You may need an appropriate loader to handle this file type. | | export class WeatherClient { > private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; | private baseUrl: string; | protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; @ ./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/fetch-data.vue?vue&type=script&lang=ts& 50:0-41 @ ./src/components/fetch-data.vue?vue&type=script&lang=ts& @ ./src/components/fetch-data.vue @ ./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/views/SingleView/SingleView.vue?vue&type=script&lang=js& @ ./src/views/SingleView/SingleView.vue?vue&type=script&lang=js& @ ./src/views/SingleView/SingleView.vue @ ./src/router.js @ ./src/main.js @ multi (webpack)-dev-server/client?http://127.0.0.1:4001/sockjs-node (webpack)/hot/dev-server.js ./src/main.js -------------------------------- Help me.

@renetik
Copy link

renetik commented Jun 8, 2019

I found a more appropriate method. (TypeStrong/ts-loader#826 (comment))

We only need to spoof the IDE's language service to achieve the goal. For example:

Demonstrates Repository: https://github.com/ZSkycat/issue-ts-loader-20180829/tree/better

Hello.vue

<template>
    <div><h1>Hello</h1></div>
</template>

<script src="./Hello.vue.ts"></script>

Hello.vue.ts

import { Component, Vue } from 'vue-property-decorator';

@Component<Hello>({
    name: 'Hello',
})
export default class Hello extends Vue {
    hello() {
        console.log('hello!');
    }
}

Used in ts file

import Hello from './Hello.vue';

The most important thing is the naming of the ts file.

  • For vscode, './Hello.vue' points to './Hello.vue.ts'
  • For webpack, './Hello.vue' points to './Hello.vue' and does not trigger type checking errors

Is there some problems with this solution ? I tried it on some components seems ok but why this is not preferred way then ?

@jaredmcateer
Copy link

jaredmcateer commented Jun 26, 2019

@rene-dohan I would say doing it that way defeats the purpose of single file components. It's fine if you want to split it your code to work around this issue but many do not. A separate type definitions file is much more palatable as that is pretty common in Typescript land.

@harvey-woo
Copy link

harvey-woo commented Mar 9, 2020

这问题有任何进展吗?或者在将来的vue 3中是否能解决,如果不,那么vue,sfc文件的地位可能不保,按照composition api和jsx的方式开展开发,可能是vue 3的最佳方向。但这意味着,这跟react已经很贴近了。
现在我正在寻找scoped css在ts的可行方案,这路线感觉比ts支持vue更靠谱

@fairking
Copy link

fairking commented Dec 21, 2021

@rene-dohan Is there some problems with this solution ? I tried it on some components seems ok but why this is not preferred way then ?

Maybe you came from React or Angular world, but I cannot imagine having components separated into many different files in my project. For me Vuejs is the best because just one single file component and a proper HTML, not tsx ugly monster. So I can focus on bringing new features instead of messing around with huge amount of different files and parts and types.

I have a working solution but sometimes it is not working as expected in bigger projects. I think if we try to push this solution to vuejs/webpack/typescript developers, it might end our pain.

MyComponent.vue:

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

<script lang="ts">
    import Vue from "vue";

    export default Vue.extend({
        data() {
            return {
                test: "abc"
            };
        },
        methods: {
            reverse(): void {
                this.test = "cba";
            }
        }
    });
</script>

<style lang="scss" scoped>
    div { background: $primaryBgColor; }
</style>

AnotherComponent.vue:

<template>
    <div>
        <MyComponent ref="myComponent" />
        <input type="button" value="Click me" @click="onClick">
    </div>
</template>

<script lang="ts">
    import Vue from "vue";
    import MyComponent from "./MyComponent.vue";
    type MyComponentType = InstanceType<typeof MyComponent>;

    export default Vue.extend({
        components: {
            MyComponent
        },
        data() {
            return {
            };
        },
        methods: {
            onClick(): void {
                (this.$refs.myComponent as MyComponentType).reverse(); // Compiling with no errors
                (this.$refs.myComponent as MyComponentType).reverse2(); // Error: 'reverse2' does not exist
            }
        }
    });
</script>

You can find the working sample here: https://github.com/fairking/vuetstest

What do you think guys?

@vincerubinetti
Copy link

vincerubinetti commented Feb 23, 2022

Interestingly, in my testing with the latest Vue CLI, I can indeed export a type from a .vue file and import it into another .vue file. But I cannot export a type from a .vue file and import it into a .ts file without an error. Any insight as to why this might be?

My current "workaround" is to just have the types in an accompanying .d.ts file alongside the .vue file that needs it. But that adds a lot of unnecessary files.

@jaredmcateer
Copy link

Interestingly, in my testing with the latest Vue CLI, I can indeed export a type from a .vue file and import it into another .vue file. But I cannot export a type from a .vue file and import it into a .ts file without an error. Any insight as to why this might be?

My current "workaround" is to just have the types in an accompanying .d.ts file alongside the .vue file that needs it. But that adds a lot of unnecessary files.

@vincerubinetti This is because the Vue tooling is aware of the Vue SFCs, but the Typescript tooling is not, which is why each project has a vue-shim.d.ts or similar that types the default export of any file matching the *.vue pattern to VueConstructor<Vue>. As you discovered having a supplementary .d.ts or simply exporting common types from a separate .ts file is the current workaround for the issue.

@fabis94
Copy link

fabis94 commented Apr 28, 2022

I would love to see this get resolved, otherwise I have to keep some component members (types, enums) in a separate file somewhere else. Certainly not the biggest issue in the world, but results in degraded code organization.

@softwareCobbler
Copy link

softwareCobbler commented Aug 3, 2022

Hi, was wondering if there is a common suffix that has been converged on, for the "code behind" file that is needed for this. i.e., Foo.vue is commonly coupled with a Foo_xyz.ts file, or similar? I had been using the "obvious" thing of Foo.vue and Foo.ts but then I ran into jest module import problems (resolving /Foo to /Foo.vue, instead of /Foo.ts) and it made me wonder what others are naming their files.

@Dimava
Copy link

Dimava commented Apr 22, 2023

I came here looking for solution, albeit I'm using Vite, not Webpack.

In the case someone comes with the same question, here's the solution:

  • vue -> ts type imports work with Vite well, nothing to do there
  • To fix VSCode showing errors, you should use Volar's Takeover mode(i.e. just disable buildin TS extension for workspace) to use Vue-compatible TS parser

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

No branches or pull requests