vuejs / vue-loader

📦 Webpack loader for Vue.js components
MIT License
4.98k stars 915 forks source link

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

Open d-akara opened 6 years ago

d-akara commented 6 years ago

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 https://github.com/vuejs/vue-loader/issues/1234

yyx990803 commented 6 years ago

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

d-akara commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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? https://github.com/Microsoft/TypeScript/issues/12846 doesn't seem to cover it.

DanielRosenwasser commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

nailfar commented 6 years ago

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 commented 6 years ago

@nailfar This will make the hot update not work

nailfar commented 6 years ago

@ZSkycat https://github.com/skyrpex/vue-jsx-hot-loader

ZSkycat commented 6 years ago

This is the two methods I tried, but there are still problems. https://github.com/TypeStrong/ts-loader/issues/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 commented 6 years ago

@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 commented 6 years ago

I found a more appropriate method. (https://github.com/TypeStrong/ts-loader/issues/826#issuecomment-416828492)

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.

HerringtonDarkholme commented 6 years ago

@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 commented 6 years ago

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 commented 5 years ago

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

Githubissues.
  • Githubissues is a development platform for aggregating issues.