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

Inline styles.css in <head> #145

Closed
KyleAMathews opened this issue Feb 25, 2016 · 15 comments
Closed

Inline styles.css in <head> #145

KyleAMathews opened this issue Feb 25, 2016 · 15 comments
Assignees

Comments

@KyleAMathews
Copy link
Contributor

No description provided.

@KyleAMathews KyleAMathews changed the title Inline smaller CSS files FEATURE: Inline smaller CSS files in <head> if lower than x KBs Feb 26, 2016
@KyleAMathews KyleAMathews changed the title FEATURE: Inline smaller CSS files in <head> if lower than x KBs FEATURE: Inline styles.css in <head> if lower than x KBs Feb 26, 2016
@KyleAMathews
Copy link
Contributor Author

Actually... I have no idea if this is possible to do automatically. If someone knows, please do share.

@KyleAMathews
Copy link
Contributor Author

Possibly could use the val-loader. Output the styles.css file then sync load the file when generating the and inline the css if < 10 kb or just add a link to the external file.

https://github.com/webpack/val-loader

@KyleAMathews
Copy link
Contributor Author

Actually easier than that. Just use the raw-loader to load the styles.css file. If it's over 10kb, include a link, if it's below 10kb, inline it. This could actually be turned into a react component pretty easily something like <CSSInliner path={"styles.css"} />

@gesposito
Copy link
Contributor

Loaders usually accept the limit parameter
https://github.com/gatsbyjs/gatsby/blob/master/lib/utils/webpack.config.js#L226

@KyleAMathews
Copy link
Contributor Author

Interestingly Google AMP's project says all css styles should be inlined in the and not be external files. Regardless of the size (though they say limit styles to 50kb)... https://www.smashingmagazine.com/2016/02/everything-about-google-accelerated-mobile-pages/

So perhaps this should be turned on by default. Webpack extracts and minifies CSS and then we inline it in the <head>

@KyleAMathews
Copy link
Contributor Author

So played around with this a bit today. To make this work, we'll need to have a special styles build step it seems so that the extracted/minified styles.css exists for the html.js to require it.

@KyleAMathews
Copy link
Contributor Author

Which is a bit weird perhaps + extra complexity but I think it's totally worth it. Inlining CSS in head in my testing improves time to first paint by ~300ms on even a very simple page by avoiding the extra request.

@KyleAMathews KyleAMathews changed the title FEATURE: Inline styles.css in <head> if lower than x KBs FEATURE: Inline styles.css in <head> Apr 4, 2016
@KyleAMathews KyleAMathews changed the title FEATURE: Inline styles.css in <head> Inline styles.css in <head> Apr 4, 2016
@KyleAMathews KyleAMathews self-assigned this Apr 4, 2016
@kylegach
Copy link
Contributor

kylegach commented May 4, 2016

I was able to get purifycss-webpack-plugin working with this gatsby-node.js:

var ExtractTextPlugin = require("extract-text-webpack-plugin");
var Purify = require("purifycss-webpack-plugin");

exports.modifyWebpackConfig = function(config, env) {
  if(env === 'production') {
    config.removeLoader('css');
    config.loader('css', function(cfg) {
      cfg.test = /\.css$/;
      cfg.loader = ExtractTextPlugin.extract(['css', 'postcss']);
      return cfg
    })
    config.plugin('extract-css',
                  ExtractTextPlugin,
                  ["styles.css", { allChunks: true }]);
    config.plugin('purify-css',
                  Purify,
                  [{
                    basePath: __dirname,
                    resolveExtensions: ['.js, .html'],
                    paths: [
                      'html.js',
                      'components/**/*.js',
                      'pages/**/{*.md,*.js,*.html}',
                      'wrappers/*.js'
                    ],
                    purifyOptions: {
                      info: true,
                      rejected: true,
                      whitelist: [
                        '*markdown*',
                        '*footnotes*',
                        '*webfonts-loaded*',
                        '*is-reverse*'
                      ]
                    }
                  }]);
  }

  return config;
}

(Explanation of Purify’s options, here.)

As you can see, that implementation is quite fragile, requiring both specifically-defined file globs for the paths to scan and carefully selected whitelisted selectors (the webfonts-loaded and is-reverse classes are specific to my project, but everyone is likely to need at least markdown).

The tool, PurifyCSS, also doesn't seem to work all that well. I have quite a few selectors in my styles (cssstats), and I expected at least of third of those to be un-used. But it only reduced the un-minified filesize by 9.5%, and here’s the full list of removed selectors:

menu
canvas
progress
progress
strong
dfn
mark
sub
sup
sub
sup
kbd
samp
hr
select
textarea
optgroup
select
fieldset
legend
textarea
ol
ol
ol>li
ol>li:before
ol
ol>li:before
.fontFamily-inherit
.textDecoration-none
.underline
.textLeft
.textJustify
.nowrap
.breakWord
.truncate
.listStyle-none
.tableCell
.overflow-scroll
.overflowx-hidden
.overflowx-scroll
.overflowx-auto
.overflowy-hidden
.overflowy-scroll
.overflowy-auto
.fit
.flex-columnReverse
.items-stretch
.self-start
.self-end
.self-center
.self-baseline
.self-stretch
.justify-around
.content-around
.content-stretch
.rounded
.rounded-top
.rounded-right
.rounded-bottom
.rounded-left
.not-rounded
.bgLighter-1
.bgLighter-2
.bgLighter-3
.bgLighter-4

I think this is largely due to my use of escaped characters in my classes, e.g. flex@md (though there’s evidence that the maintainer addressed this already) and my use of quite a few classes in total. You can see that I have many, many classes using escaped characters, yet none were removed. I know for a fact I haven’t yet used any of the ...@lg classes, so at the very least, all of those should have been removed.

Worse, there are false positives in that list. My .md content uses ordered lists, but the tool doesn’t pick up on it because it scans the files before conversion to html.

It’s that very last point that makes me think using a tool like PurifyCSS isn’t workable before the pages are built.

And, the plugin can only output a file, so it doesn’t actually address the key aspect of this issue: inline the styles when they’re small.

@KyleAMathews
Copy link
Contributor Author

KyleAMathews commented May 8, 2016

With #253 this is about ready to go once the new release is done.

Checklist for finishing this up:

  • Add new helper in gatsby-helpers for html.js that inlines css using the Webpack raw-loader.
  • Convert starters to inline css
  • Document

And some testing I just did with #253 with the blog starter. Switching to inlined css dropped the Speed Index score by 1/2(!!!) on both 3G and cable. Which is even a bigger drop than I expected.

3G:
not-inlined: http://www.webpagetest.org/result/160508_FF_8WW/
inlined: http://www.webpagetest.org/result/160508_95_8YQ/

Cable:
not-inlined: http://www.webpagetest.org/result/160508_T2_8ZK/
inlined: http://www.webpagetest.org/result/160508_C5_8ZQ/

It's crazy but with inlining CSS, the site loads just as fast on 3G as not-inlined does on cable. Nuts. Also on cable the inlined css site is 89% visually complete in 0.5 secs. That's a fast website.

This also scores us 100/100 on PageSpeed https://developers.google.com/speed/pagespeed/insights/?url=http%3A%2F%2F572ec371d6865d789f20e371.peddler-tapir-41575.netlify.com%2F&tab=mobile

/cc @scottnonnenberg @kylegach

@KyleAMathews
Copy link
Contributor Author

Tested this on my website relaterocket.co tonight for a more real-world test as this site uses https + custom fonts from Typekit. Redeploying with inlined css dropped the first-load PageSpeed from ~1600 to ~1000. Re-running the test multiple times and from multiple locations I saw significant variation from ~700-1400 but the majority at around 1000. Also re-running the same test seemed to speed up the tests. Perhaps the browser at the location does cache some things.

Before: http://www.webpagetest.org/result/160511_SG_A5T/
After: http://www.webpagetest.org/result/160511_5G_Q2J/

@KyleAMathews
Copy link
Contributor Author

So I ended up not adding the helper as well... I couldn't make it work. Webpack can still be incredibly confusing sometimes :-) I put in the helper <style dangerouslySetInnerHTML={{ __html: require('!raw!public/styles.css') }} /> much like what I ended up adding to the starters directly but it'd just complain that it couldn't find the module. I thought with our modulesDirectories as they are that'd work... anyone else want to take a crack at making a simple CSSInline component which inserts public/styles.css in the head?

@KyleAMathews
Copy link
Contributor Author

But ignoring that, things are looking great! This is a fairly significant performance win. Next big win will be making it simpler to inline images in css.

@KyleAMathews
Copy link
Contributor Author

Oh and service workers of course 💥

@ben-hamelin
Copy link

ben-hamelin commented Apr 17, 2020

Just wanted to drop a note here, just needed some quick css in the head. Used:

let css = <style dangerouslySetInnerHTML={{ __html: require('!raw-loader!./inline-styles.css') }} />

  return (
    <html {...props.htmlAttributes}>
      <head>
        <meta charSet="utf-8" />
        <meta httpEquiv="x-ua-compatible" content="ie=edge" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1, shrink-to-fit=no"
        />
        {props.headComponents}
        **{css}**

where inline-styles.css is in /src/ next to html.js

@scottnonnenberg
Copy link
Contributor

scottnonnenberg commented Apr 17, 2020 via email

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

No branches or pull requests

5 participants