vite-plugin-solid-undestructure 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +204 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1 -0
- package/dist/modules/component-detector-legacy.d.ts +3 -0
- package/dist/modules/component-detector.d.ts +8 -0
- package/dist/modules/import-manager.d.ts +11 -0
- package/dist/modules/props-transformer.d.ts +14 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 MicroDev
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# SolidJS Props Transform Plugin
|
|
2
|
+
|
|
3
|
+
A Vite plugin that automatically transforms props destructuring in SolidJS components to use `mergeProps` and `splitProps`, preserving reactivity.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
In SolidJS, destructuring props directly breaks reactivity because it converts reactive getters into static values:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// ❌ Breaks reactivity
|
|
11
|
+
function Component({ name, count }) {
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
{name}: {count}
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The correct approach is to use `splitProps` and `mergeProps`:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// ✅ Maintains reactivity
|
|
24
|
+
import { splitProps } from 'solid-js'
|
|
25
|
+
|
|
26
|
+
function Component(_props) {
|
|
27
|
+
const [{ name, count }] = splitProps(_props, ['name', 'count'])
|
|
28
|
+
// ...
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This plugin performs that transformation automatically.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- ✨ Automatically transforms destructured props to `splitProps`/`mergeProps`
|
|
37
|
+
- 🎯 Handles default values using `mergeProps`
|
|
38
|
+
- 🔄 Preserves spread parameters with `splitProps`
|
|
39
|
+
- 📦 Auto-imports `mergeProps` and `splitProps` from 'solid-js'
|
|
40
|
+
- ⚡ Skips non-component functions
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun add -D vite-plugin-solid-undestructure
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import solidUndestructure from './plugins/solid-undestructure'
|
|
52
|
+
import solid from 'vite-plugin-solid'
|
|
53
|
+
|
|
54
|
+
export default defineConfig({
|
|
55
|
+
plugins: [solidUndestructure(), solid() /* other plugins */]
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
### Basic Destructuring
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// Before
|
|
65
|
+
function Greeting({ name, age }) {
|
|
66
|
+
return (
|
|
67
|
+
<div>
|
|
68
|
+
Hello {name}, you are {age} years old
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// After
|
|
74
|
+
function Greeting(_props) {
|
|
75
|
+
return (
|
|
76
|
+
<div>
|
|
77
|
+
Hello {_props.name}, you are {_props.age} years old
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Default Values
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Before
|
|
87
|
+
function Button({ label = 'Click me', disabled = false }) {
|
|
88
|
+
return <button disabled={disabled}>{label}</button>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// After
|
|
92
|
+
import { mergeProps } from 'solid-js'
|
|
93
|
+
|
|
94
|
+
function Button(_props) {
|
|
95
|
+
const _merged = mergeProps({ label: 'Click me', disabled: false }, _props)
|
|
96
|
+
return <button disabled={_merged.disabled}>{_merged.label}</button>
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Spread Properties
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// Before
|
|
104
|
+
function Card({ title, description, ...props }) {
|
|
105
|
+
return (
|
|
106
|
+
<div {...props}>
|
|
107
|
+
<h2>{title}</h2>
|
|
108
|
+
<p>{description}</p>
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// After
|
|
114
|
+
import { splitProps } from 'solid-js'
|
|
115
|
+
|
|
116
|
+
function Card(_props) {
|
|
117
|
+
const [, props] = splitProps(_props, ['title', 'description'])
|
|
118
|
+
return (
|
|
119
|
+
<div {...props}>
|
|
120
|
+
<h2>{_props.title}</h2>
|
|
121
|
+
<p>{_props.description}</p>
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### TestComponent (Defaults + Nested Destructuring + Spread)
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
// Before
|
|
131
|
+
import { For } from 'solid-js'
|
|
132
|
+
|
|
133
|
+
function TestComponent({
|
|
134
|
+
name = 'World',
|
|
135
|
+
count = 0,
|
|
136
|
+
avatar = '/default.png',
|
|
137
|
+
items,
|
|
138
|
+
nested: { a, b },
|
|
139
|
+
...props
|
|
140
|
+
}: {
|
|
141
|
+
name?: string
|
|
142
|
+
count?: number
|
|
143
|
+
avatar?: string
|
|
144
|
+
items: string[]
|
|
145
|
+
nested: { a: number; b: number }
|
|
146
|
+
class?: string
|
|
147
|
+
onClick?: () => void
|
|
148
|
+
}) {
|
|
149
|
+
return (
|
|
150
|
+
<div {...props}>
|
|
151
|
+
<p>{props.class}</p>
|
|
152
|
+
<pre>{a}</pre>
|
|
153
|
+
<pre>{b}</pre>
|
|
154
|
+
<img src={avatar} alt={name} />
|
|
155
|
+
<h1>Hello {name}!</h1>
|
|
156
|
+
<p>Count: {count}</p>
|
|
157
|
+
<ul>
|
|
158
|
+
<For each={items}>{(item) => <li>{item}</li>}</For>
|
|
159
|
+
</ul>
|
|
160
|
+
</div>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// After
|
|
165
|
+
import { For, mergeProps, splitProps } from 'solid-js'
|
|
166
|
+
|
|
167
|
+
function TestComponent(_props) {
|
|
168
|
+
const _merged = mergeProps({ name: 'World', count: 0, avatar: '/default.png' }, _props)
|
|
169
|
+
const [, props] = splitProps(_merged, ['name', 'count', 'avatar', 'items', 'nested'])
|
|
170
|
+
return (
|
|
171
|
+
<div {...props}>
|
|
172
|
+
<p>{props.class}</p>
|
|
173
|
+
<pre>{_merged.nested.a}</pre>
|
|
174
|
+
<pre>{_merged.nested.b}</pre>
|
|
175
|
+
<img src={_merged.avatar} alt={_merged.name} />
|
|
176
|
+
<h1>Hello {_merged.name}!</h1>
|
|
177
|
+
<p>Count: {_merged.count}</p>
|
|
178
|
+
<ul>
|
|
179
|
+
<For each={_merged.items}>{(item) => <li>{item}</li>}</For>
|
|
180
|
+
</ul>
|
|
181
|
+
</div>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## How It Works
|
|
187
|
+
|
|
188
|
+
1. **Parse** — Uses `@babel/parser` to parse TypeScript/JSX files into an AST
|
|
189
|
+
2. **Detect** — Identifies functions with destructured props that return JSX
|
|
190
|
+
3. **Transform** — Rewrites destructuring into `mergeProps`/`splitProps` calls and replaces all references to destructured identifiers with property accesses on the merged/props object
|
|
191
|
+
4. **Import** — Adds necessary imports from `solid-js` if not already present
|
|
192
|
+
5. **Generate** — Outputs transformed code with source maps
|
|
193
|
+
|
|
194
|
+
## Notes
|
|
195
|
+
|
|
196
|
+
- Only transforms functions that return JSX (regular functions are left untouched)
|
|
197
|
+
- Requires the first parameter to be an object pattern (destructuring)
|
|
198
|
+
- Skips files in `node_modules`
|
|
199
|
+
|
|
200
|
+
## Testing
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
bun test
|
|
204
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* Vite plugin that transforms Solid.js component prop destructuring into reactive prop access.
|
|
4
|
+
* This ensures that props remain reactive by using mergeProps and splitProps instead of
|
|
5
|
+
* direct destructuring, which would break Solid's reactivity system.
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: () => Plugin;
|
|
8
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import S from"@babel/generator";import{parse as I}from"@babel/parser";import k from"@babel/traverse";import*as b from"@babel/types";import*as H from"@babel/types";function v(Q){if(H.isArrowFunctionExpression(Q.node)&&!H.isBlockStatement(Q.node.body))return H.isJSXElement(Q.node.body)||H.isJSXFragment(Q.node.body);let $=!1;return Q.traverse({ReturnStatement(W){if(W.getFunctionParent()?.node!==Q.node)return;let L=W.node.argument;if(!L)return;if(H.isJSXElement(L)||H.isJSXFragment(L))$=!0,W.stop();if(H.isConditionalExpression(L)){if(H.isJSXElement(L.consequent)||H.isJSXFragment(L.consequent)||H.isJSXElement(L.alternate)||H.isJSXFragment(L.alternate))$=!0,W.stop()}if(H.isLogicalExpression(L)){if(H.isJSXElement(L.left)||H.isJSXFragment(L.left)||H.isJSXElement(L.right)||H.isJSXFragment(L.right))$=!0,W.stop()}if(H.isParenthesizedExpression(L)){let U=L.expression;if(H.isJSXElement(U)||H.isJSXFragment(U))$=!0,W.stop()}}}),$}import*as q from"@babel/types";import*as _ from"@babel/types";function T(Q,$,W){let L=[];if($)L.push("mergeProps");if(W)L.push("splitProps");if(L.length===0)return;let U=Q.node.body,D=new Set;for(let Z of U){if(!_.isImportDeclaration(Z)||Z.source.value!=="solid-js")continue;for(let M of Z.specifiers)if(_.isImportSpecifier(M)&&_.isIdentifier(M.imported))D.add(M.imported.name)}let A=L.filter((Z)=>!D.has(Z));if(A.length===0)return;let R=-1;for(let Z=0;Z<U.length;Z++)if(_.isImportDeclaration(U[Z]))R=Z;let V=A.map((Z)=>_.importDeclaration([_.importSpecifier(_.identifier(Z),_.identifier(Z))],_.stringLiteral("solid-js")));if(R>=0)U.splice(R+1,0,...V);else U.unshift(...V)}function N(Q,$){let W=Q.scope.generateUidIdentifier("props"),L=$.properties,U=[],D=[],A=new Map,R=new Map,V={},Z=!1,M=null;function K(B,F=[]){B.properties.forEach((G)=>{if(q.isObjectProperty(G)){let Y=null;if(q.isIdentifier(G.key))Y=G.key.name;else if(q.isStringLiteral(G.key))Y=G.key.value;if(!Y)return;let C=[...F,Y];if(q.isObjectPattern(G.value)){if(F.length===0)U.push(Y);K(G.value,C)}else{let z=null;if(q.isIdentifier(G.value))z=G.value.name;else if(q.isAssignmentPattern(G.value)){if(q.isIdentifier(G.value.left)){if(z=G.value.left.name,q.isExpression(G.value.right)){if(F.length===0)V[Y]=G.value.right}}}if(F.length===0)if(U.push(Y),z)D.push(z),A.set(z,Y);else D.push(Y),A.set(Y,Y);else if(z)R.set(z,C);else R.set(Y,C)}}})}for(let B of L)if(q.isRestElement(B)){if(Z=!0,q.isIdentifier(B.argument))M=B.argument}else if(q.isObjectProperty(B)){let F=null,G=null;if(q.isIdentifier(B.key))F=B.key.name;else if(q.isStringLiteral(B.key))F=B.key.value;if(!F)continue;if(q.isObjectPattern(B.value)){U.push(F),K(B.value,[F]);continue}if(q.isIdentifier(B.value))G=B.value.name;else if(q.isAssignmentPattern(B.value)&&q.isIdentifier(B.value.left))G=B.value.left.name;if(U.push(F),G)D.push(G),A.set(G,F);else D.push(F),A.set(F,F);if(q.isAssignmentPattern(B.value)&&q.isExpression(B.value.right))V[F]=B.value.right}Q.node.params[0]=W;let j=[],X=Object.keys(V).length>0,E=U.length>0||Z,O;if(X){let B=q.objectExpression(Object.entries(V).map(([F,G])=>q.objectProperty(q.identifier(F),G)));if(E)O=Q.scope.generateUidIdentifier("merged"),j.push(q.variableDeclaration("const",[q.variableDeclarator(O,q.callExpression(q.identifier("mergeProps"),[B,W]))]));else O=Q.scope.generateUidIdentifier("merged"),j.push(q.variableDeclaration("const",[q.variableDeclarator(O,q.callExpression(q.identifier("mergeProps"),[B,W]))]));if(E&&M){let F=q.arrayPattern([null,M]);j.push(q.variableDeclaration("const",[q.variableDeclarator(F,q.callExpression(q.identifier("splitProps"),[O,q.arrayExpression(U.map((G)=>q.stringLiteral(G)))]))]))}}else if(E){if(O=W,M){let B=q.arrayPattern([null,M]);j.push(q.variableDeclaration("const",[q.variableDeclarator(B,q.callExpression(q.identifier("splitProps"),[W,q.arrayExpression(U.map((F)=>q.stringLiteral(F)))]))]))}}else O=W;let w=Q.get("body");if(Array.isArray(w))return;let f={Identifier(B){let F=B.parent;if(q.isMemberExpression(F)&&F.property===B.node&&!F.computed||q.isObjectProperty(F)&&F.key===B.node&&!F.computed)return;let G=B,Y=G.node.name;if(B.isBindingIdentifier())return;let C=R.get(Y);if(C){let z=q.memberExpression(q.identifier(O.name),q.identifier(C[0]));for(let J=1;J<C.length;J++)z=q.memberExpression(z,q.identifier(C[J]));G.replaceWith(z)}else if(D.includes(Y)){let z=A.get(Y)??Y;G.replaceWith(q.memberExpression(q.identifier(O.name),q.identifier(z)))}}};if(w.traverse(f),q.isBlockStatement(Q.node.body))Q.node.body.body.unshift(...j);else if(q.isExpression(Q.node.body)){let B=q.returnStatement(Q.node.body);Q.node.body=q.blockStatement([...j,B])}let x=Q.findParent((B)=>B.isProgram());if(x)T(x,X,E)}var y=k.default??k,c=S.default??S,o=()=>({name:"solid-undestructure",enforce:"pre",transform(Q,$){if(!/\.(tsx?|jsx?)$/.test($))return null;if($.includes("node_modules"))return null;if(!/\(\s*\{/.test(Q))return null;try{let L=I(Q,{sourceType:"module",plugins:["typescript","jsx"]});y(L,{Function(D){let A=D.node.params;if(A.length!==1)return;let R=A[0];if(!b.isObjectPattern(R))return;if(!v(D))return;N(D,R)}});let U=c(L,{retainLines:!0,compact:!1});return{code:U.code,map:U.map}}catch(W){return console.warn(`Failed to transform ${$}:`,W),null}}});export{o as default};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NodePath } from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
/**
|
|
4
|
+
* Determines if a function is likely a Solid component by checking if it returns JSX.
|
|
5
|
+
* Handles various JSX return patterns including conditional expressions, logical operators,
|
|
6
|
+
* and arrow functions with expression bodies.
|
|
7
|
+
*/
|
|
8
|
+
export declare function checkIfComponent(path: NodePath<t.Function>): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NodePath } from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
/**
|
|
4
|
+
* Ensures the required Solid.js imports (mergeProps, splitProps) are present in the program.
|
|
5
|
+
*
|
|
6
|
+
* For each needed import, checks if a named import from 'solid-js' already exists.
|
|
7
|
+
* If it does, nothing happens. If it doesn't, adds:
|
|
8
|
+
* import { mergeProps } from 'solid-js'
|
|
9
|
+
* import { splitProps } from 'solid-js'
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureImports(programPath: NodePath<t.Program>, needsMergeProps: boolean, needsSplitProps: boolean): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NodePath } from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
/**
|
|
4
|
+
* Transforms destructured props parameters into proper Solid.js reactive props access.
|
|
5
|
+
* Converts:
|
|
6
|
+
* function Component({ prop1, prop2 }) { ... }
|
|
7
|
+
* Into:
|
|
8
|
+
* function Component(_props) {
|
|
9
|
+
* const _merged = mergeProps(defaults, _props)
|
|
10
|
+
* const [, rest] = splitProps(_merged, ['prop1', 'prop2'])
|
|
11
|
+
* // References to prop1, prop2 become _merged.prop1, _merged.prop2
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
export declare function transformPropsDestructuring(path: NodePath<t.Function>, objectPattern: t.ObjectPattern): void;
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-solid-undestructure",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Automatically transforms props destructuring in SolidJS components",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/microdev1/vite-plugin-solid-undestructure.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"vite",
|
|
18
|
+
"solidjs",
|
|
19
|
+
"babel",
|
|
20
|
+
"plugin"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "bun build src --minify --packages=external --outfile=dist/index.js && tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
25
|
+
"type": "tsc --noEmit",
|
|
26
|
+
"lint": "eslint --cache src",
|
|
27
|
+
"format": "prettier --write *",
|
|
28
|
+
"test": "bun test tests",
|
|
29
|
+
"check:format": "prettier --check *"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@babel/generator": "^7.29.1",
|
|
33
|
+
"@babel/parser": "^7.29.0",
|
|
34
|
+
"@babel/traverse": "^7.29.0",
|
|
35
|
+
"@babel/types": "^7.29.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/babel__core": "^7.20.5",
|
|
39
|
+
"@types/babel__generator": "^7.27.0",
|
|
40
|
+
"@types/babel__traverse": "^7.28.0",
|
|
41
|
+
"@types/bun": "^1.3.9",
|
|
42
|
+
"config": "link:config",
|
|
43
|
+
"eslint": "^9.39.3",
|
|
44
|
+
"prettier": "^3.8.1",
|
|
45
|
+
"prettier-plugin-organize-imports": "^4.3.0",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"typescript-eslint": "^8.56.1"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"vite": "^7.3.1"
|
|
51
|
+
}
|
|
52
|
+
}
|