Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module script dependencies and fetch priority #10276

Open
yoavweiss opened this issue Apr 12, 2024 · 4 comments
Open

Module script dependencies and fetch priority #10276

yoavweiss opened this issue Apr 12, 2024 · 4 comments

Comments

@yoavweiss
Copy link
Collaborator

yoavweiss commented Apr 12, 2024

What is the issue with the HTML Standard?

According to descendant script fetch options, module script dependencies are fetched with an "auto" priority.
I'd love to understand the reasoning for this - given that the descendants are blocking the parent from executing, if the parent got its priority upgraded, shouldn't the same apply to these blocking dependencies?
In the inverse case, if the parent got its priority lowered, the dependencies are similarly of lower priority.

Is there a reason the descendants don't simply inherit the fetch priority from the parent?

^^ @domfarolino

@domfarolino
Copy link
Member

See #8470 (review) and #8470 (comment). @pmeenan mentioned that in the spec discussion (presumably this means discussion in the Priority Hints spec, before the HTML upstreaming was started) that priority would not cascade down / be inherited by dependent resources. The why behind this is not obvious to me, so maybe Pat can link to some of the spec discussion he referred to and help us clarify this?

@pmeenan
Copy link
Contributor

pmeenan commented May 5, 2024

It was a simplifying decision around scripts, iframes and modules that the fetchpriority was specifically just for the resource that it was tagged on and that there were no downstream effects on fetching or execution.

At least in Chrome, "auto" for a module script dependency should already be high and you wouldn't be able to boost it anymore. It's less clear that you'd want dependencies to be lowered again after the main resource was loaded or what that would even mean (i.e. for a script, would every fetch from within the script get a forced priority change, what would break if that were the case).

It's a bit clearer in the iFrame case for something like a video embed or chat widget (ignore for now that fetchpriority doesn't actually apply to iframes but consider the grouping). Just because you want the frame itself to load later doesn't mean you want it to load slowly once it has started.

So, we decided that fetchpriority on the main resource would control when things started but would then get out of the way and if we had a case for more fine-grained control over grouped execution contexts that that would be something else solved at a different time (once the use case for it was clear).

@yoavweiss
Copy link
Collaborator Author

At least in Chrome, "auto" for a module script dependency should already be high and you wouldn't be able to boost it anymore. It's less clear that you'd want dependencies to be lowered again after the main resource was loaded or what that would even mean (i.e. for a script, would every fetch from within the script get a forced priority change, what would break if that were the case).

I think there's a difference here between "script A loads script B" (in which I agree we don't want to inherit priority automatically) and "script A statically imports script B" in which script A will not execute until script B is there. I think it makes sense to consider A & B the same resource in this case, as they are totally co-dependent.

Aside: I missed the fact that module scripts don't get priority modifications in Chromium. That means that this doesn't matter in practice (in Chromium) when upgrading priority, but can definitely matter when downgrading it.

@pmeenan
Copy link
Contributor

pmeenan commented May 6, 2024

Even in the downgrade case, because of the late discover of imports, it's not obvious that cascading the downgrade is the "right" thing to do.

Example

Let's say we have a page with a module script a.js that imports b.js and there are 100 images on the page where the first one is a hero image:

...
<script type="module" src="a.js"></script>
<img src="1.jpg">
<img src="2.jpg">
...

On the browser side, some simplifying assumptions for the purpose of the example (close enough to actual behavior):

  • There are 2 priority levels, high and low
  • Module scripts and imports default to a high priority
  • Images default to a low priority
  • fetchpriority=high moves a resource to high priority
  • fetchpriority=low moves a resource to low priority

Also assume we have a browser and server that honor prioritization:

  • Resources are requested/served in priority order
  • Resources of the same priority are requested/served in the order they are discovered

Let's say that a.js is a script that is important to the page but not to the user experience, like an analytics script where you want it to run as soon as possible so you can track abandons but not block the user experience.

Default loading behavior

By default, the order for the resources being loaded would be something like:

  1. a.js is transferred
  2. 1.jpg is transferred (already in-flight when b.js is being requested)
  3. b.js is transferred
  4. a.js executes
  5. Images 2-100 are transferred

Adjusting with fetchpriority

By default, a.js is delaying the hero image so we boost the priority of the hero image and lower the priority of a.js:

...
<script type="module" src="a.js" fetchpriority=low></script>
<img src="1.jpg" fetchpriority=high>
<img src="2.jpg">
...

Priority does NOT cascade

In the case where the priority for module scripts does not affect the import priorities, b.js will be transferred at a high priority once it is discovered after a.js has loaded:

  1. 1.jpg is transferred
  2. a.js is transferred
  3. 2.jpg is transferred (already in-flight)
  4. b.js is transferred
  5. a.js executes
  6. Images 3-100 are transferred

It's not perfect, but it gets a.js out of the way of the hero image but still allows it to execute before the other images load.

Priority cascades

In the case where the priority for module scripts does affect the import priorities, b.js will be transferred at a low priority once it is discovered after a.js has loaded. This is after all of the images have already been discovered so it will be queued behind all of them:

  1. 1.jpg is transferred
  2. a.js is transferred
  3. Images 2-100 are transferred
  4. b.js is transferred
  5. a.js executes

This delays the execution of a.js until after everything else on the page has already loaded. That's a pretty big footgun if it wasn't expected.

Bundles

In the case of bundling the modules at build time, b.js would be included in a.js and it would effectively load at the same time as a.js:

  1. 1.jpg is transferred
  2. a.js is transferred (with b.js included)
  3. a.js executes
  4. Images 3-100 are transferred

Thoughts

Not cascading is the closest to "bundled" behavior and basically mimics the behavior of Fonts where late-discovery of resources that are needed by the thing they are loaded by are loaded at a high priority because they are needed now.

The discovery changes a bit when it comes to import maps or preloads so I could see making a case for "fetchpriority=low on a module script where the imports are already in-flight does not modify the priority of the existing requests" so you could get the ordering directly from the import maps or preloads but I don't think it should be the default behavior.

The other option would be to extend loading=lazy or something like that to module scripts of you want a situation where the imports all load at idle time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants