1 | function _interopDefault(ex) {
|
---|
2 | return ex && typeof ex === 'object' && 'default' in ex ? ex.default : ex;
|
---|
3 | }
|
---|
4 |
|
---|
5 | var path = _interopDefault(require('path'));
|
---|
6 | var prettyBytes = _interopDefault(require('pretty-bytes'));
|
---|
7 | var sources = _interopDefault(require('webpack-sources'));
|
---|
8 | var postcss = _interopDefault(require('postcss'));
|
---|
9 | var cssnano = _interopDefault(require('cssnano'));
|
---|
10 | var log = _interopDefault(require('webpack-log'));
|
---|
11 | var minimatch = _interopDefault(require('minimatch'));
|
---|
12 | var jsdom = require('jsdom');
|
---|
13 | var css = _interopDefault(require('css'));
|
---|
14 |
|
---|
15 | function _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 |
|
---|
25 | function _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 |
|
---|
33 | function _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 |
|
---|
86 | function 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 |
|
---|
101 | function serializeDocument(document) {
|
---|
102 | return document.$jsdom.serialize();
|
---|
103 | }
|
---|
104 | /** Like node.textContent, except it works */
|
---|
105 |
|
---|
106 | function 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 |
|
---|
138 | function 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 |
|
---|
149 | function 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 |
|
---|
160 | function 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 |
|
---|
183 | function 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 |
|
---|
199 | function 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 |
|
---|
219 | function 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 |
|
---|
250 | function 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 |
|
---|
265 | function 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 |
|
---|
282 | function 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 |
|
---|
293 | function _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 |
|
---|
307 | var 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 |
|
---|
970 | function _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 |
|
---|
984 | var 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 |
|
---|
1071 | var 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 |
|
---|
1784 | exports.core = Critters;
|
---|
1785 | exports.default = Critters$1;
|
---|
1786 | // # sourceMappingURL=critters.js.map
|
---|