Change JSX output from React to SolidJs?

How about detaching the react-jsx-ppx from the compiler entirely? So, letting users add the ppx whatever they want as one of the dependencies? It will make the compiler removing ppx dependencies.

It’s going to be slower, more fragile, harder to keep in sync, and expose a huge surface area that better be closed.

After the setback with the “props-to-variable-assignment” problem, I created a new babel plugin that replaces all occurrences of these variables with the original prop.

The plugin transforms Components like this:

function Component(Props) {
  var text = Props.text;
  return ReactDOMRe.createDOMElementVariadic("div", undefined, [text]);
}

to:

function Component(Props) {
  return ReactDOMRe.createDOMElementVariadic("div", undefined, [Props.text]);
}

With this change, components with props behave as expected. And it also made the bindings for createContext and useContext finally work. (I will update rescript-solidjs later with the new bindings)

Since handling multiple babel plugins got a little annoying, I also created a new babel preset (babel-preset-rescript-solidjs), that contains all plugins and also babel-preset-solid to automatically do all necessary tansformations.

I think we are getting pretty close to a fully working solid-app with ReScript!

1 Like

If Props.text is working for the Solid.js, then the new jsx ppx v4 will works accordingly. It will generate the output.

let make = ({x, y}:props) => body
1 Like

I think the problem is that in solid, props become a proxy object that is used to react reactivity. destructuring does not work with proxy objects IIRC

Is it just me, or does it seem really fragile to put such added semantics on top of very specific JavaScript code patterns? Like a.b works, but var b = a.b or const { b } = a don’t work? This seems like a recipe for confusion, code rot, and sudden breakage to me.

7 Likes

Well, at least in JS/TS, they have ESLint rules to check for the loss of reactivity. In ReScript, there’s not a lot of statical analysis for these kind of things so far.

But yes, maybe ReScript and proxy-based reactive subscriptions are not a good match. And it would be a shame to, e.g., lose the succintness of ({foo, bar=true, baz=7}: props) => ... and have to go:

let make = (props: props) => {
  let props = Solid.mergeProps(~bar=true, ~baz=7, props);
  ...
}

So maybe in the long run, compiled reactive frameworks like Svelte or Solid need a special language where reactivity is a first-class citizen.

Statical analysis is extra hard here because we care about the generated output, not the ReScript code per se.

I wonder if we could flip this around and turn this to our advantage. Thinking freely here with no regard to how hard it would be to implement etc, but what if we had something that tells the ReScript compiler to always do direct property access. That would mean that you could destructure, or do whatever you want on the ReScript side, without risking breaking reactivity.

2 Likes

Sounds like those “reactivity” restrictions are built on thin ice. Surely one could support that. Wondering whether one could even do a little better too, keeping the spirit but taking advantage of the language.

1 Like

My feeling is that this is not only about reactivity but also on expected behaviour. When working with reactive props it is always necessary to keep in mind that some operations break it. But if the compiler is changing the code in an unexpected way, it gets really hard (or even impossible) to handle.

In this particular case it is that the component props are reassigned to variables. I don’t know the reasons for this (Is it for readability of the code? Performance?). But it was definitely a surprise.

In other words when I write code that does not destructure or reassign variables, perhaps the compiler shouldn’t do that either (in these specific scenarios)?
That does not mean that we have to handle all cases in a way that support reactivity (although, of course, that would be an amazing feature). Constructs like ({foo, bar=true, baz=7}: props) => ... are fine as they are, because it is quite clear how they behave.

But I think, it should at least be possible (even if it is less convenient) to write reactive code.

Of course I do not know what it actually means in terms of complexity and effort, to adapt the compiler in such a way, but it would surely be awesome :wink:

The compiler transforms code in a way that preserves its semantic meaning in terms of the JavaScript output. For example, let {x} = y can be transformed into var x = y.x. If it didn’t preserve JavaScript semantics, that would be a compiler bug.

When you are talking about ‘writing reactive code’ here, you mean the SolidJS-specific code patterns that it uses to enable reactivity. But that’s not the only way to write ‘reactive’ code. There are other techniques which achieve similar effects without using special meanings for certain code patterns. E.g., RxJS uses lambdas to build reactive data flows.

It’s not clear to me that the ReScript compiler should try to especially adapt to one specific library or technology, instead of producing generic JavaScript code that preserves semantics of the source code.

6 Likes

I know one needs to work hard to shoot themselves in a foot like that, but in general the following two snippets are not equivalent:

const a = obj.a
// ... arbitrary code ...
console.log(a)
// ... arbitrary code ...
console.log(obj.a)

Example:

const obj = {b: 1, get a() { return this.b }}

const a = obj.a

// somewhere in arbitrary code
obj.b = 2

console.log(a) // 1
console.log(obj.a) // 2

Edit:

I was overthinking it. Much simpler example:

const obj = {a: 1}

const a = obj.a

// somewhere in arbitrary code
obj.a = 2

console.log(a) // 1
console.log(obj.a) // 2

On the other hand, if obj is created in ReScript, and ReScript sees that it’s an immutable record, then ReScript has a right to assume that the snippets are equivalent.


Edit 2:

Also, if ReScript code looks like <div>{val}<div> (doesn’t use a property access), I think it would be incorrect to compile it to a property access in JavaScript.

So, a solution to use of Solid with ReScript probably should include ReScript code looking like <div>{props.val}</div>

I think this won’t work either: Playground

First of all, I don’t have any experience with Solid.Js. How about this? Is it working then?

let make = (props: props) = {
  let x = props.x
  let y = props.y

  ...body
}

Surely, this output is generated by compiler except the body expression. x, y probably are used inside body.

Same here. I’m just curious and play with https://playground.solidjs.com/ :sweat_smile:

let x = props.x will lose reactivity.

Here’s a JavaScript input and corresponding Solid output:

function Test0(props) {
  let val = props.val
  return <div>{val}</div>;
}

function Test1({ val }) {
  return <div>{val}</div>;
}

function Test2(props) {
  return <div>{props.val}</div>;
}
Output
function Test0(props) {
  let val = props.val;
  return (() => {
    const _el$ = _tmpl$.cloneNode(true);

    insert(_el$, val);

    return _el$;
  })();
}

function Test1({
  val
}) {
  return (() => {
    const _el$2 = _tmpl$.cloneNode(true);

    insert(_el$2, val);

    return _el$2;
  })();
}

function Test2(props) {
  return (() => {
    const _el$3 = _tmpl$.cloneNode(true);

    insert(_el$3, () => props.val);

    return _el$3;
  })();
}

Playground

Basically what it does, it treats <div>{val}</div> and <div>{obj.val}</div> differently. Doesn’t matter if obj is props or some other random object.

You mean that only Test2 keeps the reactivity? If so, how about this? Is it working?

let make = ({x, y} as props: props) => body

Yes, only Test2 does.

Is it working?

I’m getting a error when trying this:

Unexpected token, expected ","

But if I understand the intent correctly it will probably work as long as you use props.x in JSX.

The key seem to be to allow to write props.val in JSX on ReScript side. If this is allowed in one way or another, the problem most likely will be solved.

I got your point. The example was in ReScript, not tsx though. Thank you!
Probably it will be generated in js. It is just hand written code, because the compiler does not support some kind of preserve mode, yet.

function make(props) {
  return <div> {props.x} </div>;
}
1 Like

Maybe for solid one should write the code directly and not rely on JSX. So they get control over what they write.
It’s a pity that framework writers don’t understand semantics.

5 Likes

What do you mean when saying they don’t understand semantics? Solid reactivity system is based on the usage of proxies and functions. Javascript semantics dictate that you can not detach properties from proxies if you want to keep taking advantage of the proxy. The way react does reactivity (which is not true reactivity IMO) is by just te-running everything, so then nothing matters because the entire thing will be just re-execute, so it is not that react is following any good semantics, it just brute forces it, and bevause of that anything will just work