S
SolidJS7mo ago
Liquido

How to make SolidJS tags not return HTML elements for a custom renderer?

I have created a custom SolidJS renderer that returns JSON but the JSX tags that are being called always return HTML elements.
export const renderer = createRenderer<ElementComponent>({
createElement(type) {
console.log('createElement');
return { type: type as ElementComponent['type'], children: [] };
},

createTextNode(children: string) {
console.log('createTextNode');
return { type: 'text', children };
},

replaceText(textNode: ElementText, value: string) {
console.log('replaceText');

textNode.children = value;
},

isTextNode(node) {
console.log('isTextNode');
return node.type === 'text';
},

setProperty<T>(
node: ElementComponent,
name: keyof ElementComponent,
value: T,
) {
console.log('setProp', node, value);

// node[name] = value;
},

insertNode(parent, node): void {
if (parent.type !== 'text') {
parent.children?.push(node);
}
console.log('insertNode', node);
},

removeNode(parent, node): void {
if (parent.type !== 'text') {
const index = parent.children.indexOf(node);
parent.children?.splice(index, 1);
}
},

getParentNode(node: unknown): unknown {
throw new Error('Function not implemented.');
},

getFirstChild(node): unknown {
throw new Error('Function not implemented.');
},

getNextSibling(node: unknown): unknown {
throw new Error('Function not implemented.');
},
});

const view: ElementView = { type: 'view', children: [] };

const MyComponent = () => {
return <myview><mytext>Hello world</mytext></myview>
}

renderer.render(MyComponent, view);
console.log(view);
export const renderer = createRenderer<ElementComponent>({
createElement(type) {
console.log('createElement');
return { type: type as ElementComponent['type'], children: [] };
},

createTextNode(children: string) {
console.log('createTextNode');
return { type: 'text', children };
},

replaceText(textNode: ElementText, value: string) {
console.log('replaceText');

textNode.children = value;
},

isTextNode(node) {
console.log('isTextNode');
return node.type === 'text';
},

setProperty<T>(
node: ElementComponent,
name: keyof ElementComponent,
value: T,
) {
console.log('setProp', node, value);

// node[name] = value;
},

insertNode(parent, node): void {
if (parent.type !== 'text') {
parent.children?.push(node);
}
console.log('insertNode', node);
},

removeNode(parent, node): void {
if (parent.type !== 'text') {
const index = parent.children.indexOf(node);
parent.children?.splice(index, 1);
}
},

getParentNode(node: unknown): unknown {
throw new Error('Function not implemented.');
},

getFirstChild(node): unknown {
throw new Error('Function not implemented.');
},

getNextSibling(node: unknown): unknown {
throw new Error('Function not implemented.');
},
});

const view: ElementView = { type: 'view', children: [] };

const MyComponent = () => {
return <myview><mytext>Hello world</mytext></myview>
}

renderer.render(MyComponent, view);
console.log(view);
When I console log the view, the myview and mytext are returned as HTML elements instead of JSON objects. How can I make it so that these elements are returned as plain JSON objects, not HTML elements.
No description
6 Replies
Liquido
Liquido7mo ago
My suspicion is that it has something to do with the way JSX is transformed since the root element's children is filled propertly and createElement of the renderer is not called.
lxsmnsyc
lxsmnsyc7mo ago
Are you using the universal mode for compilation?
Liquido
Liquido7mo ago
I am using Vite directly with solid plugin How do I pass the universal renderer to vite. I see that it has renderers option and generate option but I want to have both the default renderer and the custom renderer. I could not get this working. I tried the following solid Vite:
plugins: [solid({
solid: {
moduleName: '@myapp/renderer',
generate: 'universal'
}
})]
plugins: [solid({
solid: {
moduleName: '@myapp/renderer',
generate: 'universal'
}
})]
However, doing so modifies all solid inclusions to use @myapp/renderer and if I am using a solid based library (e.g @thisbeyond/solid-dnd), I cannot use the library because it fails in the browser. Is there a way to define that only specific components should use @myapp/renderer? I want to use solid DOM but with custom renderer.
alloyed
alloyed7mo ago
you could use the @jsxImportSource pragma to override things on a per file basis. https://www.typescriptlang.org/tsconfig#jsxImportSource but I'm not sure the solid plugin itself would respect it? I guess instead you could install two copies of the solid plugin with different include patterns: https://github.com/solidjs/vite-plugin-solid/blob/7e667ad0bb05ba55e0a4a3e185ecc7e3e3535e4f/src/index.ts#L29C24-L29C24 i would also maybe consider writing your renderer to extend solid/web's interface, maybe? eg. if the input tag is the name of an existing dom object, forward to html, otherwise forward to your custom logic
bigmistqke
bigmistqke7mo ago
+ it's undocumented (and typescript will yell at you), but
const Obj = () => ({ key: 'value' })

const Parent = (props) => {
console.log('children', props.children) // {key: 'value'}
return <></>
}

render(() => <Parent><Obj/></Parent>, ...)
const Obj = () => ({ key: 'value' })

const Parent = (props) => {
console.log('children', props.children) // {key: 'value'}
return <></>
}

render(() => <Parent><Obj/></Parent>, ...)
is valid solid.
Liquido
Liquido6mo ago
I will try out defining two solid plugins -- one used specifically from my project and one used for libraries. And the one in my project is a mix of both DOM and custom renderer After playing a lot with it, I finally got it working with universal renderer by create separate solid plugins depending on directory and everything works as I expect it. The only issue that I have right now is about Typescript. How can I make typescript work with universal renderer and default renderer
renderer.render(() => <MyCustomWidgetComponent />, view);
renderer.render(() => <MyCustomWidgetComponent />, view);
My renderer accepts a custom "MyElementNode" type while the JSX that is produced from MyCustomWidgetComponent is of type Element:
const MyCustomWidgetComponent = () => {
return <view>Hello world</view>
}
const MyCustomWidgetComponent = () => {
return <view>Hello world</view>
}