Web standards in the later years
When it came to jQuery, we compared it against the rapid evolution of supported web standard in modern browsers and realized:
$(selector)pattern can easily be replaced with
- CSS classname switching can now be achieved using Element.classList;
$.ajaxrequests can be performed using the Fetch Standard;
addEventListener()interface is stable enough for cross-platform use;
- We could easily encapsulate the event delegation pattern with a lightweight library;
Furthermore, the chaining Syntax didn’t satisfy how we wanted to write code going forward. For example:
$('.js-widget') .addClass('is-loading') .show()
This Syntax is simple to write, but to our standards, doesn’t communicate intent really well. Did the author expect one or more
js-widget elements on this page? Also, if we update our page markup and accidentally leave out the
js-widget classname, will an exception in the browser inform us that something went wrong? By default, jQuery silently skips the whole expresion when nothing matched the initial selector; but to us, such behavior was a bug rather than a feature.
Finally, we wanted to start annotating types with Flow to perform static type checking at build time, and we concluded推断 that the chaining Syntax doesn’t lend itself well to static analysis, since almost every result of a jQuery method call is of the same type. We chose Flow over alternatives because, at the time, features such as
@flow weak mode allowed us to progressively and efficiently start applying types to a codebase which was largely untyped.
Even with an end goal in sight, we knew that it wouldn’t be feasible to just allocate all resources we had to rewriting everything from jQuery to vanilla JS. If anything, such a rushed endeavor would likely lead to many regressions in site functionality that we would later have to weed out. Instead, we:
- Set up metrics that tracked ratio of jQuery calls used per overall line of code and monitored that graph over time to make sure that it’s either staying constant or going down, not up.
- We discouraged importing jQuery in any new code. To facilitate that using automation, we created eslint-plugin-jquery which would fail CI checks if anyone tried to use jQuery features, for example
- There were now plenty of violations of eslint rules in old code, all of which we’ve annotated with specific
eslint-disablerules in code comments. To the reader of that code, those comments would serve as a clear signal that this code doesn’t represent our current coding practices.
- We created a pull request bot that would leave a review comment on a pull request pinging our team whenever somebody tried to add a new
eslint-disablerule. This way we would get involved in code review early and suggest alternatives.
- A lot of old code had explicit coupling to external interfaces of pjax and faceBox jQuery plugins, so we’ve kept their interfaces relatively the same while we’ve internally replaced their implementation with vanilla JS. Having static type checking helped us have greater confidence around those refactorings.
- Plenty of old code interfaced with rails-behaviors, our adapter for the Ruby on Rails approach to “unobtrusive” JS, in a way that they would attach an AJAX lifecycle handler to certain forms: