JSX runtime for Vue 2.
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 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
`
$3
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.
$3
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
}
}
}
}
`
$3
You can use it with @babel/plugin-transform-react-jsx. You can check this out to see how to use it with Babel and Webpack.
$3
Please read the section below.
Usage
$3
#### Setup
`tsx
defineComponent({
setup () {
const isDisabledRef = ref(false)
return () => (
)
}
})
`
#### Render function
`tsx
Vue.extend({
data () {
return {
isDisabled: false
}
},
render () {
return (
)
}
})
`
$3
#### Setup
`tsx
setup () {
const onClick = () => {}
return () => (
)
}
`
#### Render function
`tsx
Vue.extend({
methods: {
onClick () {}
},
render () {
return
}
})
`
#### Using "on" object to assign multiple events for once
`tsx
click: onClick,
focus: onFocus,
blur: onBlur
}}>
$3
`tsx
`
Native is only available for Vue components.
$3
`tsx
// Setting HTML.
// Using Vue directive.
// Using dom prop.
// Setting text.
// Using Vue directive.
// Using dom prop.
`
$3
#### Vue ≤ 2.6
Due to the limitation, using ref is a little different from to Vue 3.
You can check this 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
)
}
})
`
$3
`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
`
$3
`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
name='John Smith'
age={100}
scopedSlots={{
default: () => Default,
nameSlot: (name: string) => Name: {name},
ageSlot: (age: number) => {
return Age: {age}
}
}}
/>
`
Output:
`html
Default
Name: John Smith
Age: 100
`
$3
#### 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
)
}
})
`
$3
#### 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. /}
`
$3
Due to the limitation, we have to use v-bind:key:
`tsx
{
userList.map(item => (
{item.name}
))
}
`
$3
`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.
$3
`tsx
`
$3
`tsx
`
$3
`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 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.
$3
After you have configured SWC (see Setup section above):
1. Install 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(),
...
]
})
``