vue-tsx-macros
Description
Set of marcos for convenient definitions of Vue JSX components.
Enhances developer experience by removing duplication and improving type inference.
Write type-safe components with less extra tooling (like vue-tsc
/Volar
).
Includes
-
typescript
types -
babel
plugin for code transformation
Features
-
component$
- infers component
name
from function or variable name - infers component
props
similar todefineProps()
andwithDefaults()
fromvue
- infers component
emits
similar todefineEmits()
fromvue
- infers component
-
useRender$
- allows to expose instance properties with correct type inference and at the same time use
render
function insidesetup
- allows to expose instance properties with correct type inference and at the same time use
-
defaultProps$
- adds type assistance for default props definition, in case you want to reuse them outside of
withDefaults()
- adds type assistance for default props definition, in case you want to reuse them outside of
How to use
- Install
npm install vue-tsx-macros
- Include babel plugin into your build configuration
- Vite
Add babel plugin to @vitejs/plugin-vue-jsx
options
import vueJsx from "@vitejs/plugin-vue-jsx";
export default {
plugins: [
vueJsx({
babelPlugins: ["vue-tsx-macros/babel-plugin"],
}),
],
};
If you are using nuxt
with vite
- use extendViteConfig
.
- Babel
Add plugin to your babel config alongside @vue/babel-plugin-jsx
{
"plugins": ["@vue/babel-plugin-jsx", "vue-tsx-macros/babel-plugin"]
}
Plugin options
{
/**
* If false, usage of emits argument in `component$` will throw compile-time error
* @default true
*/
allowEmits: boolean;
}
- Use
import { ref } from "vue";
import { component$, useRender$ } from "vue-tsx-macros";
export const Example = component$().define((props, { emit }) => {
const counterRef = ref<InstanceType<typeof Counter>>();
onMounted(() => {
const id = setInterval(() => {
counterRef.value?.increment();
}, 5000);
onUnmounted(() => clearInterval(id));
});
return () => (
<div>
{"A counter"}
<Counter ref={counterRef} />
</div>
);
});
type CounterProps = {
initial?: number;
};
type CounterEmits = {
update: (value: number) => void;
};
const Counter = component$<ExampleProps, ExampleEmits>().define(
(props, { emit }) => {
const count = ref(props.initial || 0);
const increment = (value = 1) => {
count.value += value;
emit("update", counter.value);
};
useRender$(() => (
<button onClick={() => increment()}>{count.value}</button>
));
return { increment };
}
);
Counter.inheritAttrs = false;
Transform examples
- Standard component
export const Example = component$<{ initialValue?: number }>().define(
(props) => {
const counter = ref(props.initialValue || 0);
const onClick = () => (counter.value += 1);
return () => <button onClick={onClick}>{counter.value}</button>;
}
);
export const Example = {
name: "Example",
props: ["initialValue"],
setup: (props) => {
const counter = ref(props.initialValue || 0);
const onClick = () => (counter.value += 1);
return () => <button onClick={onClick}>{counter.value}</button>;
},
};
- With defaultProps
export const Example = component$<{ initialValue?: number }>()
.withDefaults({ initialValue: 0 })
.define((props) => {
const counter = ref(props.initialValue);
const onClick = () => (counter.value += 1);
return () => <button onClick={onClick}>{counter.value}</button>;
});
const _defaultProps = {
initialValue: 0,
};
export const Example = defineComponent({
name: "Example",
props: {
initialValue: {
type: null,
default: _defaultProps["initialValue"],
},
},
setup: (props) => {
const counter = ref(props.initialValue);
const onClick = () => counter.value + 1;
return () => <button onClick={onClick}>{counter.value}</button>;
},
});
- With emits
type Props = {
initialValue: number;
};
type Emits = {
update: (value: number) => void;
};
export const Example = component$<Props, Emits>().define((props, { emit }) => {
const counter = ref(props.initialValue);
const onClick = () => {
counter.value += 1;
emit("update", counter.value);
};
return () => <button onClick={onClick}>{counter.value}</button>;
});
type Props = {
initialValue: number,
};
type Emits = {
update: (value: number) => void,
};
export const Example = defineComponent({
name: "Example",
props: ["initialValue"],
emits: ["update"],
setup: (props, { emit }) => {
const counter = ref(props.initialValue);
const onClick = () => {
counter.value += 1;
emit("update", counter.value);
};
return () => <button onClick={onClick}>{counter.value}</button>;
},
});
- Expose properties
export const Example = component$().define(() => {
const counter = ref(0);
const increment = () => (counter.value += 1);
useRender$(() => <button onClick={increment}>{counter.value}</button>);
return { increment };
});
let _render;
export const Example = defineComponent({
name: "Example",
setup: () => {
const counter = ref(0);
const increment = () => (counter.value += 1);
_render = () => <button onClick={increment}>{counter.value}</button>;
return {
increment,
};
},
render() {
return _render();
},
});
- Functional component
export const Example = component$<{ size?: number }>().functional(
({ size = 500 }) => {
return <div data-size={size} />;
}
);
export const Example = Object.assign(
({ size = 500 }) => {
return <div data-size={size} />;
},
{
displayName: "Example",
props: ["size"],
}
);
Limitations
- In
component$
, type parameters forprops
andemits
must beTypeLiteral
orTypeReference
toInterfaceDeclaration
orTypeAliasDeclaration
withTypeLiteral
in the same file (similar todefineProps()
fromvue
). Note that only explicitly enumerated properties are included.
// ok
export const Example = component$<{ size: number }>().define(...)
// ok
export interface ExampleProps {
size: number
}
export const Example = component$<Props>().define(...)
// ok
export type ExampleProps = {
size: number
}
export const Example = component$<Props>().define(...)
// compile-time error
import { SomeProps } from './some-file'
export const Example = component$<SomeProps>().define(...)
// compile-time error
export type ExampleProps = { size: number } & { extra: number }
export const Example = component$<ExampleProps>().define(...)
-
useRender$
must be used only incomponent$().define(
function.
// ok
export const Example = component$().define(() => {
const counter = ref(0);
const increment = () => (counter.value += 1);
useRender$(() => <button onClick={increment}>{counter.value}</button>);
return { increment };
});
// compile-time error
export const Example = component$().define((props) => {
const counter = ref(0);
const increment = () => {
counter.value += 1;
useRender$(() => <button onClick={increment}>{counter.value}</button>);
};
return { increment };
});