All Projects → wonderful-panda → Vue Tsx Support

wonderful-panda / Vue Tsx Support

Licence: mit
TSX (JSX for TypeScript) support library for Vue

Programming Languages

typescript
32286 projects

Labels

Projects that are alternatives of or similar to Vue Tsx Support

harshhhdev.github.io
Harsh Singh's personal blog and portfolio ⚡ built with Next.js
Stars: ✭ 23 (-94.21%)
Mutual labels:  jsx
React Server Example Tsx
⚛️ Boilerplate for isomorphic web app with React server-side rendering in TypeScript
Stars: ✭ 256 (-35.52%)
Mutual labels:  jsx
React Element To Jsx String
Turn a ReactElement into the corresponding JSX string
Stars: ✭ 349 (-12.09%)
Mutual labels:  jsx
myra
Myra is a simple and small Typescript framework for building web interfaces.
Stars: ✭ 21 (-94.71%)
Mutual labels:  jsx
lowcode
React Lowcode - prototype, develop and maintain internal apps easier
Stars: ✭ 32 (-91.94%)
Mutual labels:  jsx
Express React Fullstack
Simple, Useful Full Stack Express and React Application
Stars: ✭ 286 (-27.96%)
Mutual labels:  jsx
eslint-plugin-test-selectors
Enforces that data-test-id attributes are added to interactive DOM elements (JSX) to help with UI testing. JSX only.
Stars: ✭ 19 (-95.21%)
Mutual labels:  jsx
Gdlauncher
GDLauncher is a simple, yet powerful Minecraft custom launcher with a strong focus on the user experience
Stars: ✭ 386 (-2.77%)
Mutual labels:  jsx
Shrimpit
Shrimpit 🍤 is a small CLI analysis tool for checking unused JavaScript, JSX & Vue templates ES6 exports in your project.
Stars: ✭ 255 (-35.77%)
Mutual labels:  jsx
Vue3 Jd H5
🔥 Based on vue3.0.0, vant3.0.0, vue-router v4.0.0-0, vuex^4.0.0-0, vue-cli3, mockjs, imitating Jingdong Taobao, mobile H5 e-commerce platform! 基于vue3.0.0 ,vant3.0.0,vue-router v4.0.0-0, vuex^4.0.0-0,vue-cli3,mockjs,仿京东淘宝的,移动端H5电商平台!
Stars: ✭ 328 (-17.38%)
Mutual labels:  jsx
vue-typescript-tsx
🤓vue typeScript jsx 工程示例
Stars: ✭ 32 (-91.94%)
Mutual labels:  jsx
react-pits
React 中的坑
Stars: ✭ 29 (-92.7%)
Mutual labels:  jsx
Dukpy
Simple JavaScript interpreter for Python
Stars: ✭ 296 (-25.44%)
Mutual labels:  jsx
react-enterprise-starter-kit
Highly Scalable Awesome React Starter Kit for an enterprise application with a very easy maintainable codebase. 🔥
Stars: ✭ 55 (-86.15%)
Mutual labels:  jsx
Emoji
⭕〽️❗Awesome Omi 创意坊 - 如果你愿意,我希望未来是你和Omi~
Stars: ✭ 355 (-10.58%)
Mutual labels:  jsx
11tyby
Simple 11ty setup using TypeScript, SASS, Preact with partial hydration, and other useful things. Aims to provide the DX of Gatsby, but using 11ty!
Stars: ✭ 38 (-90.43%)
Mutual labels:  jsx
Vscode Stylelint
A Visual Studio Code extension to lint CSS/SCSS/Less with stylelint
Stars: ✭ 260 (-34.51%)
Mutual labels:  jsx
React Jsx Parser
A React component which can parse JSX and output rendered React Components.
Stars: ✭ 394 (-0.76%)
Mutual labels:  jsx
Msx
JSX for Mithril.js 0.x
Stars: ✭ 370 (-6.8%)
Mutual labels:  jsx
Sucrase
Super-fast alternative to Babel for when you can target modern JS runtimes
Stars: ✭ 4,436 (+1017.38%)
Mutual labels:  jsx

npm version build

vue-tsx-support

TSX (JSX for TypeScript) support library for Vue

⚠️ BREAKING CHANGES

If your project already uses vue-tsx-support v2, see Migration from V2 section.

TABLE OF CONTENTS

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.

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.

    💡 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

    {
      "compilerOptions": {
        "jsx": "preserve",
        "jsxFactory": "VueTsxSupport",
        "...": "..."
      },
      "include": [
        "..."
      ]
    }
    
  4. import vue-tsx-support/enable-check.d.ts somewhere,

    import "vue-tsx-support/enable-check"
    

    or add it to "include" in tsconfig.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:

// 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 string
<div id={ 1 } />;

Lower case tags are treated as unknown intrinsic element. TypeScript checks nothing for such tags.

// OK
<foo id="unknown" unknownattr={ 1 } />

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.

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 <AwesomeButton text="Click Me!" />;
  }
});

You can add type information to existing component without modifying component itself, like below:

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<AwesomeButtonProps>().convert(AwesomeButtonOrig);

You also can specify custom event types as second type parameter, and scoped slot types as third type parameter.

For example:

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<Item>;
  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:

import { VNode } from "vue";
const App = Vue.extend({
render(): VNode {
  return (
    <AwesomeList
      items={this.items}
      rowHeight={32}
      onRowClicked={p => console.log(`${p.item.text} clicked!`)}
      scopedSlots={{
        row: item => <div>{item.text}</div>
      }}
    />
  );
}
});

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.

⚠️ In some environment, as const may be needed to make prop required properly.

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 <span class={this.className} onClick={this.onClick}>{this.text}</span>;
  }
});

💡 You can use component as as shorthand of componentFactory.create.

import * as tsx from "vue-tsx-support";
const MyComponent = tsx.component({
  /* snip */
});

If your component has custom events or scoped slots, use componentFactoryOf instead.

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 (
      <div class={style.container}>
        {
          this.visibleItems.map((item, index) => (
            <div style={this.rowStyle} onClick={() => this.$emit("rowClicked", { item, index })}>
              {
                // slot name ('row') and argument types are statically checked.
                this.$scopedSlots.row({ item })
              }
            <div>
          )
        }
      </div>
    );
  }
});

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
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<MyComponentProps> {
  @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 <span class={this.className} onClick={this.onClick}>{this.text}</span>;
  }
}

⚠️ 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.
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<MyComponent, "text" | "important">
  };

  @Prop({ type: String, required: true })
  text!: string;
  @Prop(Boolean)
  important?: boolean;

  get className() {
    return this.important ? "label-important" : "label-normal";
  }
  render(): VNode {
    return <span class={this.className}>{this.text}</span>;
  }
}

You can use DeclareProps<T> instead of { props: T }.

import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";

@Component
export class MyComponent extends Vue {
  _tsx!: tsx.DeclareProps<Pick<MyComponent, "text" | "important">>;

  /* ...snip... */
}

💡 PickProps is more convenient than Pick here, it removes attributes from Vue from completion candidates. (e.g. $data, $props, and so on)

import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";

@Component
export class MyComponent extends Vue {
  _tsx!: tsx.DeclareProps<tsx.PickProps<MyComponent, "text" | "important">>;

  /* ...snip... */
}

💡 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).

import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";

@Component
export class MyComponent extends Vue {
  _tsx!: tsx.DeclareProps<tsx.AutoProps<MyComponent>>

  @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 (
      <span class={this.className} onClick={this.onClick}>
        {`${this.text}-${this.count}`}
      </span>
    );
  }
}

💡 If your component has custom events, you can specify events handlers type additionally.

import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";

@Component
export class MyComponent extends Vue {
  _tsx!: tsx.DeclareProps<PickProps<MyComponent, "text" | "important">> &
    tsx.DeclareOnEvents<{ onOk: string }>;

  /* ...snip... */
}

💡 If your component uses scoped slots, you should add type to $scopedSlots by tsx.InnerScopedSlots.

import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";

@Component
export class MyComponent extends Vue {
  _tsx!: tsx.DeclareProps<PickProps<MyComponent, "text" | "important">>;

  $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.

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 () => (
      <span class={className.value}>{p.text}</span>;
    );
  }
});

If your component has custom event or scoped slots, specify them types in 2nd argument of setup.

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<AwesomeListEvents, AwesomeListScopedSlots>) {
    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 () => (
      <div class={style.container}>
        {
          visibleItems.value.map((item, index) => (
            <div onClick={() => emitRowClicked(item, index)}>
              {
                // slot name ('row') and argument types are statically checked.
                ctx.slots.row({ item })
              }
            <div>
          )
        }
      </div>
    );
  }
});

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";

⚠️ 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

allow-props-object

Make enabled to pass props as "props".

const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK
<MyComponent props={{ foo: "foo" }} />;

APIS

modifiers

Event handler wrappers which work like some event modifiers available in template

import { modifiers as m } from "vue-tsx-support";

// Basic usage:
//  Equivalent to `<div @keydown.enter="onEnter" />`
<div onKeydown={m.enter(this.onEnter)} />;

// Use multiple modifiers:
//  Equivalent to `<div @keydown.enter.prevent="onEnter" />`
<div onKeydown={m.enter.prevent(this.onEnter)} />;

// Use without event handler:
//  Equivalent to `<div @keydown.esc.prevent />`
<div onKeydown={m.esc.prevent} />;

// Use multiple keys:
//  Equivalent to `<div @keydown.enter.esc="onEnterOrEsc" />`
<div onKeydown={m.keys("enter", "esc")(this.onEnterOrEsc)} />;

// Use exact modkey combination:
//  Equivalent to `<div @keydown.65.ctrl.alt.exact="onCtrlAltA" />`
<div onKeydown={m.keys(65).exact("ctrl", "alt")(this.onCtrlAltA)} />;

Available modifiers

  • esc, tab, enter, space, up, down, del, left, right

    Execute event handler only when specified key is pressed.
    ⚠️ del allows not only DELETE, but also BACKSPACE.
    ⚠️ left and right have another behavior when specified to mouse event
    ⚠️ combination of key modifiers (e.g. m.enter.esc) does not work. See keys

  • left, right, middle

    Execute event handler only when specified mouse button is pressed.
    ⚠️ 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.

  • keys(...args)

    Execute event handler only when one of specified key is pressed.
    Known key name("esc", "tab", "enter", ...) or number can be specified.

    // when enter or esc pressed
    <div onKeydown={m.keys("enter", "esc")(handler)} />;
    // when 'a' pressed
    <div onKeydown={m.keys(65)(handler)} />;
    
  • exact(...args)

    Execute event handler only when specified system modifier keys are all pressed, and others are not pressed.

    // when CTRL, SHIFT are both pressed, and ALT, META are both not pressed
    <div onClick={m.exact("ctrl", "shift")(handler)} />;
    

LICENSE

MIT

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].