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

Support for webpackPrefetch and webpackPreload #1317

Closed
maclockard opened this issue Dec 6, 2019 · 21 comments
Closed

Support for webpackPrefetch and webpackPreload #1317

maclockard opened this issue Dec 6, 2019 · 21 comments
Labels

Comments

@maclockard
Copy link

This is effectively just #1150 but I'm opening a new issue since I can't comment on closed issues in this repo. I filed webpack/webpack#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
Copy link

👀

@Legends
Copy link

Legends commented May 13, 2020

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
Copy link
Owner

jantimon commented May 14, 2020

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 webpack/webpack#10094

However if you need an intermediate solution please create a plugin for the html-webpack-plugin and share it with the community

@maclockard
Copy link
Author

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
Copy link

Legends commented May 15, 2020

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
Copy link
Owner

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
Copy link

Legends commented May 15, 2020

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
Copy link

Legends commented May 25, 2020

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

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

@jantimon
Copy link
Owner

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
Copy link

Legends commented Jun 9, 2020

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
Copy link

Legends commented Jun 11, 2020

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

@maclockard
Copy link
Author

@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
Copy link
Author

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
Copy link

Legends commented Jun 11, 2020

@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
Copy link

Legends commented Jun 11, 2020

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 ?

@jantimon
Copy link
Owner

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

@sokra
Copy link
Contributor

sokra commented Oct 7, 2020

Here a short behavior clarification:

  • prefetch: should start downloading after all other files have been downloaded
  • preload: should start downloading in parallel to all other files.

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}" />`);
}

@stale stale bot added the wontfix label Jun 11, 2021
@GiancarlosIO
Copy link

👀

@stale stale bot removed the wontfix label Jun 11, 2021
Repository owner deleted a comment from stale bot Jun 14, 2021
@jantimon
Copy link
Owner

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

@stale
Copy link

stale bot commented Apr 16, 2022

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
Copy link

icy0307 commented Mar 20, 2023

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?

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

No branches or pull requests

6 participants