From 627bbaac432d76a2cbf8161c4ae65a83802f3e07 Mon Sep 17 00:00:00 2001 From: bondehagen Date: Wed, 24 Dec 2025 16:08:10 +0100 Subject: [PATCH] feature: add Bun bundler support with inline HMR --- .gitignore | 1 + README.md | 28 ++ src/babel/core/constants.ts | 6 + src/babel/core/create-registry.ts | 116 +++++++-- src/babel/core/get-hot-identifier.ts | 3 +- src/runtime/index.ts | 2 + src/shared/types.ts | 2 +- tests/client/__snapshots__/bun.test.ts.snap | 268 ++++++++++++++++++++ tests/client/bun.test.ts | 162 ++++++++++++ 9 files changed, 569 insertions(+), 19 deletions(-) create mode 100644 tests/client/__snapshots__/bun.test.ts.snap create mode 100644 tests/client/bun.test.ts diff --git a/.gitignore b/.gitignore index 8617652..0f4ec2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ dist/ .vscode/ +bun.lock diff --git a/README.md b/README.md index 23acb0f..141edb4 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,14 @@ yarn add -D solid-refresh pnpm add -D solid-refresh ``` +```bash +bun add -D solid-refresh +``` + This project aims to provide HMR for Solid for various bundlers. It comes with a babel plugin and a runtime. Over time I hope to add different bundlers. Today it supports: * Vite (with option `bundler: "vite"`) +* Bun (with option `bundler: "bun"`) * Snowpack (with option `bundler: "esm"`) * Webpack (for strict ESM, use option `bundler: "webpack5"`) * Nollup @@ -29,6 +34,29 @@ This project aims to provide HMR for Solid for various bundlers. It comes with a `solid-refresh` is already built into [`vite-plugin-solid`](https://github.com/solidjs/vite-plugin-solid). +### Bun + +When using Bun's built-in development server or bundler, add the following to `.babelrc`: + +```json +{ + "env": { + "development": { + "plugins": [["solid-refresh/babel", { + "bundler": "bun" + }]] + } + } +} +``` + +> [!NOTE] +> Bun requires direct calls to `import.meta.hot.*` methods and does not support passing `import.meta.hot` as an argument to functions. The `"bun"` bundler option generates inline HMR code that is compatible with Bun's requirements. +> +> Bun's HMR API does not currently support `import.meta.hot.invalidate()`. When a component cannot be hot-reloaded (e.g., due to structural changes), the page will perform a full reload using `window.location.reload()`. + +For more information about Bun's HMR implementation, see the [Bun HMR documentation](https://bun.com/docs/bundler/hot-reloading). + ### Webpack & Rspack You can read the following guides first, respectively: diff --git a/src/babel/core/constants.ts b/src/babel/core/constants.ts index 1bf965f..41cf475 100644 --- a/src/babel/core/constants.ts +++ b/src/babel/core/constants.ts @@ -34,6 +34,12 @@ export const IMPORT_DECLINE: ImportDefinition = { source: SOLID_REFRESH_MODULE, }; +export const IMPORT_PATCH_REGISTRY: ImportDefinition = { + kind: 'named', + name: '$$patchRegistry', + source: SOLID_REFRESH_MODULE, +}; + export const IMPORT_SPECIFIERS: ImportIdentifierSpecifier[] = [ { type: 'render', diff --git a/src/babel/core/create-registry.ts b/src/babel/core/create-registry.ts index 40f0138..a9b6ad3 100644 --- a/src/babel/core/create-registry.ts +++ b/src/babel/core/create-registry.ts @@ -1,12 +1,85 @@ import type * as babel from '@babel/core'; import * as t from '@babel/types'; -import { IMPORT_REFRESH, IMPORT_REGISTRY } from './constants'; +import { + IMPORT_PATCH_REGISTRY, + IMPORT_REFRESH, + IMPORT_REGISTRY, +} from './constants'; import { getHotIdentifier } from './get-hot-identifier'; import { getImportIdentifier } from './get-import-identifier'; import { getRootStatementPath } from './get-root-statement-path'; import type { StateContext } from './types'; const REGISTRY = 'REGISTRY'; +const SOLID_REFRESH = 'solid-refresh'; +const SOLID_REFRESH_PREV = 'solid-refresh-prev'; + +function createBunInlineHMR( + state: StateContext, + path: babel.NodePath, + registryId: t.Identifier, +): t.Statement[] { + const hotMeta = getHotIdentifier(state); + const patchRegistryId = getImportIdentifier( + state, + path, + IMPORT_PATCH_REGISTRY, + ); + const hotData = t.memberExpression(hotMeta, t.identifier('data')); + const hotDataRefresh = t.memberExpression( + hotData, + t.stringLiteral(SOLID_REFRESH), + true, + ); + const hotDataPrev = t.memberExpression( + hotData, + t.stringLiteral(SOLID_REFRESH_PREV), + true, + ); + const assignRefresh = t.expressionStatement( + t.assignmentExpression( + '=', + hotDataRefresh, + t.logicalExpression('||', hotDataRefresh, registryId), + ), + ); + const assignPrev = t.expressionStatement( + t.assignmentExpression('=', hotDataPrev, registryId), + ); + const modParam = t.identifier('mod'); + const acceptCallback = t.arrowFunctionExpression( + [modParam], + t.blockStatement([ + t.ifStatement( + t.logicalExpression( + '||', + t.binaryExpression('==', modParam, t.nullLiteral()), + t.callExpression(patchRegistryId, [hotDataRefresh, hotDataPrev]), + ), + t.blockStatement([ + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.memberExpression( + t.identifier('window'), + t.identifier('location'), + ), + t.identifier('reload'), + ), + [], + ), + ), + ]), + ), + ]), + ); + const acceptCall = t.expressionStatement( + t.callExpression(t.memberExpression(hotMeta, t.identifier('accept')), [ + acceptCallback, + ]), + ); + return [assignRefresh, assignPrev, acceptCall]; +} export function createRegistry( state: StateContext, @@ -33,22 +106,31 @@ export function createRegistry( )[0], ); const pathToHot = getHotIdentifier(state); - ( - path.scope.getProgramParent().path as babel.NodePath - ).pushContainer('body', [ - t.ifStatement( - pathToHot, - t.blockStatement([ - t.expressionStatement( - t.callExpression(getImportIdentifier(state, path, IMPORT_REFRESH), [ - t.stringLiteral(state.bundler), - pathToHot, - identifier, - ]), - ), - ]), - ), - ]); + const programPath = path.scope.getProgramParent() + .path as babel.NodePath; + + if (state.bundler === 'bun') { + const bunHMRStatements = createBunInlineHMR(state, path, identifier); + programPath.pushContainer('body', [ + t.ifStatement(pathToHot, t.blockStatement(bunHMRStatements)), + ]); + } else { + programPath.pushContainer('body', [ + t.ifStatement( + pathToHot, + t.blockStatement([ + t.expressionStatement( + t.callExpression(getImportIdentifier(state, path, IMPORT_REFRESH), [ + t.stringLiteral(state.bundler), + pathToHot, + identifier, + ]), + ), + ]), + ), + ]); + } + state.imports.set(REGISTRY, identifier); return identifier; } diff --git a/src/babel/core/get-hot-identifier.ts b/src/babel/core/get-hot-identifier.ts index 2447b53..94a8402 100644 --- a/src/babel/core/get-hot-identifier.ts +++ b/src/babel/core/get-hot-identifier.ts @@ -3,9 +3,10 @@ import * as t from '@babel/types'; export function getHotIdentifier(state: StateContext): t.MemberExpression { switch (state.bundler) { - // vite/esm uses `import.meta.hot` + // vite/esm/bun uses `import.meta.hot` case 'esm': case 'vite': + case 'bun': return t.memberExpression( t.memberExpression(t.identifier('import'), t.identifier('meta')), t.identifier('hot'), diff --git a/src/runtime/index.ts b/src/runtime/index.ts index d21510f..1a387e1 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -178,6 +178,8 @@ function patchRegistry(oldRegistry: Registry, newRegistry: Registry) { return shouldInvalidateByComponents || shouldInvalidateByContext; } +export const $$patchRegistry = patchRegistry; + const SOLID_REFRESH = 'solid-refresh'; const SOLID_REFRESH_PREV = 'solid-refresh-prev'; diff --git a/src/shared/types.ts b/src/shared/types.ts index 58d8305..16ba0e6 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,6 +1,6 @@ // For HMRs that follows Snowpack's spec // https://github.com/FredKSchott/esm-hmr -export type ESMRuntimeType = 'esm' | 'vite'; +export type ESMRuntimeType = 'esm' | 'vite' | 'bun'; // For HMRs that follow Webpack's design export type StandardRuntimeType = 'standard' | 'webpack5' | 'rspack-esm'; diff --git a/tests/client/__snapshots__/bun.test.ts.snap b/tests/client/__snapshots__/bun.test.ts.snap new file mode 100644 index 0000000..8640e93 --- /dev/null +++ b/tests/client/__snapshots__/bun.test.ts.snap @@ -0,0 +1,268 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`bun (client, non-hydratable) > Context API > should support top-level VariableDeclaration 1`] = ` +"import { $$context as _$$context } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +import { createContext } from 'solid-js'; +const _REGISTRY = _$$registry(); +const Example = _$$context(_REGISTRY, "Example", createContext()); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > ExportNamedDeclaration > should transform ExportNamedDeclaration 1`] = ` +"import { template as _$template } from "solid-js/web"; +import { createComponent as _$createComponent } from "solid-js/web"; +var _tmpl$ = /*#__PURE__*/_$template(\`

Foo\`); +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +const _REGISTRY = _$$registry(); +const Foo_1 = _$$component(_REGISTRY, "Foo_1", _props => /*@refresh jsx-skip*/_tmpl$(), { + location: "example.jsx:3:15", + signature: "4b145a58" +}); +const Foo = _$$component(_REGISTRY, "Foo", function Foo(props) { + return /*@refresh jsx-skip*/_$createComponent(Foo_1, {}); +}, { + location: "example.jsx:2:13", + signature: "1f7be6ec" +}); +export { Foo }; +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > FunctionDeclaration > should generate inline HMR code for Bun 1`] = ` +"import { template as _$template } from "solid-js/web"; +import { createComponent as _$createComponent } from "solid-js/web"; +var _tmpl$ = /*#__PURE__*/_$template(\`

Foo\`); +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +const _REGISTRY = _$$registry(); +const Foo_1 = _$$component(_REGISTRY, "Foo_1", _props => /*@refresh jsx-skip*/_tmpl$(), { + location: "example.jsx:3:15", + signature: "4b145a58" +}); +const Foo = _$$component(_REGISTRY, "Foo", function Foo(props) { + return /*@refresh jsx-skip*/_$createComponent(Foo_1, {}); +}, { + location: "example.jsx:2:6", + signature: "1f7be6ec" +}); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > FunctionDeclaration > should handle createContext 1`] = ` +"import { createComponent as _$createComponent } from "solid-js/web"; +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +const Example = createContext(); +const _REGISTRY = _$$registry(); +const Foo_1 = _$$component(_REGISTRY, "Foo_1", _props => /*@refresh jsx-skip*/_$createComponent(_props.v0, { + children: "Foo" +}), { + location: "example.jsx:4:15", + signature: "23a9c5eb" +}); +const Foo = _$$component(_REGISTRY, "Foo", function Foo() { + return /*@refresh jsx-skip*/_$createComponent(Foo_1, { + get v0() { + return Example.Provider; + } + }); +}, { + location: "example.jsx:3:6", + signature: "b4605599", + dependencies: () => ({ + Example + }) +}); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > FunctionDeclaration > should handle external dependencies 1`] = ` +"import { template as _$template } from "solid-js/web"; +import { createComponent as _$createComponent } from "solid-js/web"; +import { insert as _$insert } from "solid-js/web"; +var _tmpl$ = /*#__PURE__*/_$template(\`

\`); +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +const example = 'Foo'; +const _REGISTRY = _$$registry(); +const Foo_1 = _$$component(_REGISTRY, "Foo_1", _props => /*@refresh jsx-skip*/(() => { + var _el$ = _tmpl$(); + _$insert(_el$, () => _props.v0); + return _el$; +})(), { + location: "example.jsx:4:15", + signature: "ae0f2a31" +}); +const Foo = _$$component(_REGISTRY, "Foo", function Foo() { + return /*@refresh jsx-skip*/_$createComponent(Foo_1, { + v0: example + }); +}, { + location: "example.jsx:3:6", + signature: "27e4cbbb", + dependencies: () => ({ + example + }) +}); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > FunctionDeclaration > should transform FunctionDeclaration with valid Component name 1`] = ` +"import { template as _$template } from "solid-js/web"; +import { createComponent as _$createComponent } from "solid-js/web"; +var _tmpl$ = /*#__PURE__*/_$template(\`

Foo\`); +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +const _REGISTRY = _$$registry(); +const Foo_1 = _$$component(_REGISTRY, "Foo_1", _props => /*@refresh jsx-skip*/_tmpl$(), { + location: "example.jsx:3:15", + signature: "4b145a58" +}); +const Foo = _$$component(_REGISTRY, "Foo", function Foo() { + return /*@refresh jsx-skip*/_$createComponent(Foo_1, {}); +}, { + location: "example.jsx:2:6", + signature: "c963b907" +}); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > VariableDeclarator > ArrowFunctionExpression > should transform VariableDeclarator w/ ArrowFunctionExpression 1`] = ` +"import { template as _$template } from "solid-js/web"; +import { createComponent as _$createComponent } from "solid-js/web"; +var _tmpl$ = /*#__PURE__*/_$template(\`

Foo\`); +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +const _REGISTRY = _$$registry(); +const Foo_1 = _$$component(_REGISTRY, "Foo_1", _props => /*@refresh jsx-skip*/_tmpl$(), { + location: "example.jsx:3:17", + signature: "4b145a58" +}); +const Foo = _$$component(_REGISTRY, "Foo", props => { + return /*@refresh jsx-skip*/_$createComponent(Foo_1, {}); +}, { + location: "example.jsx:2:20", + signature: "afe16871" +}); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > VariableDeclarator > ArrowFunctionExpression > should transform VariableDeclarator w/ ArrowFunctionExpression 2`] = ` +"import { template as _$template } from "solid-js/web"; +import { createComponent as _$createComponent } from "solid-js/web"; +var _tmpl$ = /*#__PURE__*/_$template(\`

Foo\`); +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +const _REGISTRY = _$$registry(); +const Foo_1 = _$$component(_REGISTRY, "Foo_1", _props => /*@refresh jsx-skip*/_tmpl$(), { + location: "example.jsx:3:17", + signature: "4b145a58" +}); +const Foo = _$$component(_REGISTRY, "Foo", () => { + return /*@refresh jsx-skip*/_$createComponent(Foo_1, {}); +}, { + location: "example.jsx:2:20", + signature: "82d7f8e8" +}); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; + +exports[`bun (client, non-hydratable) > fix render > should work with render + dispose 1`] = ` +"import { createComponent as _$createComponent } from "solid-js/web"; +import { $$component as _$$component } from "solid-refresh"; +import { $$patchRegistry as _$$patchRegistry } from "solid-refresh"; +import { $$registry as _$$registry } from "solid-refresh"; +import { render } from 'solid-js/web'; +const _REGISTRY = _$$registry(); +const JSX__cleanup_1 = _$$component(_REGISTRY, "JSX__cleanup_1", _props => /*@refresh jsx-skip*/_$createComponent(_props.v0, {}), { + location: "example.jsx:4:23", + signature: "1fdef89b" +}); +const _cleanup = render(() => /*@refresh jsx-skip*/_$createComponent(JSX__cleanup_1, { + v0: App +}), root); +if (import.meta.hot) import.meta.hot.dispose(_cleanup); +if (import.meta.hot) { + import.meta.hot.data["solid-refresh"] = import.meta.hot.data["solid-refresh"] || _REGISTRY; + import.meta.hot.data["solid-refresh-prev"] = _REGISTRY; + import.meta.hot.accept(mod => { + if (mod == null || _$$patchRegistry(import.meta.hot.data["solid-refresh"], import.meta.hot.data["solid-refresh-prev"])) { + window.location.reload(); + } + }); +}" +`; diff --git a/tests/client/bun.test.ts b/tests/client/bun.test.ts new file mode 100644 index 0000000..e28f3d4 --- /dev/null +++ b/tests/client/bun.test.ts @@ -0,0 +1,162 @@ +import { describe, it, expect } from 'vitest'; +import { transform } from '../transform'; + +describe('bun (client, non-hydratable)', () => { + describe('FunctionDeclaration', () => { + it('should generate inline HMR code for Bun', async () => { + const result = await transform( + ` + function Foo(props) { + return

Foo

; + } + `, + 'bun', + 'client', + false, + ); + + // Verify that import.meta.hot is NOT passed as an argument + expect(result).not.toContain('_$$refresh("bun", import.meta.hot'); + expect(result).toContain('import.meta.hot.data["solid-refresh"]'); + expect(result).toContain('import.meta.hot.data["solid-refresh-prev"]'); + expect(result).toContain('import.meta.hot.accept'); + + // Verify that window.location.reload() is used instead of import.meta.hot.invalidate() + // (Bun doesn't support invalidate) + expect(result).toContain('window.location.reload'); + expect(result).not.toContain('import.meta.hot.invalidate'); + + expect(result).toMatchSnapshot(); + }); + + it('should transform FunctionDeclaration with valid Component name', async () => { + expect( + await transform( + ` + function Foo() { + return

Foo

; + } + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + }); + + it('should handle external dependencies', async () => { + expect( + await transform( + ` + const example = 'Foo'; + function Foo() { + return

{example}

; + } + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + }); + + it('should handle createContext', async () => { + expect( + await transform( + ` + const Example = createContext(); + function Foo() { + return Foo; + } + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + }); + }); + + describe('VariableDeclarator', () => { + describe('ArrowFunctionExpression', () => { + it('should transform VariableDeclarator w/ ArrowFunctionExpression', async () => { + expect( + await transform( + ` + const Foo = (props) => { + return

Foo

; + } + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + + expect( + await transform( + ` + const Foo = () => { + return

Foo

; + } + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + }); + }); + }); + + describe('ExportNamedDeclaration', () => { + it('should transform ExportNamedDeclaration', async () => { + expect( + await transform( + ` + export function Foo(props) { + return

Foo

; + } + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + }); + }); + + describe('Context API', () => { + it('should support top-level VariableDeclaration', async () => { + expect( + await transform( + ` + import { createContext } from 'solid-js'; + + const Example = createContext(); + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + }); + }); + + describe('fix render', () => { + it('should work with render + dispose', async () => { + expect( + await transform( + ` + import { render } from 'solid-js/web'; + + render(() => , root); + `, + 'bun', + 'client', + false, + ), + ).toMatchSnapshot(); + }); + }); +});