function _interopDefault(ex) { return ex && typeof ex === 'object' && 'default' in ex ? ex.default : ex; } var path = _interopDefault(require('path')); var prettyBytes = _interopDefault(require('pretty-bytes')); var sources = _interopDefault(require('webpack-sources')); var postcss = _interopDefault(require('postcss')); var cssnano = _interopDefault(require('cssnano')); var log = _interopDefault(require('webpack-log')); var minimatch = _interopDefault(require('minimatch')); var jsdom = require('jsdom'); var css = _interopDefault(require('css')); function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === 'string') return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === 'Object' && o.constructor) n = o.constructor.name; if (n === 'Map' || n === 'Set') return Array.from(o); if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it; if (typeof Symbol === 'undefined' || o[Symbol.iterator] == null) { if ( Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || (allowArrayLike && o && typeof o.length === 'number') ) { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true, }; return { done: false, value: o[i++], }; }; } throw new TypeError( 'Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' ); } it = o[Symbol.iterator](); return it.next.bind(it); } /** * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ /** * Parse HTML into a mutable, serializable DOM Document, provided by JSDOM. * @private * @param {String} html HTML to parse into a Document instance */ function createDocument(html) { var jsdom$1 = new jsdom.JSDOM(html, { contentType: 'text/html', }); var window = jsdom$1.window; var document = window.document; document.$jsdom = jsdom$1; return document; } /** * Serialize a Document to an HTML String * @private * @param {Document} document A Document, such as one created via `createDocument()` */ function serializeDocument(document) { return document.$jsdom.serialize(); } /** Like node.textContent, except it works */ function setNodeText(node, text) { while (node.lastChild) { node.removeChild(node.lastChild); } node.appendChild(node.ownerDocument.createTextNode(text)); } /** * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ /** * Parse a textual CSS Stylesheet into a Stylesheet instance. * Stylesheet is a mutable ReworkCSS AST with format similar to CSSOM. * @see https://github.com/reworkcss/css * @private * @param {String} stylesheet * @returns {css.Stylesheet} ast */ function parseStylesheet(stylesheet) { return css.parse(stylesheet); } /** * Serialize a ReworkCSS Stylesheet to a String of CSS. * @private * @param {css.Stylesheet} ast A Stylesheet to serialize, such as one returned from `parseStylesheet()` * @param {Object} options Options to pass to `css.stringify()` * @param {Boolean} [options.compress] Compress CSS output (removes comments, whitespace, etc) */ function serializeStylesheet(ast, options) { return css.stringify(ast, options); } /** * Converts a walkStyleRules() iterator to mark nodes with `.$$remove=true` instead of actually removing them. * This means they can be removed in a second pass, allowing the first pass to be nondestructive (eg: to preserve mirrored sheets). * @private * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node. * @returns {(rule) => void} nonDestructiveIterator */ function markOnly(predicate) { return function (rule) { var sel = rule.selectors; if (predicate(rule) === false) { rule.$$remove = true; } rule.$$markedSelectors = rule.selectors; if (rule._other) { rule._other.$$markedSelectors = rule._other.selectors; } rule.selectors = sel; }; } /** * Apply filtered selectors to a rule from a previous markOnly run. * @private * @param {css.Rule} rule The Rule to apply marked selectors to (if they exist). */ function applyMarkedSelectors(rule) { if (rule.$$markedSelectors) { rule.selectors = rule.$$markedSelectors; } if (rule._other) { applyMarkedSelectors(rule._other); } } /** * Recursively walk all rules in a stylesheet. * @private * @param {css.Rule} node A Stylesheet or Rule to descend into. * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node. */ function walkStyleRules(node, iterator) { if (node.stylesheet) return walkStyleRules(node.stylesheet, iterator); node.rules = node.rules.filter(function (rule) { if (rule.rules) { walkStyleRules(rule, iterator); } rule._other = undefined; rule.filterSelectors = filterSelectors; return iterator(rule) !== false; }); } /** * Recursively walk all rules in two identical stylesheets, filtering nodes into one or the other based on a predicate. * @private * @param {css.Rule} node A Stylesheet or Rule to descend into. * @param {css.Rule} node2 A second tree identical to `node` * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node from the first tree, true to remove it from the second. */ function walkStyleRulesWithReverseMirror(node, node2, iterator) { if (node2 === null) return walkStyleRules(node, iterator); if (node.stylesheet) return walkStyleRulesWithReverseMirror( node.stylesheet, node2.stylesheet, iterator ); var _splitFilter = splitFilter(node.rules, node2.rules, function ( rule, index, rules, rules2 ) { var rule2 = rules2[index]; if (rule.rules) { walkStyleRulesWithReverseMirror(rule, rule2, iterator); } rule._other = rule2; rule.filterSelectors = filterSelectors; return iterator(rule) !== false; }); node.rules = _splitFilter[0]; node2.rules = _splitFilter[1]; } // Like [].filter(), but applies the opposite filtering result to a second copy of the Array without a second pass. // This is just a quicker version of generating the compliment of the set returned from a filter operation. function splitFilter(a, b, predicate) { var aOut = []; var bOut = []; for (var index = 0; index < a.length; index++) { if (predicate(a[index], index, a, b)) { aOut.push(a[index]); } else { bOut.push(a[index]); } } return [aOut, bOut]; } // can be invoked on a style rule to subset its selectors (with reverse mirroring) function filterSelectors(predicate) { if (this._other) { var _splitFilter2 = splitFilter( this.selectors, this._other.selectors, predicate ); var a = _splitFilter2[0]; var b = _splitFilter2[1]; this.selectors = a; this._other.selectors = b; } else { this.selectors = this.selectors.filter(predicate); } } function tap(inst, hook, pluginName, async, callback) { if (inst.hooks) { var camel = hook.replace(/-([a-z])/g, function (s, i) { return i.toUpperCase(); }); inst.hooks[camel][async ? 'tapAsync' : 'tap'](pluginName, callback); } else { inst.plugin(hook, callback); } } function _catch(body, recover) { try { var result = body(); } catch (e) { return recover(e); } if (result && result.then) { return result.then(void 0, recover); } return result; } var Critters = /* #__PURE__ */ (function () { function Critters(options) { this.options = Object.assign( { logLevel: 'info', externalStylesheets: [], path: '', publicPath: '', preload: 'media', reduceInlineStyles: false, // compress: false, }, options || {} ); this.options.pruneSource = this.options.pruneSource !== false; this.urlFilter = this.options.filter; if (this.urlFilter instanceof RegExp) { this.urlFilter = this.urlFilter.test.bind(this.urlFilter); } this.logger = { warn: console.log, info: console.log, }; } /** * Read the contents of a file from Webpack's input filesystem */ var _proto = Critters.prototype; _proto.readFile = function readFile(filename) { var fs = this.fs; // || compilation.outputFileSystem; return new Promise(function (resolve, reject) { var callback = function callback(err, data) { if (err) reject(err); else resolve(data); }; if (fs && fs.readFile) { fs.readFile(filename, callback); } else { require('fs').readFile(filename, 'utf8', callback); } }); }; _proto.process = (function (_process) { function process(_x) { return _process.apply(this, arguments); } process.toString = function () { return _process.toString(); }; return process; })(function (html) { try { var _temp5 = function _temp5() { // console.log('2 ', serializeDocument(document)); // go through all the style tags in the document and reduce them to only critical CSS var styles = _this2.getAffectedStyleTags(document); return Promise.resolve( Promise.all( styles.map(function (style) { return _this2.processStyle(style, document); }) ) ).then(function () { function _temp2() { // serialize the document back to HTML and we're done var output = serializeDocument(document); // var end = process.hrtime.bigint(); // console.log('Time ' + parseFloat(end - start) / 1000000.0); console.timeEnd('critters'); return output; } var _temp = (function () { if ( _this2.options.mergeStylesheets !== false && styles.length !== 0 ) { return Promise.resolve( _this2.mergeStylesheets(document) ).then(function () {}); } })(); return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp); }); }; var _this2 = this; var outputPath = _this2.options.path; var publicPath = _this2.options.publicPath; // var start = process.hrtime.bigint(); console.time('critters'); // Parse the generated HTML in a DOM we can mutate var document = createDocument(html); // if (this.options.additionalStylesheets) { // const styleSheetsIncluded = []; // (this.options.additionalStylesheets || []).forEach((cssFile) => { // if (styleSheetsIncluded.includes(cssFile)) { // return; // } // styleSheetsIncluded.push(cssFile); // const webpackCssAssets = Object.keys( // compilation.assets // ).filter((file) => minimatch(file, cssFile)); // webpackCssAssets.map((asset) => { // const tag = document.createElement('style'); // tag.innerHTML = compilation.assets[asset].source(); // document.head.appendChild(tag); // }); // }); // } // `external:false` skips processing of external sheets // console.log('1 ', serializeDocument(document)); var _temp6 = (function () { if (_this2.options.external !== false) { var externalSheets = [].slice.call( document.querySelectorAll('link[rel="stylesheet"]') ); return Promise.resolve( Promise.all( externalSheets.map(function (link) { return _this2.embedLinkedStylesheet( link, outputPath, publicPath ); }) ) ).then(function () {}); } })(); return Promise.resolve( _temp6 && _temp6.then ? _temp6.then(_temp5) : _temp5(_temp6) ); } catch (e) { return Promise.reject(e); } }); _proto.getAffectedStyleTags = function getAffectedStyleTags(document) { var styles = [].slice.call(document.querySelectorAll('style')); // `inline:false` skips processing of inline stylesheets if (this.options.reduceInlineStyles === false) { return styles.filter(function (style) { return style.$$external; }); } return styles; }; _proto.mergeStylesheets = function mergeStylesheets(document) { try { var _temp9 = function _temp9() { setNodeText(first, sheet); }; var _this4 = this; var styles = _this4.getAffectedStyleTags(document); if (styles.length === 0) { // this.logger.warn('Merging inline stylesheets into a single