I started using MooTools right when it was first released in 2006 – I was 17 at the time and I built games and worked on fun projects in almost all of my free time. The frameworks back then solved an entirely different class of problems compared to current libraries and since then many of them have become obsolete. There was this little group of people who was enticed by MooTools. We cared as much about the beauty of the code we were writing as we cared about the things we were building with it. MooTools was elegant yet powerful, it very much had Valerio’s personality encoded within it. Later complemented by Jan’s, Harald’s and Tom’s contributions, then Scott’s and mine and later Arian’s and some others – we all encoded our personalities within this neat little library, with many other contributors taking care of the things outside of the core and side-projects like Slick that Thomas and Fabio worked on. We had different generations of owners, each subsequent one learning tremendously from the previous one.
<Builtin>.prototype.* is verboten
There is two things we missed:
- When the update process for a library is manual, good luck on distributing that update all over the Internet.
- Extending builtins is the worst thing you can do if you don’t control the environment your code runs in.
MooTools was heavily inspired by another library called PrototypeJS. One difference was a simple function called Function.prototype.bind. One of the two versions eventually became part of ECMAScript 5 and it was not ours. Well, fuck. Now, how did we fix it?
In my memories it was my own ingenuity that came up with this insane hack but Sebastian and I actually came up with this solution together. This is the commit and it fills me with joy that the people who commented inline are all people who I work with every day now at Facebook. We solved this by overwriting the native implementation in the old version of MooTools (“our Python 2"). In the new version we used a simple variant of the standard but in compatibility mode we would fall back to the non-standard solution. This meant that you could run one version of MooTools with different implementations of a function based on a build-step setting. Oddly enough, this hasn’t caused any problems in practice, although I did imagine people would be gasping for air when they saw this. Yes, there was a lot of internal drama leading up to this solution but at that point in time, it was really the only thing we could think of to fix this and it was practical enough.
This is similar to what happened a month ago. This time the fix was much simpler, but given that most MooTools websites nowadays are mostly unmaintained the chances of them being upgraded at all are low – the MooTools team has not even released a new version. In fact, the previously mentioned super-old version of MooTools is still being used and has even caused trouble with React.
Move Fast with Stable Infrastructure
Extending built-ins is a great concept. If you control the environment your code runs in and you have the ability to deploy your code often, it should be used to fill in missing features to provide future APIs today. It is still impractical however – even if you control all parts of your application – you will never be able to control all possible browser environments. This is an interesting observation:
“While every version of a library we ship is final, the environment that library will run in is not. In fact, there are countless environments and they are all changing, constantly diverging and eventually converging again.”
Browsers have become the new wild west — prototyping potential standards in the wild with no opt-in/opt-out controls. If you have been using a polyfill for Promises for a while and some browser suddenly decides to ship a broken version of Promises then you are out of luck — you have to constantly stay on top of the game and watch out for this.
How do we avoid such problems in current frameworks and libraries? First off, always create wrapper modules, don’t use polyfills directly. These can feature-detect native implementations but it is better when the use of the native implementation can be toggled by a whitelist of browser versions with a known working implementation, ideally being controlled by a server side setting.
“Framework developers should create their own language based on future language proposals. It should be compiled to different versions of the implemented standard in different browsers.”
While we pretend we are using ES6/ES7 in React and at Facebook, we are really not. We have our own version of the language with custom extensions added on top.
If we constrain ourselves to only use language features that are in some way backwards compatible we can now prototype potential new features without first implementing them in the browser. New features can now be tested by framework developers, arguably the people who should be pushing the standards the most. In addition, it gives developers immediate access to new syntax features across all browsers.
If we also throw in a static type system we get the ability to add functionality to built-in objects. Instead of extending Map.prototype, one could transform function calls on Maps to wrapper calls. This requires a smart code-mod tool that can transform ASTs to ASTs that can also be used to quickly rewrite language modifications that don’t work out in practice.