Use SolidJS with ESBuild
Hey,
as stated by the documentation, SolidJS uses Babel in combination with a preset to transpile the JSX/TSX. I am currently trying to transpile my SolidJS app using ESBuild in combination with a Deno setup and ran into a few problems after realizing that SolidJS needs the Babel preset. I am now using the the esbuild-plugin-solid and that works well.
But this seems like a "workaround", because the plugin does not utilize the speed of ESBuild because it just calls Babel transpiler under the hood. I was wondering what features SolidJS requires, that ESBuild currently does not offer. ryansolid mentioned that SolidJS needs "AST level plugin manipulation". Is this just for performance so that SolidJS can be optimized or is this needed for something else? And would it be possible to transpile SolidJS without the need of Babel and the preset?
Currently ESBuild offers a "minimalistic" plugin API. ESBuild does not create an AST, so "AST level plugin manipulation" would not be possible without an other parser on top (That's what the plugin currently does). But the question is if we even need "AST level plugin manipulation" in the first place.
Maybe someone can give me a hint on why is is so "complicated and complex" to transpile SolidJS JSX/TSX.
Maybe someone can give me a hint on why is is so "complicated and complex" to transpile SolidJS JSX/TSX.
Because we use AST manipulation to turn JSX into multiple other statements that depends on context a lot. I have documented the process here, but it is unfortunately not being merged: https://github.com/atk/solid-docs-next/blob/feat/how-it-works/src/routes/advanced-concepts/jsx-transpilation.mdx
import { createSignal } from "solid-js";
const Test = () => {
const [counter, setCounter] = createSignal(0);
return <button onClick={() => setCounter(c => c + 1)}>{counter()}</div>
};
is turned into
import { template as _$template } from "solid-js/web";
import { delegateEvents as _$delegateEvents } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";
var _tmpl$ = /*#__PURE__*/_$template(`<button>`);
import { createSignal } from "solid-js";
const Test = () => {
const [counter, setCounter] = createSignal(0);
return (() => {
var _el$ = _tmpl$();
_el$.$$click = () => setCounter(c => c + 1);
_$insert(_el$, counter);
return _el$;
})();
};
_$delegateEvents(["click"]);
This means there is not a 1:1 relation from an input to an output. For example, the transpilation checks that counter is a function and directly puts it in the _$insert call, which will bind all the reactivity so counter will actually update.
This means there is not a 1:1 relation from an input to an output. For example, the transpilation checks that counter is a function and directly puts it in the _$insert call, which will bind all the reactivity so counter will actually update.
Ahh okay so it looks like this was the problem when i was using ESBuild directly without the plugin.
This is what ESBuild produces directly. From this:
import { render } from '@solid-js/web';
import { createSignal } from "@solid-js";
const Test = () => {
const [counter, setCounter] = createSignal(0);
return <button type='button' onClick={() => setCounter(c => c + 1)}>{counter()}</button>
};
render(
() => <Test></Test>,
globalThis.document.body
);
To this:
// ... a bunch of minification
var h = createHyperScript({
spread,
assign,
insert,
createComponent,
dynamicProperty,
SVGElements
});
// ../../../../AppData/Local/deno/npm/registry.npmjs.org/solid-js/1.9.10/h/jsx-runtime/dist/jsx.js
function jsx(type, props) {
return h(type, props);
}
// src/client/index.tsx
var Test = () => {
const [counter, setCounter] = createSignal(0);
return /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setCounter((c) => c + 1), children: counter() });
};
render(() => /* @__PURE__ */ jsx(Test, {}), globalThis.document.body);
//# sourceMappingURL=index.js.map
And yes when using the plugin I get this:
// ... a bunch of minification
// src/client/index.tsx
var _tmpl$ = /* @__PURE__ */ template(`<button type=button>`);
var Test = () => {
const [counter, setCounter] = createSignal(0);
return (() => {
var _el$ = _tmpl$();
_el$.$$click = () => setCounter((c) => c + 1);
insert(_el$, counter);
return _el$;
})();
};
render(() => createComponent(Test, {}), globalThis.document.body);
delegateEvents(["click"]);
//# sourceMappingURL=index.js.map