TSX (JSX for TypeScript) support library for Vue
npm install vue-tsx-support

If your project already uses vue-tsx-support v2, see Migration from V2 section.
- vue-tsx-support
- :warning: BREAKING CHANGES
- TABLE OF CONTENTS
- NEW FEATURES
- PREREQUISITE
- INSTALLATION
- Migration from V2
- USAGE
- Intrinsic elements
- Components
- Make existing components tsx-ready.
- Writing components by object-style API (Like Vue.extend)
- Writing component by class-style API (vue-class-component and/or vue-property-decorator)
- 1. Extends from Component class provided by vue-tsx-support
- 2. Add _tsx field to tell type information to TypeScript.
- Writing component by composition api (@vue/composition-api)
- OPTIONS
- allow-element-unknown-attrs
- allow-unknown-props
- enable-html-attrs
- enable-nativeon
- enable-vue-router
- allow-props-object
- APIS
- modifiers
- Available modifiers
- LICENSE
- Typesafe emit for declared events
- @vue/composition-api support
- Vue >= 2.6.0, < 3.0.0
- TypeScript >= 3.8.0 (3.9.2 or later recommended)
vue-tsx-support does not support Vue 3 because Vue 3 has it's own JSX type checker and there are some incompatibilities with Vue 2.
If you want to use composition API with vue-tsx-support, you can use @vue/composition-api.
1. Create Vue project with TypeScript and babel support.
vue-tsx-support is a type checker for TypeScript, not a transpiler.
You must install babel presets (@vue/babel-preset-app or @vue/babel-preset-jsx) separatedly.
Vue CLI may help you.
- Installation - Vue.js
- Vue CLI
- vuejs/jsx
:bulb: If you want use @vue/composition-api, @vue/babel-preset-jsx >= 1.2.1 or babel-preset-vue-vca is needed.
2. Install vue-tsx-support from npm
```
yarn add vue-tsx-support -D
3. In tsconfig.json, set "preserve" to jsx and "VueTsxSupport" to jsxFactory
`json`
{
"compilerOptions": {
"jsx": "preserve",
"jsxFactory": "VueTsxSupport",
"...": "..."
},
"include": [
"..."
]
}
4. import vue-tsx-support/enable-check.d.ts somewhere,
`typescript`
import "vue-tsx-support/enable-check"
or add it to "include" in tsconfig.json
`json`
{
"compilerOptions": {
"...": "..."
},
"include": [
"node_modules/vue-tsx-support/enable-check.d.ts",
"..."
]
}
1. In tsconfig.json, set "VueTsxSupport" to jsxFactory
2. Enable allow-props-object option (Optional)
Standard HTML elements are defined as intrinsic elements.
So, compiler can check attribute names and attribute types of them:
`jsxhref
// OK
;
// OK
;
// OK
;
// NG: because is not a valid attribute of divid
;
// NG: because must be a string`
;
Lower case tags are treated as unknown intrinsic element.
TypeScript checks nothing for such tags.
`jsx`
// OK
Basically, vue-tsx-support checks three types for each component.
- __Prop types__
Determine name, type, and required or optional of each props.
When using existing component as-is, you must specify prop types manually.
When writing component with APIs of vue-tsx-support, prop types are automatically obtained from component definition.
- __Custom event types (optional)__
If the component has custom events, you can specify custom event types additionally,
and vue-tsx-support will check if event names and argument types are correct or not.
- __Scoped slot types (optional)__
If the component has uses scoped slots, you can specify scoped slot types additionally,
and vue-tsx-support will check if scoped slot names and argument types are correct or not.
#### Make existing components tsx-ready.
By default, vue-tsx-support does not allow unknown props.
For example, below code causes compilation error.
`jsx
import Vue from "vue";
import AwesomeButton from "third-party-library/awesome-button";
export default Vue.extend({
render() {
// ERROR: because TypeScript does not know that AwesomeButton has 'text' prop.
return
}
});
`
You can add type information to existing component without modifying component itself, like below:
`typescript
import AwesomeButtonOrig from "third-party-library/awesome-button";
import * as tsx from "vue-tsx-support";
type AwesomeButtonProps = {
text: string;
raised?: boolean;
rounded?: boolean;
}
// Now, AwesomeButton has 1 required prop(text) and 2 optional props(raised, rounded)
export const AwesomeButton = tsx.ofType
`
You also can specify custom event types as second type parameter, and scoped slot types as third type parameter.
For example:
`typescript
import AwesomeListOrig from "third-party-library/awesome-list";
import * as tsx from "vue-tsx-support";
type Item = { id: string, text: string };
type AwesomeListProps = {
items: ReadonlyArray
rowHeight: number;
}
type AwesomeListEvents = {
// member name must be ['on' + event name(with capitalizing first charactor)]
onRowClicked: { item: Item, index: number };
}
type AwesomeListScopedSlots = {
row: { item: Item }
}
export const AwesomeList = tsx.ofType<
AwesomeListProps,
AwesomeListEvents,
AwesomeListScopedSlots
>().convert(AwesomeListOrig);
`
Then you can use AwesomeList like below:
`jsx${p.item.text} clicked!
import { VNode } from "vue";
const App = Vue.extend({
render(): VNode {
return (
rowHeight={32}
onRowClicked={p => console.log()}`
scopedSlots={{
row: item => {item.text}
}}
/>
);
}
});
#### Writing components by object-style API (Like Vue.extend)
If you use Vue.extend(), just replace it by componentFactory.create and your component becomes TSX-ready.
Props type is infered from props definition automatically.
For example, props type will be { text: string, important?: boolean } in below code.
:warning: In some environment, as const may be needed to make prop required properly.
`jsxas const
import { VNode } from "vue";
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: {
text: { type: String, required: true },
important: Boolean,
} as const, // is needed in some cases.`
computed: {
className(): string {
return this.important ? "label-important" : "label-normal";
}
},
methods: {
onClick(event: Event) { this.$emit("ok", event); }
},
render(): VNode {
return {this.text};
}
});
:bulb: You can use component as as shorthand of componentFactory.create.
`jsx`
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.component({
/ snip /
});
If your component has custom events or scoped slots, use componentFactoryOf instead.
`typescript
import { VNode } from "vue";
import * as tsx from "vue-tsx-support";
type AwesomeListEvents = {
onRowClicked: { item: {}, index: number };
}
type AwesomeListScopedSlots = {
row: { item: {} }
}
export const AwesomeList = tsx.componentFactoryOf< #### Writing component by class-style API ( If you prefer class-style component by using ##### 1. Extends from type MyComponentProps = { @Component get className() { :warning: Unfortunately, ##### 2. Add @Component @Prop({ type: String, required: true }) get className() { You can use @Component / ...snip... / :bulb: @Component / ...snip... / :bulb: When you can make all data, computed and methods private, you can use @Component @Prop({ type: String, required: true }) @Prop(Boolean) // data render(): VNode { :bulb: If your component has custom events, you can specify events handlers type additionally. @Component / ...snip... / :bulb: If your component uses scoped slots, you should add type to @Component $scopedSlots!: tsx.InnerScopedSlots<{ default?: string }>; / ...snip... / #### Writing component by composition api ( Vue 3 is not supported. There are 2 babel presets which support JSX syntax with To make TSX-ready component by composition api, use const MyComponent = vca.component({ If your component has custom event or scoped slots, specify them types in 2nd argument of type AwesomeListEvents = { type AwesomeListScopedSlots = { export const AwesomeList = vca.component({ return () => ( To enable each options, import them somewhere :warning: Scope of option is whole project, not a file. Make enabled to specify unknown attributes to intrinsic elements Make enabled to specify unknown props to Vue component. Make enabled to specify HTML attributes to Vue component. Make enabled to specify native event listeners to Vue component. Add definitions of Make enabled to pass props as "props". Event handler wrappers which work like some event modifiers available in template // Basic usage: // Use multiple modifiers: // Use without event handler: // Use multiple keys: // Use exact modkey combination: #### Available modifiers * Execute event handler only when specified key is pressed. * Execute event handler only when specified mouse button is pressed. * Execute event handler only when specified system modifier key is pressed. * Execute event handler only when specified system modifier key is not pressed. * Execute event handler only when event.target is the element itself (not from children). * Call Execute event handler only when one of specified key is pressed. * Execute event handler only when specified system modifier keys are all pressed, and others are not pressed. MIT
AwesomeListEvents,
AwesomListScopedSlots
>().create({
name: "AwesomeList",
props: {
items: { type: Array, required: true },
rowHeight: { type: Number, required: true }
},
computed: { / ... /},
method: {
emitRowClicked(item: {}, index: number): void {
// Equivalent to this.$emit("rowClicked", { item, index }),
// And event name and payload type are statically checked.
tsx.emitOn(this, "onRowClicked", { item, index });
}
},
render(): VNode {
return (
{
this.visibleItems.map((item, index) => (
this.$emit("rowClicked", { item, index })}>
{
// slot name ('row') and argument types are statically checked.
this.$scopedSlots.row({ item })
}
)
}
);
}
});
`vue-class-component and/or vue-property-decorator)vue-class-component and/or vue-property-decorator,Component
there are some options to make it tsx-ready. class provided by vue-tsx-support`jsx`
import { VNode } from "vue";
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";
text: string;
important?: boolean;
}
export class MyComponent extends tsx.Component
@Prop({ type: String, required: true })
text!: string;
@Prop(Boolean)
important?: boolean;
return this.important ? "label-important" : "label-normal";
}
onClick(event: MouseEvent) {
this.$emit("ok", event);
}
render(): VNode {
return {this.text};
}
}
vue-tsx-support can't infer prop types automatically in this case, so you must write type manually._tsx field to tell type information to TypeScript.`jsxprops
import { VNode } from "vue";
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";
export class MyComponent extends Vue {
_tsx!: {
// specify props type to .`
props: Pick
};
text!: string;
@Prop(Boolean)
important?: boolean;
return this.important ? "label-important" : "label-normal";
}
render(): VNode {
return {this.text};
}
}
DeclareProps instead of { props: T }.`jsx`
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps
}
PickProps is more convenient than Pick here, it removes attributes from Vue from completion candidates. (e.g. $data, $props, and so on)`jsx`
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps
}
AutoProps instead. AutoProps
picks all public members other than members from component options(render, created etc).`
jsx${this.text}-${this.count}
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps
text!: string;
important?: boolean;
private count = 0;
// computed
private get className() {
return this.important ? "label-important" : "label-normal";
}
// methods
private onClick() {
this.count += 1;
}
return (
{}`
);
}
}
`jsx`
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps
tsx.DeclareOnEvents<{ onOk: string }>;
}
$scopedSlots by tsx.InnerScopedSlots.`jsx`
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps
}
@vue/composition-api)@vue/composition-api
To use composition api with Vue 2, You can use .@vue/composition-api.@vue/babel-preset-jsx
- >= 1.2.1 (You must enable composition-api support explicitly by specifying { compositionAPI: true })babel-preset-vca-jsx
- component of vue-tsx-support/lib/vca instead of defineComponent of @vue/composition-api.`jsx`
import { computed } from "@vue/composition-api";
import * as vca from "vue-tsx-support/lib/vca";
name: "MyComponent",
props: {
text: { type: String, required: true },
important: Boolean,
},
setup(p) {
const className = computed(() => p.important ? "label-important" : "label-normal");
return () => (
{p.text};
);
}
});
setup.`jsxctx.emit("rowClicked", { item, index })
import { computed, onMounted } from "@vue/composition-api";
import * as vca from "vue-tsx-support/lib/vca";
onRowClicked: { item: {}, index: number };
}
row: { item: {} }
}
name: "AwesomeList",
props: {
items: { type: Array, required: true },
rowHeight: { type: Number, required: true }
},
setup(p, ctx: vca.SetupContext
const visibleItems = computed(() => ... );
const emitRowClicked = (item: {}, index: number) => {
// Equivalent to ,
// And event name and payload type are statically checked.
vca.emitOn(ctx, "onRowClicked", { item, index });
}
{
visibleItems.value.map((item, index) => (
emitRowClicked(item, index)}>
{
// slot name ('row') and argument types are statically checked.
ctx.slots.row({ item })
}
)
}
);
}
});
`vue-tsx-supportOPTIONS
has some options which change behaviour globally.options
See under the directory.`typescriptallow-unknown-props
// enable option`
import "vue-tsx-support/options/allow-unknown-props";`$3
jsxfoo
// OK: is unknown attribute, but can be compiled`
;`$3
jsxbar
const MyComponent = vuetsx.createComponent<{ foo: string }>({ / ... / });
// OK: is unknown prop, but can be compiled``$3
jsxmin
const MyComponent = vuetsx.createComponent<{ foo: string }>({ / ... / });
// OK: and max are valid HTML attributesmin
// NG: compiler checks type of (min must be number)``$3
jsxe
const MyComponent = vuetsx.createComponent<{ foo: string }>({ / ... / });
// OK is infered as MouseEvent`router-link$3
and router-view`$3
jsx`
const MyComponent = vuetsx.createComponent<{ foo: string }>({ / ... / });
// OK`APIS
$3
typescript
import { modifiers as m } from "vue-tsx-support";
// Equivalent to
;
// Equivalent to
;
// Equivalent to
;
// Equivalent to
;
// Equivalent to `
;esc, tab, enter, space, up, down, del, left, rightdel
:warning: allows not only DELETE, but also BACKSPACE. left
:warning: and right have another behavior when specified to mouse event m.enter.esc
:warning: combination of key modifiers (e.g. ) does not work. See keys left, right, middleleft
:warning: and right have another behavior when specified to keyboard event ctrl, shift, alt, metanoctrl, noshift, noalt, nometaselfprevent, stoppreventDefault or stopPropagation of event object before executing event handler. keys(...args)`
Known key name("esc", "tab", "enter", ...) or number can be specified. typescript`
// when enter or esc pressed
;
// when 'a' pressed
;
exact(...args)`typescript``
// when CTRL, SHIFT are both pressed, and ALT, META are both not pressed
;
LICENSE