当脚本加载为 "preload"/"modulepreload" 时会发生什么?

What happen when a script is loaded as "preload"/"modulepreload"?

模块工作者 https://web.dev/module-workers/ where we have an ability to load worker's as preloaded module, which means they can be preloaded and even pre-parsed and prefetch their dependencies (https://web.dev/module-workers/#preload-workers-with-modulepreload 上有一篇关于 web.dev 的非常有趣的文章。

如果我没记错的话,不仅Web-Workers可以作为预加载模块加载,这适用于任何js脚本,字体,css等

<link rel="preload" href="fonts/cicle_fina-webfont.woff2" as="font" type="font/woff2" crossorigin="anonymous">

<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">

文中有说法,很困扰我:

Preloaded modules can also be used by both the main thread and module workers. This is useful for modules that are imported in both contexts, or in cases where it's not possible to know in advance whether a module will be used on the main thread or in a worker.

这是否意味着模块加载也会缓存已解析的代码,这意味着如果我们在顶部使用 import 语句包含它,则不会再次解析在主线程和工作线程中使用的模块?

然而,这并没有发生,每当我们在任何领域(主线程、工作线程)上导入模块时,它们都会独立执行导入,然后在未来他们会在自己的领域中引用已解析的缓存实例。

我真的很困惑,作者到底想解释什么。以及我们如何实现它。

相关文章:https://developers.google.com/web/updates/2017/12/modulepreload#does_preloading_modules_help_performance

我不确定这篇文章从哪里得到这个想法,但阅读规范后,我没有看到任何证据

Preloaded modules can also be used by both the main thread and module workers.


如果我们检查规范,fetch and process the linked resource algorithm for modulepreload links 算法会在 步骤 5

  1. Let settings object be the link element's node document's relevant settings object.

设置对象 然后在 第 11 步 传递给 fetch a modulepreload module script graph algorithm, which will itself call fetch a single module script and fetch the descendants of and link — 最后也会调用 获取单个模块脚本 — 使用相同的设置对象

这个settings object is where the module map will be found, and this module map will be used in fetch a single module script避免多次请求同一个模块(缓存)。

  1. Let moduleMap be module map settings object's module map.

  2. If moduleMap[url] is "fetching", wait in parallel until that entry's value changes, then queue a task on the networking task source to proceed with running the following steps.

  3. If moduleMap[url] exists, asynchronously complete this algorithm with moduleMap[url], and return.

可能应该注意的是,虽然这个算法 creates a module script,它还没有执行它。


所以从那里我们可以看到 modulepreload links 不仅会获取 linked 资源,还会获取所有 sub-resources 甚至准备 module scripts 用于这些资源中的每一个,这在很大程度上对应于本文所声称的其他内容。

但是,这还不足以得出关于有问题的报价的任何结论。

我们必须去检查关于 dedicated Workers constructor, which will call the run a worker 算法传递 与我们的 modulepreload link 正在使用,这次称为“外部设置”。

在这个 run a worker 算法的第 8 步,它要求

  1. Set up a worker environment settings object with realm execution context and outside settings, and let inside settings be the result.

而这个set up a worker environment settings object算法将只使用文档的外部设置来设置inherited origin内部值和新的设置对象top-level origin 属性.

这个新的设置对象模块映射the one of its global scope,它是“最初是空的".

工作人员的环境设置对象不从外部设置继承其模块映射 .

所以当 worker 自己调用这些 fetch a single module script and fetch the descendants of and link algorithms as part of fetch a module worker script graph 时,它会检查 module map 是它自己的 inside settings模块映射在那里,它找不到我们的modulepreloadlink创建的模块脚本。


所以根据我对规范的阅读,我会说 modulepreload link 对模块 Workers 的唯一帮助是 HTTP 缓存 将已经下载了图中的所有文件。如果你打算只在 Worker 中使用这些模块,那么让它在文档端准备 module scripts 实际上是 counter-productive,一个简单的 prefetch link 可能会做得更好,除了你必须为每个 sub-resource.

创建一个这样的 link

Kaiido感谢您的详细回复。很有帮助。

我也对这个有问题的引用进行了很多搜索,然后我在 web.dev if they could update the content. You can track on Issue

上发起了一个问题

JS Engine 最繁重的工作之一是 parse/compile(What is Parse/Compile) our code and it plays an important role in time to interact of a web page and also there are many ways where we can improve TTI like only sending the code a user needs(code-splitting by webpack, chunks etc..), minification, tree shaking, http-caching, module-workers 等等.... 还有不要忘记 preload

预载的实际定义:

The basic way you could use preload is to load late-discovered resources early. While most markup-based resources are discovered fairly early by the browser’s preloader, not all resources are markup-based. Some of the resources are hidden in CSS and in JavaScript, and the browser cannot know that it is going to need them until it is already fairly late. So in many cases, these resources end up delaying the first render, the rendering of text, or loading of critical parts of the page.

简而言之:

Download a resource(preparse + compile) because you know you’d need it, but you don’t yet want to execute it.

早些时候,如果我们将脚本注入到我们想要的位置 运行,浏览器将必须先下载脚本才能执行(注意:浏览器必须先做很多事情执行您的代码),这可能需要一段时间。但是 Preload 解决了这个问题。

我相信作者试图解释相同的情况,即使用 link rel="modulepreload" 标记预加载甚至 pre-parsed 模块(不执行 + 编译的字节码被缓存)以供以后评估(re-compilation可跳过)

最后我相信我的疑问已经解决了。 非常感谢!

几个有用的链接: