Code Monkey home page Code Monkey logo

critters's Introduction

critters

Critters

Critters is a plugin that inlines your app's critical CSS and lazy-loads the rest.

critters npm

It's a little different from other options, because it doesn't use a headless browser to render content. This tradeoff allows Critters to be very fast and lightweight. It also means Critters inlines all CSS rules used by your document, rather than only those needed for above-the-fold content. For alternatives, see Similar Libraries.

Critters' design makes it a good fit when inlining critical CSS for prerendered/SSR'd Single Page Applications. It was developed to be an excellent compliment to prerender-loader, combining to dramatically improve first paint time for most Single Page Applications.

Features

  • Fast - no browser, few dependencies
  • Integrates with Webpack critters-webpack-plugin
  • Supports preloading and/or inlining critical fonts
  • Prunes unused CSS keyframes and media queries
  • Removes inlined CSS rules from lazy-loaded stylesheets

Installation

First, install Critters as a development dependency:

npm i -D critters

or

yarn add -D critters

Simple Example

import Critters from 'critters';

const critters = new Critters({
  // optional configuration (see below)
});

const html = `
  <style>
    .red { color: red }
    .blue { color: blue }
  </style>
  <div class="blue">I'm Blue</div>
`;

const inlined = await critters.process(html);

console.log(inlined);
// "<style>.blue{color:blue}</style><div class=\"blue\">I'm Blue</div>"

Usage with webpack

Critters is also available as a Webpack plugin called critters-webpack-plugin. npm

The Webpack plugin supports the same configuration options as the main critters package:

// webpack.config.js
+const Critters = require('critters-webpack-plugin');

module.exports = {
  plugins: [
+    new Critters({
+      // optional configuration
+      preload: 'swap',
+      includeSelectors: [/^\.btn/, '.banner'],
+    })
  ]
}

That's it! The resultant html will have its critical CSS inlined and the stylesheets lazy-loaded.

Usage

Critters

All optional. Pass them to new Critters({ ... }).

Parameters

  • options

Properties

  • path String Base path location of the CSS files (default: '')
  • publicPath String Public path of the CSS resources. This prefix is removed from the href (default: '')
  • external Boolean Inline styles from external stylesheets (default: true)
  • inlineThreshold Number Inline external stylesheets smaller than a given size (default: 0)
  • minimumExternalSize Number If the non-critical external stylesheet would be below this size, just inline it (default: 0)
  • pruneSource Boolean Remove inlined rules from the external stylesheet (default: false)
  • mergeStylesheets Boolean Merged inlined stylesheets into a single <style> tag (default: true)
  • additionalStylesheets Array<String> Glob for matching other stylesheets to be used while looking for critical CSS.
  • reduceInlineStyles Boolean Option indicates if inline styles should be evaluated for critical CSS. By default inline style tags will be evaluated and rewritten to only contain critical CSS. Set it to false to skip processing inline styles. (default: true)
  • preload String Which preload strategy to use
  • noscriptFallback Boolean Add <noscript> fallback to JS-based strategies
  • inlineFonts Boolean Inline critical font-face rules (default: false)
  • preloadFonts Boolean Preloads critical fonts (default: true)
  • fonts Boolean Shorthand for setting inlineFonts + preloadFonts* Values:
    • true to inline critical font-face rules and preload the fonts
    • false to don't inline any font-face rules and don't preload fonts
  • keyframes String Controls which keyframes rules are inlined.* Values:
    • "critical": (default) inline keyframes rules used by the critical CSS
    • "all" inline all keyframes rules
    • "none" remove all keyframes rules
  • compress Boolean Compress resulting critical CSS (default: true)
  • logLevel String Controls log level of the plugin (default: "info")
  • logger object Provide a custom logger interface logger
  • includeSelectors RegExp | String Provide a list of selectors that should be included in the critical CSS. Accepts both RegExp and string.

Include/exclude rules

We can include or exclude rules to be part of critical CSS by adding comments in the CSS

Single line comments to include/exclude the next CSS rule

/* critters:exclude */
.selector1 {
  /* this rule will be excluded from critical CSS */
}

.selector2 {
  /* this will be evaluated normally */
}

/* critters:include */
.selector3 {
  /* this rule will be included in the critical CSS */
}

.selector4 {
  /* this will be evaluated normally */
}

Including/Excluding multiple rules by adding start and end markers

/* critters:exclude start */

.selector1 {
  /* this rule will be excluded from critical CSS */
}

.selector2 {
  /* this rule will be excluded from critical CSS */
}

/* critters:exclude end */
/* critters:include start */

.selector3 {
  /* this rule will be included in the critical CSS */
}

.selector4 {
  /* this rule will be included in the critical CSS */
}

/* critters:include end */

Critters container

By default Critters evaluates the CSS against the entire input HTML. Critters evaluates the Critical CSS by reconstructing the entire DOM and evaluating the CSS selectors to find matching nodes. Usually this works well as Critters is lightweight and fast.

For some cases, the input HTML can be very large or deeply nested which makes the reconstructed DOM much larger, which in turn can slow down the critical CSS generation. Critters is not aware of viewport size and what specific nodes are above the fold since there is not a headless browser involved.

To overcome this issue Critters makes use of Critters containers.

A Critters container mimics the viewport and can be enabled by adding data-critters-container into the top level container thats contains the HTML elements above the fold.

You can estimate the contents of your viewport roughly and add a <div data-critters-container > around the contents.

<html>
  <body>
    <div class="container">
      <div data-critters-container>
        /* HTML inside this container are used to evaluate critical CSS */
      </div>
      /* HTML is ignored when evaluating critical CSS */
    </div>
    <footer></footer>
  </body>
</html>

Note: This is an easy way to improve the performance of Critters

Logger

Custom logger interface:

Type: object

Properties

  • trace function (String) Prints a trace message
  • debug function (String) Prints a debug message
  • info function (String) Prints an information message
  • warn function (String) Prints a warning message
  • error function (String) Prints an error message

LogLevel

Controls log level of the plugin. Specifies the level the logger should use. A logger will not produce output for any log level beneath the specified level. Available levels and order are:

  • "info" (default)
  • "warn"
  • "error"
  • "trace"
  • "debug"
  • "silent"

Type: ("info" | "warn" | "error" | "trace" | "debug" | "silent")

PreloadStrategy

The mechanism to use for lazy-loading stylesheets.

Note: JS indicates a strategy requiring JavaScript (falls back to <noscript> unless disabled).

  • default: Move stylesheet links to the end of the document and insert preload meta tags in their place.
  • "body": Move all external stylesheet links to the end of the document.
  • "media": Load stylesheets asynchronously by adding media="not x" and removing once loaded. JS
  • "swap": Convert stylesheet links to preloads that swap to rel="stylesheet" once loaded (details). JS
  • "swap-high": Use <link rel="alternate stylesheet preload"> and swap to rel="stylesheet" once loaded (details). JS
  • "js": Inject an asynchronous CSS loader similar to LoadCSS and use it to load stylesheets. JS
  • "js-lazy": Like "js", but the stylesheet is disabled until fully loaded.
  • false: Disables adding preload tags.

Type: (default | "body" | "media" | "swap" | "swap-high" | "js" | "js-lazy")

Similar Libraries

There are a number of other libraries that can inline Critical CSS, each with a slightly different approach. Here are a few great options:

License

Apache 2.0

This is not an official Google product.

critters's People

Contributors

0xflotus avatar alan-agius4 avatar ashsearle avatar developit avatar dmnsgn avatar ezekielchentnik avatar gribnoysup avatar janicklas-ralph avatar jeanmeche avatar jeripeiersbb avatar jessicasachs avatar josephperrott avatar kenchandev avatar kurtextrem avatar merceyz avatar prateekbh avatar rschristian avatar stereobooster avatar tomrav avatar yun77op avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

critters's Issues

Error: The string "", is not a valid CSS selector

Hi,
I am using the CrittersPlugin with the OptimizeCssAssetsPlugin. When I add this the fallowing css

* {
    box-sizing: border-box;
}

*::before,
*::after {
    box-sizing: inherit;
}

it gets minified to:

*{box-sizing:border-box}:after,:before{box-sizing:inherit}

Notice it is now missing the * selector and it only uses :after and :before.

If I remove the css rules for *::before and *::after or if I CrittersPlugin from the plugins array it works fine, but when I include them I get the fallowing error:

ERROR in   Error: The string "", is not a valid CSS selector

  - nwmatcher.js:827 emit
    [webpack-tutorial-boilerplate]/[nwmatcher]/src/nwmatcher.js:827:37

  - nwmatcher.js:1529 select
    [webpack-tutorial-boilerplate]/[nwmatcher]/src/nwmatcher.js:1529:11

  - nwmatcher.js:1451 Object.first
    [webpack-tutorial-boilerplate]/[nwmatcher]/src/nwmatcher.js:1451:14

  - critters.js:117 Node.querySelector
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:117:28

  - critters.js:359
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:359:37

  - Array.filter

  - critters.js:357
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:357:49

  - critters.js:166
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:166:16

  - Array.filter

  - critters.js:162 walkStyleRules
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:162:29

  - critters.js:161 walkStyleRules
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:161:18

  - critters.js:355 Critters.<anonymous>
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:355:9

  - new Promise

  - critters.js:345 Critters.processStyle
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:345:12

  - critters.js:254
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:254:76

  - Array.map

  - critters.js:254 Critters.$If_2
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:254:39

  - critters.js:244 Critters.<anonymous>
    [webpack-tutorial-boilerplate]/[critters-webpack-plugin]/dist/critters.js:244:34

Here is the dependency liist

    "autoprefixer": "^9.0.1",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-preset-env": "^1.7.0",
    "clean-webpack-plugin": "^0.1.19",
    "critters-webpack-plugin": "^1.1.0",
    "css-loader": "^1.0.0",
    "file-loader": "^1.1.11",
    "html-loader": "^0.5.5",
    "html-webpack-inline-svg-plugin": "^1.2.4",
    "html-webpack-plugin": "^3.2.0",
    "mini-css-extract-plugin": "^0.4.1",
    "node-sass": "^4.9.2",
    "optimize-css-assets-webpack-plugin": "^4.0.3",
    "postcss-loader": "^2.1.6",
    "purgecss-webpack-plugin": "^1.2.0",
    "sass-loader": "^7.0.3",
    "style-loader": "^0.21.0",
    "uglifyjs-webpack-plugin": "^1.2.7",
    "url-loader": "^1.0.1",
    "webpack": "^4.16.2",
    "webpack-cli": "^3.1.0",
    "webpack-merge": "^4.1.3"

Fails to locate css for html files in subdirectories

The html file generated is in a subdirectory of the main output directory (/project-root/dist/):
/project-root/dist/imprint/index.html

The css is located in the output directory:
/project-root/dist/styles.css

So the html generated by webpack references relatively:
<link href="../styles.css" rel="stylesheet">

Critters resolves this as follows:
filename = path.resolve(outputPath, href.replace(/^\//, ""));

Leading to path.resolve(/project-root/dist/../styles.css); which generates the non-existant /project-root/styles.css

This is broken, because it tries to resolve relatively to the main output directory and not the real location of the html-file.

problem with Angular !!

i'm trying to use this plugin with Angular, it works but i'm getting an error
Uncaught Error: In this configuration Angular requires Zone.js
and because of this error there is some features not working anymore.

and this is my code

new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/index.html',
      chunksSortMode: 'none',
    }),
    new Critters({
      pruneSource: false
    })

Does anyone have an example working with Angular ?
Thanks

Unknown pseudo-class selector and Critters broke hash/nonce value

Hi,

When i process webpack build with critters, i have 1 issue and 1 bug with another plugin :

Issue :

Critters do not include this pseudo-class selector

  ::-webkit-scrollbar-thumb -> unknown pseudo-class selector '::-webkit-scrollbar-thumb'

Bug :

I use CspHtmlWebpackPlugin and some nonce or hash added (like with webpack-subresource-integrity). Even if i put the right order for the plugins like above, the integrity fail and assets fails to download.

{
...
    new Critters({
      inlineFonts: true,
      preloadFonts: true,
      mergeStylesheets: true,
      keyframes: 'all',
      preload: 'swap',
      compress: true,
      logLevel: 'info'
    }),
    new CspHtmlWebpackPlugin(),
...
}

Seams like Critters is running after Webpack and doesn't use hooks inside a particular process build or something.. And Critters modifying JS / CSS content without re-running algorythm for new hash and nonce value previously injected in the html...
Am i clear ?

How can i get around this ?

Versions:
Webpack 4.29.6
Critters 2.3.0

Thanks

Can this work with php/Wordpress themes?

Sorry to ask what may be a fairly obvious question but I'm a designer learning to code and so maybe this is a dumb question.

I'm rebuilding a Wordpress theme and I want to use webpack to bundle my assets. So far that's all worked great. BUT, I was wondering if it would be feasible to use critters in this task? Normally with WP themes I have to do all my critical css inline manually and it's a huge pain the neck. I tried a first pass installation of Critters into my current theme's webpack config but when running npm build I get the "no html element detected" response, which makes sense because the html is being created via php on the server. But that doesn't stop other JS inclusions that pull html etc from other pages (such as swup or barbajs) from accessing the html so I'm curious if maybe there's something I'm missing in my configuration?

Any thoughts?

Cannot read property 'htmlWebpackPluginAfterHtmlProcessing' of undefined

Hey :)

Thanks for the plugin, however I would love to know why I have some troubles getting it to work.
I'm using Laravel Mix 3.0, but I get the error:

/Users/shadrix/Dropbox/Business/insta.food/Website_W_Laravel/instafood/node_modules/critters-webpack-plugin/dist/critters.js:140
        if (compilation.hooks.htmlWebpackPluginAfterHtmlProcessing) {
                              ^

TypeError: Cannot read property 'htmlWebpackPluginAfterHtmlProcessing' of undefined
    at Compiler.<anonymous> (/Users/shadrix/Dropbox/Business/insta.food/Website_W_Laravel/instafood/node_modules/critters-webpack-plugin/dist/critters.js:140:31)
    at Compiler.applyPlugins (/Users/shadrix/Dropbox/Business/insta.food/Website_W_Laravel/instafood/node_modules/tapable/lib/Tapable.js:61:14)

here are my settings

let mix = require('laravel-mix');

const webpack = require('webpack');

//https://github.com/ankurk91/laravel-mix-auto-extract
require('laravel-mix-auto-extract');

//https://github.com/GoogleChromeLabs/critters
const Critters = require('critters-webpack-plugin');

mix.js('resources/js/app.js', 'public/js')
	.js('resources/js/canvas.js', 'public/js')
	.js('resources/js/lib/moment.js', 'public/js') //to activate moment
	.sass('resources/sass/app.scss', 'public/css')
	.webpackConfig({
		plugins: [
			// reduce bundle size by ignoring moment js local files
			new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
			//ignores chinese language for the calendar
			new webpack.NormalModuleReplacementPlugin(/element-ui[\/\\]lib[\/\\]locale[\/\\]lang[\/\\]zh-CN/, 'element-ui/lib/locale/lang/en'),
			//critical css
			new Critters({
				preload: 'swap',
				preloadFonts: true
			})
		],
		//https://github.com/JeffreyWay/laravel-mix/issues/1249#issuecomment-335112415
		output: {
			publicPath: '/',
			chunkFilename: 'js/[name].[chunkhash].js',
		},
	})
	.autoExtract(); //extract all vendors

Thank you!

unknown pseudo-class selector :-webkit-autofill

Hi,
I have a css in my project that looks like this:

.q-field__input:-webkit-autofill+.q-field__label,[dir] .q-field .q-field__native:-webkit-autofill+.q-field__label {
    transform:translateY(-40%) scale(0.75)
}

When I tried to build, critters seem to be unable to understand the selector, giving the error below

「Critters」: 4 rules skipped due to selector errors:
  .q-field .q-field__input:-webkit-autofill+.q-field__label -> unknown pseudo-class selector ':-webkit-autofill+*'
  .q-field .q-field__native:-webkit-autofill+.q-field__label -> unknown pseudo-class selector ':-webkit-autofill+*'
  .q-field--dense .q-field__input:-webkit-autofill+.q-field__label -> unknown pseudo-class selector ':-webkit-autofill+*'
  .q-field--dense .q-field__native:-webkit-autofill+.q-field__label -> unknown pseudo-class selector ':-webkit-autofill+*'

Is there anything I have done wrongly?

unmatched pseudo-class :is

Currently using critters in nextjs looks like it can't handle psuedoclasses.

Error -
3 rules skipped due to selector errors:
article :is(h1,h2,h3,h4,h5,h6) -> unmatched pseudo-class :is
.dark article :is(h1,.dark h2,.dark h3,.dark h4,.dark h5,.dark h6) -> unmatched pseudo-class :is
article :is(h2,h3,h4,h6) .anchor -> unmatched pseudo-class :is

How to add to next.js

I tried adding Critters to a Next.js (version 9.1.2) Webpack configuration, but I ended up with the same error document in issue #5 .

Is there any recommended way of adding Critters into a Next.js project?

Cannot read property 'compilation' of undefined

I don't know what's going on here. Just reporting a bug.

⠋ building for production.../Users/vasyl/code/node_modules/critters-webpack-plugin/dist/critters.js:181
    compiler.hooks.compilation.tap(PLUGIN_NAME, function (compilation) {
                   ^

TypeError: Cannot read property 'compilation' of undefined
    at Critters.apply (/Users/vasyl/code/node_modules/critters-webpack-plugin/dist/critters.js:181:20)
    at Compiler.apply (/Users/vasyl/code/node_modules/tapable/lib/Tapable.js:375:16)
    at webpack (/Users/vasyl/code/node_modules/webpack/lib/webpack.js:33:19)
    at err (/Users/vasyl/code/build/build.js:19:5)
...

Webpack 3. VueJS project. Happens regardless of where in the plugin array I add critters.

I am far from being the best frontend developer. Not familiar with how webpack works either.

handling multiple HTMLWebpackPlugin instances & pruneSource

Critters doesn't appear to play nice with multiple HTMLWebpackPlugin instances when using pruneSource: true (see snippet below).

Effectively, once Critters 'prunes' the original stylesheet then processes subsequent html pages, it can't extract css when it no longer exists in the source.

I'm not sure the best way we can fix this (or if it makes sense to?). For now I'm simply not pruning the source pruneSource: false.

const URLS = [
  { "url": "/", "title": "Welcome" },
  { "url": "/tacos", "title": "Tacos" }
];

// ...

plugins: [

  new CrittersPlugin({
    mergeStylesheets: false,
    logLevel: "error",
    pruneSource: true // default
  }),

  ...URLS.map(({ url, title }) => {
    const options = {
      string: true,
      params: { url, title, mode }
    };
    const template = `!!prerender-loader?${JSON.stringify(options)}!${join(
      src,
      "index.html"
    )}`;
    return new HTMLWebpackPlugin({
      template,
      filename: join(dist, url, "index.html"),
      favicon: join(src, "assets/favicon.ico"), // perf 101, avoid penalties with missing favicon
      minify: isProd && {
        // minify our html for a tidy response
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        removeScriptTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      },
      inject: true // inject bundles assets automatically
    });
  }),

  // ...
]

Don't add noscript and change onload when already exists

When the input html contains a stylesheet which is already loaded async with the media strategy example:

<link rel="stylesheet" href="styles.40e2e5031a642e407437.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="styles.40e2e5031a642e407437.css"></noscript>

Critters will output an addition noscript tag and change the onload of the original link file.

- <link rel="stylesheet" href="styles.40e2e5031a642e407437.css" media="print" onload="this.media='all'">
+ <link rel="stylesheet" href="styles.40e2e5031a642e407437.css" media="print" onload="this.media='print'">
+ <noscript><link rel="stylesheet" href="styles.40e2e5031a642e407437.css" media="print"></noscript>
<noscript><link rel="stylesheet" href="styles.40e2e5031a642e407437.css"></noscript>

In this case while critters should extract the critical css it should not add an addition noscript tag nor change the onload to print.

Unexpected Warning: rules skipped due to selector errors

I opened up issue #20906 this in the angular-cli project and @alan-agius4 explained the following:

The root cause seems to be a bug in Critters which doesn't handle properly the @import.

Accordingly the issue I Opened has been Closed.

A similar issue, #20839 was Opened and has since been Closed.
Issue #42243 was Opened and was Closed as a duplicate of the issue I had opened.

Description:

In my styles.scss I have the following import as the first line:

@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');

Warning Text Displaying in Chrome Dev Tools:

1 rules skipped due to selector errors
0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');

I validated at fonts.google.com that the import is composed correctly. The error is specifically pointing after family=Roboto:ital,wght@0,100; to "0,300;" (and beyond).

The Warning displays upon initial view in browser and each time the browser is refreshed.

:focus-within sometimes gives an invalid selector warning

The pseudo-class :focus-within sometimes gives an invalid selector warning.

For example, this code .group:focus-within .group-focus\:block {...} produces:

  .group:focus-within .group-focus\ -> '.group:focus-within .group-focus\' is not a valid selector

I doubt that any class with :focus-within will ever be used in critical CSS, but those warnings create some noise. A workaround if to set logLevel to "error".

This issue probably causes #47.

On the other side, I am sure some simple selectors like foo::focus-within are accepted.

Incompatible with html-webpack-plugin v4

When used with latest version of html-webpack-plugin, it crashes with Error: Could not find HTML asset.

It seems the html-webpack-plugin no longer uses the file name as the key in the assets object?

I can also see there's a #43 - anything I can do to move this forward?

Compilation asset keys (path) don't mach on Windows (Unable to locate stylesheet)

On Windows 10 (CMD/PowerShell/Git Bash) the latest Critters version (1.3.5) is "Unable to locate stylesheet", but the same setup works without problems on a Unix-like system / WSL 2.

I think the problem is, that the path gets normalized in the "compilation.assets"-keys, but not in embedLinkedStylesheet. Here is the state in embedLinkedStylesheet on WSL2:

{
  outputPath: '/home/USERNAME/dev/git/PROJECT-ui/PROJECT/app/PROJECT-app-PROJECT2-dev/dist',
  normalizedPath: 'css/chunk-vendors.3a56f9b8.css',
  filename: '/home/USERNAME/dev/git/PROJECT-ui/PROJECT/app/PROJECT-app-PROJECT2-dev/dist/css/chunk-vendors.3a56f9b8.css',
  relativePath: 'css/chunk-vendors.3a56f9b8.css',
  compilationAssetKeys: [
    'css/chunk-vendors.3a56f9b8.css',
    [...60 lines omitted...]
 ]
}

And the same commit, but with a clone on Windows in PowerShell:

{
  outputPath: 'C:\\dev\\git\\PROJECT-ui\\PROJECT\\app\\PROJECT-app-PROJECT2-dev\\dist',
  normalizedPath: 'css/chunk-vendors.3a56f9b8.css',
  filename: 'C:\\dev\\git\\PROJECT-ui\\PROJECT\\app\\PROJECT-app-PROJECT2-dev\\dist\\css\\chunk-vendors.3a56f9b8.css',
  relativePath: 'css\\chunk-vendors.3a56f9b8.css',
  compilationAssetKeys: [
    'css/chunk-vendors.3a56f9b8.css',
    [...60 lines omitted...]
 ]
}

so compilation.assets[relativePath] won't match, which will produce the following error:

‼ 「Critters」: Unable to locate stylesheet: css\chunk-vendors.3a56f9b8.css

Animation keyframe detection is not using the correct index in the iterator [v1.3.3]

For any CSS where there is animation declarations, the library will throw an error if the index i is out of bounds of the Array of critical keyframe names, since it should be iterating using index j instead.

This issue was addressed in this commit which was released in v2.4.0, however this fix was never applied to the 1.x codeline, of which there are several very popular packages still using it (e.g. preact-cli)

Can we please have a hotfix release for 1.x, with that fix included?

Could not find HTML asset

Followed usage instructions, and get this error:

Error: Could not find HTML asset.
    at C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\critters-webpack-plugin\dist\critters.js:200:39
    at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:7:1)
    at AsyncSeriesHook.lazyCompileHook [as _callAsync] (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\Hook.js:35:21)
    at hooks.optimizeChunkAssets.callAsync.err (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\webpack\lib\Compilation.js:976:32)
    at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:6:1)
    at AsyncSeriesHook.lazyCompileHook [as _callAsync] (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\Hook.js:35:21)
    at hooks.additionalAssets.callAsync.err (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\webpack\lib\Compilation.js:971:36)
    at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:6:1)
    at AsyncSeriesHook.lazyCompileHook [as _callAsync] (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\Hook.js:35:21)
    at hooks.optimizeTree.callAsync.err (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\webpack\lib\Compilation.js:967:32)
    at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:6:1)
    at AsyncSeriesHook.lazyCompileHook [as _callAsync] (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\Hook.js:35:21)
    at Compilation.seal (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\webpack\lib\Compilation.js:904:27)
    at hooks.make.callAsync.err (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\webpack\lib\Compiler.js:494:17)
    at _done (eval at create (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:9:1)
    at _err1 (eval at create (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\tapable\lib\HookCodeFactory.js:24:12), <anonymous>:32:22)
    at _addModuleChain (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\webpack\lib\Compilation.js:770:12)
    at processModuleDependencies.err (C:\TFS\WorkZone\WZC\Main\Source\client\node_modules\webpack\lib\Compilation.js:709:9)
    at process._tickCallback (internal/process/next_tick.js:61:11)

Tried with html-webpack-plugin on
webpack 4.10.2
nodejs 10.3.0

TypeError: Cannot read property 'toUpperCase' of null

TL;DR This is the repo reproducing the problem.

I am using the critters plugin alongside html-webpack-plugin and mini-css-extract-plugin, I installed bulma and imported it my main js file.

When I build the project I get the fallowing error:

ERROR in   TypeError: Cannot read property 'toUpperCase' of null

  - critters.js:39 Node.get
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:39:33


  - nwmatcher.js:1444 Object.match
    [Critters Error]/[nwmatcher]/src/nwmatcher.js:1444:38


  - nwmatcher.js:1626 select
    [Critters Error]/[nwmatcher]/src/nwmatcher.js:1626:43

  - nwmatcher.js:1451 Object.first
    [Critters Error]/[nwmatcher]/src/nwmatcher.js:1451:14

  - critters.js:117 Node.querySelector
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:117:28

  - critters.js:359
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:359:37

  - Array.filter

  - critters.js:357
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:357:49

  - critters.js:166
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:166:16

  - Array.filter

  - critters.js:162 walkStyleRules
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:162:29

  - critters.js:161 walkStyleRules
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:161:18

  - critters.js:355 Critters.<anonymous>
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:355:9

  - new Promise

  - critters.js:345 Critters.processStyle
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:345:12

  - critters.js:254
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:254:76

  - Array.map

  - critters.js:254 Critters.$If_2
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:254:39

  - critters.js:244 Critters.<anonymous>
    [Critters Error]/[critters-webpack-plugin]/dist/critters.js:244:34

TypeError: Cannot read property 'compilation' of undefined

Hi,

I'm using Laravel Mix

When I add the below code.

mix.webpackConfig({
    plugins: [
        new Critters({
            // Outputs: <link rel="preload" onload="this.rel='stylesheet'">
            preload: 'swap',

            // Don't inline critical font-face rules, but preload the font URLs:
            preloadFonts: true
        })
    ],
});

I receive this error.

/Users/jakehenshall/Code/Preset/node_modules/webpack/bin/webpack.js:348
throw err;
^

TypeError: Cannot read property 'compilation' of undefined
at Critters.apply (/Users/jakehenshall/Code/Preset/node_modules/critters-webpack-plugin/dist/critters.js:181:20)
at Compiler.apply (/Users/jakehenshall/Code/Preset/node_modules/tapable/lib/Tapable.js:375:16)
at webpack (/Users/jakehenshall/Code/Preset/node_modules/webpack/lib/webpack.js:33:19)
at processOptions (/Users/jakehenshall/Code/Preset/node_modules/webpack/bin/webpack.js:335:15)
at yargs.parse (/Users/jakehenshall/Code/Preset/node_modules/webpack/bin/webpack.js:397:2)
at Object.Yargs.self.parse (/Users/jakehenshall/Code/Preset/node_modules/webpack/node_modules/yargs/yargs.js:533:18)
at Object. (/Users/jakehenshall/Code/Preset/node_modules/webpack/bin/webpack.js:152:7)
at Module._compile (internal/modules/cjs/loader.js:702:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:713:10)
at Module.load (internal/modules/cjs/loader.js:612:32)
at tryModuleLoad (internal/modules/cjs/loader.js:551:12)
at Function.Module._load (internal/modules/cjs/loader.js:543:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:744:10)
at startup (internal/bootstrap/node.js:238:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)

Thanks, Jake.

CSS does not get extracted when HtmlWebpackPlugin is set to use `hash: true`

It seems that this doesn't find the CSS output files (E_NOENT) when HtmlWebpackPlugin is set up to append the hash in the query string.

(There are alternative ways to bust the cache, but I'm not sure what the implications would be on web server config in our setup, so a little reluctant to change this)

Always set crossorigin for font files

I have preloadFonts set to true, resulting in a preload like this:

<link rel="preload" as="font" href="CanelaDeck-Bold-Web.woff2"/>

Even though this is not a cross-domain request this preload is discarded by Chrome:

A preload for 'https://develop.mydomain.xyz/CanelaDeck-Bold-Web.woff2' is found, but is not used because the request credentials mode does not match. Consider taking a look at crossorigin attribute.

If I read https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content correctly the preload needs a crossorigin="anonymous" even if it is in the same domain.

Not inserting every critical css

I started to use Critters with preact-cli and i can't really find any article about how it is decided wether a css count as critical or not. The README says "Critters inlines all CSS rules used by your document" however in my case many of the critical css are not inlined which cause a flick.

I blocked javascript and loaded my page so i can check what is actually prerendered and instantly presented in the DOM and half of the required css is missing. Any idea what cause the problem, how to debug, how to fix it or how can i force Critters to inline css for specific components? Tried several log levels but didn't help as only wrote out how much of all css has been inlined.

How to inline Vue component's styles?

Hi,
Thank you for this great project, I have a question please about inlining Vue component's styles, I'm not using Vue cli, I have a custom Webpack configuration for Vue and I'm using mini-css-extract-plugin, for the CSS I'm using an external stylesheet (Bulma) with a little bit of custom CSS for some Vue components, the thing is that Critters is working just fine for the external stylesheet but for the custom Vue component's CSS it's not, this is my Critters plugin config:

new Critters({
  external: true,
  inlineThreshold: 15*1024,
  minimumExternalSize: 15*1024,
  pruneSource: true,
  compress: true,
  logLevel: 'info'
})

The external stylesheet is inlined but the component's CSS is not, even though their size is very small, is their anything that I can do to make Critters plugin process these CSS styles?

Thank you.

Version 1.3.4 has shipped without a /dist directory

I've confirmed this on multiple devices and with both NPM + Yarn so I don't think this is just some weird failure of caching.

ls -l node_modules/critters-webpack-plugin/

total 56
-rw-r--r--  1 jaredlunde  staff   1101 Jan  3 16:17 CONTRIBUTING.md
-rw-r--r--  1 jaredlunde  staff  11341 Jan  3 16:17 LICENSE
-rw-r--r--  1 jaredlunde  staff   5981 Jan  3 16:17 README.md
-rw-r--r--  1 jaredlunde  staff   2347 Jan  3 16:17 package.json
drwxr-xr-x  6 jaredlunde  staff    192 Jan  3 16:17 src
drwxr-xr-x  7 jaredlunde  staff    224 Jan  3 16:17 test

cat node_modules/critters-webpack-plugin/package.json

{
  "name": "critters-webpack-plugin",
  "version": "1.3.4",
  "description": "Webpack plugin to inline critical CSS and lazy-load the rest.",
  "main": "dist/critters.js",
  "source": "src/index.js",
  ...
}

Further evidence seen here

Adding Critters to project removes media queries from external stylesheet

I'm using Critters in my webpack project with the following configuration:

new Critters({
  mergeStylesheets: true,
  inlineFonts: true,
})

I'm using this in combination with MiniCssExtractPlugin. When I add Critters to the plugin list (after MiniCssExtractPlugin and HtmlWebpackPlugin), critters seems to remove the content of media queries - the output still has the media queries but nothing is in them, which looks like this:

@media (max-width: 480px) {}

This breaks the styling and layout of my site.

DTS feedback

  • logLevel?: string is too permissive and should be changed to something along logLevel?: 'warn' | 'log' | 'info' | 'error'.
  • logger is not exposed as part of the options
  • the interface Options is not exported. It would be nice to have these exported accessing the options interface as such:
import Critters, { Options } from 'critters';

Critters is adding `<html><head>` and `</head><body></body></html>` to partial templates

Was testing using Critters to inline some critical CSS and noticed that it's inserting HTML/HEAD/BODY tags into output files that don't already have these tags.

These files intentionally don't have these tags because they're partials that are loaded by a separate system (in this case PHP). Removing Critters from the plugin chain prevents these tags from being added.

As an example, Critters is turning something like this:

<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,300,700" rel="preload" crossorigin="anonymous" type="text/css">

<script src="/assets/dist/modernizr.bundle.js"></script>
<script src="/assets/dist/header.bundle.js"></script>

Into this:

<html><head>

<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,300,700" rel="preload" crossorigin="anonymous" type="text/css">

<script src="/assets/dist/modernizr.bundle.js"></script>
<script src="/assets/dist/header.bundle.js"></script>

</head><body></body></html>

Possible to disable this behavior via an option?

Crashing due to Critters.prototype.readFile vs. this$1.fs

Using new Critters() without (in the usual Webpack plugins array) any options causes the compilation process to crash:

ERROR in TypeError: Cannot read property 'readFile' of undefined
  - critters.js:216 
    [LianaUI]/[critters-webpack-plugin]/dist/critters.js:216:19
 . . .
  - critters.js:215 Critters.readFile
    [LianaUI]/[critters-webpack-plugin]/dist/critters.js:215:12
  
  - critters.js:272 Critters.<anonymous>
    [LianaUI]/[critters-webpack-plugin]/dist/critters.js:272:25
. . .
  - critters.js:258 Critters.embedLinkedStylesheet
    [LianaUI]/[critters-webpack-plugin]/dist/critters.js:258:12
  
  - critters.js:233 
    [LianaUI]/[critters-webpack-plugin]/dist/critters.js:233:83
. . .

^ Seems to break on Critters.prototype.readFile = function readFile(filename, encoding) {...} because this$1.fs is undefined.

Tried with:

"html-webpack-plugin": "3.2.0"
"webpack": "4.12.0"
"webpack-dev-server": "3.1.4"
"critters-webpack-plugin": "1.0.0"

Critters is commenting out any php in the file

Critters succeeds in inlining the css and preloading the link tag, but it also comments out any other php lines on the file, both in the head and body of the document.

I am using Critters with html-webpack-plugin and mini-css-extract-plugin in an mpa.

Here's a relevant chunk of my config:

module.exports = {
        module: {
		rules: [
			{
				test: /\.scss$/,
				use: [
					MiniCssExtractPlugin.loader,
					"css-loader",
					"sass-loader"
				]
			}
		]
	},
	plugins: [
                new HtmlWebpackPlugin({
			chunks: ['common', 'index'],
			filename: 'index.php',
			template: './src/pages/_index.php'
		}),
		new Critters(),
	]
};

Refactor: additionalStylesheets appears broken

Using the config property additionalStylesheets with the refactor branch, e.g. [email protected] will lead to the following error where document.head is undefined.

(node:30702) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'appendChild' of undefined
    at /Users/mswaagman/projects/public/next.js/examples/with-linaria/node_modules/critters/dist/critters.js:9955:25
    at Array.forEach (<anonymous>)
    at /Users/mswaagman/projects/public/next.js/examples/with-linaria/node_modules/critters/dist/critters.js:9952:17

Strategy `media` is loading CSS at highest priority

With strategy not x, the actual file is still loaded with the highest priority.
This defeats the purpose of the inline styles as the actual CSS file might not be render blocking but is still in Network contention with the rest of the resources.
This makes other resources like images/fonts which are critical for FCP/LCP etc to come later than expected.

A better option would be to use media=print which forces this priority to go down to lowest, opening up the network to other more important resources.

Silent option

I'm interested in an option to silence console output. I can throw up a PR if this is of interest to others. ??

Rules are being pruned even if the rule was skipped due to an error

CSS rules are being removed from the external stylesheet even if the rules were skipped due to an error and weren't inlined.

For example the CSS that I'm using contains :focus-within which seems to be not supported by JSDOM and that's why it's throwing an error.

This is the sample output when this issue happens:

95% emitting HtmlWebpackPlugin⚠ 「Critters」: 4 rules skipped due to selector errors:
  .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link -> '.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link' is not a valid selector
  .navbar-item.is-hoverable:focus-within .navbar-dropdown -> '.navbar-item.is-hoverable:focus-within .navbar-dropdown' is not a valid selector
  .navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown -> '.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown' is not a valid selector
  .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed -> '.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed' is not a valid selector
ℹ 「Critters」: Inlined 645 B (0% of original 213 kB) of static/css/app.94517.css.

For now if we set the pruneSource option to false it solves the problem temporarily, of course this has the drawback of keeping the inlined styles in the external stylesheet.

To reproduce the problem, I have added a prune-source branch on the critters-vue-example repository, you just need to:

git clone [email protected]:mdawar/critters-vue-example.git
git checkout prune-source
npm install
npm run build

Load CSS asynchronously doesn't work

my config:

    plugins: [
      new Critters({
        preload: 'js',
        preloadFonts: true
      })
    ],
  },

output:

....
    body {
      margin: 0;
      font-weight: 400;
      font-size: 1rem;
      line-height: 1.5;
      font-family: Roboto, Arial, Helvetica, sans-serif;
      color: var(--color-dark-silver);
      background-color: var(--color-back-gray)
    }
  </style>
  <link href="/static/css/chunk-vendors.9884b996.css" rel="preload" as="style"><noscript>
    <link rel="stylesheet" href="/static/css/chunk-vendors.9884b996.css"></noscript>
  <script>function $loadcss(u, m, l) { (l = document.createElement('link')).rel = 'stylesheet'; l.href = u; document.head.appendChild(l) } $loadcss("/static/css/chunk-vendors.9884b996.css")</script>
  <link href="/static/css/app.32579bae.css" rel="preload" as="style"><noscript>
    <link rel="stylesheet" href="/static/css/app.32579bae.css"></noscript>
  <script>function $loadcss(u, m, l) { (l = document.createElement('link')).rel = 'stylesheet'; l.href = u; document.head.appendChild(l) } $loadcss("/static/css/app.32579bae.css")</script>
  <link rel="preload" as="font" crossorigin="anonymous" href="/static/fonts/Robotolight.woff">
  <link rel="preload" as="font" crossorigin="anonymous" href="/static/fonts/Roboto.woff">
  <link rel="preload" as="font" crossorigin="anonymous" href="/static/fonts/Robotoitalic.woff">
  <link rel="preload" as="font" crossorigin="anonymous" href="/static/fonts/Robotomedium.woff">
  <link rel="preload" as="font" crossorigin="anonymous" href="/static/fonts/Robotobold.woff">
  <link rel="preload" as="font" crossorigin="anonymous" href="/static/fonts/element-icons.woff">
</head>

anyway.. lighthouse says -

Screenshot 2019-06-06 at 16 37 06

Any suggestions?

Maintain order of CSS

From GoogleChromeLabs/squoosh#322.

In dev, the intro bundle appears first, followed by the main bundle.

After Critters, the intro bundle is inlined (as intended), but it's inserted after the main bundle.

This breaks CSS that depends on ordering.

Doesn't work on production mode

Need help, my Critters plugin doesn't work on production build, but works well on development mode. I am using webpack-dev-server for development.

Here's on my webpack.config.js:

if (userConfig.html.length !== 0) {
  webpackConfig.plugins = webpackConfig.plugins
    .concat(userConfig.html.map(htmlConfig => new HtmlWebpackPlugin(htmlConfig)))
    .concat([new ScriptExtHtmlWebpackPlugin({ defaultAttribute: 'async' })])
    .concat([new CrittersPlugin({})]);
}

This is userConfig I used for HTML Webpack Plugin:

const config = {
  ...
  html: [
    {
      template: './src/intro.html',
      filename: 'intro/index.html',
      chunks: ['intro/intro'],
    },
  ],
  ...
};

module.exports = config;

And this is my node script to run production build:
NODE_ENV=production webpack -r dotenv/config

Get error 'textContent' of undefined

ERROR in TypeError: Cannot read property 'textContent' of undefined

  • critters.js:225 Critters.
    [default]/[critters-webpack-plugin]/dist/critters.js:225:22

  • critters.js:220 Critters.mergeStylesheets
    [default]/[critters-webpack-plugin]/dist/critters.js:220:12

  • critters.js:197 Critters.
    [default]/[critters-webpack-plugin]/dist/critters.js:197:37

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.