{"id":15067472,"url":"https://github.com/lancercomet/vue2-jsx-runtime","last_synced_at":"2025-07-29T11:33:45.917Z","repository":{"id":43923962,"uuid":"493400995","full_name":"LancerComet/vue2-jsx-runtime","owner":"LancerComet","description":"JSX runtime for Vue 2.","archived":false,"fork":false,"pushed_at":"2023-11-18T14:52:04.000Z","size":587,"stargazers_count":25,"open_issues_count":2,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-15T21:00:04.853Z","etag":null,"topics":["jsx","jsx-renderer","jsx-runtime","tsx","tsx-renderer","typescript","vue","vuecompositionapi","vuejs2"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LancerComet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-05-17T20:17:20.000Z","updated_at":"2025-01-03T09:18:03.000Z","dependencies_parsed_at":"2024-01-02T23:49:58.628Z","dependency_job_id":null,"html_url":"https://github.com/LancerComet/vue2-jsx-runtime","commit_stats":{"total_commits":44,"total_committers":2,"mean_commits":22.0,"dds":"0.11363636363636365","last_synced_commit":"d9bcb69790224d0f1cda1ad7f596251aa50fefc2"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/LancerComet/vue2-jsx-runtime","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LancerComet%2Fvue2-jsx-runtime","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LancerComet%2Fvue2-jsx-runtime/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LancerComet%2Fvue2-jsx-runtime/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LancerComet%2Fvue2-jsx-runtime/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LancerComet","download_url":"https://codeload.github.com/LancerComet/vue2-jsx-runtime/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LancerComet%2Fvue2-jsx-runtime/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267678448,"owners_count":24126333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-29T02:00:12.549Z","response_time":2574,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jsx","jsx-renderer","jsx-runtime","tsx","tsx-renderer","typescript","vue","vuecompositionapi","vuejs2"],"created_at":"2024-09-25T01:24:19.839Z","updated_at":"2025-07-29T11:33:45.896Z","avatar_url":"https://github.com/LancerComet.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Vue 2 JSX Runtime\n\n[![npm version](https://badge.fury.io/js/@lancercomet%2Fvue2-jsx-runtime.svg)](https://badge.fury.io/js/@lancercomet%2Fvue2-jsx-runtime)\n![Testing](https://github.com/LancerComet/vue2-jsx-runtime/workflows/Test/badge.svg)\n\nThis package is designed for handling Vue 2 JSX.\n\n## What's the different between this and the official solution?\n\nThe 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.\n\nFor TypeScript users, when you use the official solution your workflow would be like:\n\n```\nTSX -\u003e Babel -\u003e Vite (ESBuild) / TSC / SWC -\u003e JS  \n```\n\nThe 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.\n\nFor 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.\n\nThe reasons I developed this package:\n\n 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.\n 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.\n\n## Setup\n\nFirst, please make sure `Vue@2` has been installed in your project, then\n\n```\nnpm install @lancercomet/vue2-jsx-runtime --save\n```\n\n### Using TSC\n\nUpdate your `tsconfig.json` with:\n\n```js\n{\n  \"compilerOptions\": {\n    ...\n    \"jsx\": \"react-jsx\",  // Please set to \"react-jsx\". \n    \"jsxImportSource\": \"@lancercomet/vue2-jsx-runtime\"  // Please set to package name.\n  }\n}\n```\n\n\u003e The reason why \"jsx\" should be set to \"react-jsx\" is this plugin has to meet the new JSX transform.\n\n### Using SWC\n\nIn `tsconfig.json`:\n\n```js\n{\n  \"compilerOptions\": {\n    ...\n    \"jsx\": \"preserve\"  // Please set to \"preserve\". \n  }\n}\n```\n\nAnd in `.swcrc`:\n\n```js\n{\n  \"jsc\": {\n    \"transform\": {\n      \"react\": {\n        \"runtime\": \"automatic\",  // Please set to \"automatic\" to enable new JSX transform.\n        \"importSource\": \"@lancercomet/vue2-jsx-runtime\",  // Please set to package name.\n        \"throwIfNamespace\": false\n      }\n    }\n  }\n}\n```\n\n### For JavaScript users\n\nYou 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.\n\n### For Vite\n\nPlease read the section below.\n\n## Usage\n\n### Passing Value\n\n#### Setup\n\n```tsx\ndefineComponent({\n  setup () {\n    const isDisabledRef = ref(false)\n    return () =\u003e (\n      \u003cbutton disabled={isDisabledRef.value}\u003eWow such a button\u003c/button\u003e\n    )\n  }\n})\n```\n\n#### Render function\n\n```tsx\nVue.extend({\n  data () {\n    return {\n      isDisabled: false\n    }\n  },\n  render () {\n    return (\n      \u003cbutton disabled={this.isDisabled}\u003eVery button\u003c/button\u003e\n    )\n  }\n})\n\n```\n\n### On\n\n#### Setup\n\n```tsx\nsetup () {\n  const onClick = () =\u003e {}\n  return () =\u003e (\n    \u003cbutton onClick={onClick}\u003eClick me\u003c/button\u003e\n  )\n}\n```\n\n#### Render function\n\n```tsx\nVue.extend({\n  methods: {\n    onClick () {}\n  },\n  render () {\n    return \u003cbutton onClick={this.onClick}\u003eClick me\u003c/button\u003e\n  }\n})\n```\n\n#### Using \"on\" object to assign multiple events for once\n\n```tsx\n\u003cdiv on={{\n  click: onClick,\n  focus: onFocus,\n  blur: onBlur\n}}\u003e\u003c/div\u003e\n```\n\n### Native on\n\n```tsx\n\u003cMyComponent onClick:native={onClick} /\u003e\n```\n\nNative is only available for Vue components.\n\n### Rendering HTML or text\n\n```tsx\n// Setting HTML.\n\u003cdiv v-html={htmlStrRef.value}\u003e\u003c/div\u003e   // Using Vue directive.\n\u003cdiv innerHTML='\u003ch1\u003eTitle\u003c/h1\u003e'\u003e\u003c/div\u003e  // Using dom prop.\n\n// Setting text.\n\u003cdiv v-text={this.displayText}\u003e\u003c/div\u003e   // Using Vue directive.\n\u003cdiv textContent={'Very Vue'}\u003e\u003c/div\u003e    // Using dom prop.\n```\n\n### HTML / Component ref\n\n#### Vue ≤ 2.6\n\nDue to the limitation, using ref is a little different from to Vue 3.\n\nYou can check [this](https://github.com/vuejs/composition-api#limitations) out for more information.\n\n```tsx\nimport { ComponentPublicInstance, defineComponent, onMounted } from '@vue/composition-api'\n\nconst Example = defineComponent({\n  setup () {\n    return () =\u003e (\n      \u003cdiv\u003eExample goes here\u003c/div\u003e\n    )\n  }\n})\n\nconst Wrapper = defineComponent({\n  setup (_, { refs }) {\n    onMounted(() =\u003e {\n      const div = refs.doge as HTMLElement\n      const example = refs.example as ComponentPublicInstance\u003cany\u003e\n    })\n\n    return () =\u003e (\n      \u003cdiv\u003e\n        \u003cdiv ref='doge'\u003eWow very doge\u003c/div\u003e\n        \u003cExample ref='example'/\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n#### Vue 2.7+\n\nVue 2.7 has its built-in composition API support, and the behavior acts as the same as Vue 3.\n\n```tsx\nimport { ref, defineComponent } from 'vue'\n\nconst Example = defineComponent({\n  setup () {\n    const dogeRef = ref\u003cHTMLElement\u003e()\n  \n    onMounted(() =\u003e {\n      console.log(dogeRef.value)\n    })\n\n    return () =\u003e (\n      \u003cdiv\u003e\n        \u003cdiv ref={dogeRef}\u003eWow very doge\u003c/div\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n### Slot\n\n```tsx\nconst Container = defineComponent({\n  setup (_, { slots }) {\n    return () =\u003e (\n      \u003cdiv\u003e\n        { slots.default?.() }\n        { slots.slot1?.() }\n        { slots.slot2?.() }\n      \u003c/div\u003e\n    )\n  }\n})\n\nconst Example = defineComponent({\n  name: 'Example',\n  setup (_, { slots }) {\n    return () =\u003e (\n      \u003cdiv\u003e{ slots.default?.() }\u003c/div\u003e\n    )\n  }\n})\n```\n\n```tsx\n\u003cContainer\u003e\n  \u003cExample\u003eDefault\u003c/Example\u003e\n  \u003cExample slot='slot1'\u003eSlot1\u003c/Example\u003e\n  \u003cExample slot='slot2'\u003eSlot2\u003c/Example\u003e\n\u003c/Container\u003e\n```\n\n### ScopedSlots\n\n```tsx\nconst MyComponent = defineComponent({\n  props: {\n    name: String as PropType\u003cstring\u003e,\n    age: Number as PropType\u003cnumber\u003e\n  },\n\n  setup (props, { slots }) {\n    return () =\u003e (\n      \u003cdiv\u003e\n        { slots.default?.() }\n        { slots.nameSlot?.(props.name) }\n        { slots.ageSlot?.(props.age) }\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n```tsx\n\u003cMyComponent\n  name='John Smith'\n  age={100}\n  scopedSlots={{\n    default: () =\u003e \u003cdiv\u003eDefault\u003c/div\u003e,\n    nameSlot: (name: string) =\u003e \u003cdiv\u003eName: {name}\u003c/div\u003e,\n    ageSlot: (age: number) =\u003e {\n      return \u003cdiv\u003eAge: {age}\u003c/div\u003e\n    }\n  }}\n/\u003e\n```\n\nOutput:\n\n```html\n\u003cdiv\u003e\n  \u003cdiv\u003eDefault\u003c/div\u003e\n  \u003cdiv\u003eName: John Smith\u003c/div\u003e\n  \u003cdiv\u003eAge: 100\u003c/div\u003e\n\u003c/div\u003e\n```\n\n### Built-in directives\n\n#### Setup\n\n```tsx\ndefineComponent({\n  setup () {\n    const isDisplayRef = ref(false)\n    const textContentRef = ref('John Smith')\n    const htmlContentRef = ref('\u003ch1\u003eJohn Smith\u003c/h1\u003e')\n\n    return () =\u003e (\n      \u003cdiv\u003e\n        \u003cdiv v-show={isDisabledRef.value}\u003ePage content\u003c/div\u003e\n        \u003cdiv v-text={textContentRef.value}\u003e\u003c/div\u003e\n        \u003cdiv v-html={htmlContentRef.value}\u003e\u003c/div\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n#### Render function\n\n```tsx\nVue.extend({\n  data () {\n    return {\n      isDisplay: false,\n      textContent: 'John Smith',\n      htmlContent: '\u003ch1\u003eJohn Smith\u003c/h1\u003e'\n    }\n  },\n  render () {\n    return (\n      \u003cdiv\u003e\n        \u003cdiv v-show={this.isDisplay}\u003ePage content\u003c/div\u003e\n        \u003cdiv v-text={this.textContent}\u003e\u003c/div\u003e\n        \u003cdiv v-html={this.htmlContent}\u003e\u003c/div\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n### v-model\n\n#### Regular usage\n\n```tsx\nimport ref from '@vue/composition-api'\nimport Vue from 'vue'\n\n// Setup.\nconst Example = defineComponent({\n  setup () {\n    const nameRef = ref('')\n    return () =\u003e (\n      \u003cdiv\u003e\n        \u003cinput v-model={nameRef}/\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n\n// In render function.\nconst Example = Vue.extend({\n  data: () =\u003e ({\n    name: ''\n  }),\n  render: () =\u003e \u003cinput v-model='name'/\u003e\n})\n```\n\n#### With modifiers\n\nYou can use modifiers to add some extra features:\n\n - `lazy, number, trim`: These are the built-in modifiers from Vue.\n - `direct`: See **\"About IME\"** section below.\n\n```tsx\nconst Example = Vue.extend({\n  data: () =\u003e ({\n    name: '',\n    age: 0\n  }),\n  render: () =\u003e (\n    \u003cdiv\u003e\n      \u003cinput v-model={['name', ['lazy']]}/\u003e\n      \u003cinput v-model={['age', ['number']]}/\u003e\n    \u003c/div\u003e\n  )\n})\n\nconst Example = defineComponent({\n  setup () {\n    const nameRef = ref('')\n    const ageRef = ref(0)\n    \n    return () =\u003e (\n      \u003cdiv\u003e\n        \u003cinput v-model={[nameRef, ['lazy']]}/\u003e\n        \u003cinput v-model={[agRef, ['number']]}/\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n#### With argument\n\nArgument of v-model is designed for binding properties.\n\nDue to limitation, binding properties in Vue 2 isn't that kinda convenient:\n\n```tsx\nconst userRef = ref({\n  detail: {\n    address: ''\n  }\n})\n\n// This works in Vue 3 but doesn't work in Vue 2.\n\u003cinput v-model={userRef.value.detail.address} /\u003e\n```\n\nWe have to use v-model like:\n\n```tsx\nconst Example = defineComponent({\n  setup () {\n    const userRef = ref({\n      username: '',\n      age: 0,\n      detail: {\n        address: ''\n      }\n    })\n    \n    return () =\u003e (\n      \u003cdiv\u003e\n        \u003cinput v-model={[userRef, 'username', ['lazy']]}/\u003e\n        \u003cinput v-model={[userRef, 'age', ['number']]}/\u003e\n        \u003cinput v-model={[userRef, 'detail.address']}/\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n#### About IME\n\nBy default, `v-model` will only assign what you have selected from IME. If you were typing in IME, `v-model` would do nothing.\n\nIf you want to disable this behavior, add `direct` modifier:\n\n```tsx\n{/* It will sync everything you have typed in IME. */}\n\u003cinput v-model={[userInputRef, ['direct']]}\u003e\n\n{/* By default, it will only assign what you have selected from IME. */}\n\u003cinput v-model={userInputRef} \u003e\n```\n\n### Key\n\nDue to the limitation, we have to use `v-bind:key`:\n\n```tsx\n\u003cTransitionGroup\u003e{\n  userList.map(item =\u003e (\n    \u003cdiv v-bind:key={item.id}\u003e{item.name}\u003c/div\u003e\n  ))\n}\u003c/TransitionGroup\u003e\n```\n\n### Transition / TransitionGroup\n\n```tsx\nimport Vue from 'vue'\n\nconst Transition = Vue.component('Transition')\nconst TransitionGroup = Vue.component('TransitionGroup')\n\nsetup () {\n  return () =\u003e (\n    \u003cdiv\u003e\n      \u003cTransitionGroup\u003e\n        \u003cdiv v-bind:key='key-1'\u003eSome element\u003c/div\u003e\n        \u003cdiv v-bind:key='key-2'\u003eSome element\u003c/div\u003e\n      \u003c/TransitionGroup\u003e\n      \u003cTransition\u003e\n        \u003cdiv\u003eSome element\u003c/div\u003e\n      \u003c/Transition\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nor\n\n```tsx\nsetup () {\n  return () =\u003e (\n    \u003cdiv\u003e\n      \u003ctransition-group\u003e\n        \u003cdiv v-bind:key='key-1'\u003eSome element\u003c/div\u003e\n        \u003cdiv v-bind:key='key-2'\u003eSome element\u003c/div\u003e\n      \u003c/transition-group\u003e\n      \u003ctransition\u003e\n        \u003cdiv\u003eSome element\u003c/div\u003e\n      \u003c/transition\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n## Compatibility\n\nThese format below are also available, but they are NOT recommended, just for compatibility.\n\n### On\n\n```tsx\n\u003cdiv v-on:click={onClick}\u003e\u003c/div\u003e\n\u003cdiv vOn:click={onClick}\u003e\u003c/div\u003e\n```\n\n### v-model\n\n```tsx\n\u003cinput vModel={userInpuptRef.value} /\u003e\n```\n\n### Key\n\n```tsx\n\u003cdiv v-bind:key='key-1' /\u003e\n\u003cdiv vBind:key='key-1' /\u003e\n```\n\n## For Vite users\n\nFor 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.\n\nFor faster compilation, SWC is recommended. You can use [unplugin-swc](https://github.com/egoist/unplugin-swc) to make Vite uses SWC.\n\nOnce 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.\n\n### Configuration\n\nAfter you have configured SWC (see Setup section above):\n\n1. Install [unplugin-swc](https://github.com/egoist/unplugin-swc).\n\n```\nnpm install unplugin-swc --save-dev\n```\n\n2. Update `vite.config.ts`:\n\n```ts\nimport { defineConfig } from 'vite'\nimport swc from 'unplugin-swc'\nimport { createVuePlugin } from 'vite-plugin-vue2'\n\nexport default defineConfig({\n  plugins: [\n    swc.vite(),\n    createVuePlugin(),\n    ...\n  ]\n})\n```\n\n## Mixing usage\n\nIf you have to use `JSX` and `SFC` together in Vite, you need to update your Vite config:\n\n```ts\nimport { defineConfig } from 'vite'\nimport swc from 'unplugin-swc'\nimport { createVuePlugin } from 'vite-plugin-vue2'\n\nconst swcPlugin = swc.vite()\n\nexport default defineConfig({\n  plugins: [\n    {\n      ...swcPlugin,\n      transform (code, id, ...args) {\n        if (\n          id.endsWith('.tsx') || id.endsWith('.ts') ||\n          (id.includes('.vue') \u0026\u0026 id.includes('lang.ts'))\n        ) {\n          return swcPlugin.transform.call(this, code, id, ...args)\n        }\n      }\n    },\n\n    createVuePlugin(),\n    ...\n  ]\n})\n```\n\nThis will make SWC to skip compiling Non-Typescript codes in Vue SFC.\n\n## Hot Reload\n\nUse [vite-plugin-vue2-hmr](https://github.com/LancerComet/vite-plugin-vue2-hmr) to enable Vue2 JSX hot reload in Vite.\n\n## Contributing\n\nFeel free to open issue or pull request to make it better.\n\n## References\n\n - [Introducing the New JSX Transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)\n - [Render Function](https://vuejs.org/guide/extras/render-function.html)\n - [vue-jsx-runtime (Vue 3)](https://github.com/dolymood/vue-jsx-runtime)\n - [@vue/composition-api](https://github.com/vuejs/composition-api)\n - [@vue/babel-plugin-jsx](https://github.com/vuejs/babel-plugin-jsx)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flancercomet%2Fvue2-jsx-runtime","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flancercomet%2Fvue2-jsx-runtime","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flancercomet%2Fvue2-jsx-runtime/lists"}