Skip to content

Commit 7e35196

Browse files
committed
Add <link> rel="modulepreload"
This allows preloading module scripts, and optionally their descendants. The processing model for this turns out to be different enough that simply extending rel="preload" is not a good option. Closes whatwg/fetch#486.
1 parent ad88237 commit 7e35196

File tree

2 files changed

+285
-24
lines changed

2 files changed

+285
-24
lines changed

images/ircfog-modules.svg

Lines changed: 59 additions & 0 deletions
Loading

source

Lines changed: 226 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2804,6 +2804,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
28042804
<li><dfn data-x="concept-request-destination" data-x-href="https://fetch.spec.whatwg.org/#concept-request-destination">destination</dfn></li>
28052805
<li><dfn data-x="concept-potential-destination" data-x-href="https://fetch.spec.whatwg.org/#concept-potential-destination">potential destination</dfn></li>
28062806
<li><dfn data-x="concept-potential-destination-translate" data-x-href="https://fetch.spec.whatwg.org/#concept-potential-destination-translate">translating</dfn> a <span data-x="concept-potential-destination">potential destination</span></li>
2807+
<li><dfn data-x="concept-script-like-destination" data-x-href="https://fetch.spec.whatwg.org/#request-destination-script-like">script-like</dfn> <span data-x="concept-request-destination">destinations</span></li>
28072808
<li><dfn data-x="concept-request-priority" data-x-href="https://fetch.spec.whatwg.org/#concept-request-priority">priority</dfn></li>
28082809
<li><dfn data-dfn-for="request" data-x="concept-request-origin" data-x-href="https://fetch.spec.whatwg.org/#concept-request-origin">origin</dfn></li>
28092810
<li><dfn data-x="concept-request-referrer" data-x-href="https://fetch.spec.whatwg.org/#concept-request-referrer">referrer</dfn></li>
@@ -6960,6 +6961,28 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
69606961
data-x="attr-crossorigin-anonymous-keyword">anonymous</code> keyword. The <i data-x="missing value default">missing value default</i>, used when the attribute is omitted, is the <dfn data-x="attr-crossorigin-none">No
69616962
CORS</dfn> state.</p>
69626963

6964+
<p>The majority of fetches governed by <span data-x="CORS settings attribute">CORS settings
6965+
attributes</span> will be done via the <span>create a potential-CORS request</span> algorithm.</p>
6966+
6967+
<p>For <span data-x="module script">module scripts</span>, certain <span data-x="CORS settings
6968+
attribute">CORS settings attributes</span> have been repurposed to have a slightly different
6969+
meaning, wherein they only impact the <span data-x="concept-request">request</span>'s <span
6970+
data-x="concept-request-credentials-mode">credentials mode</span> (since the <span
6971+
data-x="concept-request-mode">mode</span> is always "<code data-x="">cors</code>"). To perform
6972+
this translation, we define the <dfn>module script credentials mode</dfn> for a given <span>CORS
6973+
settings attribute</span> to be determined by switching on the attribute's state:</p>
6974+
6975+
<dl class="switch">
6976+
<dt><span data-x="attr-crossorigin-none">No CORS</span></dt>
6977+
<dd>"<code data-x="">omit</code>"</dd>
6978+
6979+
<dt><span data-x="attr-crossorigin-anonymous">Anonymous</span></dt>
6980+
<dd>"<code data-x="">same-origin</code>"</dd>
6981+
6982+
<dt><span data-x="attr-crossorigin-none">Use Credentials</span></dt>
6983+
<dd>"<code data-x="">include</code>"</dd>
6984+
</dl>
6985+
69636986

69646987
<h4>Referrer policy attributes</h4>
69656988

@@ -13120,6 +13143,7 @@ interface <dfn>HTMLLinkElement</dfn> : <span>HTMLElement</span> {
1312013143
<code data-x="rel-alternate">alternate</code>,
1312113144
<code data-x="rel-dns-prefetch">dns-prefetch</code>,
1312213145
<code data-x="rel-icon">icon</code>,
13146+
<code data-x="rel-modulepreload">modulepreload</code>,
1312313147
<code data-x="rel-next">next</code>,
1312413148
<code data-x="rel-pingback">pingback</code>,
1312513149
<code data-x="rel-preconnect">preconnect</code>,
@@ -13273,15 +13297,24 @@ interface <dfn>HTMLLinkElement</dfn> : <span>HTMLElement</span> {
1327313297
destination</span> is a keyword for this attribute, mapping to a state of the same name. The
1327413298
attribute must be specified on <code>link</code> elements that have a <code
1327513299
data-x="attr-link-rel">rel</code> attribute that contains the <code
13276-
data-x="rel-preload">preload</code> keyword, but must not be specified on <code>link</code>
13277-
elements which do not. <span w-nodev>The processing model for how the <code
13278-
data-x="attr-link-as">as</code> attribute is used is given in the <span
13279-
data-x="concept-link-obtain">steps to obtain the resource</span>.</span></p>
13300+
data-x="rel-preload">preload</code> keyword. It may be specified on <code>link</code> elements
13301+
that have a <code data-x="attr-link-rel">rel</code> attribute that contains the <code
13302+
data-x="rel-modulepreload">modulepreload</code> keyword; in such cases it must have a value which
13303+
is a <span data-x="concept-script-like-destination">script-like destination</span>. For other
13304+
<code>link</code> elements, it must not be specified.</p>
13305+
13306+
<p w-nodev>The processing model for how the <code data-x="attr-link-as">as</code> attribute is
13307+
used is given in the steps to obtain the resource, for <span data-x="concept-link-obtain">for
13308+
<code data-x="rel-preload">preload</code> links</span> and <a
13309+
href="#modulepreload-obtain-steps">for <code data-x="rel-modulepreload">modulepreload</code>
13310+
links</a>, respectively.</p>
1328013311

1328113312
<p class="note">The attribute does not have a <i data-x="missing value default">missing value
1328213313
default</i> or <i data-x="invalid value default">invalid value default</i>, meaning that invalid
1328313314
or missing values for the attribute map to no state. This is accounted for in the processing
13284-
model.</p>
13315+
model. For <code data-x="rel-preload">preload</code> links, both conditions are an error; for
13316+
<code data-x="rel-modulepreload">modulepreload</code> links, a missing value will be treated as
13317+
"<code data-x="">script</code>".</p>
1328513318

1328613319
<hr>
1328713320

@@ -23426,6 +23459,7 @@ interface <dfn>HTMLHyperlinkElementUtils</dfn> {
2342623459
<span>allowed in the body</span>. The <span>body-ok</span> keywords defined by this specification
2342723460
are
2342823461
<code data-x="rel-dns-prefetch">dns-prefetch</code>,
23462+
<code data-x="rel-modulepreload">modulepreload</code>,
2342923463
<code data-x="rel-pingback">pingback</code>,
2343023464
<code data-x="rel-preconnect">preconnect</code>,
2343123465
<code data-x="rel-prefetch">prefetch</code>,
@@ -23516,6 +23550,16 @@ interface <dfn>HTMLHyperlinkElementUtils</dfn> {
2351623550
<td>Imports an icon to represent the current document.</td>
2351723551
</tr>
2351823552

23553+
<tr>
23554+
<td><code data-x="rel-modulepreload">modulepreload</code></td>
23555+
<td><span data-x="external resource link">External Resource</span></td>
23556+
<td><em>not allowed</em></td>
23557+
<td class="yes"> Yes </td>
23558+
<td>Specifies that the user agent must preemptively <span data-x="fetch a single module script">fetch the module
23559+
script</span> and store it in the document's <span data-x="concept-document-module-map">module map</span> for later
23560+
evaluation. Optionally, the module's dependencies can be fetched as well.</td>
23561+
</tr>
23562+
2351923563
<tr>
2352023564
<td><code data-x="rel-license">license</code></td> <!-- seventh most used <a rel> value -->
2352123565
<td><span>Hyperlink</span></td>
@@ -24117,6 +24161,176 @@ interface <dfn>HTMLHyperlinkElementUtils</dfn> {
2411724161
</div>
2411824162

2411924163

24164+
<h5>Link type "<dfn><code data-x="rel-modulepreload">modulepreload</code></dfn>"</h5>
24165+
24166+
<p>The <code data-x="rel-modulepreload">modulepreload</code> keyword may be used with
24167+
<code>link</code> elements. This keyword creates an <span>external resource link</span>. This
24168+
keyword is <span>body-ok</span>.</p>
24169+
24170+
<p>The <code data-x="rel-modulepreload">modulepreload</code> keyword is a specialized alternative
24171+
to the <code data-x="rel-preload">preload</code> keyword, with a processing model geared toward
24172+
preloading <span data-x="module script">module scripts</span>. In particular, it uses the specific
24173+
fetch behavior for module scripts (including, e.g., a different interpretation of the <code
24174+
data-x="attr-link-crossorigin">crossorigin</code> attribute), and places the result into the
24175+
appropriate <span data-x="concept-document-module-map">module map</span> for later evaluation. In
24176+
contrast, a similar <span>external resource link</span> using the <code
24177+
data-x="rel-preload">preload</code> keyword would place the result in the preload cache, without
24178+
affecting the document's <span data-x="concept-document-module-map">module map</span>.</p>
24179+
24180+
<p>Additionally, implementations can take advantage of the fact that <span data-x="module
24181+
script">module scripts</span> declare their dependencies in order to fetch the specified module's
24182+
dependency as well. This is intended as an optimization opportunity, since the user agent knows
24183+
that, in all likelihood, those dependencies will also be needed later. It will not generally be
24184+
observable without using technology such as service workers, or monitoring on the server side.
24185+
Notably, the appropriate <code data-x="event-load">load</code> or <code
24186+
data-x="event-error">error</code> events will occur after the specified module is fetched, and
24187+
will not wait for any dependencies.</p>
24188+
24189+
<div w-nodev>
24190+
24191+
<p>The appropriate times to fetch the resource for such a link are:</p>
24192+
24193+
<ul>
24194+
<li><p>When the <span>external resource link</span> is created on a <code>link</code> element
24195+
that is already <span>browsing-context connected</span>.</p></li>
24196+
24197+
<li><p>When the <span>external resource link</span>'s <code>link</code> element <span>becomes
24198+
browsing-context connected</span>.</p></li>
24199+
24200+
<li><p>When the <code data-x="attr-link-href">href</code> attribute of the <code>link</code>
24201+
element of an <span>external resource link</span> that is already <span>browsing-context
24202+
connected</span> is changed.</p></li>
24203+
</ul>
24204+
24205+
</div>
24206+
24207+
<p class="note">Unlike some other link relations, changing the relevant attributes (such as <code
24208+
data-x="attr-link-as">as</code>, <code data-x="attr-link-crossorigin">crossorigin</code>, and
24209+
<code data-x="attr-link-referrerpolicy">referrerpolicy</code>) of such a <code>link</code>
24210+
attribute does not trigger a new fetch. This is because the document's <span
24211+
data-x="concept-document-module-map">module map</span> has already been populated by a previous
24212+
fetch, and so re-fetching would be pointless.</p>
24213+
24214+
<div w-nodev>
24215+
24216+
<p id="modulepreload-obtain-steps">To obtain the resource for such a link:</p>
24217+
24218+
<ol>
24219+
<li><p>If the <code data-x="attr-link-href">href</code> attribute's value is the empty string,
24220+
then return.</p></li>
24221+
24222+
<li><p>Let <var>destination</var> be the current state of the <code
24223+
data-x="attr-link-as">as</code> attribute (a <span
24224+
data-x="concept-request-destination">destination</span>), or "<code data-x="">script</code>" if
24225+
it is in no state.</p></li>
24226+
24227+
<li><p>If <var>destination</var> is not <span
24228+
data-x="concept-script-like-destination">script-like</span>, then <span>queue a task</span> on
24229+
the <span>networking task source</span> to <span data-x="concept-event-fire">fire an event</span>
24230+
named <code data-x="event-error">error</code> at the <code>link</code> element, and
24231+
return.</p></li>
24232+
24233+
<li><p><span data-x="parse a url">Parse</span> the <span>URL</span> given by the <code
24234+
data-x="attr-link-href">href</code> attribute, relative to the element's <span>node
24235+
document</span>. If that fails, then return. Otherwise, let <var>url</var> be the <span>resulting
24236+
URL record</span>.</p></li>
24237+
24238+
<li><p>Let <var>settings object</var> be the <code>link</code> element's <span>node
24239+
document</span>'s <span>relevant settings object</span>.</p></li>
24240+
24241+
<li><p>Let <var>credentials mode</var> be the <span>module script credentials mode</span> for the
24242+
<code data-x="attr-link-crossorigin">crossorigin</code> attribute.</p></li>
24243+
24244+
<li><p>Let <var>cryptographic nonce</var> be the value of the <code
24245+
data-x="attr-link-nonce">nonce</code> attribute, if it is specified, or the empty string
24246+
otherwise.</p></li>
24247+
24248+
<li><p>Let <var>integrity metadata</var> be the value of the <code
24249+
data-x="attr-link-integrity">integrity</code> attribute, if it is specified, or the empty string
24250+
otherwise.</p></li>
24251+
24252+
<li><p>Let <var>options</var> be a <span>script fetch options</span> whose <span
24253+
data-x="concept-script-fetch-options-nonce">cryptographic nonce</span> is <var>cryptographic
24254+
nonce</var>, <span data-x="concept-script-fetch-options-integrity">integrity metadata</span> is
24255+
<var>integrity metadata</var>, <span data-x="concept-script-fetch-options-parser">parser
24256+
metadata</span> is "<code data-x="">not-parser-inserted</code>", and <span
24257+
data-x="concept-script-fetch-options-credentials">credentials mode</span> is <var>credentials
24258+
mode</var>.</p></li>
24259+
24260+
<li><p><span>Fetch a single module script</span> given <var>url</var>, <var>settings
24261+
object</var>, <var>destination</var>, <var>options</var>, <var>settings object</var>, "<code
24262+
data-x="">client</code>", and with the <var>top-level module fetch</var> flag set. Wait until
24263+
algorithm asynchronously completes with <var>result</var>.</p></li>
24264+
24265+
<li><p>If <var>result</var> is null, <span data-x="concept-event-fire">fire an event</span>
24266+
named <code data-x="event-error">error</code> at the <code>link</code> element, and
24267+
return.</p></li>
24268+
24269+
<li><p><span data-x="concept-event-fire">Fire an event</span> named <code
24270+
data-x="event-load">load</code> at the <code>link</code> element.</p></li>
24271+
24272+
<li>
24273+
<p>Optionally, perform the following steps:</p>
24274+
24275+
<ol>
24276+
<li><p>Let <var>visited set</var> be « <var>url</var> ».</p></li>
24277+
24278+
<li><p><span data-x="fetch the descendants of and instantiate a module script">Fetch the
24279+
descendants of and instantiate</span> <var>result</var> given <var>destination</var> and
24280+
<var>visited set</var>.</p></li>
24281+
</ol>
24282+
24283+
<p class="note">Generally, performing these steps will be beneficial for performance, as it
24284+
allows pre-loading the modules that will invariably be requested later, when <span>fetch a
24285+
module script graph</span> is called. However, user agents might wish to skip them in
24286+
bandwidth-constrained situations, or situations where the relevant fetches are already in
24287+
flight.</p>
24288+
</li>
24289+
</ol>
24290+
24291+
</div>
24292+
24293+
<div class="example" id="example-modulepreload-manifest">
24294+
<p>The following snippet shows the top part of an application with several modules preloaded:</p>
24295+
24296+
<pre>&lt;!DOCTYPE html>
24297+
&lt;html lang="en">
24298+
&lt;title>IRCFog&lt;/title>
24299+
24300+
&lt;link rel="modulepreload" href="app.mjs">
24301+
&lt;link rel="modulepreload" href="helpers.mjs">
24302+
&lt;link rel="modulepreload" href="irc.mjs">
24303+
&lt;link rel="modulepreload" href="fog-machine.mjs">
24304+
24305+
&lt;script type="module" src="app.mjs">
24306+
...</pre>
24307+
24308+
<p>Assume that the module graph for the application is as follows:</p>
24309+
24310+
<img src="images/ircfog-modules.svg" width="301" height="151" alt="The module graph is rooted at app.mjs, which depends on irc.mjs and fog-machine.mjs. In turn, irc.mjs depends on helpers.mjs.">
24311+
24312+
<p>Here we see the application developer has used <code
24313+
data-x="rel-modulepreload">modulepreload</code> all of the modules in their module graph,
24314+
ensuring that the user agent initiates fetches for them all. Without such preloading, the user
24315+
agent might need to go through multiple network roundtrips before discovering <code
24316+
data-x="">helpers.mjs</code>, if technologies such as HTTP/2 Server Push are not in play. In
24317+
this way, <code data-x="rel-modulepreload">modulepreload</code> <code>link</code> elements can be
24318+
used as a sort of "manifest" of the application's modules.</p>
24319+
</div>
24320+
24321+
<div class="example" id="example-modulepreload-dynamic-import">
24322+
<p>The following code shows how <code data-x="rel-modulepreload">modulepreload</code> links can
24323+
be used in conjunction with <code>import()</code> to ensure network fetching is done ahead of
24324+
time, so that when <code>import()</code> is called, the module is already ready (but not
24325+
evaluated) in the <span>module map</span>:</p>
24326+
24327+
<pre>&lt;link rel="modulepreload" href="awesome-viewer.js">
24328+
24329+
&lt;button onclick="import('./awesome-viewer.js').then(m => m.view())">
24330+
View awesome thing
24331+
&lt;/button></pre>
24332+
</div>
24333+
2412024334
<h5>Link type "<dfn><code data-x="rel-nofollow">nofollow</code></dfn>"</h5>
2412124335

2412224336
<p>The <code data-x="rel-nofollow">nofollow</code> keyword may be used with <code>a</code> and
@@ -57839,24 +58053,12 @@ o............A....e
5783958053

5784058054
</li>
5784158055

57842-
<li><p>Let <var>CORS setting</var> be the current state of the element's <code
58056+
<li><p>Let <var>classic script CORS setting</var> be the current state of the element's <code
5784358057
data-x="attr-script-crossorigin">crossorigin</code> content attribute.</p></li>
5784458058

57845-
<li>
57846-
<p>Let <var>module script credentials mode</var> be determined by switching on <var>CORS
57847-
setting</var>:</p>
57848-
57849-
<dl class="switch">
57850-
<dt><span data-x="attr-crossorigin-none">No CORS</span></dt>
57851-
<dd>"<code data-x="">omit</code>"</dd>
57852-
57853-
<dt><span data-x="attr-crossorigin-anonymous">Anonymous</span></dt>
57854-
<dd>"<code data-x="">same-origin</code>"</dd>
57855-
57856-
<dt><span data-x="attr-crossorigin-none">Use Credentials</span></dt>
57857-
<dd>"<code data-x="">include</code>"</dd>
57858-
</dl>
57859-
</li>
58059+
<li><p>Let <var>module script credentials mode</var> be the <span>module script credentials
58060+
mode</span> for the element's <code data-x="attr-script-crossorigin">crossorigin</code> content
58061+
attribute.</p>
5786058062

5786158063
<li>
5786258064

@@ -57923,7 +58125,7 @@ o............A....e
5792358125
<dt>"<code data-x="">classic</code>"</dt>
5792458126
<dd>
5792558127
<p><span>Fetch a classic script</span> given <var>url</var>, <var>settings object</var>,
57926-
<var>options</var>, <var>CORS setting</var>, and <var>encoding</var>.</p>
58128+
<var>options</var>, <var>classic script CORS setting</var>, and <var>encoding</var>.</p>
5792758129
</dd>
5792858130

5792958131
<dt>"<code data-x="">module</code>"</dt>
@@ -117348,8 +117550,8 @@ interface <dfn>External</dfn> {
117348117550
<tr>
117349117551
<th> <code data-x="">as</code>
117350117552
<td> <code data-x="attr-link-as">link</code>
117351-
<td> <span data-x="concept-potential-destination">Potential destination</span> for a preload request (for <code data-x="attr-link-rel">rel</code>="<code data-x="rel-preload">preload</code>")
117352-
<td> <span data-x="concept-potential-destination">Potential destination</span>
117553+
<td> <span data-x="concept-potential-destination">Potential destination</span> for a preload request (for <code data-x="attr-link-rel">rel</code>="<code data-x="rel-preload">preload</code>" and <code data-x="attr-link-rel">rel</code>="<code data-x="rel-modulepreload">modulepreload</code>")
117554+
<td> <span data-x="concept-potential-destination">Potential destination</span>, for <code data-x="attr-link-rel">rel</code>="<code data-x="rel-preload">preload</code>"; <span data-x="concept-script-like-destination">script-like destination</span>, for <code data-x="attr-link-rel">rel</code>="<code data-x="rel-modulepreload">modulepreload</code>"
117353117555
<tr>
117354117556
<th> <code data-x="">async</code>
117355117557
<td> <code data-x="attr-script-async">script</code>

0 commit comments

Comments
 (0)