source: trip-planner-front/node_modules/critters/dist/critters_original.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 58.2 KB
Line 
1function _interopDefault(ex) {
2 return ex && typeof ex === 'object' && 'default' in ex ? ex.default : ex;
3}
4
5var path = _interopDefault(require('path'));
6var prettyBytes = _interopDefault(require('pretty-bytes'));
7var sources = _interopDefault(require('webpack-sources'));
8var postcss = _interopDefault(require('postcss'));
9var cssnano = _interopDefault(require('cssnano'));
10var log = _interopDefault(require('webpack-log'));
11var minimatch = _interopDefault(require('minimatch'));
12var jsdom = require('jsdom');
13var css = _interopDefault(require('css'));
14
15function _unsupportedIterableToArray(o, minLen) {
16 if (!o) return;
17 if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
18 var n = Object.prototype.toString.call(o).slice(8, -1);
19 if (n === 'Object' && o.constructor) n = o.constructor.name;
20 if (n === 'Map' || n === 'Set') return Array.from(o);
21 if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
22 return _arrayLikeToArray(o, minLen);
23}
24
25function _arrayLikeToArray(arr, len) {
26 if (len == null || len > arr.length) len = arr.length;
27
28 for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
29
30 return arr2;
31}
32
33function _createForOfIteratorHelperLoose(o, allowArrayLike) {
34 var it;
35
36 if (typeof Symbol === 'undefined' || o[Symbol.iterator] == null) {
37 if (
38 Array.isArray(o) ||
39 (it = _unsupportedIterableToArray(o)) ||
40 (allowArrayLike && o && typeof o.length === 'number')
41 ) {
42 if (it) o = it;
43 var i = 0;
44 return function () {
45 if (i >= o.length)
46 return {
47 done: true,
48 };
49 return {
50 done: false,
51 value: o[i++],
52 };
53 };
54 }
55
56 throw new TypeError(
57 'Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'
58 );
59 }
60
61 it = o[Symbol.iterator]();
62 return it.next.bind(it);
63}
64
65/**
66 * Copyright 2018 Google LLC
67 *
68 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
69 * use this file except in compliance with the License. You may obtain a copy of
70 * the License at
71 *
72 * http://www.apache.org/licenses/LICENSE-2.0
73 *
74 * Unless required by applicable law or agreed to in writing, software
75 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
76 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
77 * License for the specific language governing permissions and limitations under
78 * the License.
79 */
80/**
81 * Parse HTML into a mutable, serializable DOM Document, provided by JSDOM.
82 * @private
83 * @param {String} html HTML to parse into a Document instance
84 */
85
86function createDocument(html) {
87 var jsdom$1 = new jsdom.JSDOM(html, {
88 contentType: 'text/html',
89 });
90 var window = jsdom$1.window;
91 var document = window.document;
92 document.$jsdom = jsdom$1;
93 return document;
94}
95/**
96 * Serialize a Document to an HTML String
97 * @private
98 * @param {Document} document A Document, such as one created via `createDocument()`
99 */
100
101function serializeDocument(document) {
102 return document.$jsdom.serialize();
103}
104/** Like node.textContent, except it works */
105
106function setNodeText(node, text) {
107 while (node.lastChild) {
108 node.removeChild(node.lastChild);
109 }
110
111 node.appendChild(node.ownerDocument.createTextNode(text));
112}
113
114/**
115 * Copyright 2018 Google LLC
116 *
117 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
118 * use this file except in compliance with the License. You may obtain a copy of
119 * the License at
120 *
121 * http://www.apache.org/licenses/LICENSE-2.0
122 *
123 * Unless required by applicable law or agreed to in writing, software
124 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
125 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
126 * License for the specific language governing permissions and limitations under
127 * the License.
128 */
129/**
130 * Parse a textual CSS Stylesheet into a Stylesheet instance.
131 * Stylesheet is a mutable ReworkCSS AST with format similar to CSSOM.
132 * @see https://github.com/reworkcss/css
133 * @private
134 * @param {String} stylesheet
135 * @returns {css.Stylesheet} ast
136 */
137
138function parseStylesheet(stylesheet) {
139 return css.parse(stylesheet);
140}
141/**
142 * Serialize a ReworkCSS Stylesheet to a String of CSS.
143 * @private
144 * @param {css.Stylesheet} ast A Stylesheet to serialize, such as one returned from `parseStylesheet()`
145 * @param {Object} options Options to pass to `css.stringify()`
146 * @param {Boolean} [options.compress] Compress CSS output (removes comments, whitespace, etc)
147 */
148
149function serializeStylesheet(ast, options) {
150 return css.stringify(ast, options);
151}
152/**
153 * Converts a walkStyleRules() iterator to mark nodes with `.$$remove=true` instead of actually removing them.
154 * This means they can be removed in a second pass, allowing the first pass to be nondestructive (eg: to preserve mirrored sheets).
155 * @private
156 * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node.
157 * @returns {(rule) => void} nonDestructiveIterator
158 */
159
160function markOnly(predicate) {
161 return function (rule) {
162 var sel = rule.selectors;
163
164 if (predicate(rule) === false) {
165 rule.$$remove = true;
166 }
167
168 rule.$$markedSelectors = rule.selectors;
169
170 if (rule._other) {
171 rule._other.$$markedSelectors = rule._other.selectors;
172 }
173
174 rule.selectors = sel;
175 };
176}
177/**
178 * Apply filtered selectors to a rule from a previous markOnly run.
179 * @private
180 * @param {css.Rule} rule The Rule to apply marked selectors to (if they exist).
181 */
182
183function applyMarkedSelectors(rule) {
184 if (rule.$$markedSelectors) {
185 rule.selectors = rule.$$markedSelectors;
186 }
187
188 if (rule._other) {
189 applyMarkedSelectors(rule._other);
190 }
191}
192/**
193 * Recursively walk all rules in a stylesheet.
194 * @private
195 * @param {css.Rule} node A Stylesheet or Rule to descend into.
196 * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node.
197 */
198
199function walkStyleRules(node, iterator) {
200 if (node.stylesheet) return walkStyleRules(node.stylesheet, iterator);
201 node.rules = node.rules.filter(function (rule) {
202 if (rule.rules) {
203 walkStyleRules(rule, iterator);
204 }
205
206 rule._other = undefined;
207 rule.filterSelectors = filterSelectors;
208 return iterator(rule) !== false;
209 });
210}
211/**
212 * Recursively walk all rules in two identical stylesheets, filtering nodes into one or the other based on a predicate.
213 * @private
214 * @param {css.Rule} node A Stylesheet or Rule to descend into.
215 * @param {css.Rule} node2 A second tree identical to `node`
216 * @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.
217 */
218
219function walkStyleRulesWithReverseMirror(node, node2, iterator) {
220 if (node2 === null) return walkStyleRules(node, iterator);
221 if (node.stylesheet)
222 return walkStyleRulesWithReverseMirror(
223 node.stylesheet,
224 node2.stylesheet,
225 iterator
226 );
227
228 var _splitFilter = splitFilter(node.rules, node2.rules, function (
229 rule,
230 index,
231 rules,
232 rules2
233 ) {
234 var rule2 = rules2[index];
235
236 if (rule.rules) {
237 walkStyleRulesWithReverseMirror(rule, rule2, iterator);
238 }
239
240 rule._other = rule2;
241 rule.filterSelectors = filterSelectors;
242 return iterator(rule) !== false;
243 });
244
245 node.rules = _splitFilter[0];
246 node2.rules = _splitFilter[1];
247} // Like [].filter(), but applies the opposite filtering result to a second copy of the Array without a second pass.
248// This is just a quicker version of generating the compliment of the set returned from a filter operation.
249
250function splitFilter(a, b, predicate) {
251 var aOut = [];
252 var bOut = [];
253
254 for (var index = 0; index < a.length; index++) {
255 if (predicate(a[index], index, a, b)) {
256 aOut.push(a[index]);
257 } else {
258 bOut.push(a[index]);
259 }
260 }
261
262 return [aOut, bOut];
263} // can be invoked on a style rule to subset its selectors (with reverse mirroring)
264
265function filterSelectors(predicate) {
266 if (this._other) {
267 var _splitFilter2 = splitFilter(
268 this.selectors,
269 this._other.selectors,
270 predicate
271 );
272 var a = _splitFilter2[0];
273 var b = _splitFilter2[1];
274
275 this.selectors = a;
276 this._other.selectors = b;
277 } else {
278 this.selectors = this.selectors.filter(predicate);
279 }
280}
281
282function tap(inst, hook, pluginName, async, callback) {
283 if (inst.hooks) {
284 var camel = hook.replace(/-([a-z])/g, function (s, i) {
285 return i.toUpperCase();
286 });
287 inst.hooks[camel][async ? 'tapAsync' : 'tap'](pluginName, callback);
288 } else {
289 inst.plugin(hook, callback);
290 }
291}
292
293function _catch(body, recover) {
294 try {
295 var result = body();
296 } catch (e) {
297 return recover(e);
298 }
299
300 if (result && result.then) {
301 return result.then(void 0, recover);
302 }
303
304 return result;
305}
306
307var Critters = /* #__PURE__ */ (function () {
308 function Critters(options) {
309 this.options = Object.assign(
310 {
311 logLevel: 'info',
312 externalStylesheets: [],
313 path: '',
314 publicPath: '',
315 preload: 'media',
316 reduceInlineStyles: false, // compress: false,
317 },
318 options || {}
319 );
320 this.options.pruneSource = this.options.pruneSource !== false;
321 this.urlFilter = this.options.filter;
322
323 if (this.urlFilter instanceof RegExp) {
324 this.urlFilter = this.urlFilter.test.bind(this.urlFilter);
325 }
326
327 this.logger = {
328 warn: console.log,
329 info: console.log,
330 };
331 }
332 /**
333 * Read the contents of a file from Webpack's input filesystem
334 */
335
336 var _proto = Critters.prototype;
337
338 _proto.readFile = function readFile(filename) {
339 var fs = this.fs; // || compilation.outputFileSystem;
340
341 return new Promise(function (resolve, reject) {
342 var callback = function callback(err, data) {
343 if (err) reject(err);
344 else resolve(data);
345 };
346
347 if (fs && fs.readFile) {
348 fs.readFile(filename, callback);
349 } else {
350 require('fs').readFile(filename, 'utf8', callback);
351 }
352 });
353 };
354
355 _proto.process = (function (_process) {
356 function process(_x) {
357 return _process.apply(this, arguments);
358 }
359
360 process.toString = function () {
361 return _process.toString();
362 };
363
364 return process;
365 })(function (html) {
366 try {
367 var _temp5 = function _temp5() {
368 // console.log('2 ', serializeDocument(document));
369 // go through all the style tags in the document and reduce them to only critical CSS
370 var styles = _this2.getAffectedStyleTags(document);
371
372 return Promise.resolve(
373 Promise.all(
374 styles.map(function (style) {
375 return _this2.processStyle(style, document);
376 })
377 )
378 ).then(function () {
379 function _temp2() {
380 // serialize the document back to HTML and we're done
381 var output = serializeDocument(document);
382 // var end = process.hrtime.bigint();
383 // console.log('Time ' + parseFloat(end - start) / 1000000.0);
384 console.timeEnd('critters');
385
386 return output;
387 }
388
389 var _temp = (function () {
390 if (
391 _this2.options.mergeStylesheets !== false &&
392 styles.length !== 0
393 ) {
394 return Promise.resolve(
395 _this2.mergeStylesheets(document)
396 ).then(function () {});
397 }
398 })();
399
400 return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
401 });
402 };
403
404 var _this2 = this;
405
406 var outputPath = _this2.options.path;
407 var publicPath = _this2.options.publicPath;
408 // var start = process.hrtime.bigint();
409 console.time('critters');
410 // Parse the generated HTML in a DOM we can mutate
411
412 var document = createDocument(html); // if (this.options.additionalStylesheets) {
413 // const styleSheetsIncluded = [];
414 // (this.options.additionalStylesheets || []).forEach((cssFile) => {
415 // if (styleSheetsIncluded.includes(cssFile)) {
416 // return;
417 // }
418 // styleSheetsIncluded.push(cssFile);
419 // const webpackCssAssets = Object.keys(
420 // compilation.assets
421 // ).filter((file) => minimatch(file, cssFile));
422 // webpackCssAssets.map((asset) => {
423 // const tag = document.createElement('style');
424 // tag.innerHTML = compilation.assets[asset].source();
425 // document.head.appendChild(tag);
426 // });
427 // });
428 // }
429 // `external:false` skips processing of external sheets
430 // console.log('1 ', serializeDocument(document));
431
432 var _temp6 = (function () {
433 if (_this2.options.external !== false) {
434 var externalSheets = [].slice.call(
435 document.querySelectorAll('link[rel="stylesheet"]')
436 );
437 return Promise.resolve(
438 Promise.all(
439 externalSheets.map(function (link) {
440 return _this2.embedLinkedStylesheet(
441 link,
442 outputPath,
443 publicPath
444 );
445 })
446 )
447 ).then(function () {});
448 }
449 })();
450
451 return Promise.resolve(
452 _temp6 && _temp6.then ? _temp6.then(_temp5) : _temp5(_temp6)
453 );
454 } catch (e) {
455 return Promise.reject(e);
456 }
457 });
458
459 _proto.getAffectedStyleTags = function getAffectedStyleTags(document) {
460 var styles = [].slice.call(document.querySelectorAll('style')); // `inline:false` skips processing of inline stylesheets
461
462 if (this.options.reduceInlineStyles === false) {
463 return styles.filter(function (style) {
464 return style.$$external;
465 });
466 }
467
468 return styles;
469 };
470
471 _proto.mergeStylesheets = function mergeStylesheets(document) {
472 try {
473 var _temp9 = function _temp9() {
474 setNodeText(first, sheet);
475 };
476
477 var _this4 = this;
478
479 var styles = _this4.getAffectedStyleTags(document);
480
481 if (styles.length === 0) {
482 // this.logger.warn('Merging inline stylesheets into a single <style> tag skipped, no inline stylesheets to merge');
483 return Promise.resolve();
484 }
485
486 var first = styles[0];
487 var sheet = first.textContent;
488
489 for (var i = 1; i < styles.length; i++) {
490 var node = styles[i];
491 sheet += node.textContent;
492 node.remove();
493 }
494
495 var _temp10 = (function () {
496 if (_this4.options.compress !== false) {
497 var before = sheet;
498 // var processor = postcss([cssnano()]);
499 return Promise.resolve(
500 // processor.process(before, {
501 // from: undefined,
502 // })
503 before
504 ).then(function (result) {
505 // @todo sourcemap support (elsewhere first)
506 sheet = result.css;
507 });
508 }
509 })();
510
511 return Promise.resolve(_temp9(_temp10));
512 } catch (e) {
513 return Promise.reject(e);
514 }
515 };
516
517 _proto.embedLinkedStylesheet = function embedLinkedStylesheet(
518 link,
519 outputPath,
520 publicPath
521 ) {
522 try {
523 var _temp14 = function _temp14(_result2) {
524 if (_exit2) return _result2;
525 // CSS loader is only injected for the first sheet, then this becomes an empty string
526 var cssLoaderPreamble =
527 "function $loadcss(u,m,l){(l=document.createElement('link')).rel='stylesheet';l.href=u;document.head.appendChild(l)}";
528 var lazy = preloadMode === 'js-lazy';
529
530 if (lazy) {
531 cssLoaderPreamble = cssLoaderPreamble.replace(
532 'l.href',
533 "l.media='print';l.onload=function(){l.media=m};l.href"
534 );
535 } // console.log('sheet ', sheet);
536 // the reduced critical CSS gets injected into a new <style> tag
537
538 console.log('here1');
539 var style = document.createElement('style');
540 style.$$external = true;
541 style.appendChild(document.createTextNode(sheet)); // style.innerHTML = sheet;
542
543 link.parentNode.insertBefore(style, link);
544 console.log('here2');
545
546 if (
547 _this6.options.inlineThreshold &&
548 sheet.length < _this6.options.inlineThreshold
549 ) {
550 style.$$reduce = false;
551
552 _this6.logger.info(
553 '\x1B[32mInlined all of ' +
554 href +
555 ' (' +
556 sheet.length +
557 ' was below the threshold of ' +
558 _this6.options.inlineThreshold +
559 ')\x1B[39m'
560 ); // if (asset) {
561 // delete compilation.assets[relativePath];
562 // } else {
563 // this.logger.warn(
564 // ` > ${href} was not found in assets. the resource may still be emitted but will be unreferenced.`
565 // );
566 // }
567
568 link.parentNode.removeChild(link);
569 return;
570 } // drop references to webpack asset locations onto the tag, used for later reporting and in-place asset updates
571
572 style.$$name = href;
573 style.$$asset = asset;
574 style.$$assetName = relativePath; // style.$$assets = compilation.assets;
575
576 style.$$links = [link]; // Allow disabling any mutation of the stylesheet link:
577
578 if (preloadMode === false) return;
579 var noscriptFallback = false;
580
581 if (preloadMode === 'body') {
582 document.body.appendChild(link);
583 } else {
584 link.setAttribute('rel', 'preload');
585 link.setAttribute('as', 'style');
586
587 if (preloadMode === 'js' || preloadMode === 'js-lazy') {
588 var script = document.createElement('script');
589 var js =
590 cssLoaderPreamble +
591 '$loadcss(' +
592 JSON.stringify(href) +
593 (lazy ? ',' + JSON.stringify(media || 'all') : '') +
594 ')';
595 script.appendChild(document.createTextNode(js));
596 link.parentNode.insertBefore(script, link.nextSibling);
597 style.$$links.push(script);
598 cssLoaderPreamble = '';
599 noscriptFallback = true;
600 } else if (preloadMode === 'media') {
601 // @see https://github.com/filamentgroup/loadCSS/blob/af1106cfe0bf70147e22185afa7ead96c01dec48/src/loadCSS.js#L26
602 link.setAttribute('rel', 'stylesheet');
603 link.removeAttribute('as');
604 link.setAttribute('media', 'print');
605 link.setAttribute(
606 'onload',
607 "this.media='" + (media || 'all') + "'"
608 );
609 noscriptFallback = true;
610 } else if (preloadMode === 'swap') {
611 link.setAttribute('onload', "this.rel='stylesheet'");
612 noscriptFallback = true;
613 } else {
614 var bodyLink = document.createElement('link');
615 bodyLink.setAttribute('rel', 'stylesheet');
616 if (media) bodyLink.setAttribute('media', media);
617 bodyLink.setAttribute('href', href);
618 document.body.appendChild(bodyLink);
619 style.$$links.push(bodyLink);
620 }
621 }
622
623 if (_this6.options.noscriptFallback !== false && noscriptFallback) {
624 var noscript = document.createElement('noscript');
625 var noscriptLink = document.createElement('link');
626 noscriptLink.setAttribute('rel', 'stylesheet');
627 noscriptLink.setAttribute('href', href);
628 if (media) noscriptLink.setAttribute('media', media);
629 noscript.appendChild(noscriptLink);
630 link.parentNode.insertBefore(noscript, link.nextSibling);
631 style.$$links.push(noscript);
632 }
633 };
634
635 var _exit2;
636
637 var _this6 = this;
638
639 var href = link.getAttribute('href');
640 var media = link.getAttribute('media');
641 var document = link.ownerDocument;
642 var preloadMode = _this6.options.preload; // skip filtered resources, or network resources if no filter is provided
643 // if (this.urlFilter ? this.urlFilter(href) : href.match(/^(https?:)?\/\//)) {
644
645 if (
646 _this6.urlFilter
647 ? _this6.urlFilter(href)
648 : !(href || '').match(/\.css$/)
649 ) {
650 return Promise.resolve();
651 } // CHECK - the output path
652 // path on disk (with output.publicPath removed)
653
654 var normalizedPath = href.replace(/^\//, '');
655 var pathPrefix = (publicPath || '').replace(/(^\/|\/$)/g, '') + '/';
656
657 if (normalizedPath.indexOf(pathPrefix) === 0) {
658 normalizedPath = normalizedPath
659 .substring(pathPrefix.length)
660 .replace(/^\//, '');
661 }
662
663 var filename = path.resolve(outputPath, normalizedPath); // try to find a matching asset by filename in webpack's output (not yet written to disk)
664
665 var relativePath = path
666 .relative(outputPath, filename)
667 .replace(/^\.\//, '');
668 var asset = ''; // compilation.assets[relativePath];
669
670 console.log('filename ', filename); // Attempt to read from assets, falling back to a disk read
671
672 var sheet = asset && asset.source();
673
674 var _temp15 = (function () {
675 if (!sheet) {
676 var _temp16 = _catch(
677 function () {
678 return Promise.resolve(_this6.readFile(filename)).then(function (
679 _this5$readFile
680 ) {
681 sheet = _this5$readFile;
682 }); // this.logger.warn(
683 // `Stylesheet "${relativePath}" not found in assets, but a file was located on disk.${
684 // this.options.pruneSource
685 // ? ' This means pruneSource will not be applied.'
686 // : ''
687 // }`
688 // );
689 },
690 function (e) {
691 console.log(e); // this.logger.warn(`Unable to locate stylesheet: ${relativePath}`);
692
693 _exit2 = 1;
694 }
695 );
696
697 if (_temp16 && _temp16.then) return _temp16.then(function () {});
698 }
699 })();
700
701 return Promise.resolve(
702 _temp15 && _temp15.then ? _temp15.then(_temp14) : _temp14(_temp15)
703 );
704 } catch (e) {
705 return Promise.reject(e);
706 }
707 };
708 /**
709 * Parse the stylesheet within a <style> element, then reduce it to contain only rules used by the document.
710 */
711
712 _proto.processStyle = function processStyle(style, document) {
713 try {
714 var _this8 = this;
715
716 if (style.$$reduce === false) return Promise.resolve();
717 var name = style.$$name ? style.$$name.replace(/^\//, '') : 'inline CSS';
718 var options = _this8.options; // const document = style.ownerDocument;
719
720 var head = document.querySelector('head');
721 var keyframesMode = options.keyframes || 'critical'; // we also accept a boolean value for options.keyframes
722
723 if (keyframesMode === true) keyframesMode = 'all';
724 if (keyframesMode === false) keyframesMode = 'none'; // basically `.textContent`
725
726 var sheet =
727 style.childNodes.length > 0 &&
728 [].map
729 .call(style.childNodes, function (node) {
730 return node.nodeValue;
731 })
732 .join('\n'); // store a reference to the previous serialized stylesheet for reporting stats
733
734 var before = sheet; // Skip empty stylesheets
735
736 if (!sheet) return Promise.resolve();
737 var ast = parseStylesheet(sheet);
738 var astInverse = options.pruneSource ? parseStylesheet(sheet) : null; // a string to search for font names (very loose)
739
740 var criticalFonts = '';
741 var failedSelectors = [];
742 var criticalKeyframeNames = []; // Walk all CSS rules, marking unused rules with `.$$remove=true` for removal in the second pass.
743 // This first pass is also used to collect font and keyframe usage used in the second pass.
744
745 walkStyleRules(
746 ast,
747 markOnly(function (rule) {
748 if (rule.type === 'rule') {
749 // Filter the selector list down to only those match
750 rule.filterSelectors(function (sel) {
751 // Strip pseudo-elements and pseudo-classes, since we only care that their associated elements exist.
752 // This means any selector for a pseudo-element or having a pseudo-class will be inlined if the rest of the selector matches.
753 if (sel !== ':root') {
754 sel = sel
755 .replace(/(?:>\s*)?::?[a-z-]+\s*(\{|$)/gi, '$1')
756 .trim();
757 }
758
759 if (!sel) return false;
760
761 try {
762 return document.querySelector(sel) != null;
763 } catch (e) {
764 failedSelectors.push(sel + ' -> ' + e.message);
765 return false;
766 }
767 }); // If there are no matched selectors, remove the rule:
768
769 if (rule.selectors.length === 0) {
770 return false;
771 }
772
773 if (rule.declarations) {
774 for (var i = 0; i < rule.declarations.length; i++) {
775 var decl = rule.declarations[i]; // detect used fonts
776
777 if (
778 decl.property &&
779 decl.property.match(/\bfont(-family)?\b/i)
780 ) {
781 criticalFonts += ' ' + decl.value;
782 } // detect used keyframes
783
784 if (
785 decl.property === 'animation' ||
786 decl.property === 'animation-name'
787 ) {
788 // @todo: parse animation declarations and extract only the name. for now we'll do a lazy match.
789 var names = decl.value.split(/\s+/);
790
791 for (var j = 0; j < names.length; j++) {
792 var _name = names[j].trim();
793
794 if (_name) criticalKeyframeNames.push(_name);
795 }
796 }
797 }
798 }
799 } // keep font rules, they're handled in the second pass:
800
801 if (rule.type === 'font-face') return; // If there are no remaining rules, remove the whole rule:
802
803 var rules =
804 rule.rules &&
805 rule.rules.filter(function (rule) {
806 return !rule.$$remove;
807 });
808 return !rules || rules.length !== 0;
809 })
810 );
811
812 if (failedSelectors.length !== 0) {
813 _this8.logger.warn(
814 failedSelectors.length +
815 ' rules skipped due to selector errors:\n ' +
816 failedSelectors.join('\n ')
817 );
818 }
819
820 var shouldPreloadFonts =
821 options.fonts === true || options.preloadFonts === true;
822 var shouldInlineFonts =
823 options.fonts !== false && options.inlineFonts === true;
824 var preloadedFonts = []; // Second pass, using data picked up from the first
825
826 walkStyleRulesWithReverseMirror(ast, astInverse, function (rule) {
827 // remove any rules marked in the first pass
828 if (rule.$$remove === true) return false;
829 applyMarkedSelectors(rule); // prune @keyframes rules
830
831 if (rule.type === 'keyframes') {
832 if (keyframesMode === 'none') return false;
833 if (keyframesMode === 'all') return true;
834 return criticalKeyframeNames.indexOf(rule.name) !== -1;
835 } // prune @font-face rules
836
837 if (rule.type === 'font-face') {
838 var family, src;
839
840 for (var i = 0; i < rule.declarations.length; i++) {
841 var decl = rule.declarations[i];
842
843 if (decl.property === 'src') {
844 // @todo parse this properly and generate multiple preloads with type="font/woff2" etc
845 src = (decl.value.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/) ||
846 [])[2];
847 } else if (decl.property === 'font-family') {
848 family = decl.value;
849 }
850 }
851
852 if (src && shouldPreloadFonts && preloadedFonts.indexOf(src) === -1) {
853 preloadedFonts.push(src);
854 var preload = document.createElement('link');
855 preload.setAttribute('rel', 'preload');
856 preload.setAttribute('as', 'font');
857 preload.setAttribute('crossorigin', 'anonymous');
858 preload.setAttribute('href', src.trim());
859 head.appendChild(preload);
860 } // if we're missing info, if the font is unused, or if critical font inlining is disabled, remove the rule:
861
862 if (
863 !family ||
864 !src ||
865 criticalFonts.indexOf(family) === -1 ||
866 !shouldInlineFonts
867 ) {
868 return false;
869 }
870 }
871 });
872 sheet = serializeStylesheet(ast, {
873 compress: _this8.options.compress !== false,
874 }).trim(); // If all rules were removed, get rid of the style element entirely
875
876 if (sheet.trim().length === 0) {
877 if (style.parentNode) {
878 style.parentNode.removeChild(style);
879 }
880
881 return Promise.resolve();
882 }
883
884 var afterText = '';
885
886 if (options.pruneSource) {
887 var sheetInverse = serializeStylesheet(astInverse, {
888 compress: _this8.options.compress !== false,
889 }); // const asset = style.$$asset;
890 // if (asset) {
891 // if external stylesheet would be below minimum size, just inline everything
892
893 var minSize = _this8.options.minimumExternalSize;
894
895 if (minSize && sheetInverse.length < minSize) {
896 _this8.logger.info(
897 '\x1B[32mInlined all of ' +
898 name +
899 ' (non-critical external stylesheet would have been ' +
900 sheetInverse.length +
901 'b, which was below the threshold of ' +
902 minSize +
903 ')\x1B[39m'
904 );
905
906 setNodeText(style, before); // remove any associated external resources/loaders:
907
908 if (style.$$links) {
909 for (
910 var _iterator = _createForOfIteratorHelperLoose(style.$$links),
911 _step;
912 !(_step = _iterator()).done;
913
914 ) {
915 var link = _step.value;
916 var parent = link.parentNode;
917 if (parent) parent.removeChild(link);
918 }
919 } // delete the webpack asset:
920
921 delete style.$$assets[style.$$assetName];
922 return Promise.resolve();
923 }
924
925 var percent = (sheetInverse.length / before.length) * 100;
926 afterText =
927 ', reducing non-inlined size ' +
928 (percent | 0) +
929 '% to ' +
930 prettyBytes(sheetInverse.length); // style.$$assets[style.$$assetName] = new sources.LineToLineMappedSource(
931 // sheetInverse,
932 // style.$$assetName,
933 // before
934 // );
935 // } else {
936 // this.logger.warn(
937 // 'pruneSource is enabled, but a style (' +
938 // name +
939 // ') has no corresponding Webpack asset.'
940 // );
941 // }
942 // }
943 // replace the inline stylesheet with its critical'd counterpart
944
945 setNodeText(style, sheet); // output stats
946 // const percent = ((sheet.length / before.length) * 100) | 0;
947 // this.logger.info(
948 // '\u001b[32mInlined ' +
949 // prettyBytes(sheet.length) +
950 // ' (' +
951 // percent +
952 // '% of original ' +
953 // prettyBytes(before.length) +
954 // ') of ' +
955 // name +
956 // afterText +
957 // '.\u001b[39m'
958 // );
959 }
960
961 return Promise.resolve();
962 } catch (e) {
963 return Promise.reject(e);
964 }
965 };
966
967 return Critters;
968})(); // Original code picks 1 html and during prune removes CSS files from assets. What if theres another dependency
969
970function _catch$1(body, recover) {
971 try {
972 var result = body();
973 } catch (e) {
974 return recover(e);
975 }
976
977 if (result && result.then) {
978 return result.then(void 0, recover);
979 }
980
981 return result;
982}
983
984var PLUGIN_NAME = 'critters-webpack-plugin';
985/**
986 * The mechanism to use for lazy-loading stylesheets.
987 * _[JS]_ indicates that a strategy requires JavaScript (falls back to `<noscript>`).
988 *
989 * - **default:** Move stylesheet links to the end of the document and insert preload meta tags in their place.
990 * - **"body":** Move all external stylesheet links to the end of the document.
991 * - **"media":** Load stylesheets asynchronously by adding `media="not x"` and removing once loaded. _[JS]_
992 * - **"swap":** Convert stylesheet links to preloads that swap to `rel="stylesheet"` once loaded. _[JS]_
993 * - **"js":** Inject an asynchronous CSS loader similar to [LoadCSS](https://github.com/filamentgroup/loadCSS) and use it to load stylesheets. _[JS]_
994 * - **"js-lazy":** Like `"js"`, but the stylesheet is disabled until fully loaded.
995 * @typedef {(default|'body'|'media'|'swap'|'js'|'js-lazy')} PreloadStrategy
996 * @public
997 */
998
999/**
1000 * Controls which keyframes rules are inlined.
1001 *
1002 * - **"critical":** _(default)_ inline keyframes rules that are used by the critical CSS.
1003 * - **"all":** Inline all keyframes rules.
1004 * - **"none":** Remove all keyframes rules.
1005 * @typedef {('critical'|'all'|'none')} KeyframeStrategy
1006 * @private
1007 * @property {String} keyframes Which {@link KeyframeStrategy keyframe strategy} to use (default: `critical`)_
1008 */
1009
1010/**
1011 * Controls log level of the plugin. Specifies the level the logger should use. A logger will
1012 * not produce output for any log level beneath the specified level. Available levels and order
1013 * are:
1014 *
1015 * - **"info"** _(default)_
1016 * - **"warn"**
1017 * - **"error"**
1018 * - **"trace"**
1019 * - **"debug"**
1020 * - **"silent"**
1021 * @typedef {('info'|'warn'|'error'|'trace'|'debug'|'silent')} LogLevel
1022 * @public
1023 */
1024
1025/**
1026 * All optional. Pass them to `new Critters({ ... })`.
1027 * @public
1028 * @typedef Options
1029 * @property {Boolean} external Inline styles from external stylesheets _(default: `true`)_
1030 * @property {Number} inlineThreshold Inline external stylesheets smaller than a given size _(default: `0`)_
1031 * @property {Number} minimumExternalSize If the non-critical external stylesheet would be below this size, just inline it _(default: `0`)_
1032 * @property {Boolean} pruneSource Remove inlined rules from the external stylesheet _(default: `true`)_
1033 * @property {Boolean} mergeStylesheets Merged inlined stylesheets into a single <style> tag _(default: `true`)_
1034 * @property {String[]} additionalStylesheets Glob for matching other stylesheets to be used while looking for critical CSS _(default: ``)_.
1035 * @property {String} preload Which {@link PreloadStrategy preload strategy} to use
1036 * @property {Boolean} noscriptFallback Add `<noscript>` fallback to JS-based strategies
1037 * @property {Boolean} inlineFonts Inline critical font-face rules _(default: `false`)_
1038 * @property {Boolean} preloadFonts Preloads critical fonts _(default: `true`)_
1039 * @property {Boolean} fonts Shorthand for setting `inlineFonts`+`preloadFonts`
1040 * - Values:
1041 * - `true` to inline critical font-face rules and preload the fonts
1042 * - `false` to don't inline any font-face rules and don't preload fonts
1043 * @property {String} keyframes Controls which keyframes rules are inlined.
1044 * - Values:
1045 * - `"critical"`: _(default)_ inline keyframes rules used by the critical CSS
1046 * - `"all"` inline all keyframes rules
1047 * - `"none"` remove all keyframes rules
1048 * @property {Boolean} compress Compress resulting critical CSS _(default: `true`)_
1049 * @property {String} logLevel Controls {@link LogLevel log level} of the plugin _(default: `"info"`)_
1050 */
1051
1052/**
1053 * Create a Critters plugin instance with the given options.
1054 * @public
1055 * @param {Options} options Options to control how Critters inlines CSS.
1056 * @example
1057 * // webpack.config.js
1058 * module.exports = {
1059 * plugins: [
1060 * new Critters({
1061 * // Outputs: <link rel="preload" onload="this.rel='stylesheet'">
1062 * preload: 'swap',
1063 *
1064 * // Don't inline critical font-face rules, but preload the font URLs:
1065 * preloadFonts: true
1066 * })
1067 * ]
1068 * }
1069 */
1070
1071var Critters$1 = /* #__PURE__ */ (function () {
1072 /** @private */
1073 function Critters(options) {
1074 this.options = Object.assign(
1075 {
1076 logLevel: 'info',
1077 externalStylesheets: [],
1078 },
1079 options || {}
1080 );
1081 this.options.pruneSource = this.options.pruneSource !== false;
1082 this.urlFilter = this.options.filter;
1083
1084 if (this.urlFilter instanceof RegExp) {
1085 this.urlFilter = this.urlFilter.test.bind(this.urlFilter);
1086 }
1087
1088 this.logger = log({
1089 name: 'Critters',
1090 unique: true,
1091 level: this.options.logLevel,
1092 });
1093 }
1094 /**
1095 * Invoked by Webpack during plugin initialization
1096 */
1097
1098 var _proto = Critters.prototype;
1099
1100 _proto.apply = function apply(compiler) {
1101 var _this = this;
1102
1103 // hook into the compiler to get a Compilation instance...
1104 tap(compiler, 'compilation', PLUGIN_NAME, false, function (compilation) {
1105 // ... which is how we get an "after" hook into html-webpack-plugin's HTML generation.
1106 if (
1107 compilation.hooks &&
1108 compilation.hooks.htmlWebpackPluginAfterHtmlProcessing
1109 ) {
1110 tap(
1111 compilation,
1112 'html-webpack-plugin-after-html-processing',
1113 PLUGIN_NAME,
1114 true,
1115 function (htmlPluginData, callback) {
1116 _this
1117 .process(compiler, compilation, htmlPluginData.html)
1118 .then(function (html) {
1119 callback(null, {
1120 html: html,
1121 });
1122 })
1123 .catch(callback);
1124 }
1125 );
1126 } else {
1127 // If html-webpack-plugin isn't used, process the first HTML asset as an optimize step
1128 tap(compilation, 'optimize-assets', PLUGIN_NAME, true, function (
1129 assets,
1130 callback
1131 ) {
1132 var htmlAssetName;
1133
1134 for (var name in assets) {
1135 if (name.match(/\.html$/)) {
1136 htmlAssetName = name;
1137 break;
1138 }
1139 }
1140
1141 if (!htmlAssetName) {
1142 return callback(Error('Could not find HTML asset.'));
1143 }
1144
1145 var html = assets[htmlAssetName].source();
1146 if (!html) return callback(Error('Empty HTML asset.'));
1147
1148 _this
1149 .process(compiler, compilation, String(html))
1150 .then(function (html) {
1151 assets[htmlAssetName] = new sources.RawSource(html);
1152 callback();
1153 })
1154 .catch(callback);
1155 });
1156 }
1157 });
1158 };
1159 /**
1160 * Read the contents of a file from Webpack's input filesystem
1161 */
1162
1163 _proto.readFile = function readFile(compilation, filename) {
1164 var fs = this.fs || compilation.outputFileSystem;
1165 return new Promise(function (resolve, reject) {
1166 var callback = function callback(err, data) {
1167 if (err) reject(err);
1168 else resolve(data);
1169 };
1170
1171 if (fs && fs.readFile) {
1172 fs.readFile(filename, callback);
1173 } else {
1174 require('fs').readFile(filename, 'utf8', callback);
1175 }
1176 });
1177 };
1178 /**
1179 * Apply critical CSS processing to html-webpack-plugin
1180 */
1181
1182 _proto.process = function process(compiler, compilation, html) {
1183 try {
1184 var _temp5 = function _temp5() {
1185 // go through all the style tags in the document and reduce them to only critical CSS
1186 var styles = [].slice.call(document.querySelectorAll('style'));
1187 return Promise.resolve(
1188 Promise.all(
1189 styles.map(function (style) {
1190 return _this3.processStyle(style, document);
1191 })
1192 )
1193 ).then(function () {
1194 function _temp2() {
1195 // serialize the document back to HTML and we're done
1196 return serializeDocument(document);
1197 }
1198
1199 var _temp = (function () {
1200 if (
1201 _this3.options.mergeStylesheets !== false &&
1202 styles.length !== 0
1203 ) {
1204 return Promise.resolve(
1205 _this3.mergeStylesheets(document)
1206 ).then(function () {});
1207 }
1208 })();
1209
1210 return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
1211 });
1212 };
1213
1214 var _this3 = this;
1215
1216 var outputPath = compiler.options.output.path;
1217 var publicPath = compiler.options.output.publicPath; // Parse the generated HTML in a DOM we can mutate
1218
1219 var document = createDocument(html);
1220
1221 if (_this3.options.additionalStylesheets) {
1222 var styleSheetsIncluded = [];
1223 (_this3.options.additionalStylesheets || []).forEach(function (
1224 cssFile
1225 ) {
1226 if (styleSheetsIncluded.includes(cssFile)) {
1227 return;
1228 }
1229
1230 styleSheetsIncluded.push(cssFile);
1231 var webpackCssAssets = Object.keys(compilation.assets).filter(
1232 function (file) {
1233 return minimatch(file, cssFile);
1234 }
1235 );
1236 webpackCssAssets.map(function (asset) {
1237 var tag = document.createElement('style');
1238 tag.innerHTML = compilation.assets[asset].source();
1239 document.head.appendChild(tag);
1240 });
1241 });
1242 } // `external:false` skips processing of external sheets
1243
1244 var _temp6 = (function () {
1245 if (_this3.options.external !== false) {
1246 var externalSheets = [].slice.call(
1247 document.querySelectorAll('link[rel="stylesheet"]')
1248 );
1249 return Promise.resolve(
1250 Promise.all(
1251 externalSheets.map(function (link) {
1252 return _this3.embedLinkedStylesheet(
1253 link,
1254 compilation,
1255 outputPath,
1256 publicPath
1257 );
1258 })
1259 )
1260 ).then(function () {});
1261 }
1262 })();
1263
1264 return Promise.resolve(
1265 _temp6 && _temp6.then ? _temp6.then(_temp5) : _temp5(_temp6)
1266 );
1267 } catch (e) {
1268 return Promise.reject(e);
1269 }
1270 };
1271
1272 _proto.mergeStylesheets = function mergeStylesheets(document) {
1273 try {
1274 var _temp9 = function _temp9() {
1275 setNodeText(first, sheet);
1276 };
1277
1278 var _this5 = this;
1279
1280 var styles = [].slice.call(document.querySelectorAll('style'));
1281
1282 if (styles.length === 0) {
1283 _this5.logger.warn(
1284 'Merging inline stylesheets into a single <style> tag skipped, no inline stylesheets to merge'
1285 );
1286
1287 return Promise.resolve();
1288 }
1289
1290 var first = styles[0];
1291 var sheet = first.textContent;
1292
1293 for (var i = 1; i < styles.length; i++) {
1294 var node = styles[i];
1295 sheet += node.textContent;
1296 node.remove();
1297 }
1298
1299 var _temp10 = (function () {
1300 if (_this5.options.compress !== false) {
1301 var before = sheet;
1302 var processor = postcss([cssnano()]);
1303 return Promise.resolve(
1304 processor.process(before, {
1305 from: undefined,
1306 })
1307 ).then(function (result) {
1308 // @todo sourcemap support (elsewhere first)
1309 sheet = result.css;
1310 });
1311 }
1312 })();
1313
1314 return Promise.resolve(
1315 _temp10 && _temp10.then ? _temp10.then(_temp9) : _temp9(_temp10)
1316 );
1317 } catch (e) {
1318 return Promise.reject(e);
1319 }
1320 };
1321 /**
1322 * Inline the target stylesheet referred to by a <link rel="stylesheet"> (assuming it passes `options.filter`)
1323 */
1324
1325 _proto.embedLinkedStylesheet = function embedLinkedStylesheet(
1326 link,
1327 compilation,
1328 outputPath,
1329 publicPath
1330 ) {
1331 try {
1332 var _temp14 = function _temp14(_result2) {
1333 if (_exit2) return _result2;
1334 // CSS loader is only injected for the first sheet, then this becomes an empty string
1335 var cssLoaderPreamble =
1336 "function $loadcss(u,m,l){(l=document.createElement('link')).rel='stylesheet';l.href=u;document.head.appendChild(l)}";
1337 var lazy = preloadMode === 'js-lazy';
1338
1339 if (lazy) {
1340 cssLoaderPreamble = cssLoaderPreamble.replace(
1341 'l.href',
1342 "l.media='print';l.onload=function(){l.media=m};l.href"
1343 );
1344 } // the reduced critical CSS gets injected into a new <style> tag
1345
1346 var style = document.createElement('style');
1347 style.appendChild(document.createTextNode(sheet));
1348 link.parentNode.insertBefore(style, link);
1349
1350 if (
1351 _this7.options.inlineThreshold &&
1352 sheet.length < _this7.options.inlineThreshold
1353 ) {
1354 style.$$reduce = false;
1355
1356 _this7.logger.info(
1357 '\x1B[32mInlined all of ' +
1358 href +
1359 ' (' +
1360 sheet.length +
1361 ' was below the threshold of ' +
1362 _this7.options.inlineThreshold +
1363 ')\x1B[39m'
1364 );
1365
1366 if (asset) {
1367 delete compilation.assets[relativePath];
1368 } else {
1369 _this7.logger.warn(
1370 ' > ' +
1371 href +
1372 ' was not found in assets. the resource may still be emitted but will be unreferenced.'
1373 );
1374 }
1375
1376 link.parentNode.removeChild(link);
1377 return;
1378 } // drop references to webpack asset locations onto the tag, used for later reporting and in-place asset updates
1379
1380 style.$$name = href;
1381 style.$$asset = asset;
1382 style.$$assetName = relativePath;
1383 style.$$assets = compilation.assets;
1384 style.$$links = [link]; // Allow disabling any mutation of the stylesheet link:
1385
1386 if (preloadMode === false) return;
1387 var noscriptFallback = false;
1388
1389 if (preloadMode === 'body') {
1390 document.body.appendChild(link);
1391 } else {
1392 link.setAttribute('rel', 'preload');
1393 link.setAttribute('as', 'style');
1394
1395 if (preloadMode === 'js' || preloadMode === 'js-lazy') {
1396 var script = document.createElement('script');
1397 var js =
1398 cssLoaderPreamble +
1399 '$loadcss(' +
1400 JSON.stringify(href) +
1401 (lazy ? ',' + JSON.stringify(media || 'all') : '') +
1402 ')';
1403 script.appendChild(document.createTextNode(js));
1404 link.parentNode.insertBefore(script, link.nextSibling);
1405 style.$$links.push(script);
1406 cssLoaderPreamble = '';
1407 noscriptFallback = true;
1408 } else if (preloadMode === 'media') {
1409 // @see https://github.com/filamentgroup/loadCSS/blob/af1106cfe0bf70147e22185afa7ead96c01dec48/src/loadCSS.js#L26
1410 link.setAttribute('rel', 'stylesheet');
1411 link.removeAttribute('as');
1412 link.setAttribute('media', 'print');
1413 link.setAttribute(
1414 'onload',
1415 "this.media='" + (media || 'all') + "'"
1416 );
1417 noscriptFallback = true;
1418 } else if (preloadMode === 'swap') {
1419 link.setAttribute('onload', "this.rel='stylesheet'");
1420 noscriptFallback = true;
1421 } else {
1422 var bodyLink = document.createElement('link');
1423 bodyLink.setAttribute('rel', 'stylesheet');
1424 if (media) bodyLink.setAttribute('media', media);
1425 bodyLink.setAttribute('href', href);
1426 document.body.appendChild(bodyLink);
1427 style.$$links.push(bodyLink);
1428 }
1429 }
1430
1431 if (_this7.options.noscriptFallback !== false && noscriptFallback) {
1432 var noscript = document.createElement('noscript');
1433 var noscriptLink = document.createElement('link');
1434 noscriptLink.setAttribute('rel', 'stylesheet');
1435 noscriptLink.setAttribute('href', href);
1436 if (media) noscriptLink.setAttribute('media', media);
1437 noscript.appendChild(noscriptLink);
1438 link.parentNode.insertBefore(noscript, link.nextSibling);
1439 style.$$links.push(noscript);
1440 }
1441 };
1442
1443 var _exit2;
1444
1445 var _this7 = this;
1446
1447 var href = link.getAttribute('href');
1448 var media = link.getAttribute('media');
1449 var document = link.ownerDocument;
1450 var preloadMode = _this7.options.preload; // skip filtered resources, or network resources if no filter is provided
1451
1452 if (
1453 _this7.urlFilter
1454 ? _this7.urlFilter(href)
1455 : href.match(/^(https?:)?\/\//)
1456 ) {
1457 return Promise.resolve();
1458 } // path on disk (with output.publicPath removed)
1459
1460 var normalizedPath = href.replace(/^\//, '');
1461 var pathPrefix = (publicPath || '').replace(/(^\/|\/$)/g, '') + '/';
1462
1463 if (normalizedPath.indexOf(pathPrefix) === 0) {
1464 normalizedPath = normalizedPath
1465 .substring(pathPrefix.length)
1466 .replace(/^\//, '');
1467 }
1468
1469 var filename = path.resolve(outputPath, normalizedPath); // try to find a matching asset by filename in webpack's output (not yet written to disk)
1470
1471 var relativePath = path
1472 .relative(outputPath, filename)
1473 .replace(/^\.\//, '');
1474 var asset = compilation.assets[relativePath]; // Attempt to read from assets, falling back to a disk read
1475
1476 var sheet = asset && asset.source();
1477
1478 var _temp15 = (function () {
1479 if (!sheet) {
1480 var _temp16 = _catch$1(
1481 function () {
1482 return Promise.resolve(
1483 _this7.readFile(compilation, filename)
1484 ).then(function (_this6$readFile) {
1485 sheet = _this6$readFile;
1486
1487 _this7.logger.warn(
1488 'Stylesheet "' +
1489 relativePath +
1490 '" not found in assets, but a file was located on disk.' +
1491 (_this7.options.pruneSource
1492 ? ' This means pruneSource will not be applied.'
1493 : '')
1494 );
1495 });
1496 },
1497 function () {
1498 _this7.logger.warn(
1499 'Unable to locate stylesheet: ' + relativePath
1500 );
1501
1502 _exit2 = 1;
1503 }
1504 );
1505
1506 if (_temp16 && _temp16.then) return _temp16.then(function () {});
1507 }
1508 })();
1509
1510 return Promise.resolve(
1511 _temp15 && _temp15.then ? _temp15.then(_temp14) : _temp14(_temp15)
1512 );
1513 } catch (e) {
1514 return Promise.reject(e);
1515 }
1516 };
1517 /**
1518 * Parse the stylesheet within a <style> element, then reduce it to contain only rules used by the document.
1519 */
1520
1521 _proto.processStyle = function processStyle(style) {
1522 try {
1523 var _this9 = this;
1524
1525 if (style.$$reduce === false) return Promise.resolve();
1526 var name = style.$$name ? style.$$name.replace(/^\//, '') : 'inline CSS';
1527 var options = _this9.options;
1528 var document = style.ownerDocument;
1529 var head = document.querySelector('head');
1530 var keyframesMode = options.keyframes || 'critical'; // we also accept a boolean value for options.keyframes
1531
1532 if (keyframesMode === true) keyframesMode = 'all';
1533 if (keyframesMode === false) keyframesMode = 'none'; // basically `.textContent`
1534
1535 var sheet =
1536 style.childNodes.length > 0 &&
1537 [].map
1538 .call(style.childNodes, function (node) {
1539 return node.nodeValue;
1540 })
1541 .join('\n'); // store a reference to the previous serialized stylesheet for reporting stats
1542
1543 var before = sheet; // Skip empty stylesheets
1544
1545 if (!sheet) return Promise.resolve();
1546 var ast = parseStylesheet(sheet);
1547 var astInverse = options.pruneSource ? parseStylesheet(sheet) : null; // a string to search for font names (very loose)
1548
1549 var criticalFonts = '';
1550 var failedSelectors = [];
1551 var criticalKeyframeNames = []; // Walk all CSS rules, marking unused rules with `.$$remove=true` for removal in the second pass.
1552 // This first pass is also used to collect font and keyframe usage used in the second pass.
1553
1554 walkStyleRules(
1555 ast,
1556 markOnly(function (rule) {
1557 if (rule.type === 'rule') {
1558 // Filter the selector list down to only those match
1559 rule.filterSelectors(function (sel) {
1560 // Strip pseudo-elements and pseudo-classes, since we only care that their associated elements exist.
1561 // This means any selector for a pseudo-element or having a pseudo-class will be inlined if the rest of the selector matches.
1562 if (sel !== ':root') {
1563 sel = sel
1564 .replace(/(?:>\s*)?::?[a-z-]+\s*(\{|$)/gi, '$1')
1565 .trim();
1566 }
1567
1568 if (!sel) return false;
1569
1570 try {
1571 return document.querySelector(sel) != null;
1572 } catch (e) {
1573 failedSelectors.push(sel + ' -> ' + e.message);
1574 return false;
1575 }
1576 }); // If there are no matched selectors, remove the rule:
1577
1578 if (rule.selectors.length === 0) {
1579 return false;
1580 }
1581
1582 if (rule.declarations) {
1583 for (var i = 0; i < rule.declarations.length; i++) {
1584 var decl = rule.declarations[i]; // detect used fonts
1585
1586 if (
1587 decl.property &&
1588 decl.property.match(/\bfont(-family)?\b/i)
1589 ) {
1590 criticalFonts += ' ' + decl.value;
1591 } // detect used keyframes
1592
1593 if (
1594 decl.property === 'animation' ||
1595 decl.property === 'animation-name'
1596 ) {
1597 // @todo: parse animation declarations and extract only the name. for now we'll do a lazy match.
1598 var names = decl.value.split(/\s+/);
1599
1600 for (var j = 0; j < names.length; j++) {
1601 var _name = names[j].trim();
1602
1603 if (_name) criticalKeyframeNames.push(_name);
1604 }
1605 }
1606 }
1607 }
1608 } // keep font rules, they're handled in the second pass:
1609
1610 if (rule.type === 'font-face') return; // If there are no remaining rules, remove the whole rule:
1611
1612 var rules =
1613 rule.rules &&
1614 rule.rules.filter(function (rule) {
1615 return !rule.$$remove;
1616 });
1617 return !rules || rules.length !== 0;
1618 })
1619 );
1620
1621 if (failedSelectors.length !== 0) {
1622 _this9.logger.warn(
1623 failedSelectors.length +
1624 ' rules skipped due to selector errors:\n ' +
1625 failedSelectors.join('\n ')
1626 );
1627 }
1628
1629 var shouldPreloadFonts =
1630 options.fonts === true || options.preloadFonts === true;
1631 var shouldInlineFonts =
1632 options.fonts !== false && options.inlineFonts === true;
1633 var preloadedFonts = []; // Second pass, using data picked up from the first
1634
1635 walkStyleRulesWithReverseMirror(ast, astInverse, function (rule) {
1636 // remove any rules marked in the first pass
1637 if (rule.$$remove === true) return false;
1638 applyMarkedSelectors(rule); // prune @keyframes rules
1639
1640 if (rule.type === 'keyframes') {
1641 if (keyframesMode === 'none') return false;
1642 if (keyframesMode === 'all') return true;
1643 return criticalKeyframeNames.indexOf(rule.name) !== -1;
1644 } // prune @font-face rules
1645
1646 if (rule.type === 'font-face') {
1647 var family, src;
1648
1649 for (var i = 0; i < rule.declarations.length; i++) {
1650 var decl = rule.declarations[i];
1651
1652 if (decl.property === 'src') {
1653 // @todo parse this properly and generate multiple preloads with type="font/woff2" etc
1654 src = (decl.value.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/) ||
1655 [])[2];
1656 } else if (decl.property === 'font-family') {
1657 family = decl.value;
1658 }
1659 }
1660
1661 if (src && shouldPreloadFonts && preloadedFonts.indexOf(src) === -1) {
1662 preloadedFonts.push(src);
1663 var preload = document.createElement('link');
1664 preload.setAttribute('rel', 'preload');
1665 preload.setAttribute('as', 'font');
1666 preload.setAttribute('crossorigin', 'anonymous');
1667 preload.setAttribute('href', src.trim());
1668 head.appendChild(preload);
1669 } // if we're missing info, if the font is unused, or if critical font inlining is disabled, remove the rule:
1670
1671 if (
1672 !family ||
1673 !src ||
1674 criticalFonts.indexOf(family) === -1 ||
1675 !shouldInlineFonts
1676 ) {
1677 return false;
1678 }
1679 }
1680 });
1681 sheet = serializeStylesheet(ast, {
1682 compress: _this9.options.compress !== false,
1683 }).trim(); // If all rules were removed, get rid of the style element entirely
1684
1685 if (sheet.trim().length === 0) {
1686 if (style.parentNode) {
1687 style.parentNode.removeChild(style);
1688 }
1689
1690 return Promise.resolve();
1691 }
1692
1693 var afterText = '';
1694
1695 if (options.pruneSource) {
1696 var sheetInverse = serializeStylesheet(astInverse, {
1697 compress: _this9.options.compress !== false,
1698 });
1699 var asset = style.$$asset;
1700
1701 if (asset) {
1702 // if external stylesheet would be below minimum size, just inline everything
1703 var minSize = _this9.options.minimumExternalSize;
1704
1705 if (minSize && sheetInverse.length < minSize) {
1706 _this9.logger.info(
1707 '\x1B[32mInlined all of ' +
1708 name +
1709 ' (non-critical external stylesheet would have been ' +
1710 sheetInverse.length +
1711 'b, which was below the threshold of ' +
1712 minSize +
1713 ')\x1B[39m'
1714 );
1715
1716 setNodeText(style, before); // remove any associated external resources/loaders:
1717
1718 if (style.$$links) {
1719 for (
1720 var _iterator = _createForOfIteratorHelperLoose(style.$$links),
1721 _step;
1722 !(_step = _iterator()).done;
1723
1724 ) {
1725 var link = _step.value;
1726 var parent = link.parentNode;
1727 if (parent) parent.removeChild(link);
1728 }
1729 } // delete the webpack asset:
1730
1731 delete style.$$assets[style.$$assetName];
1732 return Promise.resolve();
1733 }
1734
1735 var _percent = (sheetInverse.length / before.length) * 100;
1736
1737 afterText =
1738 ', reducing non-inlined size ' +
1739 (_percent | 0) +
1740 '% to ' +
1741 prettyBytes(sheetInverse.length);
1742 style.$$assets[
1743 style.$$assetName
1744 ] = new sources.LineToLineMappedSource(
1745 sheetInverse,
1746 style.$$assetName,
1747 before
1748 );
1749 } else {
1750 _this9.logger.warn(
1751 'pruneSource is enabled, but a style (' +
1752 name +
1753 ') has no corresponding Webpack asset.'
1754 );
1755 }
1756 } // replace the inline stylesheet with its critical'd counterpart
1757
1758 setNodeText(style, sheet); // output stats
1759
1760 var percent = ((sheet.length / before.length) * 100) | 0;
1761
1762 _this9.logger.info(
1763 '\x1B[32mInlined ' +
1764 prettyBytes(sheet.length) +
1765 ' (' +
1766 percent +
1767 '% of original ' +
1768 prettyBytes(before.length) +
1769 ') of ' +
1770 name +
1771 afterText +
1772 '.\x1B[39m'
1773 );
1774
1775 return Promise.resolve();
1776 } catch (e) {
1777 return Promise.reject(e);
1778 }
1779 };
1780
1781 return Critters;
1782})();
1783
1784exports.core = Critters;
1785exports.default = Critters$1;
1786// # sourceMappingURL=critters.js.map
Note: See TracBrowser for help on using the repository browser.