Raise the lower bound of targeted JS version from ES5 to ES2015

Hi all,

It is time to target to modern JS since ES5!

We plan to target to a modern version of JavaScript (ES2015) starting from June this year.

Please let us know if there are some features in ES2015 that you want to avoid explicitly, thanks!

22 Likes

Just a curiosity: how hard it would be to allow different versions of ES? Not because I feel the necessity, but maybe it could be helpful in the future: if you can just add support for let’s say ES2017, as a developer you don’t have to worry about breaking changes for future integrations. Do you think that it could be something doable?

EDIT I forgot to say that I really love the idea! Thanks for your work :heart:

2 Likes

Supporting a flag to allow user to choose which target they want is indeed a good idea and doable. The reality is that it is already lots of work for me to maintain the compiler/build system, so if we can reuse some existing work(tsc, babel, google closure compiler,esbuild etc), it is better for me to invest the limited time in more valuable places.

5 Likes

You are already doing a great job, so take the decision that will make you life easier for the same objective. My suggestion was motivated by the fact I ignore the efforts needed for the two approaches, take it with a grain of salt :relaxed:.

Going back to the main topic:

  • Do you think that the migration to ES6 will allow to improve the situation with Promises, or this is out of scope?
  • ES6 iterators look like a good candidate for a module type Iterator abstraction, but this is more a lib feature than a compiler feature. Is this out of scope or something that will be take into account?

Thank you again for your work! Keep going! :muscle:

Amazing, looking forward to the modernization! :rocket:

Out of scope but will be solved with the existing approach taken by @ryyppy, a good first step. If you were referring to async/await, those aren’t ES2015.

  • ES6 iterators look like a good candidate for a module type Iterator abstraction, but this is more a lib feature than a compiler feature. Is this out of scope or something that will be take into account?

Intentionally out of scope. We try to avoid iterators whenever we can. As it is right now it’s a slow and bulky implementation.

The obvious main candidates are let and arrow functions, both a small perf and readability boost, and gives the compiler an easier time generating the code too.

Destructuring and rest spread would have been interesting, but unfortunately those also invoke iterators, so makes the compiled code unpredictably slow.

2 Likes

Sorry for the ignorance but, what is this about? The generated JS code? So you are going to generate more modern js output? If that is the case, why would people using rescript care? Don’t they just use the language and forget about what it outputs?

Part of the philosophy of ReScript is that we care about the JS output code size, quality, etc. If we didn’t, we could just use Js_of_ocaml, which generates output that looks like minified low-level code.

1 Like

Some features that are attractive from the backend point of view:

  • lexical scope let
  • arrow function
  • ES6 style classes
1 Like

For me when it comes to frontend the size of bundle output is really important thing. React itself have in size like 30kb gzipped, and when you deal with slow mobile connection it starting feel slow for your user.

Call me crazy but if for example we can cut “function (param)” to “(x) =>” in whole app we can save kB sended over the wire and this is great. This is a thing that i normally dont care about when writing backend, but frontend code is different beast :smiley:

Im aware of that my example is kind of stupid, but in general more modern code usually results in smaller output size.

I know that they are some tools already that will do exactly that, and I know that this is not main motiviation from the team, but if this will be done by default, for me it is a great news :smiley:

2 Likes

I already run my production build through terser with strong settings to save on bundle size (I’m not supporting IE11 in my project). I am 110% in favour of changes like this.

We like the arrow change but I’m not sure that the removal of the word function is gonna change anything to your bundle size post-gzip. Have you tried manually removing those words from your bundle and gzip again as a rough comparison?

Other good candidates that come to mind:

Default function parameters:

let f = (~a=1, ~b=2, ()) => a + b
// before
function f(aOpt, bOpt, param) {
  var a = aOpt !== undefined ? aOpt : 1;
  var b = bOpt !== undefined ? bOpt : 2;
  return a + b | 0;
}
// after
let f = (a=1, b=2, param) => a + b | 0;

Template strings

let f = (name) => `Hello ${name}`
// before
function f(name) {
  return "Hello " + name;
}
// after
let f = (name) => `Hello ${name}`

Array destructuring (not sure about the perf, but great improvement in readability)

let f = () => {
  let (state, setState) = React.useState(() => 1)
  Js.log2(state, setState)
}
// before
function f(param) {
  var match = React.useState(function () {
        return 1;
      });
  console.log(match[0], match[1]);
  
}
// after
let f = (param) => {
  var [state, setState] = React.useState(() => 1);
  console.log(state, setState);
}

Object destructuring (same):

@react.component
let f = (~foo, ~baz, ~bar=<span />) => {
  <div> foo bar baz </div>
}

// before
function Playground$f(Props) {
  var foo = Props.foo;
  var baz = Props.baz;
  var barOpt = Props.bar;
  var bar = barOpt !== undefined ? Caml_option.valFromOption(barOpt) : React.createElement("span", undefined);
  return React.createElement("div", undefined, foo, bar, baz);
}

// after (possibly)
let Playground$f = ({foo, baz, bar: barOpt}) => {
  let bar = barOpt !== undefined ? Caml_option.valFromOption(barOpt) : React.createElement("span", undefined);
  return React.createElement("div", undefined, foo, bar, baz);
}
3 Likes

Array destructuring does have a perf drop - enough for create-react-app to transform useState calls into explicit indices (see https://www.npmjs.com/package/babel-plugin-optimize-react?activeTab=readme)

Yeah as I’ve mentioned above:

Destructuring and rest spread would have been interesting, but unfortunately those also invoke iterators, so makes the compiled code unpredictably slow.

Default function parameters needs some further considerations. Template strings could be interesting.

It is interesting that you mentioned default arguments, this indeed can be simplified a bit (only those optional types that can be specialised though).

https://rescript-lang.org/try?code=DYUwLgBAZhC8EAoB+APWAmANATwJSwD4BvAKAnIgCkBnAOmAHsBzBAIldwG4yKUBqbCQC+JEqEgBXONAQBmXEA

It seems f can be simplified as below (and looks beautiful):

function f (x=2,y){
   ...
}

I wonder if there is any performance penalty for default arguments? Is there any existing benchmark?

Just realized one thing–using this in callbacks will work only if we continue to use function and not arrow functions: Bind to JS Function | ReScript Language Manual

Any code out there using this-based callbacks could potentially break if ReScript switches to arrow functions. Of course, I may be worrying over nothing and it may be simple to emit function when @this is detected!

3 Likes