[ANN] New (nightly) release of styled-ppx working with JSX4

Hi ReScripters,

JSX4 is truly awesome.

Pushed a nightly version of styled-ppx with support for JSX4 and I would appreciate some testing. It’s currently only transforming JSX4, so JSX3 will break.

npm install @davesnx/styled-ppx@0.35.1-a090860.0

The unbreall issue for JSX4 lives here: [Feature request] Jsx version 4 support · Issue #327 · davesnx/styled-ppx · GitHub.

The reason of making this iteratively and “broken” is to accomplish feature maturity before we can answer some questions regarding compatibility.

These are doubts that I’m still unsure, feel free to jump and discuss this with me, either here or the GH issue:

  • Would you expect styled-ppx to transform to JSX4 for all files or conditionally based on @@jsxVersion? Would you expect to read bs-config jsx field? Maybe just adding a flag to the ppx?
  • (If we do the flag) What happens when JSX4 isn’t enabled? Explode or gracefully do the JSX3 transformation? It looks more safe, but misleading.
  • Do we want to support the v3-compatible mode? Can we do it?
  • Since we generate React components, do we want to support the mode as well? I can generate both modes in styled-ppx output, again a flag would be preferred?
  • What happens when JSX4 is enabled in Reason (.re) files? Should automatically use JSX3 for these cases or crash?

Aside from those, if you think I missed something, please let me know.

7 Likes

I found an issue with JSX4 and styled-ppx. I’m pinging @cristianoc @cknitt @moondaddi for help just in case you can give some guidance here.

styled-ppx has 2 ways of defining components: static and dynamic.

  • static: module Static = %styled.div("display: block")
  • dynamic: module Dynamic = %styled.div((~param1) => "display: $(param1)"))

In both cases, the resulting code would need to create a div and expose all props (previously called makeProps) as props of the new defined component (Static or Dynamic in this example)

This is currently done without using [@react.component] and generating makeProps with bs.deriving abstract, so then props become an object (which match the represetation in JavaScript) and can use the bs.deriving access (propNameGet(props)) which gets translated into props.propName.

This method was preferred from other approaches for minimize the JS output, even thought the code generation might suffer a bit (because we render all makeProps to all styled components).

So, this works great for both cases. In static only makeProps, in dynamic makeProps + any parameter the user defined in the payload.


Since I’m exploring the JSX4 support, this might need to change a bit.

Since I need to expose all JsxDOM.domProps, using [@react.component] is discarded.

The approach left was to keep the generation of props the same way, removing the bs.deriving abstract and treat it as a record field, but I ran into an issue where I generate a record type (the same shape as ReScript expects) and I got an error of Some required record fields are missing: [...the list of all domProps]. I suspect have something to do with not having the attribute react.component, but I’m unsure about those details.

Let me paste the generated code in case my explanation is poor:

For static components (works perfectly):

module OneSingleProperty = {
  type props = JsxDOM.domProps
  let make = (props: props) => {
    // .. stuff
    React.createElement("div")
  }

For dynamic components (fails at the crash descried above):
Pasting the reason code to discard any translation issue. Her

module DynamicComponent = {
  type props('var) = {
    [@res.optional]
    ref: ReactDOM.domRef,
    [@res.optional]
    children: React.element,
    [@res.optional]
    about: option(string),
    // ... all makeProps with `about?: option(xxx)`
    var: 'var,
  };

  let make = (props: props('var)) => {
    let className = styles(~var=props.var, ());
    let stylesObject = {"className": className, "ref": props.ref};
    let newProps = assign2(Js.Obj.empty(), Obj.magic(props), stylesObject);
    ignore(deleteProp(newProps, "var"));
    createVariadicElement("div", newProps);
  };
};

I’m got paranoid at the beginning where I wasn’t sure if the parsetree from Reason translated well (classic ppx issue nowadays) but I think there’s no problem in the translation.


The questions I have are:

Thanks for reading until here.

Not sure I understand the question, but a couple of comments:

  • This compiles fine: let props : JsxDOM.domProps = {}
  • You cannot extend or modify type domProps, but you can use a field of type domProps inside another record.
  • There’s no subtyping for records.

That said, a few upcoming things might possibly be useful, such as Explore record type extension · Issue #5659 · rescript-lang/rescript-compiler · GitHub which lest one extend an existing record without having to repeat all the fields.

I would benefit a lot from type extension, that would be awesome.

I don’t rely on subtyping on my definition of the component, since I disable the type-checker with a lovely Obj.magic.

Taking a fresher look, I found out that this code compiles and works great https://rescript-lang.org/try?version=v10.1.2&code=ALBWGcA8GEHsDsBmBLA5gCgN4DcCmAncZBALgAIAWAGjIFtYATXcgIgGMAbAQ3CLZYC+ASgBQI+gwCuHXGQDKAFy4LkbMgF4ymEWTIKAngAdZh-LEPgAPAHJauXl1S4a1lXYB8GrTt1l8uRAB+cgAlXC42BQARAHkAWQA6BlhaMMQqH102AAtkDgZ-eGCyMIiFBNwZO3gFDN8yLgAjWEkFYvAFfGR4VDrfCLZcGoAJXDRstvIOrp6+3QHcQ0myae7ezIa2QaXobK5CXGXV2Y2F3gBpXH12zrW5zbZJWmllXBuZ9fqy4iKp25OvgwGMgVHh3ndThw0PBqgoAEI8SrdN5-D73LgcDiwADuADFpBw5Gx-ENis1YBx0ZicWFYPgmPhwQD+hxDHtGodVEzPiyjv8efNaIYoQpJExuej8E1VLi6bQJaculwAIKRZB4JjgQbwBhcGoKr5K5UKFJc8jkymK5BcOGScDXc2wCmS61wDhsFr68jdWpWrhu7pMSDFH0u-0U8CGPUhmphuA1MwccAG-pKqL2YnIDkMRoOlb8sPppR5ZOoiGG61RZDgJoyBhkp2WitcACi+DM+DsDicKfmSpbkCjOtw9cdzr9uKx2JNvYaSoA4lLGtmG+Pm8NkEDSWOm6nrZd9OBsnSFI8FKX82i-QAZJqVWf7a23jmYke5h9K6+4PAcGO+5txIwGKrrufbWnE0gqFC8AomQFphhBHAqOAlS4JEtawfBfoxNi8AXscApztaAAK3CDMe+QEB+JGwEQeGHH+YalMk8AcHmWHNmEMjYHqfJXpxuAAI6SMg-ijnBjZMRSI4Zl0Sw-NRXAhDiHqSF6ZChn6ynYoGuDBt6sZaTikbRgZ-57lwcioZEI4gWGVnnsgABesGac2cgnopABqGKSLgtBcPpZCIFiyhhj5HB+bQ3TFCFsBhX6EV+fAOKxaF5lgVwSWHHpfHlv0WpDHlzLzPa8BsHZpwKLcjStLgAByXB2A+1VdLVCi4AAKkYsEEeirSwHAQoyB1D4DbKjwXhx-QDaRXDsZJpwDWEeAHA+TnIM8CjZLOjSIri-jCUMbB5n1Gx7Sh0G4HIuSIMVhEXbgxFmCgMi7c0QVnfUHKoN0AD6u3WvhBbnadIP1GwGJsIB4pliVZCQ4YozjPd9w5BiMg9L14O+Oj+AObOORoQA1rZO5oyC2P8bj3C8I1zVw4RnDIIYhNQoYxHKDtjNo+znPbQAqvAILA9TWTsyE0hU-lWQUnSACSNQEIYFLKApPMbB6WL4IrHX4Cr3AqAguJ5Hrosywjcv4M9sCvdL8Na3SYTDh8hMRoxmsUnIQ4exDCAdepX24-7RUtsCSiNG95Oe0rNREnJCjdcYbuxwoij6DISf20zIeQAocRDJIKedO70d+3SDDmw7Zi8HSaAxWXuOSIQdKE59ONZGDYtkOJQe6LqSizgPyg9UPrydZt2f3EwgwyFKo0a-UTCIFwkGVUvASr0huwk2TElrr4y9bwo2VDwEVGN-3GZFUPyCIIgdq4PGHS8bfjKL4foloUbvyXhbwI1kjnvaa-dqzCnmrfbA1ZW4f37qaeAvEESXWREPHErF4q9w7j3KUqBUAYXXofZuQ927dwYF3C2I4nAwynhsVCPEf6ziGBhBEbBiaoDMGpTB3djpZ0YTqAGsCyB6RVjBQOWDcoEAQRwMI4AWj4EGOAMIwkv5cItq9X8gj1ExCjGwEE5D4bqMllHP+BjTYXxMYRV6esZGzisQQIWItbFYkYG6GBFj7hxUYNoiIejbGwEmvgzRKRlB+JqLiJqeR9GWP9nIZyND6iIBiXE5UDBQB2lRhsRJcdOiHByKEtOBhjF92Cv7HySoxHdyyQoAA6mMVAEw-GdkabQVUDCgmdgLttRgzTOr7CcBkhJZh5SaJIWoqJ9xUAAEZZyoAAEwzLYmyem8TfCoEWdkGIXQipqwQMMOuTl-bAUEWsowGytk1B2fALyBAVCQw0e4jYJy2RpBmVKYERVOpSjwokppxy3nICKg488s49g9DWCC8IDIq6EWyHUhpgjchbl-iAsguR6m+18MeLoTkUnYAABogv2Zs+u8ACUIv8EEclARbw9BBQoWgHBZTvwefUCYChDAtmUdgWcqgEA8tUfDTcuBYDsK4GyM0LLfCbUcLgZ2DJwWCP+jy+A8zFWsWrAMqV8BDCtGocqjq7DfFqr1tsTVuhKa0HllEHlHVaA21Zoq21LzHX+SJOYTCi16gWt4YI4ms5ibTN9aqyVuhiYAGZ-UUH9ZIyocRlBdFGfDUm+AYIcCBV+Ho21o0poVSGsgpNxkbALcRWAPpoX3ALd7K65ai1XAnl2aN+gfV5uJoGWc3AXztr1KgdtQxUDbRSWkjovbqoEG9j42lgioT1JUD0VxzLilQmirO1A8ZcDKh6EUrBUJh1Tv8UcvNziHX71AmQKcGLdABWFogewZq6D7GTS2HUs4Ar4GTcjGdL6H0EDiJuL9b6x1KHwHe19yagU1vqKBgg1TNxZsEQFcAfr4M8GJvGAOChwNfsQ5h5D21-JqzuV+xNhEAqQAzf27mGlDKQZHNaF9tGuCLhaMe4p0Vf5uV8Gx8jcGqMZToIwFZl7IIs2MSirawDPW+AQQzPNKVyqCbIClCKm5XgEN0PAJ4MR0J4Ag74W2iAUJ3vMD4gws53XwAESe+47r55uOKeYNwTwL1kAruY+z5yjMecuWZrov1f72dWnFbEZnAtTjJXm2AoWcQAE0QsECuiWogbSIurSup1XIrCYK8FnFGH0myGQ5b1LRXAga81Rm2txyjxTyt6381gmrki0OAuFsCwRDWU2fL1OAH5wyyvKFqzhsrZFcAUQK210tSt8AtjwDUXTugVZlpyxN88xpwvVeW+AY0sXxtluNAALSWx0Nz9WKXIGI-cUwlQME5f8ChfAeBlSsj2Dd+wBAHuRm-iEHZN3NognVLgQb1WzDGGA4W+oC6sH-NgExyQLHIdcGBHaWc-gEcxFYgtA+ugKVrchwEbbeb-D3OKYUeVPRdY30Ef4Ywyg4BqTvVT8I0QiGU6EiJMSam-Cs5UQOAORAEBzc58osSuJGfN3sMj+wcjBgS5fsBmXa8WerRQuJFFiYFNmCUAvAnxlnNmGxOO9j1HfD4HOxsfAYPfA1h1B9WcWoMQKa1O623HpjAq8k7oLUiZoI9sEShPexTLrfwk5jlYexk6+7D7KoYpOfd5qIC5Zz8fxe+6xOHuPOjc0B59mZe4H3HjcHwM-JQFSLZ55eFNwcCAKfp9wP7rBH3MQ71YRzyMKOGCdOPAKwi4B5G2-kVEfxfe2A0tjwH+RBNfdAeWBxj3U+Yj30M7bhQDB0xQO85P-yVX6+2u5Rv49cUEr1A6CCHIE8ZAC46OYedS-zDeN0aZyfXRSbbQ4fUxLf2+WP+QM-7Ir-sjpdUGJiywvxLlJiXzMFJiiB4D2HbAgUf1gEgOgP00X3gNJmvGRERnAIQNwHQJglAAmywNJl-T1iXRBEINwDvyNTj1ANwBgwYB4wD0KVglKEiFiESHThkASD43ACeFfQtw91h0u14CaxqDDhBAwmqX2GFknSsw2B4PwBXkGCJHt1t0PFtRH0kBlVnAjkViDGcwjhkGygFyA36W0L6UOBx27hMMOHx2KQ6jzg3RyDsywXsOiDQjpHXzzVcMq20NyjlQIEzxcJBC3SsNgEszsK+W6zlG0J6nCJcJ6ltlnEkFK2KUkGDVSJdgS1og-zq27k4Xi2RAAMy3sAFzUl5VhjzTKI9CYDhE3GQCSOFmqNlW7QUzKPPGIgIBbF61SJQjjTh27mwEezZDvFuVnB4kigU3GL8gFzwEiDpBbHvm-jGIID51yItlWgUFxUsPWJuVxVsKwQ2OJT822PhkON826H2IGOGG7UCIGPliYBFSlHFQqkESgVwGxDhFgFN3qDeOxF6XwFMNeOrCzDyCoOKWwDjTwwCluQPXBP3UaFnB0noK327iRO2gF2xArnHV0RkOKWxCeMRK6BXT1UEW+N8EgBSKwUgHSKpN2D1FTSshkDmIh27kgA-XhTzUgGgmJlaU0K12KS5O6B5PkTV1nEFPgGJmGApTFO5OUhCItnFOJhulSlJO5IngUHlPhkVObQFIZSQQU0gAZRHzFIZTwhNI4DwjxW5PNOxIU34LIH0EpO7n0BpOdLpPgAZOshNBZIticlnAORSA3QYE5jWPhgQGVGaDl1CHCEiGmyKgSALmBC4C4I0E8DaOs3gA3WlR-ifXEhYIUDjJqASCzOhJ+BTPUDTJawzJLMuXlj1k8PzMLPKBrJ-nLMrJBGrOFlLIQEUH2GWEbJm2bK7MuTbLIHTI2AQDhEimZQHPjImjtFHPHPqAQGgD1DmjzFnKLMTOtEXKrInPgFXPgHXPSz-2KE3PKG3OTIUFTLHL3OXIPNBR7GjLKCbISCZVoF3I7P3OgChGb2fNjMHITJaBQk-L4xXJSBViSx+FzLPJjILMAqGkgpyNAozMQuyJ-l7KjJKDgtfLQqgoQBQu-IgvQp+AFkMAHmYJwoQuIvwvgEIvvKazzgLg01gpfMAsAkfnor0wPPMA3KovjJ-JZmaH2AYC4t0BXNaFYoAoEvZmEvpDEpc3gAH0kCAUEr-OwrYvjI4pAuvIrNvK-PvKiBwSkvgq0uAtwAUoQCMscBgv-NMq3PMssqUpwSfT1hMtfO0ost0vbLAucpssgDILso8scu8v0t8ustQC-C4DBCCvYpCpvKXO4oipiFWncris4tCsSvEr8tQEwv7P4ocoyoSrvKSuBzSrMqKr0qysUqiGbkuXdKfI0ukq3IYycq6Pkj3nPITNasypKuyqfWJB6mX3KpaqTLauHDzIKovJ6uKoMu4rbA7BGumrGt6rmuyvnIvC6o2qcsVh1Xys0qLPfKcoPAH1wiWoSAPDktEtWt8oPGehKPOsuvinkpuozIPDIsequCuqcuvAwRHCgMHliq0pmqqr6sUt+oRxHE6QR2UC4HOsvJ+owTyvhpBp8ozM8tOt-i6s8qcs8tcvMWxvitBrWsUs8qipiqavsovKJrRv3M8sAgpsJsqtpvvM8piEkqBsKp0tmt8rZtSs5upuZrCvRvMo+oFqAqFuqoQE5iO3OsEsMG+tev3M5kfhRpWp5ozPXLVp3KVvvPXNzWxtRuFuVrMHYQevFoRt1u4q+w6gasooOuWp1o1v3PjgpHuS6oFnlicqslwFJkmodu6vVuJt8p9tbRkMNqDpZu4sZKWPFpjrVAIqtuyt7NfH9uasdqvOdvvLkBUuXXOqOqTsUpzo+2fQtqNqlvgHrQB3ItUzLsjuNvvM6haByEPNnnOqbskByCco7pyFssptfJ7uyG7ubuyAZvtvToSEHuHs7uyGRvFqnsLoQE6zwhyL7q6uXtoqcq8gpCeCfkfPHqpsDqduDozMkL+3DqmqPszpPv3OqVhXvHFrvtrw4AUq7BrEatsBKJlXuDcGYDIFcEnj6AEDEF0BGnvVJi8HQFMHMHAHIGgYsBsDfu-v-t-vcCEBvO0HqDAaQacC8HgfAASBwdwAAFp3A4RKhyhtFWz+kYNtp0wV5IJ0AWAeAGBEAWBRAsHDg9BJ5wA8HgcCHf6NgwGYJsR7VeHNAYhGhQBCHHBVAoH+GOHfAAB6JRnuVCDqMgfBwhr+pwDYFRtRkaEwfhrgyeDYSwYEbALQBIaxkRsRgQdwDYXQTAfMhIAidAAAA1GGpDIAABJMAiGBAaB9AWgEZfy-a6BZB3GyAABqaJ7hrsIQYB+oSwJRixhx3QYB4B8QRgKWMgZUQwQwLwTBsgYAFHSIBID0IUKvGoHwbBrgCBzQdAdBvS4p3QSwdJ+oMgSwXs25CJ7sXAdQFgLEbgFgeJgZlgWZWZUZpRjptpmZnwTJkQIAA while my generated code breaks, so it’s probably a parsetree issue.

Will update here more findings in case somebody rings the bell.

Big facepal, I didn’t know res.optional vs ns.optional would matter.

1 Like

Yes that was changed, for consistency reasons.
An observation: while it is possible typically to use another syntax (.re or .ml), one will get into these kinds of issues.
One way to look at this: .ml is an alternative way to describe the AST used by ReScript. That’s true by definition, as the (untyped) AST, for technical reasons is identical.
That does not mean that is is the same language: the .ml way is more like looking at the compilation result, and the compiler does change over time.

Totally, it’s a recurring problem since I need to support OCaml, Reason and ReScript on the same ppx. This makes those changes interesting but If I keep track of of the progress on the compiler this isn’t a big of a deal.