jantimon / html-webpack-plugin

Simplifies creation of HTML files to serve your webpack bundles
MIT License
10.71k stars 1.31k forks source link

Support for webpackPrefetch and webpackPreload #1317

Closed maclockard closed 2 years ago

maclockard commented 4 years ago

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:

GiancarlosIO commented 4 years ago

👀

Legends commented 4 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.

jantimon commented 4 years ago

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

maclockard commented 4 years ago

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.

Legends commented 4 years ago

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.

jantimon commented 4 years ago

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?

Legends commented 4 years ago

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...

Legends commented 4 years ago

@jantimon I have a couple of questions regarding this new plugin.

My static app uses multiple html-webpack-plugins. My entry script index imports theapp.jsmodule, 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:

image

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 headtag.

Hm. Personally I would remove the added resource references (link, script, script) from the headand put them before body-closing-tag and in the headI 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?

jantimon commented 4 years ago

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?

Legends commented 4 years ago

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.

Legends commented 4 years ago

@maclockard I am not sure if I understand, but webpack adds prefetch/preload tags to the head section? See my previous comment.

maclockard commented 4 years ago

@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.

maclockard commented 4 years ago

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

Legends commented 4 years ago

@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.

Legends commented 4 years ago

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 prefetchand 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 compileand one during runtime.

1.) Now I could be satisfied with webpack adding prefetchstatements during runtimeand omit adding them in my plugin or 2.) somehow tell webpack not to inject the prefetchstatements into the headtag, because my plugin will already do it during compiletime. 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 ?

jantimon commented 4 years ago

I guess way 1 is simpler so probably that's a good way to go 👍

sokra commented 4 years ago

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}" />`);
}
GiancarlosIO commented 3 years ago

👀

jantimon commented 3 years ago

Would anyone be interested to turn the code above into a PR?

stale[bot] commented 2 years ago

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.

icy0307 commented 1 year ago

This issue should not be closed. I create a pr with test in 'html-webpack-inject-preload' repo but I think it is more appropriate to add this feature in html-webpack-plugin as a default behavior. Could you look into it @jantimon and share some thoughts?