npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vue-tsx-support

v3.2.0

Published

TSX (JSX for TypeScript) support library for Vue

Downloads

12,304

Readme

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