A Vite plugin that automatically transforms props destructuring in SolidJS components to use mergeProps and splitProps, preserving reactivity.
In SolidJS, destructuring props directly breaks reactivity because it converts reactive getters into static values:
// ❌ Breaks reactivity
function Component({ name, count }) {
return (
<div>
{name}: {count}
</div>
)
}The correct approach is to use splitProps and mergeProps:
// ✅ Maintains reactivity
import { splitProps } from 'solid-js'
function Component(_props) {
const [{ name, count }] = splitProps(_props, ['name', 'count'])
// ...
}This plugin performs that transformation automatically.
- ✨ Automatically transforms destructured props to
splitProps/mergeProps - 🎯 Handles default values using
mergeProps - 🔄 Preserves spread parameters with
splitProps - 📦 Auto-imports
mergePropsandsplitPropsfrom 'solid-js' - ⚡ Skips non-component functions
bun add -D vite-plugin-solid-undestructureimport solidUndestructure from './plugins/solid-undestructure'
import solid from 'vite-plugin-solid'
export default defineConfig({
plugins: [solidUndestructure(), solid() /* other plugins */]
})// Before
function Greeting({ name, age }) {
return (
<div>
Hello {name}, you are {age} years old
</div>
)
}
// After
function Greeting(_props) {
return (
<div>
Hello {_props.name}, you are {_props.age} years old
</div>
)
}// Before
function Button({ label = 'Click me', disabled = false }) {
return <button disabled={disabled}>{label}</button>
}
// After
import { mergeProps } from 'solid-js'
function Button(_props) {
const _merged = mergeProps({ label: 'Click me', disabled: false }, _props)
return <button disabled={_merged.disabled}>{_merged.label}</button>
}// Before
function Card({ title, description, ...props }) {
return (
<div {...props}>
<h2>{title}</h2>
<p>{description}</p>
</div>
)
}
// After
import { splitProps } from 'solid-js'
function Card(_props) {
const [, props] = splitProps(_props, ['title', 'description'])
return (
<div {...props}>
<h2>{_props.title}</h2>
<p>{_props.description}</p>
</div>
)
}// Before
import { For } from 'solid-js'
function TestComponent({
name = 'World',
count = 0,
avatar = '/default.png',
items,
nested: { a, b },
...props
}: {
name?: string
count?: number
avatar?: string
items: string[]
nested: { a: number; b: number }
class?: string
onClick?: () => void
}) {
return (
<div {...props}>
<p>{props.class}</p>
<pre>{a}</pre>
<pre>{b}</pre>
<img src={avatar} alt={name} />
<h1>Hello {name}!</h1>
<p>Count: {count}</p>
<ul>
<For each={items}>{(item) => <li>{item}</li>}</For>
</ul>
</div>
)
}
// After
import { For, mergeProps, splitProps } from 'solid-js'
function TestComponent(_props) {
const _merged = mergeProps({ name: 'World', count: 0, avatar: '/default.png' }, _props)
const [, props] = splitProps(_merged, ['name', 'count', 'avatar', 'items', 'nested'])
return (
<div {...props}>
<p>{props.class}</p>
<pre>{_merged.nested.a}</pre>
<pre>{_merged.nested.b}</pre>
<img src={_merged.avatar} alt={_merged.name} />
<h1>Hello {_merged.name}!</h1>
<p>Count: {_merged.count}</p>
<ul>
<For each={_merged.items}>{(item) => <li>{item}</li>}</For>
</ul>
</div>
)
}- Parse — Uses
@babel/parserto parse TypeScript/JSX files into an AST - Detect — Identifies functions with destructured props that return JSX
- Transform — Rewrites destructuring into
mergeProps/splitPropscalls and replaces all references to destructured identifiers with property accesses on the merged/props object - Import — Adds necessary imports from
solid-jsif not already present - Generate — Outputs transformed code with source maps
- Only transforms functions that return JSX (regular functions are left untouched)
- Requires the first parameter to be an object pattern (destructuring)
- Skips files in
node_modules
bun test