TSX (JSX for TypeScript) support library for Vue
- Caution: BREAKING CHANGE
- Install and enable
- Using intrinsic elements
- Using custom component
- Options
- LICENSE
-
v1.0.0
- Support Vue >= 2.5 only.
createComponent
is deprecated. use componentFactory or component instead.
-
v0.5.0:
- Rename
extend
toextendFrom
(undocumented api)
- Rename
-
v0.4.0:
- The way to enable compiler check has changed. See 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..."
]
}
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 } />;
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.
Create tsx-supported component from component options. (Partially compatible with Vue.extend
)
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"]);
Shorthand of componentFactory.create
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.component({
props: {
text: { type: String, required: true },
important: Boolean,
},
/* snip */
});
Return componentFactory with additional types (events and scoped slots)
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>]
}}
/>;
Base class of class base component
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 */
}
Make existing component tsx-supported.
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);
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.
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.
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.
Make enabled to specify unknown attributes to intrinsic elements
// OK:`foo` is unknown attribute, but can be compiled
<div foo="foo" />;
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" />;
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" />;
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
Add definitions of router-link
and router-view
MIT