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 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
+ ```
@@ -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,3 @@
1
+ import { NodePath } from '@babel/traverse';
2
+ import * as t from '@babel/types';
3
+ export declare function checkIfComponentLegacy(path: NodePath<t.Function>): boolean;
@@ -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
+ }