Skip to content

Commit 043b0b3

Browse files
committed
feat(vue-jsx): allow esbuild to perform ts transformation
1 parent 9be175d commit 043b0b3

File tree

21 files changed

+516
-13
lines changed

21 files changed

+516
-13
lines changed

packages/plugin-vue-jsx/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,34 @@ Default: `['defineComponent']`
4343

4444
The name of the function to be used for defining components. This is useful when you have a custom `defineComponent` function.
4545

46+
### tsTransform
47+
48+
Type: `'babel' | 'built-in'`
49+
50+
Default: `'babel'`
51+
52+
Defines how `typescript` transformation is handled for `.tsx` files.
53+
54+
`'babel'` - `typescript` transformation is handled by `@babel/plugin-transform-typescript` during `babel` invocation for JSX transformation.
55+
56+
`'built-in'` - `babel` is invoked only for JSX transformation and then `typescript` transformation is handled by the same toolchain used for `.ts` files (currently `esbuild`).
57+
58+
### babelPlugins
59+
60+
Type: `any[]`
61+
62+
Default: `undefined`
63+
64+
Provide additional plugins for `babel` invocation for JSX transformation.
65+
66+
### tsPluginOptions
67+
68+
Type: `any`
69+
70+
Default: `undefined`
71+
72+
Defines options for `@babel/plugin-transform-typescript` plugin.
73+
4674
## HMR Detection
4775

4876
This plugin supports HMR of Vue JSX components. The detection requirements are:

packages/plugin-vue-jsx/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"homepage": "https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue-jsx#readme",
3737
"dependencies": {
3838
"@babel/core": "^7.27.7",
39+
"@babel/plugin-syntax-typescript": "^7.27.1",
3940
"@babel/plugin-transform-typescript": "^7.27.1",
4041
"@rolldown/pluginutils": "^1.0.0-beta.21",
4142
"@vue/babel-plugin-jsx": "^1.4.0"

packages/plugin-vue-jsx/src/index.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function vueJsxPlugin(options: Options = {}): Plugin {
4949
babelPlugins = [],
5050
defineComponentName = ['defineComponent'],
5151
tsPluginOptions = {},
52+
tsTransform,
5253
...babelPluginOptions
5354
} = options
5455
const filter = createFilter(include, exclude)
@@ -67,9 +68,17 @@ function vueJsxPlugin(options: Options = {}): Plugin {
6768
return {
6869
// only apply esbuild to ts files
6970
// since we are handling jsx and tsx now
70-
esbuild: {
71-
include: /\.ts$/,
72-
},
71+
esbuild:
72+
tsTransform === 'built-in'
73+
? {
74+
// For 'built-in' we still need esbuild to transform ts syntax for `.tsx` files.
75+
// So we add `.jsx` extension to `exclude` and keep original `include`.
76+
// https://github.com/vitejs/vite/blob/v6.3.5/packages/vite/src/node/plugins/esbuild.ts#L246
77+
exclude: /\.jsx?$/,
78+
}
79+
: {
80+
include: /\.ts$/,
81+
},
7382
define: {
7483
__VUE_OPTIONS_API__:
7584
parseDefine(config.define?.__VUE_OPTIONS_API__) ?? true,
@@ -108,6 +117,9 @@ function vueJsxPlugin(options: Options = {}): Plugin {
108117
},
109118

110119
transform: {
120+
// Use 'pre' stage for 'built-in'
121+
// to run jsx transformation before esbuild transformation.
122+
order: tsTransform === 'built-in' ? 'pre' : undefined,
111123
filter: {
112124
id: {
113125
include: include ? makeIdFiltersToMatchWithQuery(include) : undefined,
@@ -123,14 +135,26 @@ function vueJsxPlugin(options: Options = {}): Plugin {
123135
if (filter(id) || filter(filepath)) {
124136
const plugins = [[jsx, babelPluginOptions], ...babelPlugins]
125137
if (id.endsWith('.tsx') || filepath.endsWith('.tsx')) {
126-
plugins.push([
127-
// @ts-ignore missing type
128-
await import('@babel/plugin-transform-typescript').then(
129-
(r) => r.default,
130-
),
131-
// @ts-ignore
132-
{ ...tsPluginOptions, isTSX: true, allowExtensions: true },
133-
])
138+
if (tsTransform === 'built-in') {
139+
// For 'built-in' add "syntax" plugin
140+
// to enable parsing without transformation.
141+
plugins.push([
142+
// @ts-ignore missing type
143+
await import('@babel/plugin-syntax-typescript').then(
144+
(r) => r.default,
145+
),
146+
{ isTSX: true },
147+
])
148+
} else {
149+
plugins.push([
150+
// @ts-ignore missing type
151+
await import('@babel/plugin-transform-typescript').then(
152+
(r) => r.default,
153+
),
154+
// @ts-ignore
155+
{ ...tsPluginOptions, isTSX: true, allowExtensions: true },
156+
])
157+
}
134158
}
135159

136160
if (!ssr && !needHmr) {

packages/plugin-vue-jsx/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ export interface Options extends VueJSXPluginOptions, FilterOptions {
1111
/** @default ['defineComponent'] */
1212
defineComponentName?: string[]
1313
tsPluginOptions?: any
14+
/** @default 'babel' */
15+
tsTransform?: 'babel' | 'built-in'
1416
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { expect, test } from 'vitest'
2+
import { page } from '~utils'
3+
4+
test('should render', async () => {
5+
expect(await page.textContent('.decorators-ts')).toMatch('1')
6+
expect(await page.textContent('.decorators-tsx')).toMatch('2')
7+
expect(await page.textContent('.decorators-vue-ts')).toMatch('3')
8+
expect(await page.textContent('.decorators-vue-tsx')).toMatch('4')
9+
expect(await page.textContent('.decorators-legacy-ts')).toMatch('5')
10+
expect(await page.textContent('.decorators-legacy-tsx')).toMatch('6')
11+
expect(await page.textContent('.decorators-legacy-vue-ts')).toMatch('7')
12+
expect(await page.textContent('.decorators-legacy-vue-tsx')).toMatch('8')
13+
})
14+
15+
test('should update', async () => {
16+
await page.click('.decorators-ts')
17+
expect(await page.textContent('.decorators-ts')).toMatch('2')
18+
await page.click('.decorators-tsx')
19+
expect(await page.textContent('.decorators-tsx')).toMatch('3')
20+
await page.click('.decorators-vue-ts')
21+
expect(await page.textContent('.decorators-vue-ts')).toMatch('4')
22+
await page.click('.decorators-vue-tsx')
23+
expect(await page.textContent('.decorators-vue-tsx')).toMatch('5')
24+
await page.click('.decorators-legacy-ts')
25+
expect(await page.textContent('.decorators-legacy-ts')).toMatch('6')
26+
await page.click('.decorators-legacy-tsx')
27+
expect(await page.textContent('.decorators-legacy-tsx')).toMatch('7')
28+
await page.click('.decorators-legacy-vue-ts')
29+
expect(await page.textContent('.decorators-legacy-vue-ts')).toMatch('8')
30+
await page.click('.decorators-legacy-vue-tsx')
31+
expect(await page.textContent('.decorators-legacy-vue-tsx')).toMatch('9')
32+
})
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { defineComponent, h, ref } from 'vue'
2+
3+
function methodDecorator(
4+
target: unknown,
5+
propertyKey: string,
6+
descriptor: PropertyDescriptor,
7+
) {
8+
const originalMethod = descriptor.value
9+
descriptor.value = function () {
10+
const result = originalMethod.call(this)
11+
this.value.value += 1
12+
return result
13+
}
14+
}
15+
16+
export default defineComponent(() => {
17+
class Counter {
18+
value = ref(5)
19+
20+
@methodDecorator
21+
increment() {}
22+
}
23+
24+
const counter = new Counter()
25+
const inc = () => counter.increment()
26+
27+
return () =>
28+
h(
29+
'button',
30+
{
31+
class: 'decorators-legacy-ts',
32+
onClick: inc,
33+
},
34+
`decorators legacy ts ${counter.value.value}`,
35+
)
36+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { defineComponent, ref } from 'vue'
2+
3+
function methodDecorator(
4+
target: unknown,
5+
propertyKey: string,
6+
descriptor: PropertyDescriptor,
7+
) {
8+
const originalMethod = descriptor.value
9+
descriptor.value = function () {
10+
const result = originalMethod.call(this)
11+
this.value.value += 1
12+
return result
13+
}
14+
}
15+
16+
export default defineComponent(() => {
17+
class Counter {
18+
value = ref(6)
19+
20+
@methodDecorator
21+
increment() {}
22+
}
23+
24+
const counter = new Counter()
25+
const inc = () => counter.increment()
26+
27+
return () => (
28+
<button class="decorators-legacy-tsx" onClick={inc}>
29+
{`decorators legacy tsx ${counter.value.value}`}
30+
</button>
31+
)
32+
})
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<script setup lang="ts">
2+
import { defineComponent, h, ref } from 'vue'
3+
4+
function methodDecorator(
5+
target: unknown,
6+
propertyKey: string,
7+
descriptor: PropertyDescriptor,
8+
) {
9+
const originalMethod = descriptor.value
10+
descriptor.value = function () {
11+
const result = originalMethod.call(this)
12+
this.value.value += 1
13+
return result
14+
}
15+
}
16+
17+
const Component = defineComponent(() => {
18+
class Counter {
19+
value = ref(7)
20+
21+
@methodDecorator
22+
increment() {}
23+
}
24+
25+
const counter = new Counter()
26+
const inc = () => counter.increment()
27+
28+
return () =>
29+
h(
30+
'button',
31+
{
32+
class: 'decorators-legacy-vue-ts',
33+
onClick: inc,
34+
},
35+
`decorators legacy vue ts ${counter.value.value}`,
36+
)
37+
})
38+
</script>
39+
<template>
40+
<Component />
41+
</template>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script setup lang="tsx">
2+
import { defineComponent, h, ref } from 'vue'
3+
4+
function methodDecorator(
5+
target: unknown,
6+
propertyKey: string,
7+
descriptor: PropertyDescriptor,
8+
) {
9+
const originalMethod = descriptor.value
10+
descriptor.value = function () {
11+
const result = originalMethod.call(this)
12+
this.value.value += 1
13+
return result
14+
}
15+
}
16+
17+
const Component = defineComponent(() => {
18+
class Counter {
19+
value = ref(8)
20+
21+
@methodDecorator
22+
increment() {}
23+
}
24+
25+
const counter = new Counter()
26+
const inc = () => counter.increment()
27+
28+
return () => (
29+
<button class="decorators-legacy-vue-tsx" onClick={inc}>
30+
{`decorators legacy vue tsx ${counter.value.value}`}
31+
</button>
32+
)
33+
})
34+
</script>
35+
<template>
36+
<Component />
37+
</template>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"target": "es2020",
5+
"experimentalDecorators": true
6+
}
7+
}

0 commit comments

Comments
 (0)