A Case For JS Classes Without Classes
Since ECMAScript 2015 Specification has been published, the JS world has split in 2 kind of projects: those that never used new features unless widely available or properly polyfilled, and those transpiled.
The history of JS and Web development, in terms of transpilation, can be summarized pretty much through a single word: Babel.
The amount of projects that depend on its transpilation is huge, so huge that only today its preset-es2015 has been downloaded 120K+ times, 2.5 million times in the last month.
While 2016 and 2017 only add few extra features, both 2015 and latest bring in normalization for ES2015 Standard, including the
classkeyword, once upon a time reserved, hence transpiled together with much more else.
ES2015 Classes Are Un-Polyfillable
While the Babel documentation describe classes as such:
ES2015 classes are a simple sugar over the prototype-based OO pattern. Having a single convenient declarative form makes class patterns easier to use, and encourages interoperability. Classes support prototype-based inheritance, super calls, instance and static methods and constructors.
native classes are in fact much more than that, and there are things by design that cannot possibly be done using good old prototypal inheritance.
Following a very basic example:
While copy and pasted in your current browser would simply work and log true, the Babel produced code will be plain broken and for two reasons:
- there is a hard to solve, long-time standing, bug about constructors that are incapable of returning the right instance, so that instead of a
new List()operation will return an
Array, so that its
pushwill simply return the new length …
- Symbol.species are completely ignored, so that even having a basic extend like the following one,
(new List).slice()will produce an
Array, instead of a new
List, losing all extra features added to the real-world class and its prototype.
Not Only Array …
The latest specifcations regarding Custom Elements has a mandatory need for native classes and/or a natively available
Reflect.construct(NativeClass,args,UserClass), otherwise it wouldn’t work as expected because you cannot simply wite
new HTMLElement()or use
Now we have “a little problem”: those 2.5 million of downloads for Babel preset 2015 most likely keep transpiling classes, unaware of possible bugs already mentioned, keep targeting too old browsers that won’t be able to use Custom Elements unless polyfilled.
While I’ve got covered the latter part, modern browsers, that are all compatible with native ES2015 classes, are running broken and unnecessarily bloated code, preserving the ability to use modern language features like real extends and species.
Unable To Deliver Robust Code
The sad situation I’ve encountered at some point, preparing something to talk and show at the next conference about Web Components, is that I couldn’t create a library or examples immune to these problems.
I wanted to show snippets to copy and paste, exercise, use even in production, when suddenly I’ve realized all those 2.5 million projects behind Babel wouldn’t have worked, even if they were targeting modern evergreen browsers instead of old versions of IE.
The following basic example works just fine without transpilers, but it would break badly once transpiled via Babel
Nope, the transpiled code will inevitably produce the following error:
Failed to construct ‘HTMLElement’: Please use the ‘new’ operator, this DOM object constructor cannot be called as a function.
Not Everyone Controls The Environment
The most obvious arguments I’ve heard after I’ve presented my library were about disabling the Babel class transformer.
Accordingly with this logic, I should tell anyone that would like to use, read, copy and paste my examples, to disable on their projects the Babel class transformer, as if they’re surely in charge of such env, and as if this decision is that lightweight to take, suddenly breaking all supported target browsers that maybe haven’t got classes yet (or ever) carelessly.
But how about I don’t ask anything to anyone, so that every project can keep it like it is, but since dependencies are rarely parsed via Babel, I can ask to install a little opt-in utility that will explicitly use native
classkeyword, being fully ignored by Babel transformer? Looks like a win, doesn’t it?
Let’s see how the previous code would look like now:
You’ve got to admit it’s pretty damn close to a native class definition, and on top of that, look how cleaner the Babel output comes out!
Published on GitHub few minutes ago, classtrophobic uses literal objects to define classes in a way that feels just as natural, without suffering Babel transformations. Indeed, the resulting code will basically be exactly the same as you wrote it, dropping any possible transpiled indirection, resulting in lighter, and most of time faster to execute, code.
The returned value is a native class at all effects, created through a regular static
The runtime goes close to zero, and while super calls won’t probably be as performant as the currently transpiled Babel one, the code is guaranteed to work like native one would do. Consistency with specifications and native behavior are the only goal I am trying to reach, distributing code that just work, no matter which environment it is.
The minified gzipped version of this library is around 800 bytes, and it really provides the bare minimum essential to simulate classes via plain, literal, objects.
When Is Classtrophobic Appropriate?
Unless you really want to, you don’ t have to change all your classes into object literals.
If you don’t subclass, Babel has no known issues. If you do subclass, be careful with the constructor and be sure what is returned is the instance of the class you defined, not its parent. If that’s the case, or you never had problems, maybe you can simply ignore classtrophobic.
This library purpose is indeed to explicitly opt out from the class transformer, or opt-in for Custom Elements definitions, or native subclasses, but definitively it shouldn’t be a lock-in.
Contrarily, I really cannot wait for the entirity of the Web and the Server to fully support latest specifcations, so be ready to drop Babel transpiler, at least old presets, and refactor whenever you want classtrophobic code into native classes. You’ll spend very little time, with granted zero side effects, only performance improvements in both class definition and usage.
That day, everybody will win, ‘till then though … feel free to create classes that won’t be affected anyhow from your favorite transpilers (keeping in mind, classtrophobic still works only on engines that support native classes).
Update for all ES5 browsers
I’ve created a version of Classtrophobic for ES5 that works on every mobile browser and also IE11+ on Desktop.
Compatibility is not an issue anymore, enjoy classes via literals!