Skip to content

Commit 717f3a1

Browse files
committed
feat: add restructure plugin
1 parent e2d2fff commit 717f3a1

File tree

5 files changed

+222
-30
lines changed

5 files changed

+222
-30
lines changed

README.md

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,54 @@ Convert Vue JSX to Vapor.
1212
npm i unplugin-vue-jsx-vapor
1313
```
1414

15+
> [!CAUTION]
16+
> The destructuring of props in a functional component will cause loss of reactivity.
17+
18+
```tsx
19+
function Comp({ foo }) {
20+
return <div>{foo}</div>
21+
}
22+
23+
const foo = ref('foo')
24+
export default <Comp foo={foo.value} />
25+
```
26+
27+
#### Solutions
28+
29+
1. Pass a ref variable as prop:
30+
31+
```tsx
32+
function Comp({ foo }) {
33+
return <div>{foo.value}</div>
34+
}
35+
36+
const foo = ref('foo')
37+
export default <Comp foo={foo} />
38+
```
39+
40+
2. Turn on the restructure option to restructure props.
41+
42+
```ts
43+
// vite.config.ts
44+
export default defineConfig({
45+
plugins: [
46+
VueJsxVapor({
47+
restructure: true,
48+
}),
49+
],
50+
})
51+
```
52+
53+
```tsx
54+
function Comp({ foo }) {
55+
return <div>{foo}</div>
56+
}
57+
// Will be convert to:
58+
function Comp(_ctx0) {
59+
return <div>{_ctx0.foo}</div>
60+
}
61+
```
62+
1563
<details>
1664
<summary>Vite</summary><br>
1765

@@ -20,9 +68,7 @@ npm i unplugin-vue-jsx-vapor
2068
import VueJsxVapor from 'unplugin-vue-jsx-vapor/vite'
2169

2270
export default defineConfig({
23-
plugins: [
24-
VueJsxVapor(),
25-
],
71+
plugins: [VueJsxVapor()],
2672
})
2773
```
2874

@@ -38,9 +84,7 @@ Example: [`playground/`](./playground/)
3884
import VueJsxVapor from 'unplugin-vue-jsx-vapor/rollup'
3985

4086
export default {
41-
plugins: [
42-
VueJsxVapor(),
43-
],
87+
plugins: [VueJsxVapor()],
4488
}
4589
```
4690

@@ -53,9 +97,7 @@ export default {
5397
// webpack.config.js
5498
module.exports = {
5599
/* ... */
56-
plugins: [
57-
require('unplugin-vue-jsx-vapor/webpack')(),
58-
],
100+
plugins: [require('unplugin-vue-jsx-vapor/webpack')()],
59101
}
60102
```
61103

@@ -67,11 +109,7 @@ module.exports = {
67109
```ts
68110
// nuxt.config.js
69111
export default defineNuxtConfig({
70-
modules: [
71-
[
72-
'unplugin-vue-jsx-vapor/nuxt',
73-
],
74-
],
112+
modules: [['unplugin-vue-jsx-vapor/nuxt']],
75113
})
76114
```
77115

@@ -86,9 +124,7 @@ export default defineNuxtConfig({
86124
// vue.config.js
87125
module.exports = {
88126
configureWebpack: {
89-
plugins: [
90-
require('unplugin-vue-jsx-vapor/webpack')(),
91-
],
127+
plugins: [require('unplugin-vue-jsx-vapor/webpack')()],
92128
},
93129
}
94130
```
@@ -104,9 +140,7 @@ import { build } from 'esbuild'
104140
import VueJsxVapor from 'unplugin-vue-jsx-vapor/esbuild'
105141

106142
build({
107-
plugins: [
108-
VueJsxVapor()
109-
],
143+
plugins: [VueJsxVapor()],
110144
})
111145
```
112146

src/core/transformRestructure.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {
2+
MagicString,
3+
babelParse,
4+
generateTransform,
5+
getLang,
6+
walkAST,
7+
} from '@vue-macros/common'
8+
import { walkIdentifiers } from '@vue-vapor/compiler-core'
9+
import { isFunctionExpression } from './utils'
10+
import type { Node } from '@babel/types'
11+
12+
export function transformRestructure(code: string, id: string) {
13+
const s = new MagicString(code)
14+
const ast = babelParse(code, getLang(id))
15+
16+
let index = 0
17+
walkAST<Node>(ast, {
18+
enter(node) {
19+
if (isFunctionExpression(node)) {
20+
const result = new Map()
21+
for (const param of node.params) {
22+
const paths = `_ctx${index++}`
23+
if (resolveParams(param, paths, result)) {
24+
s.overwrite(param.start!, param.end!, paths)
25+
}
26+
}
27+
if (!result.size) return
28+
29+
walkIdentifiers(
30+
node.body,
31+
(id, parent, __, ___, isLocal) => {
32+
if (!isLocal && result.get(id.name)) {
33+
s.overwrite(
34+
id.start!,
35+
id.end!,
36+
parent.type === 'ObjectProperty' && parent.shorthand
37+
? `${id.name}: ${result.get(id.name)}`
38+
: result.get(id.name),
39+
)
40+
}
41+
},
42+
false,
43+
)
44+
}
45+
},
46+
})
47+
48+
return generateTransform(s, id)
49+
}
50+
51+
function resolveParams(
52+
param: Node,
53+
paths: string = '',
54+
result: Map<string, string>,
55+
) {
56+
const elements =
57+
param.type === 'ObjectPattern'
58+
? param.properties
59+
: param.type === 'ArrayPattern'
60+
? param.elements
61+
: []
62+
if (!elements.length) return
63+
64+
elements.forEach((element, index) => {
65+
if (element?.type === 'Identifier') {
66+
result.set(element.name, `${paths}[${index}]`)
67+
} else if (
68+
element?.type === 'ObjectProperty' &&
69+
element.key.type === 'Identifier'
70+
) {
71+
if (!resolveParams(element.value, `${paths}.${element.key.name}`, result))
72+
result.set(element.key.name, `${paths}.${element.key.name}`)
73+
} else if (element) {
74+
resolveParams(element, `${paths}[${index}]`, result)
75+
}
76+
})
77+
return true
78+
}

src/index.ts

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,74 @@
11
import { type UnpluginFactory, createUnplugin } from 'unplugin'
2-
import { createFilter } from 'vite'
2+
import { createFilter, transformWithEsbuild } from 'vite'
33
import { transformVueJsxVapor } from './core/transform'
4+
import { transformRestructure } from './core/transformRestructure'
45
import type { Options } from './types'
56

67
export const unpluginFactory: UnpluginFactory<Options | undefined> = (
78
options = {},
8-
) => ({
9-
enforce: 'pre',
10-
name: 'unplugin-vue-jsx-vapor',
11-
transformInclude: createFilter(
9+
) => {
10+
const transformInclude = createFilter(
1211
options?.include || /\.[jt]sx$/,
1312
options?.exclude,
14-
),
15-
transform(code, id) {
16-
return transformVueJsxVapor(code, id, options)
17-
},
18-
})
13+
)
14+
return [
15+
{
16+
name: 'unplugin-vue-jsx-vapor',
17+
vite: {
18+
config(config) {
19+
return {
20+
// only apply esbuild to ts files
21+
// since we are handling jsx and tsx now
22+
esbuild: {
23+
include: /\.ts$/,
24+
},
25+
define: {
26+
__VUE_OPTIONS_API__: config.define?.__VUE_OPTIONS_API__ ?? true,
27+
__VUE_PROD_DEVTOOLS__:
28+
config.define?.__VUE_PROD_DEVTOOLS__ ?? false,
29+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__:
30+
config.define?.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ ?? false,
31+
},
32+
}
33+
},
34+
},
35+
transformInclude,
36+
transform(code, id) {
37+
const result = transformVueJsxVapor(code, id, options)
38+
return result
39+
},
40+
},
41+
{
42+
name: 'unplugin-esbuild',
43+
transformInclude,
44+
transform(code, id) {
45+
return transformWithEsbuild(code, id, {
46+
target: 'esnext',
47+
charset: 'utf8',
48+
minify: false,
49+
minifyIdentifiers: false,
50+
minifySyntax: false,
51+
minifyWhitespace: false,
52+
treeShaking: false,
53+
keepNames: false,
54+
supported: {
55+
'dynamic-import': true,
56+
'import-meta': true,
57+
},
58+
})
59+
},
60+
},
61+
...(options?.restructure
62+
? [
63+
{
64+
name: 'unplugin-restructure',
65+
transformInclude,
66+
transform: transformRestructure,
67+
},
68+
]
69+
: []),
70+
]
71+
}
1972

2073
export const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)
2174

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export interface Options {
66
include?: FilterPattern
77
exclude?: FilterPattern
88
compile?: CompilerOptions
9+
restructure?: boolean
910
}

test/transformReconstruct.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { transformRestructure } from '../src/core/transformRestructure'
3+
4+
describe('transform', () => {
5+
test('transform reconstruct', () => {
6+
const { code } = transformRestructure(
7+
`const App = ([[[,foo]], {id: {foo: [bar]}}], { baz }) => {
8+
function onClick({ foo }){
9+
return { foo, baz: baz.baz }
10+
};
11+
return [ foo, bar, baz ]
12+
}`,
13+
'tsx',
14+
)!
15+
expect(code).toMatchInlineSnapshot(
16+
`
17+
"const App = (_ctx0, _ctx1) => {
18+
function onClick(_ctx2){
19+
return { foo: _ctx2.foo, baz: _ctx1.baz.baz }
20+
};
21+
return [ _ctx0[0][0][1], _ctx0[1].id.foo[0], _ctx1.baz ]
22+
}"
23+
`,
24+
)
25+
})
26+
})

0 commit comments

Comments
 (0)