- Rename to critters-webpack-plugin

- Format output using pretty-bytes
- Insert inlined style tags immediately after their source link tags
- Add option to turn off external stylesheet processing
- Add `async` option that converts critical'd external sheets to `<link rel="preload" as="style" onload="this.rel='stylesheet'">`
This commit is contained in:
Jason Miller
2018-04-03 09:59:05 -04:00
parent 34c47242a9
commit 0066e20315
2 changed files with 29 additions and 7 deletions

View File

@@ -3,10 +3,11 @@ const path = require('path');
const parse5 = require('parse5'); const parse5 = require('parse5');
const nwmatcher = require('nwmatcher'); const nwmatcher = require('nwmatcher');
const css = require('css'); const css = require('css');
const prettyBytes = require('pretty-bytes');
const treeUtils = parse5.treeAdapters.htmlparser2; const treeUtils = parse5.treeAdapters.htmlparser2;
const PLUGIN_NAME = 'html-webpack-inline-critical-css-plugin'; const PLUGIN_NAME = 'critters-webpack-plugin';
const PARSE5_OPTS = { const PARSE5_OPTS = {
treeAdapter: treeUtils treeAdapter: treeUtils
@@ -25,6 +26,11 @@ const ElementExtensions = {
return this.tagName; return this.tagName;
} }
}, },
insertBefore: function (child, referenceNode) {
if (!referenceNode) return this.appendChild(child);
treeUtils.insertBefore(this, child, referenceNode);
return child;
},
appendChild: function (child) { appendChild: function (child) {
treeUtils.appendChild(this, child); treeUtils.appendChild(this, child);
return child; return child;
@@ -82,7 +88,13 @@ const DocumentExtensions = {
addEventListener: Object addEventListener: Object
}; };
module.exports = class HtmlWebpackInlineCriticalCssPlugin { /** Critters: Webpack Plugin Edition!
* @class
* @param {Object} options
* @param {Boolean} [options.external=true] Fetch and inline critical styles from external stylesheets
* @param {Boolean} [options.async=true] If `false`, only already-inline stylesheets will be reduced to critical rules.
*/
module.exports = class CrittersWebpackPlugin {
constructor(options) { constructor(options) {
this.options = options || {}; this.options = options || {};
} }
@@ -112,6 +124,7 @@ module.exports = class HtmlWebpackInlineCriticalCssPlugin {
const externalSheets = document.querySelectorAll('link[rel="stylesheet"]'); const externalSheets = document.querySelectorAll('link[rel="stylesheet"]');
Promise.all(externalSheets.map(function(link) { Promise.all(externalSheets.map(function(link) {
if (self.options.external===false) return;
const href = link.getAttribute('href'); const href = link.getAttribute('href');
if (href.match(/^(https?:)?\/\//)) return Promise.resolve(); if (href.match(/^(https?:)?\/\//)) return Promise.resolve();
const filename = path.resolve(outputPath, href.replace(/^\//, '')); const filename = path.resolve(outputPath, href.replace(/^\//, ''));
@@ -121,7 +134,12 @@ module.exports = class HtmlWebpackInlineCriticalCssPlugin {
const style = document.createElement('style'); const style = document.createElement('style');
style.$$name = href; style.$$name = href;
style.appendChild(document.createTextNode(sheet)); style.appendChild(document.createTextNode(sheet));
link.parentNode.appendChild(style); // @TODO insertBefore link.parentNode.insertBefore(style, link.nextSibling);
if (self.options.async) {
link.setAttribute('rel', 'preload');
link.setAttribute('as', 'style');
link.setAttribute('onload', "this.rel='stylesheet'");
}
}); });
})) }))
.then(function() { .then(function() {
@@ -199,8 +217,9 @@ module.exports = class HtmlWebpackInlineCriticalCssPlugin {
} }
style.appendChild(document.createTextNode(sheet)); style.appendChild(document.createTextNode(sheet));
} }
const name = style.$$name; const name = style.$$name ? style.$$name.replace(/^\//, '') : 'inline CSS';
console.log('\u001b[32mInlined CSS Size' + (name ? (' ('+name+')') : '') + ': ' + (sheet.length / 1000).toPrecision(2) + 'kb (' + ((before.length - sheet.length) / before.length * 100 | 0) + '% of original ' + (before.length / 1000).toPrecision(2) + 'kb)\u001b[39m'); const percent = (before.length - sheet.length) / before.length * 100 | 0;
console.log('\u001b[32mCritters: inlined ' + prettyBytes(sheet.length) + ' (' + percent + '% of original ' + prettyBytes(before.length) + ') of ' + name + '.\u001b[39m');
}); });
} }
}; };

View File

@@ -9,7 +9,7 @@ const PreloadWebpackPlugin = require('preload-webpack-plugin');
const ReplacePlugin = require('webpack-plugin-replace'); const ReplacePlugin = require('webpack-plugin-replace');
const CopyPlugin = require('copy-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin'); const WorkboxPlugin = require('workbox-webpack-plugin');
const HtmlInlineCssPlugin = require('./config/html-webpack-inline-critical-css-plugin'); const CrittersPlugin = require('./config/critters-webpack-plugin');
const WatchTimestampsPlugin = require('./config/watch-timestamps-plugin'); const WatchTimestampsPlugin = require('./config/watch-timestamps-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
@@ -175,8 +175,11 @@ module.exports = function(_, env) {
// Inject <link rel="preload"> for resources // Inject <link rel="preload"> for resources
isProd && new PreloadWebpackPlugin(), isProd && new PreloadWebpackPlugin(),
isProd && new HtmlInlineCssPlugin(),
isProd && new CrittersPlugin({
// convert critical'd <link rel="stylesheet"> to <link rel="preload" as="style" onload="this.rel='stylesheet'">
async: true
}),
// Inline constants during build, so they can be folded by UglifyJS. // Inline constants during build, so they can be folded by UglifyJS.
new webpack.DefinePlugin({ new webpack.DefinePlugin({