Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/wonderful-panda/vue-tsx-support
TSX (JSX for TypeScript) support library for Vue
https://github.com/wonderful-panda/vue-tsx-support
jsx tsx typescript vue
Last synced: 7 days ago
JSON representation
TSX (JSX for TypeScript) support library for Vue
- Host: GitHub
- URL: https://github.com/wonderful-panda/vue-tsx-support
- Owner: wonderful-panda
- License: mit
- Created: 2017-08-07T13:06:45.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-03-04T03:27:12.000Z (almost 2 years ago)
- Last Synced: 2024-10-29T17:27:11.967Z (2 months ago)
- Topics: jsx, tsx, typescript, vue
- Language: TypeScript
- Size: 1.54 MB
- Stars: 578
- Watchers: 4
- Forks: 40
- Open Issues: 25
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- stars - wonderful-panda/vue-tsx-support
README
[![npm version](https://badge.fury.io/js/vue-tsx-support.svg)](https://badge.fury.io/js/vue-tsx-support)
[![build](https://github.com/wonderful-panda/vue-tsx-support/workflows/build/badge.svg)](https://github.com/wonderful-panda/vue-tsx-support/actions?query=workflow%3Abuild)# vue-tsx-support
TSX (JSX for TypeScript) support library for Vue## :warning: BREAKING CHANGES
If your project already uses vue-tsx-support v2, see [Migration from V2](#migration-from-v2) section.
## TABLE OF CONTENTS
- [vue-tsx-support](#vue-tsx-support)
- [:warning: BREAKING CHANGES](#warning-breaking-changes)
- [TABLE OF CONTENTS](#table-of-contents)
- [NEW FEATURES](#new-features)
- [PREREQUISITE](#prerequisite)
- [INSTALLATION](#installation)
- [Migration from V2](#migration-from-v2)
- [USAGE](#usage)
- [Intrinsic elements](#intrinsic-elements)
- [Components](#components)
- [Make existing components tsx-ready.](#make-existing-components-tsx-ready)
- [Writing components by object-style API (Like `Vue.extend`)](#writing-components-by-object-style-api-like-vueextend)
- [Writing component by class-style API (`vue-class-component` and/or `vue-property-decorator`)](#writing-component-by-class-style-api-vue-class-component-andor-vue-property-decorator)
- [1. Extends from `Component` class provided by `vue-tsx-support`](#1-extends-from-component-class-provided-by-vue-tsx-support)
- [2. Add `_tsx` field to tell type information to TypeScript.](#2-add-_tsx-field-to-tell-type-information-to-typescript)
- [Writing component by composition api (`@vue/composition-api`)](#writing-component-by-composition-api-vuecomposition-api)
- [OPTIONS](#options)
- [allow-element-unknown-attrs](#allow-element-unknown-attrs)
- [allow-unknown-props](#allow-unknown-props)
- [enable-html-attrs](#enable-html-attrs)
- [enable-nativeon](#enable-nativeon)
- [enable-vue-router](#enable-vue-router)
- [allow-props-object](#allow-props-object)
- [APIS](#apis)
- [modifiers](#modifiers)
- [Available modifiers](#available-modifiers)
- [LICENSE](#license)## NEW FEATURES
- Typesafe emit for declared events
- @vue/composition-api support## PREREQUISITE
- 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](https://github.com/vuejs/composition-api).
## INSTALLATION
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](https://vuejs.org/v2/guide/installation.html)
- [Vue CLI](https://cli.vuejs.org/)
- [vuejs/jsx](https://github.com/vuejs/jsx):bulb: If you want use @vue/composition-api, `@vue/babel-preset-jsx` >= 1.2.1 or [babel-preset-vue-vca](https://github.com/luwanquan/babel-preset-vca-jsx) 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",
"..."
]
}
```### Migration from V2
1. In `tsconfig.json`, set `"VueTsxSupport"` to `jsxFactory`
2. Enable `allow-props-object` option (Optional)
## USAGE
### Intrinsic elements
Standard HTML elements are defined as intrinsic elements.
So, compiler can check attribute names and attribute types of them:```jsx
;
// OK
// OK
;
// OK
;
// NG: because `href` is not a valid attribute of `div`
;
// NG: because `id` must be a string
;
```Lower case tags are treated as unknown intrinsic element.
TypeScript checks nothing for such tags.```jsx
// OK```
### Components
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().convert(AwesomeButtonOrig);
```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
import { VNode } from "vue";
const App = Vue.extend({
render(): VNode {
return (
console.log(`${p.item.text} clicked!`)}
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.
```jsx
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, // `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<
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 })
}
)
}
);
}
});
```#### Writing component by class-style API (`vue-class-component` and/or `vue-property-decorator`)
If you prefer class-style component by using `vue-class-component` and/or `vue-property-decorator`,
there are some options to make it tsx-ready.##### 1. Extends from `Component` 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";type MyComponentProps = {
text: string;
important?: boolean;
}@Component
export class MyComponent extends tsx.Component {
@Prop({ type: String, required: true })
text!: string;
@Prop(Boolean)
important?: boolean;get className() {
return this.important ? "label-important" : "label-normal";
}
onClick(event: MouseEvent) {
this.$emit("ok", event);
}
render(): VNode {
return {this.text};
}
}
```:warning: Unfortunately, `vue-tsx-support` can't infer prop types automatically in this case, so you must write type manually.
##### 2. Add `_tsx` field to tell type information to TypeScript.
```jsx
import { VNode } from "vue";
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";@Component
export class MyComponent extends Vue {
_tsx!: {
// specify props type to `props`.
props: Pick
};@Prop({ type: String, required: true })
text!: string;
@Prop(Boolean)
important?: boolean;get className() {
return this.important ? "label-important" : "label-normal";
}
render(): VNode {
return {this.text};
}
}
```You can use `DeclareProps` instead of `{ props: T }`.
```jsx
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";@Component
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps>;/* ...snip... */
}
```:bulb: `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";@Component
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps>;/* ...snip... */
}
```:bulb: When you can make all data, computed and methods private, you can use `AutoProps` instead.
`AutoProps` picks all public members other than members from component options(`render`, `created` etc).
```jsx
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";@Component
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps>@Prop({ type: String, required: true })
text!: string;@Prop(Boolean)
important?: boolean;// data
private count = 0;
// computed
private get className() {
return this.important ? "label-important" : "label-normal";
}
// methods
private onClick() {
this.count += 1;
}render(): VNode {
return (
{`${this.text}-${this.count}`}
);
}
}
```:bulb: If your component has custom events, you can specify events handlers type additionally.
```jsx
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";@Component
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps> &
tsx.DeclareOnEvents<{ onOk: string }>;/* ...snip... */
}
```:bulb: If your component uses scoped slots, you should add type to `$scopedSlots` by `tsx.InnerScopedSlots`.
```jsx
import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";@Component
export class MyComponent extends Vue {
_tsx!: tsx.DeclareProps>;$scopedSlots!: tsx.InnerScopedSlots<{ default?: string }>;
/* ...snip... */
}
```#### Writing component by composition api (`@vue/composition-api`)
Vue 3 is not supported.
To use composition api with Vue 2, You can use `@vue/composition-api`.There are 2 babel presets which support JSX syntax with `@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`To make TSX-ready component by composition api, use `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";const MyComponent = vca.component({
name: "MyComponent",
props: {
text: { type: String, required: true },
important: Boolean,
},
setup(p) {
const className = computed(() => p.important ? "label-important" : "label-normal");
return () => (
{p.text};
);
}
});
```If your component has custom event or scoped slots, specify them types in 2nd argument of `setup`.
```jsx
import { computed, onMounted } from "@vue/composition-api";
import * as vca from "vue-tsx-support/lib/vca";type AwesomeListEvents = {
onRowClicked: { item: {}, index: number };
}type AwesomeListScopedSlots = {
row: { item: {} }
}export const AwesomeList = vca.component({
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 `ctx.emit("rowClicked", { item, index })`,
// And event name and payload type are statically checked.
vca.emitOn(ctx, "onRowClicked", { item, index });
}return () => (
{
visibleItems.value.map((item, index) => (
emitRowClicked(item, index)}>
{
// slot name ('row') and argument types are statically checked.
ctx.slots.row({ item })
}
)
}
);
}
});
```## OPTIONS
`vue-tsx-support` has some options which change behaviour globally.
See under the `options` directory.To enable each options, import them somewhere
```typescript
// enable `allow-unknown-props` option
import "vue-tsx-support/options/allow-unknown-props";
```:warning: Scope of option is whole project, not a file.
### allow-element-unknown-attrs
Make enabled to specify unknown attributes to intrinsic elements
```jsx
;
// OK:`foo` is unknown attribute, but can be compiled
```### allow-unknown-props
Make enabled to specify unknown props to Vue component.
```jsx
const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK: `bar` is unknown prop, but can be compiled
;
```### enable-html-attrs
Make enabled to specify HTML attributes to Vue component.
```jsx
const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK: `min` and `max` are valid HTML attributes
;
// NG: compiler checks type of `min` (`min` must be number)
;
```### enable-nativeon
Make enabled to specify native event listeners to Vue component.
```jsx
const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK
... } />; // and `e` is infered as MouseEvent
```### enable-vue-router
Add definitions of `router-link` and `router-view`
### allow-props-object
Make enabled to pass props as "props".
```jsx
const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK
;
```## APIS
### modifiers
Event handler wrappers which work like some event modifiers available in template
```typescript
import { modifiers as m } from "vue-tsx-support";// Basic usage:
`
// Equivalent to `
;// Use multiple modifiers:
`
// Equivalent to `
;// Use without event handler:
`
// Equivalent to `
;// Use multiple keys:
`
// Equivalent to `
;// Use exact modkey combination:
`
// Equivalent to `
;
```#### Available modifiers
* `esc`, `tab`, `enter`, `space`, `up`, `down`, `del`, `left`, `right`
Execute event handler only when specified key is pressed.
:warning: `del` allows not only DELETE, but also BACKSPACE.
:warning: `left` and `right` have another behavior when specified to mouse event
:warning: combination of key modifiers (e.g. `m.enter.esc`) does not work. See [keys](#keys)* `left`, `right`, `middle`
Execute event handler only when specified mouse button is pressed.
:warning: `left` and `right` have another behavior when specified to keyboard event* `ctrl`, `shift`, `alt`, `meta`
Execute event handler only when specified system modifier key is pressed.
* `noctrl`, `noshift`, `noalt`, `nometa`
Execute event handler only when specified system modifier key is not pressed.
* `self`
Execute event handler only when event.target is the element itself (not from children).
* `prevent`, `stop`
Call `preventDefault` or `stopPropagation` of event object before executing event handler.
Execute event handler only when one of specified key is pressed.
Known key name("esc", "tab", "enter", ...) or number can be specified.```typescript
;
// when enter or esc pressed
// when 'a' pressed
;
```* `exact(...args)`
Execute event handler only when specified system modifier keys are all pressed, and others are not pressed.
```typescript
;
// when CTRL, SHIFT are both pressed, and ALT, META are both not pressed
```## LICENSE
MIT