TSX (JSX for TypeScript) support library for Vue
- Caution: BREAKING CHANGE
- Install and enable
- Using intrinsic elements
- Using custom component
- Options
- LICENSE
-
V2.0.0
- Support Vue >= 2.5.13 only
- Support TypeScript >= 2.8 only
-
v1.0.0
- Support Vue >= 2.5 only.
createComponentis deprecated. use componentFactory or component instead.
-
v0.5.0:
- Rename
extendtoextendFrom(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} />;But text is required actually, you may think compilation should be failed when text does not specified.
There are sevaral ways to achieve it.
- Instead of
required: true, specifyrequired: true as true. This turns type ofrequiredboolean to 'true', and vue-tsx-support can know it is required in compile time.
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: {
text: { type: String, required: true as true },
important: Boolean,
},
/* snip */
});FYI, vue-strict-prop make this easy.
import * as tsx from "vue-tsx-support";
import p from "vue-strict-prop";
const MyComponent = tsx.componentFactory.create({
props: {
text: p(String).required,
important: Boolean,
},
/* snip */
});- Specify required prop names as second argument
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: {
text: { type: String, required: true },
important: Boolean,
},
/* snip */
}, ["text"]);In above examples, props type will be { text: string, important?: boolean }.
// NG: `text` is required
<MyComponent />;
<MyComponent 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 */
});Shorthand of componentFactory.create
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.component({
props: {
text: { type: String, required: true },
important: Boolean,
},
/* snip */
});When you want to extend your component from other than Vue, you can use extendFrom
import * as tsx from "vue-tsx-support";
// This is equivalent to `const MyComponent = Base.extend({ /* snip */ });`
const MyComponent = tsx.extendFrom(Base).create({
/* snip */
});You can use mixin to add mixin type-safely.
import * as tsx from "vue-tsx-support";
const StorageMixin = {
methods: {
getItem(string name): string {
return localStorage.getItem(name);
},
setItem(string name, string value): void {
localStorage.setItem(name, value);
}
}
}
const MyComponent = tsx.mixin(StorageMixin).create(
// You can use this.getItem and this.setItem here
{
props: {
name: String
},
data() {
return { value: "" }
},
mounted() {
this.value = this.getItem(this.name);
},
render(): VNode {
return (
<button onClick={() => this.setItem(this.name, this.value)}>
SAVE
</button>
);
}
}
);
// You can add 2 or more mixins by method chain
const MyComponent.mixin(FirstMixin).mixin(SecondMixin).create({
/* 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 MouseEventAdd definitions of router-link and router-view
MIT