Closed maclockard closed 2 years ago
👀
This should be part of html-webpack-plugin
itself, with the option for disabling it, as jantimon has mentioned, webpack api access for this feature is too slow.
As I have seen, there is stale PR for this. Did someone try the PR?
But it should be included instead of being ignored. Just add an option for disabling it and print out a warning message during compilation ? The warning should also be documented in the docs.
This feature can later be adapted when there is quicker api access available.
The problem is that this pr would slow down html-webpack-plugin a lot for all users.
That's why I would prefer to wait for https://github.com/webpack/webpack/issues/10094
However if you need an intermediate solution please create a plugin for the html-webpack-plugin and share it with the community
An opt-in option rather than an opt-out option might be a good middle ground. I suspect it might take a while for webpack/webpack#10094 to be worked on. Unfortunately, I don't know the webpack code base well enough to take a crack at it myself. Since html-webpack-plugin is currently the defacto standard for embedding webpack bundles in html, that issue blocks a lot of users from taking full advantage of preload/prefetch.
Disabled by default
is how I would go, later on, when webpack provides a fast api access, we just change the implementation and make it Enabled by default
.
But jantimon is the plugin owner, so probably he knows best.
I really understand that you want me to add this asap for free but unfortunately right now my time is limited 😞.
Please create a plugin so we can experiment with the intermediate solution. We can merge it into the html-webpack-plugin once all problems are sorted out.
What do you think?
Yes, time pressure is currently an issue. @loveky Perhaps we can extract the code for the plugin from an already existing PR ?
What do you think? I didn't take a deeper look at it...
@jantimon I have a couple of questions regarding this new plugin.
My static app uses multiple html-webpack-plugin
s. My entry script index
imports theapp.js
module, in which I have put the following two imports:
import(/* webpackPreload: true */ "../sass/scrollbars");
import(/* webpackPrefetch: true */ "gsap");
during the webpack build this results in:
and renders as follows:
<link rel="stylesheet" type="text/css" href="2.2.css"> // should be preloaded
<script charset="utf-8" src="chunkFilename.2.bundle.js"></script> // should be preloaded
<script charset="utf-8" src="chunkFilename.1.bundle.js"></script> // <-- should be prefetched
</head>
What should the prefetch-preload-plugin
do now in this example above?
Put the following resource hints above the three statements?
<link rel="preload" as="stylesheet" href="2.2.css">
<link rel="preload" as="script" href="chunkFilename.2.bundle.js">
<link rel="prefetch" href="chunkFilename.1bundle.js">
Somehow it does not make sense, to put the resource hints right before where the resources get requested. But the html-webpack-plugin
has put the resource references which should be preloaded/prefetched
right into the head
tag.
Hm. Personally I would remove the added resource references (link, script, script) from the head
and put them before body-closing-tag
and in the head
I would put:
<link onload="this.rel='stylesheet'" rel="preload" as="stylesheet" href="2.2.css">
<link rel="preload" as="script" href="chunkFilename.2.bundle.js">
<link rel="prefetch" href="chunkFilename.1bundle.js">
Can you shed some light on this?
You can try the defer loading strategy:
new HtmlWebpackPlugin({
scriptLoading: 'defer'
})
In that configuration all scripts are added into the head of the document and there is probably no performance gain for preload.
Wouldn’t it make more sense to load only ressources which are lazy loaded?
Perhaps someone might find this interesting, posting it here:
Actually, this all works without using a plugin, for example webpackPrefetch
inside an entry module has just to be wrapped:
setTimeout(() => {
import(/* webpackPrefetch: true */"./a.js").then((res) => { alert(res) });
}, 0);
or:
document.addEventListener("DOMContentLoaded", (e) => {
const btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
import(/* webpackPrefetch: true */"./a.js").then((res) => { alert(res) });
});
})
Now I get the <link rel="prefetch" src="xyz.js"..../>
in my head tag!
If you instead call it directly in your entry script, like:
import(/* webpackPrefetch: true */"./a.js")
this will add a normal script tag tom your head and do a regular fetch without prefetching.
@maclockard I am not sure if I understand, but webpack adds prefetch/preload tags to the head section? See my previous comment.
@Legends Yeah, webpack itself has supported prefetch/preload for some time now, however, it can only add the script tags for a secondary chunk group to the HTML at runtime. Webpack alone can't add script tags to HTML at build time, instead, you need a plugin like html-webpack-plugin to add the needed script tags to the HTML when building the bundle.
A consequence of this is that before the webpack runtime code can add the script tags for the secondary chunk group, the initial chunk group must first load. This is particularly problematic in the case of wanting to use preload for the secondary chunk group.
This is significant in the case of preload where you want the secondary chunk group to be loaded in parallel with the initial chunk group. Needing to wait for the initial chunk group to fully load defeats the purpose of preloading.
Sorry I just saw some more of your previous comments, so I think you are probably aware of this already. leaving it up though in case it helps someone else understand the problem.
Interesting that html-webpack-plugin correctly adds the tags if inside of a callback. @jantimon is that expected behavior? I would be wary of relying on it if it was not
@maclockard Ah, I got my prefetch-preload-plugin in my webconfig, that's why I had them after compile. Yes, webpack will add them during runtime, and yes this is a problem especially for preloaded
assets.
Sorry for the noise.
I've create a very small test repo where I prefetch/preload
js files.
Everything webpack does in this respect is add link rel='prefetch'
during runtime.
It's not adding link rel='preload'
for /* webpackPreload:true */
requests, it wouldn't make sense anyway.
So now I have my little plugin (not part of the repo above) which adds prefetch
and preload
links into the head tag.
This means I have prefetch
statements already available during compile time, but webpack will still add them during runtime
, means I have two prefetch
statements injected for each prefetch request
, one during compile
and one during runtime
.
1.) Now I could be satisfied with webpack
adding prefetch
statements during runtime
and omit adding them in my plugin
or
2.) somehow tell webpack not to inject the prefetch
statements into the head
tag, because my plugin will already do it during compile
time. This could perhaps be smarter if this is a larger prefetch
.
@maclockard , @jantimon
What do you think?
If 2.) is the way to go, is there a way to tell webpack not to inject the prefetches
?
I guess way 1
is simpler so probably that's a good way to go 👍
Here a short behavior clarification:
There is no need to html-webpack-plugin to add prefetch tags. webpack already adds at runtime and that's not too late as they are intended to download after the other files.
Preload can't be added by webpack when it has a chance to run code, it's already too late. So preload need to be added to the HTML.
Also note that many users use preload incorrectly when they actually should use prefetch. When using preload you must use the script (call import()
) within a few seconds. So you must call import()
on module evaluation or after a short timeout. Afaik Chrome even prints a warning when not using the preloaded script within 3 seconds. Using preload slows down the other files too, as it downloads in parallel. So only use preload when the imported module is mandatory for correct function of the app and the app is useless without it.
To get the preloaded files without the Stats you can use this:
entrypoint // is the Entrypoint which should be rendered.
const preloaded = entrypoint.getChildrenByOrders(moduleGraph, chunkGraph).preload; // is ChunkGroup[] | undefined
if (!preloaded) return;
const chunks = new Set();
for (const group of preloaded) { // the order of preloaded is relevant
for (const chunk of group.chunks) chunks.add(chunk);
}
const files = new Set()
for (const chunk of chunks) {
for (const file of chunk.files) files.add(file);
}
for (const file of files) {
add(`<link rel="preload" href="${publicPath}${file}" />`);
}
👀
Would anyone be interested to turn the code above into a PR?
This issue had no activity for at least half a year. It's subject to automatic issue closing if there is no activity in the next 15 days.
This is effectively just https://github.com/jantimon/html-webpack-plugin/issues/1150 but I'm opening a new issue since I can't comment on closed issues in this repo. I filed https://github.com/webpack/webpack/issues/10094 which I understand to be a blocker for working on this.
Hopefully, folks wanting this feature will find this issue first and understand the current blockers.
Past issues: