Skip to content

ghostrin/vue-tsx-support

 
 

Repository files navigation

Build Status

vue-tsx-support

TSX (JSX for TypeScript) support library for Vue

Table of contents

Caution: BREAKING CHANGE

  • v1.0.0

  • v0.5.0:

    • Rename extend to extendFrom (undocumented api)
  • v0.4.0:

Install and enable

Install from npm:

npm install vue-tsx-support -S

And refer vue-tsx-support/enable-check.d.ts from somewhere to enable compiler check. (CHANGED since v0.4.0)

///<reference path="node_modules/vue-tsx-support/enable-check.d.ts" />
// or
import "vue-tsx-support/enable-check"

or in tsconfig.json

{
  "compilerOptions": {
    "...snip...": "...snip..."
  },
  "include": [
    "node_modules/vue-tsx-support/enable-check.d.ts",
    "...snip..."
  ]
}

Using intrinsic elements

Standard HTML elements are defined as intrinsic elements. So, compiler can check attribute names and attribute types of them:

// OK
<div id="title" />;
// OK
<input type="number" min={ 0 } max={ 100 } />;
// OK
<a href={ SOME_LINK } />;
// NG: because `href` is not a valid attribute of `div`
<div href={ SOME_LINK } />;
// NG: because `id` must be a number
<div id={ 1 } />;

Using custom component

By default, vue-tsx-support does not allow unknown props.

For example, if you have this component :

import Vue from "vue";

const MyComponent = Vue.extend({
    props: {
        text: { type: String, required: true },
        important: Boolean,
    },
    computed: {
        className() {
            return this.important ? "label-important" : "label-normal";
        }
    },
    methods: {
        onClick(event) { this.$emit("ok", event); }
    },
    template: "<span :class='className' @click='onClick'>{{ text }}</span>"
});

Below code will cause compilation error because compiler does not know MyComponent has prop text.

// Compilation error(TS2339): Property `text` does not exist on type '...'
<MyComponent text="foo" />;

You must add types to the component by apis memtions below, or enable allow-unknown-props option.

available APIs to add type information

componentFactory

Create tsx-supported component from component options. (Partially compatible with Vue.extend)

Usage
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
    props: {
        text: { type: String, required: true },
        important: Boolean,
    },
    computed: {
        className(): string {
            return this.important ? "label-important" : "label-normal";
        }
    },
    methods: {
        onClick(event) { this.$emit("ok", event); }
    },
    render(): VNode {
        return <span class={this.className} onClick={this.onClick}>{this.text}</span>;
    }
});

componentFactory.create can infer types of props from component options same as Vue.extend. In the above example, props type will be { text?: string, important?: boolean }.

NOTE: all props are regarded as optional even if required: true specified.

// both `text` and `important` are regarded as optional
// So below 3 cases are all valid.
<MyComponent />;
<MyComponent text="foo" />;
<MyComponent important={true} />;

If you want to make some props required, you must specify required prop names as second parameter.

import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
    props: {
        text: { type: String, required: true },
        important: Boolean,
    },
    /* snip */
}, ["text"]);

In the above example, props type will be { text: string, important?: boolean }.

// NG: `text` is required
<MyComponent />;
// OK: `important` is optional
<MyComponent text="foo" />;
// OK
<MyComponent text="foo" important={true} />;

NOTE: shorthand props definition(like props: ["foo", "bar"]) is currently not supported.

// Does not work
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
    props: ["text", "important"],
    /* snip */
}, ["text"]);

component

Shorthand of componentFactory.create

import * as tsx from "vue-tsx-support";
const MyComponent = tsx.component({
    props: {
        text: { type: String, required: true },
        important: Boolean,
    },
    /* snip */
});

componentFactoryOf

Return componentFactory with additional types (events and scoped slots)

Usage

If your component has custom events, you may want to specify event listener. But below example does not work.

import * as tsx from "vue-tsx-support";

const MyComponent = tsx.component({
    render(): VNode {
        return <button onClick={this.$emit("ok")}>OK</button>;
    }
});

// Compilation error: 'onOK' is not a property of MyComponent
<MyComponent onOk={() => console.log("ok")} />;

In such situations, you must specify event types by componentFactoryOf

import * as tsx from "vue-tsx-support";

interface Events {
    // all memebers must be prefixed by 'on'
    onOk: void;
    onError: { code: number, detail: string };
}

const MyComponent = tsx.componentFactoryOf<Events>().create({
    render(): VNode {
        return (
            <div>
              <button onClick={() => this.$emit("ok")}>OK</button>
              <button onClick={() => this.$emit("error", { code: 9, detail: "unknown" })}>Raise Error</button>
            </div>
        );
    }
});

// OK
<MyComponent onOk={() => console.log("ok")} />;
<MyComponent onError={p => console.log("ng", p.code, p.detail)} />;

You can also specify types of scoped slots if your component uses it.

import * as tsx from "vue-tsx-support";

interface ScopedSlots {
    default: { text: string };
}

const MyComponent = tsx.componentFactoryOf<{}, ScopedSlots>().create({
    props: {
        text: String
    },
    render(): VNode {
        // type of `$scopedSlots` is checked statically
        return <div>
                 { this.$scopedSlots.default({ text: this.text || "default text" }) }
               </div>;
    }
});

// type of `scopedSlots` is checked statically, too
<MyComponent scopedSlots={{
        default: p => [<span>p.text</span>]
    }}
/>;

// NG: 'default' is missing in scopedSlots
<MyComponent scopedSlots={{
        defualt: p => [<span>p.text</span>]
    }}
/>;

Component

Base class of class base component

Usage

If you write your component with vue-class-component, you can it tsx-supported by extending from this class.

import component from "vue-class-component";
import * as tsx from "vue-tsx-support";

interface MyComponentProps {
    text: string;
    important?: boolean;
}

@component({
    props: {
        text: { type: String, required: true },
        important: Boolean
    },
    /* snip */
})
class MyComponent extends tsx.Component<MyComponentProps> {
    /* snip */
}

Unfortunately, you must write props interface and props definition separately.

If you want, you can specify event types and scoped slot types as 2nd and 3rd type parameter

import component from "vue-class-component";
import * as tsx from "vue-tsx-support";

interface MyComponentProps {
    text: string;
    important?: boolean;
}

interface Events {
    onOk: void;
    onError: { code: number, detail: string };
}

interface ScopedSlots {
    default: { text: string };
}


@component({
    props: {
        text: { type: String, required: true },
        important: Boolean
    },
    /* snip */
})
class MyComponent extends tsx.Component<MyComponentProps, Events, ScopedSlots> {
    /* snip */
}

ofType

Make existing component tsx-supported.

Usage

If you can't modify existing component definition, wrap it by ofType and convert

import ThirdPartyComponent from "third-party-library";
import * as tsx from "vue-tsx-support";

interface MyComponentProps { /* ... */ }

const MyComponent = tsx.ofType<MyComponentProps>().convert(ThirdPartyComponent);

Of course you can specify event types and scoped slot types if you want.

const MyComponent = tsx.ofType<MyComponentProps, Events, ScopedSlots>().convert(ThirdPartyComponent);

Other attributes

Native event listeners and dom properties

Sometimes you may want to specify native event listener or dom property to the component like below. But unfortunately, vue-tsx-support does not support this.

// NG: because `nativeOnClick` is not a prop of MyComponent
<MyComponent text="foo" nativeOnClick={ ... } />
// NG: because `domPropInnerHTML` is not a prop of MyComponent
<MyComponent text="foo" domPropInnerHTML={ ... } />

To avoid compilation error, you must use kebab-case attribute name.

// OK
<Component nativeOn-click={ ... } />
// OK
<Component domProp-innerHTML={ ... } />

Or use JSX-spread style.

// OK
<Component { ...{ nativeOn: { click: ... } } } />
// OK
<Component { ...{ domProps: { innerHTML: ... } } } />

For native events, there is an another solution. See enable-nativeon option.

HTML attributes attached to the root element

And sometimes, you may want to specify HTML attributes to the component like below. But unfortunately, vue-tsx-support does not support this, too.

// NG: because `min` and `max` are not props of SomeInputComponent
<SomeInputComponent min={ 0 } max={ 100 } />

To avoid compilation error, you must use JSX-spread style.

// OK
<SomeInputComponent { ...{ attrs: { min: 0, max: 100 } } } />

Or enable enable-html-attributes option.

Options

vue-tsx-support has some options which change behaviour globally. See under the options directory.

To enable each options, import them somewhere

// enable `allow-unknown-props` option
import "vue-tsx-support/options/allow-unknown-props";

NOTE: Scope of option is whole project, not a file.

allow-element-unknown-attrs

Make enabled to specify unknown attributes to intrinsic elements

// OK:`foo` is unknown attribute, but can be compiled
<div foo="foo" />;

allow-unknown-props

Make enabled to specify unknown props to Vue component.

const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK: `bar` is unknown prop, but can be compiled
<MyComponent foo="foo" bar="bar" />;

enable-html-attrs

Make enabled to specify HTML attributes to Vue component.

const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK: `min` and `max` are valid HTML attributes
<MyComponent foo="foo" min={ 0 } max={ 100 } />;
// NG: compiler checks type of `min` (`min` must be number)
<MyComponent foo="foo" min="a" />;

enable-nativeon

Make enabled to specify native event listeners to Vue component.

const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK
<MyComponent foo="foo" nativeOnClick={ e => ... } />; // and `e` is infered as MouseEvent

enable-vue-router

Add definitions of router-link and router-view

LICENSE

MIT

About

TSX (JSX for TypeScript) support library for Vue

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 93.6%
  • JavaScript 6.4%