Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/lancercomet/vue2-jsx-runtime

JSX runtime for Vue 2.
https://github.com/lancercomet/vue2-jsx-runtime

jsx jsx-renderer jsx-runtime tsx tsx-renderer typescript vue vuecompositionapi vuejs2

Last synced: 23 days ago
JSON representation

JSX runtime for Vue 2.

Awesome Lists containing this project

README

        

# Vue 2 JSX Runtime

[![npm version](https://badge.fury.io/js/@lancercomet%2Fvue2-jsx-runtime.svg)](https://badge.fury.io/js/@lancercomet%2Fvue2-jsx-runtime)
![Testing](https://github.com/LancerComet/vue2-jsx-runtime/workflows/Test/badge.svg)

This package is designed for handling Vue 2 JSX.

## What's the different between this and the official solution?

The official solution is a set of Babel plugins which convert JSX to Vue render function, and this package is the [New JSX Transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) implement for Vue 2. They are just two different ways to achieve the goal.

For TypeScript users, when you use the official solution your workflow would be like:

```
TSX -> Babel -> Vite (ESBuild) / TSC / SWC -> JS
```

The Babel just slows down the whole process, and we all know that these compilers actually support JSX transforming out of box. So if we have a Vue 2 New JSX Transform runtime for those compilers, we can just get rid of Babel.

For JavaScript users, you have to use Babel with it to transform JSX into JavaScript codes. [This example](https://github.com/LancerComet/vue2-jsx-runtime-webpack) shows how to use it with Babel and Webpack.

The reasons I developed this package:

1. I want to use Vite (it's fast) without ESBuild (doesn't support EmitDecoratorMetadata), so I have to use SWC + Vite, and I also need Vue 2 JSX support, but I don't want to bring Babel in.
3. Using `v-model` in `JSX-returing-setup()` with the official solution will break the Vue 2 app. It has been a long time but still not being fixed yet.

## Setup

First, please make sure `Vue@2` has been installed in your project, then

```
npm install @lancercomet/vue2-jsx-runtime --save
```

### Using TSC

Update your `tsconfig.json` with:

```js
{
"compilerOptions": {
...
"jsx": "react-jsx", // Please set to "react-jsx".
"jsxImportSource": "@lancercomet/vue2-jsx-runtime" // Please set to package name.
}
}
```

> The reason why "jsx" should be set to "react-jsx" is this plugin has to meet the new JSX transform.

### Using SWC

In `tsconfig.json`:

```js
{
"compilerOptions": {
...
"jsx": "preserve" // Please set to "preserve".
}
}
```

And in `.swcrc`:

```js
{
"jsc": {
"transform": {
"react": {
"runtime": "automatic", // Please set to "automatic" to enable new JSX transform.
"importSource": "@lancercomet/vue2-jsx-runtime", // Please set to package name.
"throwIfNamespace": false
}
}
}
}
```

### For JavaScript users

You can use it with [@babel/plugin-transform-react-jsx](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx). You can [check this out](https://github.com/LancerComet/vue2-jsx-runtime-webpack) to see how to use it with Babel and Webpack.

### For Vite

Please read the section below.

## Usage

### Passing Value

#### Setup

```tsx
defineComponent({
setup () {
const isDisabledRef = ref(false)
return () => (
Wow such a button
)
}
})
```

#### Render function

```tsx
Vue.extend({
data () {
return {
isDisabled: false
}
},
render () {
return (
Very button
)
}
})

```

### On

#### Setup

```tsx
setup () {
const onClick = () => {}
return () => (
Click me
)
}
```

#### Render function

```tsx
Vue.extend({
methods: {
onClick () {}
},
render () {
return Click me
}
})
```

#### Using "on" object to assign multiple events for once

```tsx


```

### Native on

```tsx

```

Native is only available for Vue components.

### Rendering HTML or text

```tsx
// Setting HTML.

// Using Vue directive.
// Using dom prop.

// Setting text.

// Using Vue directive.
// Using dom prop.
```

### HTML / Component ref

#### Vue ≤ 2.6

Due to the limitation, using ref is a little different from to Vue 3.

You can check [this](https://github.com/vuejs/composition-api#limitations) out for more information.

```tsx
import { ComponentPublicInstance, defineComponent, onMounted } from '@vue/composition-api'

const Example = defineComponent({
setup () {
return () => (

Example goes here

)
}
})

const Wrapper = defineComponent({
setup (_, { refs }) {
onMounted(() => {
const div = refs.doge as HTMLElement
const example = refs.example as ComponentPublicInstance
})

return () => (


Wow very doge



)
}
})
```

#### Vue 2.7+

Vue 2.7 has its built-in composition API support, and the behavior acts as the same as Vue 3.

```tsx
import { ref, defineComponent } from 'vue'

const Example = defineComponent({
setup () {
const dogeRef = ref()

onMounted(() => {
console.log(dogeRef.value)
})

return () => (


Wow very doge


)
}
})
```

### Slot

```tsx
const Container = defineComponent({
setup (_, { slots }) {
return () => (


{ slots.default?.() }
{ slots.slot1?.() }
{ slots.slot2?.() }

)
}
})

const Example = defineComponent({
name: 'Example',
setup (_, { slots }) {
return () => (

{ slots.default?.() }

)
}
})
```

```tsx

Default
Slot1
Slot2

```

### ScopedSlots

```tsx
const MyComponent = defineComponent({
props: {
name: String as PropType,
age: Number as PropType
},

setup (props, { slots }) {
return () => (


{ slots.default?.() }
{ slots.nameSlot?.(props.name) }
{ slots.ageSlot?.(props.age) }

)
}
})
```

```tsx

Default
,
nameSlot: (name: string) =>
Name: {name}
,
ageSlot: (age: number) => {
return
Age: {age}

}
}}
/>
```

Output:

```html


Default

Name: John Smith

Age: 100


```

### Built-in directives

#### Setup

```tsx
defineComponent({
setup () {
const isDisplayRef = ref(false)
const textContentRef = ref('John Smith')
const htmlContentRef = ref('

John Smith

')

return () => (


Page content




)
}
})
```

#### Render function

```tsx
Vue.extend({
data () {
return {
isDisplay: false,
textContent: 'John Smith',
htmlContent: '

John Smith

'
}
},
render () {
return (

Page content




)
}
})
```

### v-model

#### Regular usage

```tsx
import ref from '@vue/composition-api'
import Vue from 'vue'

// Setup.
const Example = defineComponent({
setup () {
const nameRef = ref('')
return () => (




)
}
})

// In render function.
const Example = Vue.extend({
data: () => ({
name: ''
}),
render: () =>
})
```

#### With modifiers

You can use modifiers to add some extra features:

- `lazy, number, trim`: These are the built-in modifiers from Vue.
- `direct`: See **"About IME"** section below.

```tsx
const Example = Vue.extend({
data: () => ({
name: '',
age: 0
}),
render: () => (





)
})

const Example = defineComponent({
setup () {
const nameRef = ref('')
const ageRef = ref(0)

return () => (





)
}
})
```

#### With argument

Argument of v-model is designed for binding properties.

Due to limitation, binding properties in Vue 2 isn't that kinda convenient:

```tsx
const userRef = ref({
detail: {
address: ''
}
})

// This works in Vue 3 but doesn't work in Vue 2.

```

We have to use v-model like:

```tsx
const Example = defineComponent({
setup () {
const userRef = ref({
username: '',
age: 0,
detail: {
address: ''
}
})

return () => (






)
}
})
```

#### About IME

By default, `v-model` will only assign what you have selected from IME. If you were typing in IME, `v-model` would do nothing.

If you want to disable this behavior, add `direct` modifier:

```tsx
{/* It will sync everything you have typed in IME. */}

{/* By default, it will only assign what you have selected from IME. */}

```

### Key

Due to the limitation, we have to use `v-bind:key`:

```tsx
{
userList.map(item => (

{item.name}

))
}
```

### Transition / TransitionGroup

```tsx
import Vue from 'vue'

const Transition = Vue.component('Transition')
const TransitionGroup = Vue.component('TransitionGroup')

setup () {
return () => (



Some element

Some element



Some element



)
}
```

or

```tsx
setup () {
return () => (



Some element

Some element



Some element



)
}
```

## Compatibility

These format below are also available, but they are NOT recommended, just for compatibility.

### On

```tsx



```

### v-model

```tsx

```

### Key

```tsx



```

## For Vite users

For Vite users, it's better to use TSC or SWC instead of built-in ESBuild. Because ESBuild is very finicky at handling JSX for now, and it gives you no room to change its behavior.

For faster compilation, SWC is recommended. You can use [unplugin-swc](https://github.com/egoist/unplugin-swc) to make Vite uses SWC.

Once you have switched to SWC (TSC) from ESBuild, you will not only be able to use JSX, but also get more features like `emitDecoratorMetadata` which is not supported by ESBuild, and the whole process is still darn fast.

### Configuration

After you have configured SWC (see Setup section above):

1. Install [unplugin-swc](https://github.com/egoist/unplugin-swc).

```
npm install unplugin-swc --save-dev
```

2. Update `vite.config.ts`:

```ts
import { defineConfig } from 'vite'
import swc from 'unplugin-swc'
import { createVuePlugin } from 'vite-plugin-vue2'

export default defineConfig({
plugins: [
swc.vite(),
createVuePlugin(),
...
]
})
```

## Mixing usage

If you have to use `JSX` and `SFC` together in Vite, you need to update your Vite config:

```ts
import { defineConfig } from 'vite'
import swc from 'unplugin-swc'
import { createVuePlugin } from 'vite-plugin-vue2'

const swcPlugin = swc.vite()

export default defineConfig({
plugins: [
{
...swcPlugin,
transform (code, id, ...args) {
if (
id.endsWith('.tsx') || id.endsWith('.ts') ||
(id.includes('.vue') && id.includes('lang.ts'))
) {
return swcPlugin.transform.call(this, code, id, ...args)
}
}
},

createVuePlugin(),
...
]
})
```

This will make SWC to skip compiling Non-Typescript codes in Vue SFC.

## Hot Reload

Use [vite-plugin-vue2-hmr](https://github.com/LancerComet/vite-plugin-vue2-hmr) to enable Vue2 JSX hot reload in Vite.

## Contributing

Feel free to open issue or pull request to make it better.

## References

- [Introducing the New JSX Transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)
- [Render Function](https://vuejs.org/guide/extras/render-function.html)
- [vue-jsx-runtime (Vue 3)](https://github.com/dolymood/vue-jsx-runtime)
- [@vue/composition-api](https://github.com/vuejs/composition-api)
- [@vue/babel-plugin-jsx](https://github.com/vuejs/babel-plugin-jsx)