What is JavaScript Streaming?

Under normal circumstances, when a browser encounters a synchronous (blocking) reference to a JavaScript file in an HTML file (that is, the script tag has no async or defer attribute), it must download and execute the entire file before continuing to process the remainder of the HTML page. Typical JavaScript sources contain much code that is not used very often, if ever.

Our experiments have found that anywhere from 10% to 50% of a given JavaScript source file is not actually needed in the context that it is deployed – a particular browser on a particular platform. By identifying such code and dividing the JavaScript appropriately, we are able to reduce the number of bytes of data that need to be transferred for the JavaScript to execute on the client, which can translate to significant improvements in browser performance.

To identify which code is actually used, we first instrument a version of the original JavaScript file and measure the utilization of the various functions in the source file. We then use that information to construct an optimized version containing only the functions we observed to be used. We can never be absolutely sure that certain functions are not needed, so we use Nanovisor.js on the client side to demand-load additional required JavaScript functions.

In essence, JavaScript Streaming:

  • creates an instrumented version of the source JavaScript that allows us to record which functions are actually referenced;
  • constructs an optimized version of the source JavaScript (once we've collected a suitable amount of data from the instrumented version);
  • provides a fallback service so we can deliver – in real-time – those functions that the browser needs which were removed but turn out in fact to be necessary.

The service continuously monitors the actual usage of JavaScript and optimizes and re-optimizes JavaScript source files based on that feedback.

How it works

When the service first receives a request for a JavaScript file, it transfers  the original JavaScript file to the requesting client, and at the same time creates an instrumented version and stores it in cache. In the instrumented version we:

  • assign a unique identifier to each function (typically an integer that records where in the source the function occurs). This ID is used to uniquely identify each function in the source.
  • add an extra statement at the beginning of each function that will record the timestamp of the function when it is first called.

On the next request for this same file, the service returns the instrumented code. A usage trace is beaconed back to the service as soon as this code is loaded, and the browser continues to send usage traces back after 2, 4, 8, 16 seconds and so on, until an upper limit of 10 minutes is reached. A randomly-generated unique ID is sent along with the trace data so the service can collate trace data from multiple browsers.

The service then uses the trace data to determine how to partition the JavaScript source into clusters of correlated functions. The service generates clusters by partitioning individual traces – based on when functions are called for the first time – and then aggregating the results of multiple traces.

A single trace consists of an array of function identifiers and timestamps. The service looks for gaps in this trace by sorting the array by timestamps and then identifying gaps within the timestamp data. If there is a large-enough gap between one function and the next, the service decides to mark a partition boundary.

Once it has processed a collection of traces and determined their associated partitioning, the service assigns correlated individual functions to specific clusters. The service then constructs an optimized version of the JavaScript source. The fundamental optimization is that any functions that are not in the first cluster are omitted from the result. Of course, we cannot simply delete these functions – they might actually be needed at some point! Instead, we replace each function body with one that fetches the real function, replaces itself with that function, and then re-enters it for execution.

The service does not simply send the optimized code for every request it receives after this point – instead, for about 4% of requests it will send the instrumented code again. This ensures that the service can respond in a timely manner to changing user behavior.

JavaScript Streaming also has SmartSense, which continuously monitors page load times and, if performance decreases, automatically triggers re-profiling of the script. If re-profiling is triggered too often, then JavaScript Streaming is temporarily disabled automatically for the script (this can occur if A/B testing is going on, which can trigger a re-profiling loop).

Where does JavaScript Streaming have the most impact on performance?

JavaScript Streaming benefits performance primarily by reducing the sizes of JavaScript files, which translates to a shorter download time. Our initial testing has found that JavaScript Streaming provides the most benefit for sites that use a large amount of synchronous (blocking) JavaScript early in the page load. (Scripts with the async attribute are executed asynchronously with the rest of the page – that is, the script will be executed while the browser continues parsing the page. Scripts with the defer attribute are executed when the browser has finished parsing the page.)

We have found that having larger JavaScript files (30 KB +) provides more opportunity for optimization than a small number of small JavaScript files.

Where does JavaScript Streaming have the least impact on performance?

The following would be poor candidates for using JavaScript Streaming:

  • Sites that use little or no JavaScript.
  • Sites with JavaScript split into many small files (less than 20 KB).
  • Sites with JavaScript that are tagged with the async or defer attribute, which don’t block rendering of the page.
  • Sites that use a lot of inline JavaScript – that is, where the code itself is present wrapped in a script tag. JavaScript Streaming only responds to requests for external JavaScript files (those referenced in an HTML page by a script tag with a src attribute).

What do I need to do to enable JavaScript Streaming?

JavaScript Streaming does not require changes to the backend web servers nor to the code that makes up the web site or application. As JavaScript files flow through the Instart platform, the service automatically learns how to optimize them, and continues to relearn as the web site or application is updated over time.

All that is required is that your configuration be set up to enable JavaScript Streaming on the site. Like all of our features, it can be selectively enabled for certain hosts, specific directories, or under certain conditions.