A native Bun plugin for compiling SolidJS projects without Babel.
Status: DOM, SSR, and universal output are implemented via Solid’s runtime helpers (DOM templates + insert, SSR ssr() templates, ssrHydrationKey, ssrClassList/ssrStyle/ssrAttribute, etc). AST-based parsing (TypeScript or its native preview) is used; as with any compiler, validate against your app.
This plugin transforms JSX in .jsx and .tsx files into optimized SolidJS DOM expressions using Bun's native capabilities.
- Zero Babel dependency - Pure JavaScript/TypeScript implementation
- Fast compilation - Leverages Bun's native transpiler
- SolidJS DOM support - Templates, reactivity, events, components
- SSR + universal modes - Uses Solid server helpers and
ssr()templates (with optional hydration keys) - Event delegation - Automatic event delegation for common events
- Special attributes -
classList,style,ref,use:*directives - Collision-safe output - Avoids shadowing user identifiers in generated helpers/templates
- Optional inline source maps - Improve debugging by mapping transformed output back to input
bun add @nathanld/gasMigrating from babel-preset-solid? See MIGRATION.md.
- Add end-to-end SSR render-to-string + client hydrate checks across
generate: "dom" | "ssr"andruntime: "dom" | "ssr" | "universal". - Expand golden coverage: delegated vs non-delegated events, nested spreads with children, portals/fragments, classList/style object combos, hydration-key expectations.
- Align fully with Solid server runtime helpers (including any needed
ssrSpreadsemantics) and verify hydration marker parity. - Wire CI matrix for
bun testandbun buildin all presets.
Create a preload file to enable the plugin at runtime:
// preload.ts
import { preload } from "@nathanld/gas";
preload({
generate: "dom"
});Configure in bunfig.toml:
preload = ["./preload.ts"]Use the plugin with Bun.build():
// build.ts
import { gasPlugin } from "@nathanld/gas";
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
plugins: [gasPlugin({ generate: "dom" })]
});When using Bun's HTML dev server or static-site bundler, configure gas as a bundler plugin via bunfig.toml:
[serve.static]
plugins = ["@nathanld/gas/bun-plugin"]
preload = ["./preload.ts"] # optional; enables runtime plugin for non-HTML usage@nathanld/gas/bun-pluginis a ready-to-use bundler plugin instance for DOM output.- For custom options, prefer wiring
gasPlugin(...)directly viaBun.build()as shown above.
Server-side rendering is available via generate: "ssr". Set hydratable: true to emit hydration keys.
import { gasPlugin } from "@nathanld/gas";
await Bun.build({
entrypoints: ["./src/server.tsx"],
outdir: "./dist",
plugins: [
gasPlugin({
generate: "ssr",
hydratable: true
})
]
});interface GasPluginOptions {
// Output mode
// Note: for Babel compatibility, "universal" is accepted (treated as generate: "ssr" + runtime: "universal").
generate?: "dom" | "ssr" | "universal";
// Enable hydratable output (DOM hydration + SSR hydration keys)
hydratable?: boolean;
// Module name for Solid runtime imports (defaults depend on `runtime`)
moduleName?: string;
// Runtime preset:
// - "dom" (solid-js/web)
// - "ssr" (solid-js/web server build)
// - "universal" (solid-js/universal)
runtime?: "dom" | "ssr" | "universal";
// Built-in components that receive special compilation
builtIns?: string[];
// Enable/disable delegated event output (defaults to true)
delegateEvents?: boolean;
// Additional delegated event names (extends the default set)
delegatedEvents?: string[];
// Wrap conditionals in memos for fine-grained reactivity
wrapConditionals?: boolean;
// DOM template closing-tag minimization (babel-preset-solid parity)
omitNestedClosingTags?: boolean;
omitLastClosingTag?: boolean;
// Optimize HTML by omitting quotes around safe attribute values
omitQuotes?: boolean;
// Restrict JSX transformation to files with a matching @jsxImportSource pragma (e.g. "solid-js")
requireImportSource?: string | false;
// Convert context to custom elements (set element._$owner = getOwner())
contextToCustomElements?: boolean;
// Static marker comment (default "@once")
staticMarker?: string;
// Wrapper function names (or false for wrapperless mode)
effectWrapper?: string | false;
memoWrapper?: string | false;
// Enable HTML structure validation for JSX output
validate?: boolean;
// Enable development mode
dev?: boolean;
// Generate inline source maps for transformed modules (Bun plugin only)
sourceMap?: boolean | "inline";
// File filter regex pattern
filter?: RegExp;
}{
generate: "dom",
hydratable: false, // enables DOM hydration when generate: "dom", and hydration keys when generate: "ssr"
runtime: undefined, // or "dom" | "ssr" | "universal"
moduleName: "solid-js/web", // overridden when runtime is set
builtIns: [
"For", "Show", "Switch", "Match", "Suspense",
"SuspenseList", "Portal", "Index", "Dynamic", "ErrorBoundary"
],
delegateEvents: true,
delegatedEvents: [],
wrapConditionals: true,
omitNestedClosingTags: false,
omitLastClosingTag: true,
omitQuotes: true,
requireImportSource: false, // or "solid-js" to require /** @jsxImportSource solid-js */
contextToCustomElements: false,
staticMarker: "@once",
effectWrapper: "effect",
memoWrapper: "memo",
validate: true,
dev: false,
sourceMap: false,
filter: /\.[tj]sx$/
}- Inline source maps: set
sourceMap: "inline"(ortrue) when using the Bun plugin to emit an inlinesourceMappingURLso stack traces and devtools can map back to the original file. - Dev output: set
dev: trueto include additional debug comments in generated code.
// Input
const App = () => <div class="container">Hello World</div>;
// Output
import { template as _$template } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/ _$template(`<div class="container">Hello World</div>`);
const App = () => _tmpl$();// Input
const App = () => {
const [count, setCount] = createSignal(0);
return <div>Count: {count()}</div>;
};
// Output
import { template as _$template, insert as _$insert } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/ _$template(`<div>Count: </div>`);
const App = () => {
const [count, setCount] = createSignal(0);
return (() => {
const _el$ = _tmpl$();
_$insert(_el$.firstChild.parentNode, () => count(), _el$.firstChild.nextSibling);
return _el$;
})();
};// Input
const App = () => <button onClick={() => console.log("clicked")}>Click me</button>;
// Output
import { template as _$template, delegateEvents as _$delegateEvents } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/ _$template(`<button>Click me</button>`);
const App = () => {
const _el$ = _tmpl$();
_el$.$$click = () => console.log("clicked");
return _el$;
};
_$delegateEvents(["click"]);// Input
const App = () => (
<MyComponent name="World">
<span>Hello</span>
</MyComponent>
);
// Output
import { createComponent as _$createComponent, template as _$template } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/ _$template(`<span>Hello</span>`);
const App = () =>
_$createComponent(MyComponent, {
name: "World",
get children() {
return _tmpl$();
}
});// Input
const App = () => <For each={items()}>{item => <div>{item.name}</div>}</For>;
// Output
import { template as _$template, insert as _$insert } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/ _$template(`<div></div>`);
const App = () =>
For({
get each() {
return items();
},
get children() {
return item => {
const _el$ = _tmpl$();
_$insert(_el$, () => item.name);
return _el$;
};
}
});- Static attributes:
<div class="foo"> - Dynamic attributes:
<div class={className()}> - Boolean attributes:
<input disabled> - Spread props:
<div {...props}>
ref: Element referencesclassList: Dynamic class objectstyle: Dynamic style objectuse:*: Directivesprop:*: Force propertyattr:*: Force attributeon:*: Non-delegated eventsoncapture:*: Capture phase events
- Delegated events:
onClick,onInput, etc. - Non-delegated:
on:scroll,on:load - Capture phase:
oncapture:click
- HTML elements
- SVG elements (with proper namespace)
- Custom elements
- Fragments:
<>...</>
- Function components
- Built-in components (For, Show, Switch, etc.)
- Member expression components (Foo.Bar)
gas is designed to be compatible with the SolidJS ecosystem:
- SolidJS runtime: Works with all SolidJS runtime APIs (
createSignal,createEffect,For,Show, etc.) - SolidJS libraries: Compatible with SolidJS community libraries that use standard JSX patterns
- SolidStart: Can be used with SolidStart when building with Bun (SolidStart's default Vite setup uses
babel-preset-solid) - Output parity: Generates code compatible with
babel-preset-solidoutput, so components work interchangeably
Note: gas is a Bun-specific plugin. For Vite, Webpack, or other bundlers, continue using babel-preset-solid or @solidjs/vite-plugin.
Creates a Bun plugin instance.
import { gasPlugin } from "@nathanld/gas";
const plugin = gasPlugin({
generate: "dom",
hydratable: false
});Convenience function for preload scripts.
import { preload } from "@nathanld/gas";
preload({ generate: "dom" });Low-level API to transform JSX source code.
import { transformJSX } from "@nathanld/gas";
const result = transformJSX(sourceCode, resolvedOptions);Low-level API that returns both transformed code and a source map (useful for tooling and debugging).
import { transformJSXWithMap } from "@nathanld/gas";
const { code, map } = transformJSXWithMap(sourceCode, resolvedOptions, "input.tsx");Check if source code contains JSX.
import { hasJSX } from "@nathanld/gas";
if (hasJSX(sourceCode)) {
// Transform the code
}# Install dependencies
bun install
# Run tests
bun test
# Build
bun run buildMIT