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">
<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.
Why doesn't <link rel="preload"> 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.
So is <link rel="modulepreload"> just <link rel="preload"> for modules?
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