Two.js: Use ES6 features in the codebase?

Created on 11 Feb 2021  路  17Comments  路  Source: jonobr1/two.js

There are several ES6 features which may come in useful in the codebase--the first that comes to mind is declaring variables with let and const, which are block-scoped unlike var (this avoids accidentally redeclaring variables inside if-statements).

In order to maintain compatibility with non-ES6 browsers, Babel can be used during the build process to transpile the code into ES5. The downside is that the transpiled code may be slightly less readable. Some of this can be fixed using source maps (so debuggers can "map" transpiled code back to non-transpiled code and display the latter) but in my experience that doesn't always work, at least on Firefox.

enhancement

All 17 comments

I think it's fine to switch to let and const. It looks like there is high coverage on caniuse for ES6. I'm not a huge fan of source maps and transpilers, but the build step could export an ES5 compatible version of the code, right?

Also, I'd love to get your thoughts on how we could do something like:

import Two from 'two.js';

and also

import { Path } from 'two.js';

@jonobr1 re: your import / export question, this is pretty straightforward:

export { Two as default, Path, ... };

then:

import Two from './two.js';
import { Path } from './two.js';

or on single line:

import Two, { Path } from './two.js';

Thanks for the example. That makes sense and I should clarify my question a bit. Can we do both those import styles, but also organize and export the code so that when you import a specific class like import { Path } from 'two.js'; that it only imports what's needed instead of the entire library?

Indeed! If you want to be able to import the default class as well as specific feature classes from the same package entry point, you would export them all from one source file as in the example above. (Within your package, you would maintain feature classes in their own source files, and import them into a centralized file specifically for exporting from the package).

Alternatively, you can export specific feature classes from different package entry points (see package.json subpath exports and subpath patterns).

Cool, thank you for the explanation. I think based on how things are currently configured we can do the following:

import Two from 'two.js';
import { Path } from 'two.js/src';

The latter example is ideal for tree shaking and the codebase is not entirely setup to perform this optimally. It would be great to include this when implementing ES6 features into the codebase.

I think it's fine to switch to let and const. It looks like there is high coverage on caniuse for ES6. I'm not a huge fan of source maps and transpilers, but the build step could export an ES5 compatible version of the code, right?

Not wanting to deal with irritating source maps and transpilers is super understandable. Out of curiosity, why would a ES5 compatible version of this library still be needed? (IE11 exists, but even Microsoft is finally abandoning it. And someone who targets IE11 should have a build pipeline that lets them transpile their dependencies themselves.)

Good point. There isn't a big reason beyond this is a big undertaking and would be hard to "undo" that work if needed. That being said, I agree, let's continue forward!

Do you have an es6 conversion branch? Also, have you considered a typescript conversion? Not only would it help clean up the code, but you can build compile down down to es3 (not that we should :)).

There isn't a working branch for this feature yet on the Two.js repository. But, feel free to fork your own!

Here is the conversion branch: https://github.com/jonobr1/two.js/tree/es6

I've only done Vector and Anchor so far, but they seem to respond well to TypeScript's built-in declarations generator. This is going to take awhile though because I'd like to move away from both the Two.Events model as well as all the flags to check if something needs to be updated or not.

In lieu of flags I think we can use ES6 Proxys in the parent class to hijack and know exactly when their child (in the case of a Two.Group this would be a Two.Path, in the case of a Two.Path this would be its vertices property which is a Two.Collection, etc.) is updating and respond accordingly. Interested to hear other ideas / suggestions if you have them!

Here's to progress, slow, but steady 馃槄

I'd avoid using Proxy for performance-sensitive code--I believe it doesn't JIT-compile well. Is there any reason to use them over setters/getters? I think Proxy is only necessary if you want to track addition/removal of properties.

This article also affirms your assessment: https://thecodebarbarian.com/thoughts-on-es6-proxies-performance happens to make me chuckle a few times too.

I liked the technique because it's top down instead of bottom up. With a proxy a parent object could intervene and listen to what its child is doing. With defineProperty (which I love, if you couldn't tell from the source code already) there needed to be a way to bubble up activity to its parent.

Can certainly still use defineProperty, but semantically TyepScript won't know what to do with that for generating declarations.

If you don't need to set anything fancy, you can just use get/set class field syntax:

class Foo {
  constructor () {
    this._bar = null;
  }

  get bar () {
    return this._bar;
  }

  set bar (newBar) {
    this._bar = newBar;
  }
}

Good point. That would definitely help the TypeScript inference. But, I would still need to use the flag systems.

Currently have src/vector.js and src/anchor.js translated.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iandanforth picture iandanforth  路  6Comments

EdeMeijer picture EdeMeijer  路  7Comments

adamdburton picture adamdburton  路  6Comments

trusktr picture trusktr  路  8Comments

Sudhanshu4122 picture Sudhanshu4122  路  6Comments