It is 9:18 AM on August 21, 2021. You have just finished eating your space-breakfast, and you’re ready to get back to work maintaining the web presence for Omni Consumer Products. After about an hour, you find your latest change fails an acceptance test. It turns out to be a bug in “RedactSelect”, an open source “multiselect” web component you’ve been using. Looks like it hasn’t been updated in years, owing largely to its maturity and stability. “No problem,” you think, “I’ll just fix the bug and fork it.”
…except the source code looks a little strange. It’s using the @ symbol in a way you’re not familiar with–almost like it was a private field. That can’t be, though, because private fields in ES2020 are denoted with a #. You check the project’s build process, and sure enough, it is built using a long-outdated “transpiler.”
You spend the next few hours making advanced web queries like, “babel 6.17.0 private field syntax”. Once you’re getting a handle on those semantics, you stumble across a bug report for the transpiler: at that version, it was outputting subtly-buggy code under certain conditions. Unfortunately, the fix didn’t come until the next major release which happens to be incompatible with “RedactSelect.”
It’s now 7:03 PM, and you still haven’t fed your robot dog or taken out the cyber garbage (much less completed the feature you were working on). You decide to cut your losses and just find a new component in the morning. The next few months are punctuated by bug reports for integration issues with the replacement.
Inspired by the Future
As a proposal matures, it passes through a number of “stages”, each designed to help spec authors, platform implementors, and application developers collaborate. Here are the expectations for a given proposal as it advances:
Stage 1: The committee expects to devote time to examining the problem space, solutions and cross-cutting concerns
Stage 2: The committee expects the feature to be developed and eventually included in the standard
Stage 3: The solution is complete and no further work is possible without implementation experience, significant usage and external feedback.
Stage 4: The addition will be included in the soonest practical standard revision
My concern is that, as an industry, we have not internalized the distinctions between these stages.
“Function decorators,” came a reply from the audience.
“Actually, that’s not part of ES2016. Even it’s inclusion in ES2017 is debatable.”
“Oh, 2016. That introduced destructuring assignment.”
“Not quite–destructuring binding was standardized in 2015.”
You might think I’m being a little academic here. Maybe it seems snooty of me to expect others to keep track of such technicalities… But downplaying the relevance of the proposals’ “stages” has two real dangers.
The Threat to the Ecosystem
The first (as described in the doomsaying at the onset of this post) is that, in our rush to build on top of an evolving platform, we fragment our infrastructure over time. I’ll point out the ironic value of the name “Babel”–a project whose widespread adoption has the potential to very truly confound the language.
This is not a new problem, though, and it’s something we’re already dealing with today. Consider jQuery, a library deployed to millions of websites. Up until June of 2016, it included an implementation of Promise that was not standards compliant. We seem to be getting by just fine despite this.
When the same class of specification violation occurs at a language level, though, the effect is much more severe. It is much harder to debug (or even identify) issues that arise from the code’s syntax (the correctness of which we generally take for granted) than those that come from libraries we interact with.
The Threat to the Platform
TC-39 operates based on the consensus of its members–an inter-disciplinary group comprised not only of researchers and runtime implementors, but practitioners from organizations like the JS Foundation, Tilde, Bocoup, and Shape Security. As a result, consensus is derived not just from some idealized design, but from the realities of the industry. Take for example the following dialog on the subject of modules from last month’s proceedings:
Dave Herman: Design constraints: – It needs to be possible to import named exports from CJS – [using the require function to load an ECMASCript module] needs to [return] synchronously
Jeff Morrison: Are these technical needs or ecosystem needs?
James Snell: These are ecosystem needs. Babel today can do these things. Those users will want to be able to not change their code. If we say that doesn’t work, we’re violating a concern.
This demonstrates how user expectations push the committee to make difficult decisions. The more eagerly we build and deploy systems on proposed extensions, the more difficult it becomes for standards bodies to amend the design. Remember: it’s not “done” until stage 4! In extreme cases, this could lead to final designs that include sub-optimal aspects informed by “web reality.” That’s not a theoretic concern, either. Already, the specification devotes an entire section to the various irregularities that came about in this way.
These threats are credible only to the extent that we collectively adopt early-stage proposals. If we, as an industry, take a more conservative tack, then we don’t need to worry.
We might respond by refusing to use any syntax that has not been formally ratified by ECMA. In other words, “We won’t use ES20XX features until ES20XX is published.” In this case, all code across all projects would be fully standard-compliant at all times, and we wouldn’t have to worry about fragmentation or curtailing the design process.
If we instead develop an awareness of each proposal’s current “stage” and exercise some sensitivity to that status, then we can participate in the advancement of ECMAScript in a way that is both effective and responsible. This requires some nuance, so we probably can’t define any hard-and-fast rules. I can make some general suggestions, though:
Stage 2 and below: Reserve for personal experiments–not projects with any dependents. Of course, it’s always safe to experiment on a clearly-labeled “unstable” branch. Just know that larger projects may require more refactoring in the event of change. (Remember that Object.observe advanced to this stage before ultimately being withdrawn.) Share your experiences on the es-discuss mailing list or on the proposal’s issue tracker.
Stage 3: Implement in non-critical production code. Your experience in a more realistic setting may uncover new wrinkles–share those immediately! Be cautious about using in larger projects because nothing is set in stone.
Stage 4: Use as you wish. This proposal is effectively standardized; only formalities remain. Feedback is nice but no longer effective.
There’s definitely some room for “fudging” between these stages; being dogmatic isn’t going to serve anyone. However, this strategy does have one aspect that we should consider non-negotiable: feedback. Developers who experiment with early-stage proposals have a certain responsibility to engage in the process.
So get out there and start experimenting: bind some functions, decorate some methods, and cancel some Promises. Use these early experiments to satisfy your curiosity and provide feedback, but please think twice before building your next product with any features not yet standardized.