Preload modules

Sérgio Gomes

Published: November 23, 2024

Module-based development offers some real advantages in terms of cacheability, helping you reduce the number of bytes you need to ship to your users. The finer granularity of the code also helps with the loading story, by letting you prioritize the critical code in your application.

However, module dependencies introduce a loading problem, in that the browser needs to wait for a module to load before it finds out what its dependencies are. One way around this is by preloading the dependencies, so that the browser knows about all the files ahead of time and can keep the connection busy.

<link rel="preload"> is a way of declaratively requesting resources ahead of time, before the browser needs them.

<head>
  <link rel="preload" as="style" href="critical-styles.css">
  <link rel="preload" as="font" crossorigin type="font/woff2" href="myfont.woff2">
</head>

This works particularly well with resources such as fonts, which are often hidden inside CSS files, sometimes several levels deep. In that situation, the browser would have to wait for multiple roundtrips before finding out that it needs to fetch a large font file, when it could have used that time to start the download and take advantage of the full connection bandwidth.

<link rel="preload"> and its HTTP header equivalent provide a simple, declarative way of letting the browser know straight away about critical files that will be needed as part of the current navigation. When the browser sees the preload, it starts a high priority download for the resource, so that by the time it's actually needed it's either already fetched or partly there. However, it doesn't work for modules.

This is where things get tricky. There are several credentials modes for resources, and in order to get a cache hit they must match, otherwise you end up fetching the resource twice. Needless to say, double-fetching is bad, because it wastes the user's bandwidth and makes them wait longer, for no good reason.

For <script> and <link> tags, you can set the credentials mode with the crossorigin attribute. However, it turns out that a <script type="module"> with no crossorigin attribute indicates a credentials mode of omit, which doesn't exist for <link rel="preload">. This means that you would have to change the crossorigin attribute in both your <script> and <link> to one of the other values, and you might not have an easy way of doing so if what you're trying to preload is a dependency of other modules.

Furthermore, fetching the file is only the first step in actually running the code. First, the browser has to parse and compile it. Ideally, this should happen ahead of time as well, so that when the module is needed, the code is ready to run. However, V8 (Chrome's JavaScript engine) parses and compiles modules differently from other JavaScript. <link rel="preload"> doesn't provide any way of indicating that the file being loaded is a module, so all the browser can do is load the file and put it in the cache. Once the script is loaded using a <script type="module"> tag (or it's loaded by another module), the browser parses and compiles the code as a JavaScript module.

In a nutshell, yes. By having a specific link type for preloading modules, we can write simple HTML without worrying about what credentials mode we're using. The defaults just work.

<head>
  <link rel="modulepreload" href="super-critical-stuff.mjs">
</head>
[...]
<script type="module" src="super-critical-stuff.mjs">

And since the browser now knows that what you're preloading is a module, it can be smart and parse and compile the module as soon as it's done fetching, instead of waiting until it tries to run.

Browser Support

  • Chrome: 66.
  • Edge: 79.