Introducing Mojito-JSCheck

https://github.com/yahoo/mojito-jscheck

Most web application developers who have written server-side code must have wished, at one time or another, to know if the client had JavaScript enabled. Of course, most of the time we can employ graceful degradation and progressive enhancement strategies to address the possible lack of JavaScript on the client. Strictly adhering to these principles would seem to obviate the need to check on the availability of JavaScript. However, in some cases, such a facility would prove valuable. For instance, my team developed an image lazy-loading module for Mojito, but it had a cost: because of the need to include fallbacks for the “no js” case, it sent significantly more markup and code to the client than necessary. Specifically, lazy-loaded image markup with a fallback for the “no js” case would look like this:


<img data-src="...">
<noscript>
   <img src="...">
</noscript>
...
<script>
   [JS code to handle the lazy loading...]
</script>

Whereas, if we knew whether JavaScript was available on the client, we could simply output:


<img data-src="...">
...
<script>
   [JS code to handle the lazy loading...]
</script>
//or
<img src="...">

There are other critical examples. Some page loading strategies, such as Facebook’s BigPipe, must know whether the client can support JavaScript while the request is being handled because BigPipe cannot employ graceful degradation (it is heavily reliant on client side JavaScript).

Impossible?

After thinking long and hard about the problem, we concluded that no matter how effectively the client communicated its status to the server, the server could never know the client’s JavaScript status with certainty. It could change at any time, potentially with each request. The server would always lag one step behind the client.

A stark conclusion, yet a usable jscheck was within reach. The key was to focus our attention on the “no js” case. While strictly speaking we cannot know the client’s JavaScript status in real-time, Mojito-JSCheck provides a simple, yet robust solution to the problem.

The ingredients

JSCheck employs a cookie, a query string parameter, a meta tag, and a script tag. As such, it could be implemented on a variety of systems.

Our specific implementation, Mojito-JSCheck, makes it easy to integrate a JavaScript check into any Mojito application. (Mojito is an open-source JavaScript-based MVC framework for the client and the server.) It is available as an npm package, mojito-jscheck, which includes just two main files: a Mojito add-on and a Mojito middleware file. You can read the code if you’re curious about the details. Familiarity with Mojito and its architecture is not required to understand how Mojito-JSCheck works. Therefore, you may consider applying this approach to your own projects, whether or not you use Mojito.

Core concepts

There are three possible JS states: “enabled”, “disabled”, and “indeterminate”. The “indeterminate” state is only used when jscheck is disabled in the application’s configuration.

Walkthrough

Imagine an average user, whose browser has JS enabled by default. (95 to 99% of the requests we serve at Yahoo! come from user agents with JavaScript enabled.) They visit your web page. Nothing in the headers of their request to your page indicates whether JavaScript is enabled. We have no clue. But we may reasonably assume that JavaScript is enabled, based on the numbers above, though of course we employ graceful degradation and progressive enhancement strategies to ensure a useable page regardless.

What if the user now turned JavaScript off, or came to the page with JavaScript off in the first place? To handle this, we place a <META> refresh tag within a <NOSCRIPT> block. The meta tag redirects the user to the same URL with “&js=0” appended. At this point, the JSCheck’s middleware comes into the picture. As the server processes the new (redirected) request, the middleware looks for “js=0” in the query string, in which case it knows JavaScript is disabled. The middleware then sets a cookie indicating that the client lacks JavaScript support.

Mojito is organized around modular units of execution called mojits (“module-widgets”). Any mojit using the Mojito-JSCheck add-on can then leverage this information. For instance, a mojit which displays an image grid might decide not to lazy-load its images. A simple API call to ac.jscheck.status() would return “disabled”.

What if a user turns JavaScript back on?

Getting back to our story, recall that our user now has JavaScript turned off. Whenever the Mojito-JSCheck add-on sees that JavaScript is off, it writes a tiny script to the bottom of each page. The script deletes the JSCheck cookie and removes “js=0” from the location URL. As long as the user browses with JavaScript off, this script has no effect. But as soon as they turn JavaScript on, the script will execute, clearing the cookie and the parameter in the URL, and Mojito-JSCheck will no longer report its status as “disabled”.

Again, note that this approach to performing a JavaScript check does not depend upon Mojito; you could adopt a similar approach within any front-end stack.

Given those considerations, generally, you should still write your code with JS-enabled users in mind, coupled with graceful degradation and progressive enhancements strategies for the “no js” case. However, Mojito-JSCheck allows you to reliably optimize your code.

Awesome sauce! I want to use it. But how?

You could design your own JSCheck system—for any front-end stack—based on the narrative above. But if you use Mojito, it’s very easy to plug Mojito-JSCheck into your app. See the README file for details.

Good luck. I hope you find Mojito-JSCheck useful, and that it enables you to achieve more with Mojito.

~James Alley and the Search Frontend Platform team @ Yahoo!

 


FAQ

  1. Q: Why not return a status of “indeterminate” upon the first request?
    A: Although strictly speaking the status at that point is indeterminate, we instead assume JavaScript is enabled, and serve the page accordingly. If JavaScript is disabled, which will only be the case infrequently, Mojito-JSCheck will redirect the user immediately anyway.

  2. Q. Mojito-JSCheck uses redirects. Aren’t redirects evil?
    A. Redirects are evil. But Mojito-JSCheck only redirects users if their status changes, and the assumption is that users do not switch their JavaScript status all the time.