Despite common conception, JSX is not exclusive to React, although it was initially developed for and popularized by React as it’s way for writing markup within JavaScript. And even though Vue has it’s own alternative template syntax, official docs acknowledge it’s limitations and the benefits that come from having the full programmatic power of JavaScript at your disposal when generation the markup and recognize the need for it for solving complex scenarios.
Let’s have a look on what are the options with using it in Vue.
How JSX is possible in Vue
Vue templates are syntactic sugar for you to right a more readable and DX-friendly way to create virtual DOM nodes (vnodes how Vue calls them) in an HTML-like markup. You can check in the compiled templates that what it translates into under the hood are render functions with calls to function that creates vnodes that is h()
function. You can read more on relationship between templates and render function in Vue docs.
Vue also provides APIs that allow us to skip the template compilation step and directly author render functions via exporting h()
function that you can manually call. This is described in docs on Render Functions & JSX.
JSX here comes into play here as an alternative syntactic sugar to define render functions, but contrary to Vue templates that are static JSX is written as part of your code directly so can be manipulated with it.
Setup
create-vue
and Vue CLI both have options for scaffolding projects with pre-configured JSX support. In order to manually enable an existing Vite-based Vue project with JSX you should perform the following configuration. (If you have different environment, see Vue docs on JSX/TSX for configuring options)
Install Vite plugin with command like:
Next, add plugin to the vite.config.ts:
If you are using Typescript, make changes to tsconfig.json :
Vue docs are not so exhaustive in the section covering the topic and just tell that in Composition API a render function can be returned from setup() hook to be treated as component’s render output. Then they only showcase usage with h() function for explicitly creating vnodes:
The docs then imply that the render function can be written with JSX instead but don’t provide a complete example on this so let’s explore the options more closely.
The equivalent of this with JSX would be:
The file extension should be .jsx
here to enable it.
Or the same in Typescript in .tsx
and wrapped with defineComponent
:
The former is what most articles show as examples and this may suggest that in order to use JSX in Vue one should ditch SFC and instead declare Options API style component mixing it up with setup()
hook if you want to stick to the Composition API. But this is not the case.
You can use to JSX or TSX inside Vue SFC with specifying lang="jsx"
or lang="tsx"
on <script>
tag:
You can even use JSX in <script setup>
with lang="tsx"
or lang="tsx"
. In this scenario you can declare **Functional Components** in <script setup>
and use them in <template>
to render part of the markup**.**
Functional components are a lightweight form of component that must be pure functions and can’t have state or side-effect (pretty much like Function components in React before React hooks).
You can declare a single **Functional Components** to produce your entire SFC markup like we did in the previous examples with component definition, you just need to call it not as a function but instantiate as a component:
But perhaps the most potent is that you can still use <template>
for most of your template like you normally would in Vue and occasionally embed a **Functional Component** to produce some parts of the markup that can’t be easily expressed with the template syntax.
One of the notable limitations is inability to define a template part and reuse it across template, for example when some block needs to appear in different places in the markup depending on some condition (although there is now an alternative pure-template solution for this problem that is explored in another article).
This part can be extracted into a **Functional Component** and reused across <template>
:
Functional Component can be passed props, just like you would pass them to normal components: