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

Option to disable modulepreload #5120

Closed
4 tasks done
jpvincent opened this issue Sep 28, 2021 · 24 comments · Fixed by #9938
Closed
4 tasks done

Option to disable modulepreload #5120

jpvincent opened this issue Sep 28, 2021 · 24 comments · Fixed by #9938

Comments

@jpvincent
Copy link

Clear and concise description of the problem

Hi
The current version of Chrome is pretty aggressive with the preload and modulepreload directive : it will download the JS first, then get the CSS files. Because CSS files are blocking page rendering, the preload directive will delay rendering.
You can use webpagetest to see the effect
image
The vertical green bar is the moment the page is displayed. Chrome rushes on the preloaded JS files first
then request the CSS files
image

In this test, it needs an additional few hundred milliseconds after the last blocking CSS file to calculate the page layout and displays it. If the JS files were not preloaded, the CSS would be downloaded sooner, and so the page first paint. We use SSR, so the first paint is good enough for the user to see and we can allow the user to see the page without JS for a few seconds.

Safari iOS and Firefox behave less aggressively and will just get the CSS first and the rest later.
image

Suggested solution

I would need to deactivate manually the modulepreload on the JS files, at least as long as Chrome has this behaviour.

Alternative

No response

Additional context

No response

Validations

@jpvincent
Copy link
Author

Re-tested it with Chrome 95 because it announced they changed some heuristic on the preload directive, but the behavious is still the same.
We are losing more than half of a seconde on this page for the first display : being able to prevent the preload really would help.

@spacedawwwg
Copy link

I'm currently having to run a transform on top of the generated HTML to strip out the preload tags for the exact same reason above.

It's not limited to JS either, it's preloading SVG's etc.

As @jpvincent says, I think a config option to simply disable the behaviour would be a welcome addition.

@jpvincent
Copy link
Author

Hi @spacedawwwg : are you striping out the HTML outside of Vite or is it an option I'm not aware of ?

@spacedawwwg
Copy link

Hi @spacedawwwg : are you striping out the HTML outside of Vite or is it an option I'm not aware of ?

Outside of vite - I have a few transform scripts I run to do 'extras' I've not figured in vite yet

@jpvincent
Copy link
Author

Here are more statistics, on a global level this time :

https://almanac.httparchive.org/en/2021/resource-hints#correlation-with-core-web-vitals
The more you preload, the less your performance is good, as measured by the Core Web Vitals from the Chrome Report User eXperience (used by Google Search to rank websites).
image

Please really consider being able to turn off preload of JS in Vite.

@miohtama
Copy link

@jpvincent do you know the historical rationale why <link type="modulepreload"> has been chosen?

@miohtama
Copy link

Tracked down the generation of <link> tags here - if someone wants to explore the code:

// when not inlined, inject <script> for entry and modulepreload its dependencies

@miohtama
Copy link

For understandting the issue more deeply, here is a historical Google Chrome article on modulepreload.

@miohtama
Copy link

Here is my writeup how to transform generated HTML in SvelteKit hooks using cheetah. By changing <link type=modulepreload> to the end of <body> <script> tags I managed to reduce the page loading time significantly (4.0 -> 2.5 to FCP on mobile).

@jpvincent
Copy link
Author

No, I dont have any rationale, my guess being that the team chose this because it is said to be a good practice. But here we need more nuance, as this default does not work for everyone.
Thank for you research in the Vite code and your proposed solution, I hope to test it in a few weeks and see if it fits.

@benmccann
Copy link
Collaborator

The time between the first .js file and the last .css file on https://www.radiofrance.fr/ starting to load is 20ms on a page that takes 2s total. So we're talking about a 1% speedup at the very most. There's probably much bigger wins to be had.

I'm surprised there's 50 JS files being loaded. That 20ms would likely be smaller if there were fewer files. I would generally expect things not to be so granularly chunked, but it's hard to advise on that without access to the source.

There's some weird stuff in these files. E.g. it looks like https://www.radiofrance.fr/client/chunks/index-83a12101.js includes the package.json:

m={"babel-cli":"^6.26.0","babel-core":"^6.1.2","babel-eslint":"^8.0.1","babel-loader":"7.1.5","babel-plugin-transform-object-rest-spread":"^6.26.0","babel-plugin-transform-runtime":"^6.23.0","babel-polyfill":"^6.2.0","babel-preset-env":"^1.6.0","babel-register":"^6.22.0",chai:"^4.1.1",eslint:"^5.4.0","eslint-config-airbnb":"^17.1.0","eslint-loader":"^2.0.0","eslint-plugin-import":"^2.2.0","eslint-plugin-jsx-a11y":"^6.0.2","eslint-plugin-mocha":"^5.0.0","eslint-plugin-react":"^7.11.1",jsdom:"^12.0.0",mocha:"^5.0.5","pre-commit":"^1.2.2",replace:"^1.0.0","semver-regex":"^3.1.0",sinon:"^6.1.5",webpack:"^4.4.0","webpack-cli":"^3.1.0"},_={build:"webpack --display-modules",prepublishOnly:"babel ./src -d ./module && npm run replace",replace:'replace "rfplayer/src" "rfplayer/module" ./module -r --include="*.js"',test:"mocha",lint:"eslint ./src ./test"},g="src/index.js",p="module/index.js",E={type:"git",url:"git@gitlab.dev.dnm.radiofrance.fr/lecteur-commun/rfplayer-plugin-html5-sound-adapter.git"}

As does https://www.radiofrance.fr/client/chunks/index-3715f060.js:

we={"babel-cli":"^6.26.0","babel-core":"^6.1.2","babel-eslint":"^8.0.1","babel-loader":"7.1.4","babel-plugin-rewire":"^1.1.0","babel-plugin-transform-object-rest-spread":"^6.26.0","babel-plugin-transform-runtime":"^6.23.0","babel-polyfill":"^6.2.0","babel-preset-env":"^1.6.0","babel-register":"^6.22.0",chai:"^4.1.1","chai-as-promised":"^7.1.1",eslint:"^4.9.0","eslint-config-airbnb":"^16.1.0","eslint-loader":"^2.0.0","eslint-plugin-import":"^2.2.0","eslint-plugin-jsx-a11y":"^6.0.2","eslint-plugin-mocha":"^5.0.0","eslint-plugin-react":"^7.4.0","event-emitter":"^0.3.5",jsdom:"^11.2.0",mocha:"^5.0.5","pre-commit":"^1.2.2",replace:"^1.0.0",rfplayer:"^3.0.0-alpha.7","semver-regex":"^3.1.1",sinon:"^4.5.0",webpack:"^4.5.0","webpack-cli":"^3.1.2"},ke={build:"webpack --display-modules",prepublishOnly:"babel ./src -d ./module && npm run replace",replace:'replace "rfplayer/src" "rfplayer/module" ./module -r --include="*.js"',test:"mocha",lint:"eslint ./src ./test"},Re="src/index.js",Le={type:"git",url:"git@gitlab.dev.dnm.radiofrance.fr/lecteur-commun/rfplayer-plugin-smarttag.git"},Ne="Radio France",Ce="CECILL-B",$e="https://nexus-production.dnm.radiofrance.fr/repository/npm-group/rfplayer-plugin-smarttag/-/rfplayer-plugin-smarttag-3.1.3.tgz",je="sha512-UT65/9LRJhQfgk65bNR3+acFsOFsZaus8KdFac80WDAnhh7mNt6Bq0saAditYTGs2/ZMH48iE+zgJsn2FFScxw==",Ue="rfplayer-plugin-smarttag@3.1.3";var xe={name:Pe,version:Ie,description:Ae,"rfplayer-plugin":{minimumCoreVersion:"3.0.0"},dependencies:Oe,module:Me,devDependencies:we,scripts:ke,main:Re,repository:Le,author:Ne,license:Ce,"pre-commit":["lint","test"],_resolved:$e,_integrity:je,_from:Ue}

You'd probably get just as big of a win by not including that stuff. I'm also wondering why babel and webpack would be used in a Vite project...

I also see Microsoft copyright notices and am not sure they really need to be included?

/*! *****************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */

And then there's fun stuff like https://api.privacy-center.org/v1/locations/current?fields[]=country_code&fields[]=region_code which takes over 300ms to load a very tiny response of {"country_code":"US","region_code":"CA"}

I'm not sure an option to disable preloading would really be worth the added complexity especially when there's lower hanging fruit...

@miohtama
Copy link

I'm not sure an option to disable preloading would really be worth the added complexity especially when there's lower hanging fruit...

Is the complexity of this issue of:

  • Changing <link type="modulepreload"> to <script defer>
  • Adding a new option for this
  • Adding a new unit test for the new option
  • Documenting the option

If you are open to new patches I am happy to get it done, but if the discussion is going to be stalled whether the issue is real or not, then it is going to be much more nuanced conversation.

@patak-dev
Copy link
Member

We should properly justify adding each new option. Your list isn't complete. The cost of a new feature isn't the time it will take to add it, but the added complexity to the code base and how it will interact later with other features that will impact the long-term maintenance of the project.

@dominikg
Copy link
Contributor

dominikg commented Dec 27, 2021

This one reminded me of an old issue from vite1 days
#881

modulepreload requiring a polyfill in most non-chromium browsers makes it a less than ideal candidate imho. If script type module are correctly defered like the spec suggests that would be a clean alternative, no?

@Brain2000
Copy link

We need to disable it as well, but for different reasons. Our project is enormous and preloading every single module like this is not warranted.

@jpvincent
Copy link
Author

The time between the first .js file and the last .css file on https://www.radiofrance.fr/ starting to load is 20ms on a page that takes 2s total. So we're talking about a 1% speedup at the very most. There's probably much bigger wins to be had.

hi @benmccann . That's HTTP/2 at play here : the 20 ms you are talking about is the moment Chrome is queuing the file (white color). Before that, there is a 100ms of wait. Take a look at the darker green color : that is the actual transit on the network.
I should add those informations : CSS total size is 16 Kb, latency is 70 ms and bandwith 12 Mbp/s. It takes 400 ms to get the last byte of those 16Kb of CSS on a fast network, because the network is busy getting hundreds of Kb of JS + 58 Kb of fonts before (all preloaded).
Here is a link to original test from the screenshot : https://www.webpagetest.org/result/210928_BiDcSQ_200daba0ee17c95a6eacff5d7518536e/5/breakdown/ so we can discuss further.

Delaying CSS delays First Paint, but on Chrome it also delays the start of the download of the images, because they chose to wait for the layout information before starting getting them.

@jpvincent
Copy link
Author

@benmccann : thank you for taking a look at what's inside the JS files or about the latency on the server. The frontend is based on SvelteKit.
However the point here is that whatever is in those JS files, they should not block the rendering at all, respecting the philosophy of defer . The preload directive as currently used by Chrome is de facto a problem. They give no indication of reverting this behaviour to get back to what Safari and Firefox are still doing.
We should be able to opt-out of the preload "optimization" if we see it does not serve us.

@benmccann
Copy link
Collaborator

I'm afraid I have no idea what I'm supposed to be looking at in the link you posted. Is it possible to use Chrome screenshots instead of webpagetest.org screenshots to discuss this?

Before that, there is a 100ms of wait.

I don't see that at all or am unsure what you're referring to. I just went to https://radiofrance.fr/ now. The JS started downloading at 830.35ms

Screenshot from 2021-12-29 08-47-20

The CSS started downloading at 854.03ms

Screenshot from 2021-12-29 08-47-38

That's a difference of less than 25ms.

the network is busy getting hundreds of Kb of JS + 58 Kb of fonts

So you're also suggesting the network is being saturated? That's a different claim than the one made before that CSS files don't start downloading until later. That's easier for me to believe, but there's been no clear evidence of that shared here.

Changing <link type="modulepreload"> to <script defer>
modulepreload requiring a polyfill in most non-chromium browsers makes it a less than ideal candidate imho. If script type module are correctly defered like the spec suggests that would be a clean alternative, no?

You can't just swap one for another. modulepreload only fetches, but doesn't execute the script whereas <script defer> does, so they're not synonyms for each other. What you could do is simply remove the modulepreload tags. This will mean that the JS is only downloaded when encountered. If script A depends on B depends on C depends on D, you will end up needing to make many round trips instead of getting everything up front

We should be able to opt-out of the preload "optimization" if we see it does not serve us.

We try to have best practices enabled by default, as few options as possible, and clear explanations of what options do and when to change them. When considering whether to add a new option it must be clear that it's helpful, that it shouldn't just be made the default for all cases, and if it is to be added then there needs to be clear documentation regarding the tradeoffs of setting it.

@benmccann
Copy link
Collaborator

I found an issue in SvelteKit affecting the download order while investigating this: sveltejs/kit@d138efe

Can we consider this issue as solved now?

@Brain2000
Copy link

Brain2000 commented Dec 30, 2021

I figured out how to disable it, you have to remove __vitePreload( ) in the index.js file. To be safe, I'm doing it dynamically using an IIS managed handler that replaces all javascript files on the fly with the following:

jsFile = Replace(jsFile, "__vitePreload=", "__vitePreload=(e,a)=>{ return e(); },__oldVitePreload=");

and all html files on the fly with the following:

html = Regex.Replace(html, "^\s*<link rel=\"modulepreload\".*?>$", "", RegexOptions.IgnoreCase | RegexOptions.Multiline);

I also set the cache-control header with max-age and immutable, such as this:

context.Response.Headers.Add("Cache-Control", "private, immutable, max-age=604800")

and now my importmap is working, everything is caching properly, and both methods that vite uses to preload are gone.

@Brain2000
Copy link

Brain2000 commented Dec 30, 2021

I found an issue in SvelteKit affecting the download order while investigating this: sveltejs/kit@d138efe

Can we consider this issue as solved now?

It should only be considered resolved when there is way to properly disable both preload features.
You have to remember that <link rel="modulepreload" href="..."> does not work with an importmap [yet], but import...from and import( ) do work with an importmap.

@jpvincent
Copy link
Author

I found an issue in SvelteKit affecting the download order while investigating this: sveltejs/kit@d138efe

Can we consider this issue as solved now?

In theory it's fixing the original problem I had : First Paint being delayed by JS preloads in Chrome.
I'm waiting for a deploy of this patch and test it live on a page with real browsers.
The preloads could still slow down other requests like the main visible image, so having an option to disable preload always seems like a good idea to me, but let's wait for a deploy.

@benmccann
Copy link
Collaborator

It should only be considered resolved when there is way to properly disable both preload features.

As mentioned earlier, a justification would need to be provided for adding a new option. Happy to consider, but we would need a detailed explanation of why it's required.

You have to remember that does not work with an importmap [yet], but import...from and import( ) do work with an importmap

It might be better to file a new issue if this is the desired reason. Almost all the discussion here is about @jpvincent's issue, which is quite different. Please don't assume prior knowledge of importmaps when discussing since it's a new feature I haven't seen before

I'm waiting for a deploy of this patch and test it live on a page with real browsers

As a heads up, a new version of SvelteKit with the patch was released a few hours ago

The preloads could still slow down other requests like the main visible image, so having an option to disable preload always seems like a good idea to me,

Another alternative in this scenario would be to add a preload for the main image appearing higher in the document

@github-actions github-actions bot locked and limited conversation to collaborators Jan 14, 2022
@aleclarson aleclarson reopened this Mar 20, 2022
@patak-dev
Copy link
Member

Closing as @sapphi-red pointed out this issue is a duplicate of #5991, we can keep discussing there

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

Successfully merging a pull request may close this issue.

9 participants