ReScript / NextJS Template 2021 Update

Hey everyone,

The rescript-nextjs-template just received some new changes (updated all dependencies, restructured the project directories, added some more helpful comments on the Fast-Refresh mechanisms).

The fast refresh now actually works out of the box with the existing example code. Previously the example code exported React components in a subtly wrong way which caused Fast-Refresh to always do a full page refresh, instead of partially updating the UI.

I don’t know why, but for me it’s quite tricky to just look at ReScript / React file and say: “Yep, this will properly work with fast refresh”, so currently I like to separate pages/*.js files and ReScript pages completely (more on that in the updated README and example source code).

I also made some additions in the README regarding Tips / Q&A section.

Here is the PR to see all the files that have changed.

Ah, and all code is in ReScript syntax now of course.

10 Likes

I also ran into the fast refresh issue, so I decided to patch react-refresh. I also PR’ed it to the React repo: https://github.com/facebook/react/pull/20522.

I think fast-refresh should only allow an additional $ in the name regex, and the rest should be handled by the ReScript toolchain.

I am talking to @rickyvetter about it to figure out how to get more control over the function name output.

Fast refresh allows $ in the name. The problem is that it expects components to start with a capitalized letter (which mostly happens, but sometimes the function is called make or make$1 (or any other number). Then there is a case that the function might be called $$default if you declare a react component directly with the name default. The last thing can be prevented with an alias to make (so that case can potentially be removed from this PR).

The best way to handle this would be to tag react components with an attribute on the function object (in ReScript we KNOW that a function is a react component anyway because we annotate them). This can be done inside of the PPX. Then the PR can just check for the existence of this attribute.

Ooh, right, just checked the regex, it’s only for the first letter then.

There is a potentially better solution that is already used internally, which needs to be cleaned up and upstreamed into the PPX.

The problem why we get these $$default / make names is due the fact that we can’t use the @react.component decorator for functions where we need to have explicit control over the props type, right?

In the example of Next, I need to do

type props;

let default = (props) => React.element
let getServerSideProps: Next.GetServerSideProps<props>

so I can’t really use @react.component here, which would actually solve our problem by uppercasing the resulting JS function accordingly.

the export.$$default = MyComp assignment wouldn’t be a problem for fast-refresh, since it is looking at the function name instead.

So what we actually need is a way to tell @react.component that we want to use the classical (props) => {} style instead, and give it a concrete props type that’s used in the code generation.

That’s essentially a solution that would solve all the edge-cases, if I am not mistaken?

1 Like

Yes, you are right, I thought I had some components that compiled down to make but they were not using the annotation indeed (I also use the same trick with nextJS). We still need the part of this PR that recognizes subcomponents (objects with a make function).

Not sure if I understand this one, can you give a concrete example? Afaik, if I do

module MyModule = {
  @react.component
  let make = () => {...}
}

let default = MyModule.make

…fast-refresh should actually work for default… or at least that’s what I was observing in my experiments? It’s also probably worth noting that make is not a hard binding name requirement,… you could even add a @react.component annotation to any other identifier name.

For when you want to expose more than one react component.

Okay, so you want the exported module (let Foo = { make: reactFn }) to be detected as a React component? Is that even valid? I thought that for the fast-refresh check, the entity being checked must be the actual React component / function, and not an object containing a React component function?

(talking about this specific change)

AFAIK it detects if there are possible non-react components exported. If that is the case it does a full reload. So if an object with just a make function is being detected as a React component it is just being “cleared” as a possible react component so the page doesn’t need a full reload. I have been writing interface files for files that have multiple components (so they are not being exported). This will save you from needing to do that in most cases (unless you have other non-react let bindings or modules that are automatically exported in ReScript).

1 Like

Right right. I mean in the end it depends on the React team if they want to support such change, even if it doesn’t seem to influence their current setup. I’d personally be cautious if I were them, since it seems a little exotic to add ReScript specifics in that particular scenario.

What I can promise though is that there is a plan that Ricky will schedule some plans / updates for the next react-ppx / rescript-react version early January, that hopefully will allow us to circumvent at least some of the edge cases we just discussed. The last point, detecting a nested module with a make function, will most likely not be solved by the new changes though, and would still require additional toplevel aliases / interface files to hide them.

I am going to gut out any code that is meant to appease react fast refresh from my ReScript nextjs project. In my case, I would rather not have to document and orient each new contributor on special react fast refresh rules.

1 Like

As soon as we published the new react-jsx version, it’s gonna be more straight-forward, because we will always tell ppl to use the @react.component decorator which will take care of the function names.

Luckily, it’s very easy to recognize the symptomps when fast-refresh is not working. For now, it’s just a matter of having a quick checklist on what to look out for when a fast-refresh warning pops up in the console, I guess.

Yeah. It’s fine as a package patch right now. I don’t think there is any easy way to export multiple components without ending up with an object and a make function unless there are large changes in how the compiler works or how the React integration works.

I was having my hopes up that ReScript might get some attention from the React team because ReScript is also a Facebook project and used internally as well :smiley:

Will still have the resi file noise, I assume

Hi @ryyppy based of you template, I created this one https://github.com/tomvardasca/rescript-next-linaria-template, please let me know what you think.

The only thing missing is for this setup with Linaria is css code highlight and completion. The highlight part is easy, but the completion is another thing, tried to look up but did not find an easy way. That’s why I ended up using stylelint.

1 Like

I haven’t attempted to find a better option, but the enormous number of transitive dependencies for nextjs seems like it will slow down nextjs development in the long run. Maybe one of the vue based options or others have found a way to present a similar feature set with fewer dependencies.

Luckily they are improving on the situation, as stated in this tweet:

That’s one of the advantages using a company supported framework.

Afaik NuxtJS is the closest to NextJS in Vue world. Doesn’t look better on their dependencies situation either.

There needs to be a bigger mindshift in the JS community first before we can enjoy libraries / frameworks with zero dependencies. I doubt this will ever change though.

2 Likes

I looked briefly at nuxt, gridsome, and elderjs. elderjs’s dep hierarchy is not bad, but I think they haven’t brought in PostCSS yet.

When you look, babel seems to contribute a big chunk of the hierarchy.


Also considering elm-pages as an ideal.

All we need to make this happen is ReScript minus interop :laughing:

1 Like