I plan to use solid for Atom projects. However, Atom has its own DOM library called etch, which is used in the core of Atom as well as many of its packages. etch has some limitations, it has an average performance, and forces a certain style of programming.
https://github.com/atom/etch
To make the transition seamless, I want to make a drop-in replacement library that uses solid as its backend but has the same API as etch. Could you help me with this?
I have made a demo of this etch-solid library:
https://codesandbox.io/s/etch-solid-pwonz?file=/src/index.jsx
These are the source code of the API (initialize, update, destroy):
https://github.com/atom/etch/blob/master/lib/component-helpers.js
and here the dom function:
https://github.com/atom/etch/blob/9b90d6ad88b90b8a273992c540879b915d9967a2/lib/dom.js#L4
P.S: The other approach is to use meta-programming to transpile etch code to solid code. However, this can be complex, but I am open to that approach too. Something like this babel plugin or this transformer
Details
In the benchmarks, etch is behind solid so this can be beneficial in general (~25% improvement). This can enhance the responsiveness of the editor.
https://krausest.github.io/js-framework-benchmark/2020/table_chrome_84.0.4147.89.html

The last dramatic change for improving the performance happened back in 2017 by introducing etch. Now it is the time to replace that with a faster modern framework. Even if we do not use it for text rendering, other parts of the Atom can benefit from it.
https://blog.atom.io/2017/06/22/a-new-approach-to-text-rendering.html
https://blog.atom.io/2018/01/10/the-state-of-atoms-performance.html
P.S:
Mimicking the component format and lifecycles should be doable but I'm unclear of what the underpinnings are. Solid assumes it's using standard DOM APIs with its compiler output. So if those are available we could mimic it somewhat. My concern would be inefficiencies around the update lifecycle which makes little to no sense here. I mean we could probably mimick it but I'm not sure if the result would make sense. We have no single commit stage. While updates can be batched, it's only those batched updates that update.
That being said when I get a moment I'd love to see what we can do here. I've been travelling (moving from Canada to the US this past week and a bit). I have a few other issues to catch up on but I will take a look in a few days when I get a chance. I think it would be interesting to have better Atom integration.
Mimicking the component format and lifecycles should be doable but I'm unclear of what the underpinnings are. Solid assumes it's using standard DOM APIs with its compiler output. So if those are available we could mimic it somewhat.
For this part, I am confident that we can do it. We can:
createState(this) to create the states. render method is available in an object (could be a different library to support classes).destroy on cleanupupdate upon state updating.My concern would be inefficiencies around the update lifecycle which makes little to no sense here. I mean we could probably mimick it but I'm not sure if the result would make sense. We have no single commit stage. While updates can be batched, it's only those batched updates that update.
The only thing remaining is scheduling. If you think this is inefficient then we can mimic it, but give a warning about inefficiencies and how to update to better code which does not sacrifice the performance. Not all of the etch components make use of scheduling, so this may not be an issue for those.
After all my plan is to improve the performance. It is totally understandable that we should update some parts of the code for better speed.
There is this possibility that we implement scheduling and it turns out to be not that bad, and it can be a useful feature in certain cases.
That being said when I get a moment I'd love to see what we can do here. I've been travelling (moving from Canada to the US this past week and a bit). I have a few other issues to catch up on but I will take a look in a few days when I get a chance. I think it would be interesting to have better Atom integration.
No worries! Thank you so much!
What I was thinking is I don't even need to change Solid if we can define Components with a helper wrapper. Like a:
function fromClass(ClassType) {
return (props) => {
const comp = new ClassType(props);
comp.initialize();
createEffect(() => {
// Need to figure out how to properly call this.. set the right deps. this is the tricky part
// Do we need this lifecycle? It doesn't make much sense from Solid's perspective.
comp.update();
})
onCleanup(() => comp.destroy());
return comp.render();
}
}
class BaseComponent {
constructor(props) {
this.props = props;
this.state = createState({});
}
initialize() {}
render() {}
destroy() {}
update() {}
}
class MyApp() extends BaseComponent {
initialize() {}
render() {
/* ... */
,,}
}
export default fromClass(MyApp);
Still not sure how to do update but couldn't something like this work?
I think if we can make my example working, it might be easier. The reason is that extending from a base component requires calling the super methods first. But in etch, the user can call etch.update(this) or similar things any time in their method. The benefit of keeping the original behavior is that this send the whole class to the functions, and it allows us to pretty much modify any of the properties or methods of the class.
Resolved in the next comment
My idea for etch.update was just this:
https://codesandbox.io/s/etch-solid-pwonz?file=/src/etch-solid.js:286-372
// etch.update
export function update(component) {
component.setState(component);
}
However, I could not make it work, so I might need to try different things!
One other thing is that, the components processed by etch contain an element property which can be used with any DOM API. Similarly, in its JSX, it supports any component that has element, render, and update methods.
// build a component instance in a standard way...
let component = new MyComponent({foo: 1, bar: 2})
// use the component's associated DOM element however you wish...
document.body.appendChild(component.element)
See this: https://github.com/atom/etch#nesting-non-etch-components-within-etch-components
I think that is similar to what solid-element does.
@aminya
https://codesandbox.io/s/etch-solid-forked-l7eft?file=/src/index.jsx
I have fixed your example. :)
@mduclehcm Thank you so much! Yeah. It works now! :tada:
@mduclehcm I noticed that you have hardcoded props in etch-solid, but the user may choose any name for their properties. That's why I was creating a state from the whole component.
@aminya
https://codesandbox.io/s/etch-solid-forked-l7eft?file=/src/index.jsx
I think solid-js should support class syntax in future.
https://codesandbox.io/s/etch-solid-forked-4s6kl?file=/src/index.jsx
Other version using object proxy.
@aminya
https://codesandbox.io/s/etch-solid-forked-s6koh?file=/src/index.jsx
Base on @ryansolid 's version.
I don't particularly see how native classes bring value. The model is a run once and bind handler model. Nothing classes do is particularly beneficial here. Ultimately from an execution perspective, you just end up running the classes as a single function. There is no need for separate calls. Because the requirements are so minimal classes don't even serve as good guideline in how to structure your code. I mean there is a render function (you don't even really need a constructor). There is no prescribed lifecycle.
At that point I mean you can really do anything to fit what you want. My example shows how you could turn a class into a function, you can do the same with a Vue-esque config object. There is no need to bring that extra weight into the library to just have people hack around their own preferences anyway to name methods the way they want, to use initialize vs constructor, or whatever.
I don't see a need to come up with an opinionated structure on top that is mostly artificial for the library's functionality. But I can see many different scenarios where people want to build their own structures.. custom classes, Web Components you name it. So as I see it, a function is the lowest common denominator.
@aminya @mduclehcm I'm glad you got something working. Admittedly I hadn't gotten into looking into the code on this one yet and was only talking generally how to transform classes into functions so components could be nested. I can see from the example my understanding of the update call was wrong. I as thinking of it as a ComponentWillUpdate but it's more of a setState I guess. It's very directed. I'm not clear how that nests but the solution using proxies seems interesting.
My concern is the Atom codebase that we should rewrite. I agree with you that there is not much gain here, but for us, it is easier to write wrappers to keep the API the same (although it comes with performance sacrifice). Later we can change the hot parts of the code to be native solid code for that extra boost.
@mduclehcm's code is a very good start. I think we just need to add an element property to match what I discussed in my comment. We can use solid-element for that.
solid-element is a web component solution. Which seems out of place and pure overhead here. Components don't necessarily have a single root element because we support fragments. Sometimes components don't return DOM elements at all. Which element is it supposed to be? We could artificially wrap each Component with a div or something.
Etch does not have a general render function. But the component created by it has an element property which can be appended to the document like a normal HTML element. Changing the states automatically updates the element.
I made a pure etch example for you to check here: https://codesandbox.io/s/etch-75q9k?file=/src/index.jsx
Ok that's fine. It looks like it only supports single top level elements. As long as you use it that way and make sure to wrap anything dynamic in a div you won't have any issues taking the element that you return from render and assigning it to the component.
Etch does not have a general render function. But the component created by it has an
elementproperty which can be appended to the document like a normal HTML element. Changing the states automatically updates the element.I made a pure
etchexample for you to check here: https://codesandbox.io/s/etch-75q9k?file=/src/index.jsx
etch .element work the same way as solid ref work (auto forward ref). you can create proxy property to turn inner solid ref.
Ok that's fine. It looks like it only supports single top level elements
Just to clarify, it supports nesting non-etch components, if that is what you meant.
https://github.com/atom/etch#nesting-non-etch-components-within-etch-components
As long as you use it that way and make sure to wrap anything dynamic in a div you won't have any issues taking the element that you return from render and assigning it to the component.
If I understand correctly, solid renders the JSX in render, but it does not return an HTMLElement. Is there a way to get that element? If so, we can define a getter for the component to return the rendered JSX when someone calls .element.
@aminya solid render JSX to HTMLElement. But solid component don't have to return JSX. It's can return anything or nothing.
In solid, you can do something like this.
const msg = <div>hello</div>;
document.body.appendChild(msg);
That means if we define something like this, it should do the job:
get element() {
return this.render()
}
This is equivalent to adding this to the etch.initialize
https://codesandbox.io/s/etch-solid-element-5i88x?file=/src/etch-solid.js:343-480
// element property
Object.defineProperty(component, "element", {
get: function () {
return component.render();
}
});
Using it would be like this:
https://codesandbox.io/s/etch-solid-element-5i88x?file=/src/index.jsx:610-662
// using element
app.appendChild(instance.element);
@aminya But reactive system will stop working if you render solid component outside computation root.
@aminya But reactive system will stop working if you render solid component outside computation root.
In my example, the elements are reactive, but I get this error: "computations created outside a createRoot or render will never be disposed".
If there is no workaround for this, we should improve the warning to be more descriptive (maybe giving a suggested fix).
We may be able to detect and fix this in the babel plugin in some cases. component.element and component.render can be found in the code. For example, we can replace parent.appendChild(component.element) with render(()=>component.element, parent). (render code). Then if the code was not fixable, we can throw warnings.
The fact that it works means that we are backward compatible, but we should state in the documentation of etch-solid that the users should make this change to get a better performance.
The warning is just about potential memory leaks. It works fine for most things. But the observer pattern needs to be explicitly disposed. I do that automatically with roots. There are a couple other warnings for other things created outside of root where they don't work. Unfortunately this is not particularly detectable as I'd need to hijack any DOM insert call which I feel is wrong. Tracing usage is not trivial especially cross module.
I made a repository to make the ideas more solid :)
https://github.com/atom-ide-community/etch-solid
I have fixed your example. :)
https://codesandbox.io/s/etch-solid-element-5i88x?file=/src/etch-solid.js
Using this method the component is copied many times!

What about this: https://codesandbox.io/s/etch-solid-element-forked-xnv24?file=/src/etch-solid.js
What about this: codesandbox.io/s/etch-solid-element-forked-xnv24?file=/src/etch-solid.js
Thanks. This is a better design that solves the excess copying issue as well as disposing issue. I am not sure why reactivity (update) does not work though.
I made your solution more general. The names of the states are automatically found.
https://codesandbox.io/s/etch-solid-element-forked-yk6jo?file=/src/etch-solid.js:247-361
I could not fix update though.
Sorry didn't look there. Yeah the props thing aren't working at all. Is it supposed to be this.props or this.$state.props. I see before it was doing some binding to sort of fool things but that was also sort of breaking Hello if you removed the infinite recursion. Keep in mind nothing is being copied over and over, just going back and forth between the same 2 objects.
I see now. The expectation is plain editable POJOs. This isn't exactly simple. It looks like the expectation is state to look like: this.counter and props to be this.props. @mduclehcm accomplished this by hijacking this and rebinding them in the helper methods. But the second you are out of them they won't work that way. Still that solution is the easiest. Refresh the previous sandbox I sent. Updates work now as should local state.
Awesome! Great work, everyone! The code now works without a need for a babel plugin, and it disposes automatically.
I am going to commit this to the repository.
Keep in mind the binding might not be right for other lifecycle methods. And local state changes like event handlers can't just write to state. I think there is more to do here. Like now the reads work but writes outside of update probably do not. Unless you are fine with explicit setState function. I just added it to component. Maybe that's the easiest solution but it'd be inconsistent with how you did props.
This issue seems to have gone stale I think the idea is new issues should be tracked on the repo, https://github.com/atom-ide-community/etch-solid. I've made some changes in Solid recently that should make projects like this easier with improved lifecycle handling. Feel free to shoot me a mention and I will check it out. If you have any specific questions feel to ask in discord chat or open an issue.
Most helpful comment
Sorry didn't look there. Yeah the props thing aren't working at all. Is it supposed to be
this.propsorthis.$state.props. I see before it was doing some binding to sort of fool things but that was also sort of breakingHelloif you removed the infinite recursion. Keep in mind nothing is being copied over and over, just going back and forth between the same 2 objects.I see now. The expectation is plain editable POJOs. This isn't exactly simple. It looks like the expectation is state to look like:
this.counterand props to bethis.props. @mduclehcm accomplished this by hijackingthisand rebinding them in the helper methods. But the second you are out of them they won't work that way. Still that solution is the easiest. Refresh the previous sandbox I sent. Updates work now as should local state.