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

Add preload to script and link tags in production builds #3319

Open
muhammadtarek opened this issue Oct 24, 2017 · 29 comments
Open

Add preload to script and link tags in production builds #3319

muhammadtarek opened this issue Oct 24, 2017 · 29 comments

Comments

@muhammadtarek
Copy link

I want to preload styles and main script. But the current config generates the .html without preload support. So is there any way to make that happens?

@maximalism2
Copy link

@muhammadtarek
I have the same issue, and as a temporary solution I've made a little node-script, which runs automatically after main build process finishes.

Here it is:

const fs = require('fs');
const pathToEntry = './build/index.html';
const bundlesRegExp = /\/static\/\w+\/\w+.[a-z0-9]+.\w{2,3}/g;
const splitBy = '</title>';

const builtHTMLContent = fs.readFileSync(pathToEntry).toString();
const links = builtHTMLContent.match(bundlesRegExp);
const parts = builtHTMLContent.split(splitBy);

let fileWithPreload = [
  parts[0],
  splitBy,
];

links.forEach(link => {
  let fileType = 'script';

  if (/\.css$/.test(link)) {
    fileType = 'style';
  }

  fileWithPreload = [
    ...fileWithPreload,
    `<link rel="preload" href="${link}" as="${fileType}">`,
  ];
});

fileWithPreload = [
  ...fileWithPreload,
  parts[1],
];

fs.writeFileSync(pathToEntry, fileWithPreload.join(''));

to run it automatically after each prod build add node ./addPreloadLink.js to npm script build like this:

{
  "scripts": {
    "build":  "react-scripts build && node ./addPreloadLinks.js"
  }
}

Hope it will help you 🙂

@muhammadtarek
Copy link
Author

It works. Thank you @necinc
Closing issue

@gaearon
Copy link
Contributor

gaearon commented Oct 30, 2017

Can you explain why this is useful? The JS/CSS bundle generated by CRA is required for the application to work, and there is nothing to display until it has loaded, so why would preload make any difference?

@muhammadtarek
Copy link
Author

I need to request the JS/CSS bundle as soon as possible which will reduce the loading time that's as far I understand

@maximalism2
Copy link

@muhammadtarek
Actually yes, there is no reason to put preload link in the head of generated index.html, since the size of initial html is so small so reduction of loading time won't be tangible.
It's better to add preload to stream from your SSR.

@muhammadtarek
Copy link
Author

@necinc Thanks, I didn't know that.

@gaearon
Copy link
Contributor

gaearon commented Oct 30, 2017

If I understand it right, "preload" is useful when you don't use a script/link right away but will likely need it in the future. Browser then optimistically starts loading it with a low priority.

This is different from CRA case where you can't possibly render anything without these tags.

It might, however, be useful for codesplit chunks.

@muhammadtarek
Copy link
Author

You're right. I misunderstood it. Thanks for clarification

@samccone
Copy link
Contributor

samccone commented Nov 5, 2017

Hey all, just chiming in here. link rel preload is useful since it enables critical resources to start downloading before the page has finished parsing... As soon as the streaming HTML parser encounters this tag the browser knows to go ahead and start fetching the asset.

Putting link rel preload for critical CSS and JS can increase boot time on the order of 10+%.

Take for instance this page
image

The other script tags that are higher in the DOM can actually cause the critical main script to load later resulting is substandard perf on low quality connections.

One of the talks I have given on this topic:
https://youtu.be/RWLzUnESylc?t=579

--

Let me know if I can be of any help here!

Thanks as always.

@gaearon gaearon reopened this Nov 5, 2017
@gaearon
Copy link
Contributor

gaearon commented Nov 5, 2017

In case of CRA the page is usually tiny (a hundred bytes at most) but I'd be open to supporting this if it's contributed upstream to html-webpack-plugin. Thanks for explaining!

@strunkandwhite
Copy link

Here's what I believe to be another use case:

I have an above-the-fold image that needs to be loaded as quickly as possible. Currently, the browser only knows to fetch the image after the bundle containing the reference to that image has finished loading and parsing. Adding support for rel='preload' would allow me to begin loading that image more or less immediately.

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

@jacklenehan I don't think this is related to this issue? The issue is about generated script tags. I don't see how putting preload on them would make loading an image referenced from the bundle faster.

@strunkandwhite
Copy link

strunkandwhite commented Jan 9, 2018

@gaearon sorry, my comment wasn't clear - I wanted to add rel='preload' to a <link> in the <head> of my page to tell the browser to start loading the relevant image as soon as possible. So e.g.

<head>
...
  <link rel="preload" href="foo.jpg" as="image">
...
</head>

where foo.jpg is an image that I know my bundle is going to ask for as soon as it's loaded and parsed, and will be the first thing the user sees. I suppose it might be possible to accomplish this by sticking foo.jpg in /static and adding <link rel="preload" href="%PUBLIC_URL%/foo.jpg" as="image">, but that feels hacky.

@gaearon
Copy link
Contributor

gaearon commented Jan 9, 2018

What is hacky about this? (Assuming you mean public/)

I think I don't understand what other solution you have in mind.

@oscar-b
Copy link

oscar-b commented Apr 17, 2018

What is hacky about this? (Assuming you mean public/)
I think I don't understand what other solution you have in mind.

You would have to keep your resources in two places, manually adding the preload links when you reference assets in your code using import.

A perf boost of 10% (as @samccone said) with a webpack plugin like https://github.com/GoogleChromeLabs/preload-webpack-plugin should be a no brainer, imo. It should be part of best practices when building a React app.

@stereobooster
Copy link
Contributor

Webpack 4 can do this:

import(/* webpackPrefetch: true */ "DashboardPage")

@MattSidor
Copy link

Another use case: preload font files in <head> before they're requested in the webpack-built CSS.

<link rel="preload" href="/static/media/some-font.4497aa9e.woff2" as="font" type="font/woff2" crossorigin>

I get dinged for not having this in the Chrome Lighthouse audit.

This might be more complicated than preloading .js assets though.

@buildbreakdo
Copy link
Contributor

buildbreakdo commented Aug 1, 2018

Script above creates malformed HTML appending </title><link rel="preload" href="/static/css/main.FOO.css" as="style"><link rel="preload" href="/static/js/main.BAR.js" as="script"> as a sibling to the <html> element. Heads up for anyone who stumbles here.

Hacked a fix together based on what's above..

// postbuild.js
const fs = require('fs');
const pathToEntry = './build/index.html';
const bundlesRegExp = /\/static\/\w+\/\w+.[a-z0-9]+.\w{2,3}/g;

const builtHTMLContent = fs.readFileSync(pathToEntry).toString();
const links = builtHTMLContent.match(bundlesRegExp);

const linkAs = {
  css: 'style',
  js: 'script'
}
const linkPreloads = links.map(link =>
  `<link rel="preload" as="${linkAs[link.split('.').pop()]}" href="${link}">`
).join('');

const htmlWithPreload = builtHTMLContent.replace(
  '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">',
  '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">' + linkPreloads
)

fs.writeFileSync(pathToEntry, htmlWithPreload);
{
  "scripts": {
    "build":  "react-scripts build && node ./postbuild.js"
  }
}

Be careful with how many things you preload. Chrome only makes 6 requests at a time. Great thread on preload from the Chrome Lighthouse audit crowd (+bonus Paul Irish). For anyone interested in a deeper dive on this topic: GoogleChrome/lighthouse#3106

@JCofman
Copy link

JCofman commented Oct 26, 2018

@necinc script has worked well for me 🙏 but since CRA 2.0 I had to rewrite the REGEX to match the new chunks name pattern. I have tried the recommendation which @stereobooster has mentioned but this works only for Webpack >= 4.6

const fs = require("fs");

const pathToEntry = "./build/index.html";
const bundlesRegExp = /\/static\/\w+\/\w+.[a-z0-9]+.[a-z0-9]+.\w{2,3}/g;
const splitBy = "</title>";

const builtHTMLContent = fs.readFileSync(pathToEntry).toString();
const links = builtHTMLContent.match(bundlesRegExp);
const parts = builtHTMLContent.split(splitBy);

let fileWithPreload = [parts[0], splitBy];

links.forEach(link => {
  let fileType = "script";

  if (/\.css$/.test(link)) {
    fileType = "style";
  }
  fileWithPreload = [
    ...fileWithPreload,
    `<link rel="preload" href=".${link}" as="${fileType}">`
  ];
});

fileWithPreload = [...fileWithPreload, parts[1]];

fs.writeFileSync(pathToEntry, fileWithPreload.join(""));

@KatSick
Copy link

KatSick commented Nov 5, 2018

Can I use /* webpackPrefetch: true */ in latest [email protected] ?
It looks like it does not work

@FezVrasta
Copy link
Contributor

It looks like a feature of the HTML webpack plugin that is not yet shipped :-/

@KatSick
Copy link

KatSick commented Nov 9, 2018

It looks like a feature of the HTML webpack plugin that is not yet shipped :-/

aha! many thanks for the answer. where can I track the progress or contribute to this feature?

@FezVrasta
Copy link
Contributor

I'm not sure I read it in the comments of the Medium article that describes the feature :-(

@stale
Copy link

stale bot commented Dec 9, 2018

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.

@stale stale bot added the stale label Dec 9, 2018
@ianschmitz ianschmitz removed the stale label Dec 11, 2018
@ianschmitz
Copy link
Contributor

It looks like we're waiting on jantimon/html-webpack-plugin#1014

@stale
Copy link

stale bot commented Jan 12, 2019

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.

@stale stale bot added the stale label Jan 12, 2019
@FezVrasta
Copy link
Contributor

Bad stale bot, bad bot, don't act this way

@stale
Copy link

stale bot commented Jan 18, 2019

This issue has been automatically closed because it has not had any recent activity. If you have a question or comment, please open a new issue.

@stale stale bot closed this as completed Jan 18, 2019
@FezVrasta
Copy link
Contributor

No recent activity? What the fk

@lock lock bot locked and limited conversation to collaborators Jan 24, 2019
@ianschmitz ianschmitz reopened this Feb 14, 2019
@stale stale bot removed the stale label Feb 14, 2019
@ianschmitz ianschmitz added this to the 2.x milestone Feb 14, 2019
@iansu iansu modified the milestones: 2.x, 3.x Mar 10, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests