New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

vue-tsx-support

Package Overview
Dependencies
Maintainers
1
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

vue-tsx-support

TSX (JSX for TypeScript) support library for Vue

  • 3.2.0
  • latest
  • Source
  • npm
  • Socket score

Version published
Maintainers
1
Created
Source

npm version build

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

    :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

    {
      "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.

:warning: 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>;
  }
});

:bulb: 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>;
  }
}

: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.
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... */
}

:bulb: 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... */
}

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

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>
    );
  }
}

:bulb: 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... */
}

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

:warning: 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.
    :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

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

  • 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

Keywords

FAQs

Package last updated on 19 Jun 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc