Part 9: Externally reference JavaScript and CSS
This is part 9 of a multiple part series on web performance improvements. The first Introductory blog can be found here. In previous part we discussed the impact of HTTP request cycle on performance and how we can overcome it. In this part we will discuss the role of Javascript and CSS in page request cycle and its impact on performance.
Javascript and CSS can be included in a page in two ways, Inline or Externally. Both has its own pro and cons and impact performance of the page. Let’s go through the pro and cons of both first.
Inline vs. External
Let’s try to understand using an example. Consider there are two pages, as shown in image below. Page 1 has all the HTML, CSS and Javascript in single file (inline) and Page 2 CSS and Javascript are referenced externally. So in cae of Page 2 there are 1 HTML, 1 CSS & 4 Javascript files.
Although the total amount of data downloaded is the same, the inline example is more faster than the external example. This is primarily because the external example suffers from the overhead of multiple HTTP requests. The external example, though, benefits from the stylesheet and scripts being downloaded in parallel, but the difference of 1 HTTP request compared to 6 is what makes the inline example faster.
So theoretically, Inline Is Faster as compared to External. Despite these results and findings, using external files in the real world examples generally produces faster pages. This is due to caching of external files by the browser. HTML documents, at least those that contain dynamic content, are typically not configured to be cached. When the HTML documents are not cached, the inline JavaScript and CSS is downloaded every time the HTML document is requested. On the other hand, if the JavaScript and CSS are in external files cached by the browser, the size of the HTML document is reduced without increasing the number of HTTP requests.
The key factor, then, is the frequency with which external JavaScript and CSS components are cached relative to the number of HTML documents requested. This factor, although difficult to quantify, can be gauged using the following metrics.
- Page Views
Two typical cases are:
- If a typical user visits the website once per month. Between visits, it’s likely that any external JavaScript and CSS files have been purged from the browser’s cache, even if the components have a far future Expires header. So fewer page views per user, the stronger the argument for inlining JavaScript and CSS.
- On the other hand, if a typical user has many page views, the browser is more likely to have external components in its cache. The benefit of serving JavaScript and CSS using external files grows along with the number of page views per user per month or page views per user per session.
- Empty Cache vs. Primed Cache
Knowing the potential for users to cache external components is critical to comparing inlining versus external files. The percentage of page views with a primed cache is higher than the percentage of unique users with a primed cache because many users perform multiple page views per session. Users may show up once during the day with an empty cache, but make several subsequent page views with a primed cache. These metrics vary depending on the type of web site. Knowing these statistics helps in estimating the potential benefit of using external files versus inlining. If the nature of your site results in higher primed cache rates for your users, the benefit of using external files is greater. If a primed cache is less likely, inlining becomes a better choice.
- Component Reuse
If every page on your site uses the same JavaScript and CSS, using external files will result in a high reuse rate for these components. Using external files becomes more advantageous in this situation because the JavaScript and CSS components are already in the browser’s cache while users navigate across pages. Ultimately, your decision about the boundaries for JavaScript and CSS external files affects the degree of component reuse. If you can find a balance that results in a high reuse rate, the argument is stronger for deploying your JavaScript and CSS as external files. If the reuse rate is low, inlining might make more sense.
Inline/External, how to decide?
In analyzing the tradeoffs between inlining versus using external files, the key is the frequency with which external JavaScript and CSS components are cached relative to the number of HTML documents requested. The three metrics i.e. page views, empty cache vs. primed cache, and component reuse that can help us determine the best option. The right answer for any specific web site depends on these metrics. Many web sites fall in the middle of these metrics. They get 5–15 page views per user per month, with 2–5 page views per user per session. Empty cache visits are in the range 40–60% of unique users per day have a primed cache, and 75–85% of page views per day are performed with a primed cache. There’s a fair amount of JavaScript and CSS reuse across pages, resulting in a handful of files that cover every major page type. For sites that have these metrics, the best solution is generally to deploy the Java-Script and CSS as external files. This is demonstrated by the example where the external components can be cached by the browser. Loading this page repeatedly and comparing the results to those of the first example, “Inlined JS and CSS,” shows that using external files with a far future Expires header is the fastest approach.
Especial Cases- Home Pages
There are some exceptional cases where we go against the defined metrics. One of such exception is home page where inlining is preferable. A home page is the URL chosen as the browser’s default page, such as FaceBook home page http://www.facebook.com. Let’s look at the three metrics from the perspective of home pages:
- Page views
- Empty cache vs. primed cache
- Component reuse
Home pages have a high number of page views per month. Ideally, whenever the browser is opened, the home page is visited. However, often the home page is visited only once per session.
The primed cache percentage might be lower than other sites. Many user for security reasons, elect to clear the cache every time they close the browser. The next time they open the browser it generates an empty cache page view of the home page.
The reuse rate is low. Many home pages are the only page a user visits on the site, so there is really no reuse.
Analyzing these metrics, there’s an inclination toward inlining over using external files. Home pages have one more factor that tips the scale toward inlining, a high demand for responsiveness, even in the empty cache scenario. If a company decides to launch a campaign encouraging users to set their home pages to the company’s site, the last thing they want is a slow home page. For the company’s home page campaign to succeed, the page must be fast.
The balancing act
Most of the scenarios when looked with performance in view point to inlining. But in real applications it is still inefficient to add all that Java-Script and CSS to the page and not take advantage of the browser’s cache. So what is the solution. How we decide what is good and bad for performance. Following two techniques allow to gain the benefits of inlining, as well as caching external files.
- Post-Onload Download
Some home pages typically have only one page view per session. However, that’s not the case for all home pages. For home pages that are the first of many page views, we want to inline the Java-Script and CSS for the home page, but leverage external files for all secondary page views. This is accomplished by dynamically downloading the external components in the home page after it has completely loaded (via the onload event). This places the external files in the browser’s cache in anticipation of the user continuing on to other pages.
The post-onload download JavaScript code associates the doOnload function with the document’s onload event. After a one-second delay to make sure the page is completely rendered, the appropriate JavaScript and CSS files are downloaded. This is done by creating the appropriate DOM elements (script and link, respectively) and assigning the specific URL:
function doOnload( ) {
setTimeout(“downloadComponents( )”, 1000);
}
window.onload = doOnload;
// Download external components dynamically using JavaScript.
function downloadComponents( ) {
downloadJS(“http://stevesouders.com/hpws/testsma.js”);
downloadCSS(“http://stevesouders.com/hpws/testsm.css”);
}
// Download a script dynamically.
function downloadJS(url) {
var elem = document.createElement(“script”);
elem.src = url;
document.body.appendChild(elem);
}
// Download a style-sheet dynamically.
function downloadCSS(url) {
var elem = document.createElement(“link”);
elem.rel = “style-sheet”;
elem.type = “text/css”;
elem.href = url;
document.body.appendChild(elem);
}In these pages, the JavaScript and CSS are loaded twice into the page inline then external. This to work code has to deal with double definition. Scripts, for example, can define but can’t execute any functions. CSS that uses relative metrics may be problematic if applied twice. Inserting these components into an invisible IFrame is a more advanced approach that avoids these problems.
- Dynamic Inlining
If a home page server knew whether a component was in the browser’s cache, it could make the optimal decision about whether to inline or use external files. Although there is no way for a server to see what’s in the browser’s cache, cookies can be used as an indicator. By returning a session-based cookie with the component, the home page server can make a decision about inlining based on the absence or presence of the cookie. If the cookie is absent, the JavaScript or CSS is inlined. If the cookie is present, it’s likely the external component is in the browser’s cache and external files are used.
Since every user starts off without the cookie, there has to be a way to bootstrap the process. This is accomplished by using the post-onload download technique from the previous example. The first time a user visits the page, the server sees that the cookie is absent and it generates a page that inlines the components. The server then adds JavaScript to dynamically download the external files (and set a cookie) after the page has loaded. The next time the page is visited, the server sees the cookie and generates a page that uses external files.
The beauty of this approach is how forgiving it is. If there’s a mismatch between the state of the cookie and the state of the cache, the page still works. It’s not as optimized as it could be. The session-based cookie technique errs on the side of inlining even though the components are in the browser’s cache—if the user reopens the browser, the session-based cookie is absent but the components may still be cached. Changing the cookie from session-based to short-lived (hours or days) addresses this issue, but moves toward erring on the side of using external files when they’re not truly in the browser’s cache. Either way, the page still works, and across all users there is an improvement in response times by more intelligently choosing between inlining versus using external files.