This commit is contained in:
CHEVALLIER Abel
2025-11-13 16:23:22 +01:00
parent de9c515a47
commit cb235644dc
34924 changed files with 3811102 additions and 0 deletions

203
node_modules/beasties/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Google Inc.
Copyright 2024 Daniel Roe
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

313
node_modules/beasties/README.md generated vendored Normal file
View File

@@ -0,0 +1,313 @@
<p align="center">
<img src="https://i.imgur.com/J0jv1Sz.png" width="240" height="240" alt="beasties">
<h1 align="center">Beasties</h1>
</p>
> Beasties is a plugin that inlines your app's [critical CSS] and lazy-loads the rest. It is a maintained fork of [GoogleChromeLabs/critters](https://github.com/GoogleChromeLabs/critters)
## beasties [![npm](https://img.shields.io/npm/v/beasties.svg)](https://www.npmjs.org/package/beasties)
It's a little different from [other options](#similar-libraries), because it **doesn't use a headless browser** to render content. This tradeoff allows Beasties to be very **fast and lightweight**. It also means Beasties inlines all CSS rules used by your document, rather than only those needed for above-the-fold content. For alternatives, see [Similar Libraries](#similar-libraries).
Beasties' design makes it a good fit when inlining critical CSS for prerendered/SSR'd Single Page Applications. It was developed to be an excellent compliment to [prerender-loader](https://github.com/GoogleChromeLabs/prerender-loader), combining to dramatically improve first paint time for most Single Page Applications.
## Features
- Fast - no browser, few dependencies
- Integrates with Webpack [beasties-webpack-plugin]
- Supports preloading and/or inlining critical fonts
- Prunes unused CSS keyframes and media queries
- Removes inlined CSS rules from lazy-loaded stylesheets
## Installation
First, install Beasties as a development dependency:
```sh
npm i -D beasties
```
or
```sh
yarn add -D beasties
```
## Simple Example
```js
import Beasties from 'beasties'
const beasties = new Beasties({
// optional configuration (see below)
})
const html = `
<style>
.red { color: red }
.blue { color: blue }
</style>
<div class="blue">I'm Blue</div>
`
const inlined = await beasties.process(html)
console.log(inlined)
// "<style>.blue{color:blue}</style><div class=\"blue\">I'm Blue</div>"
```
## Usage with Vite
Beasties can be used with Vite through [vite-plugin-beasties](https://www.npmjs.org/package/vite-plugin-beasties). [![npm](https://img.shields.io/npm/v/vite-plugin-beasties.svg)](https://www.npmjs.org/package/vite-plugin-beasties)
Just add it to your Vite configuration:
```js
// vite.config.ts
import { defineConfig } from 'vite'
import { beasties } from 'vite-plugin-beasties'
export default defineConfig({
plugins: [
beasties({
// optional beasties configuration
options: {
preload: 'swap',
}
})
]
})
```
The plugin will process the output for your `index.html` and inline critical CSS while lazy-loading the rest.
## Usage with webpack
Beasties is also available as a Webpack plugin called [beasties-webpack-plugin](https://www.npmjs.org/package/beasties-webpack-plugin). [![npm](https://img.shields.io/npm/v/beasties-webpack-plugin.svg)](https://www.npmjs.org/package/beasties-webpack-plugin)
The Webpack plugin supports the same configuration options as the main `beasties` package:
```diff
// webpack.config.js
+const Beasties = require('beasties-webpack-plugin');
module.exports = {
plugins: [
+ new Beasties({
+ // optional configuration
+ preload: 'swap',
+ })
]
}
```
That's it! The resultant html will have its critical CSS inlined and the stylesheets lazy-loaded.
## Usage
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Beasties
All optional. Pass them to `new Beasties({ ... })`.
#### Parameters
- `options`
#### Properties
- `path` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Base path location of the CSS files _(default: `''`)_
- `publicPath` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Public path of the CSS resources. This prefix is removed from the href _(default: `''`)_
- `external` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Inline styles from external stylesheets _(default: `true`)_
- `inlineThreshold` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** Inline external stylesheets smaller than a given size _(default: `0`)_
- `minimumExternalSize` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** If the non-critical external stylesheet would be below this size, just inline it _(default: `0`)_
- `pruneSource` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Remove inlined rules from the external stylesheet _(default: `false`)_
- `mergeStylesheets` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Merged inlined stylesheets into a single `<style>` tag _(default: `true`)_
- `additionalStylesheets` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** Glob for matching other stylesheets to be used while looking for critical CSS.
- `reduceInlineStyles` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Option indicates if inline styles should be evaluated for critical CSS. By default inline style tags will be evaluated and rewritten to only contain critical CSS. Set it to `false` to skip processing inline styles. _(default: `true`)_
- `allowRules` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) | [RegExp](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp)>** Always include rules matching these selectors or patterns in the critical CSS, regardless of whether they match elements in the document. _(default: `[]`)_
- `preload` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Which [preload strategy](#preloadstrategy) to use
- `noscriptFallback` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Add `<noscript>` fallback to JS-based strategies
- `inlineFonts` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Inline critical font-face rules _(default: `false`)_
- `preloadFonts` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Preloads critical fonts _(default: `true`)_
- `fonts` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Shorthand for setting `inlineFonts` + `preloadFonts`\* Values:
- `true` to inline critical font-face rules and preload the fonts
- `false` to don't inline any font-face rules and don't preload fonts
- `keyframes` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Controls which keyframes rules are inlined.\* Values:
- `"critical"`: _(default)_ inline keyframes rules used by the critical CSS
- `"all"` inline all keyframes rules
- `"none"` remove all keyframes rules
- `compress` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Compress resulting critical CSS _(default: `true`)_
- `logLevel` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Controls [log level](#loglevel) of the plugin _(default: `"info"`)_
- `logger` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Provide a custom logger interface [logger](#logger)
### Include/exclude rules
We can include or exclude rules to be part of critical CSS by adding comments in the CSS
Single line comments to include/exclude the next CSS rule
```css
/* beasties:exclude */
.selector1 {
/* this rule will be excluded from critical CSS */
}
.selector2 {
/* this will be evaluated normally */
}
/* beasties:include */
.selector3 {
/* this rule will be included in the critical CSS */
}
.selector4 {
/* this will be evaluated normally */
}
```
Including/Excluding multiple rules by adding start and end markers
```css
/* beasties:exclude start */
.selector1 {
/* this rule will be excluded from critical CSS */
}
.selector2 {
/* this rule will be excluded from critical CSS */
}
/* beasties:exclude end */
```
```css
/* beasties:include start */
.selector3 {
/* this rule will be included in the critical CSS */
}
.selector4 {
/* this rule will be included in the critical CSS */
}
/* beasties:include end */
```
### Programmatically including rules with allowRules
In addition to comment-based inclusion, you can use the `allowRules` option to programmatically include specific selectors or patterns in the critical CSS, regardless of whether they match elements in the document. This is useful for cases where you know certain styles should always be included.
```js
const beasties = new Beasties({
// Always include these selectors in critical CSS
allowRules: [
// Exact selector match
'.always-include',
// Regular expression pattern
/^\.modal-/
]
})
```
With this configuration, any CSS rules with selectors that match `.always-include` exactly or start with `.modal-` will be included in the critical CSS, even if no matching elements exist in the document.
### Beasties container
By default Beasties evaluates the CSS against the entire input HTML. Beasties evaluates the Critical CSS by reconstructing the entire DOM and evaluating the CSS selectors to find matching nodes. Usually this works well as Beasties is lightweight and fast.
For some cases, the input HTML can be very large or deeply nested which makes the reconstructed DOM much larger, which in turn can slow down the critical CSS generation. Beasties is not aware of viewport size and what specific nodes are above the fold since there is not a headless browser involved.
To overcome this issue Beasties makes use of **Beasties containers**.
A Beasties container mimics the viewport and can be enabled by adding `data-beasties-container` into the top level container thats contains the HTML elements above the fold.
You can estimate the contents of your viewport roughly and add a <div `data-beasties-container` > around the contents.
```html
<html>
<body>
<div class="container">
<div data-beasties-container>
/* HTML inside this container are used to evaluate critical CSS */
</div>
/* HTML is ignored when evaluating critical CSS */
</div>
<footer></footer>
</body>
</html>
```
_Note: This is an easy way to improve the performance of Beasties_
### Logger
Custom logger interface:
Type: [object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)
#### Properties
- `trace` **function ([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String))** Prints a trace message
- `debug` **function ([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String))** Prints a debug message
- `info` **function ([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String))** Prints an information message
- `warn` **function ([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String))** Prints a warning message
- `error` **function ([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String))** Prints an error message
### LogLevel
Controls log level of the plugin. Specifies the level the logger should use. A logger will
not produce output for any log level beneath the specified level. Available levels and order
are:
- **"info"** _(default)_
- **"warn"**
- **"error"**
- **"trace"**
- **"debug"**
- **"silent"**
Type: (`"info"` | `"warn"` | `"error"` | `"trace"` | `"debug"` | `"silent"`)
### PreloadStrategy
The mechanism to use for lazy-loading stylesheets.
Note: <kbd>JS</kbd> indicates a strategy requiring JavaScript (falls back to `<noscript>` unless disabled).
- **default:** Move stylesheet links to the end of the document and insert preload meta tags in their place.
- **"body":** Move all external stylesheet links to the end of the document.
- **"media":** Load stylesheets asynchronously by adding `media="not x"` and removing once loaded. <kbd>JS</kbd>
- **"swap":** Convert stylesheet links to preloads that swap to `rel="stylesheet"` once loaded ([details](https://www.filamentgroup.com/lab/load-css-simpler/#the-code)). <kbd>JS</kbd>
- **"swap-high":** Use `<link rel="alternate stylesheet preload">` and swap to `rel="stylesheet"` once loaded ([details](http://filamentgroup.github.io/loadCSS/test/new-high.html)). <kbd>JS</kbd>
- **"swap-low":** Use `<link rel="alternate stylesheet">` (no `preload` in `rel` here!) and swap to `rel="stylesheet"` once loaded ([details](http://filamentgroup.github.io/loadCSS/test/new-low.html)). It ensures lowest priority compared to `swap` and `swap-high`. <kbd>JS</kbd>
- **"js":** Inject an asynchronous CSS loader similar to [LoadCSS](https://github.com/filamentgroup/loadCSS) and use it to load stylesheets. <kbd>JS</kbd>
- **"js-lazy":** Like `"js"`, but the stylesheet is disabled until fully loaded.
- **false:** Disables adding preload tags.
Type: (default | `"body"` | `"media"` | `"swap"` | `"swap-high"` | `"swap-low"` | `"js"` | `"js-lazy"`)
## Similar Libraries
There are a number of other libraries that can inline Critical CSS, each with a slightly different approach. Here are a few great options:
- [Critical](https://github.com/addyosmani/critical)
- [Penthouse](https://github.com/pocketjoso/penthouse)
- [webpack-critical](https://github.com/lukeed/webpack-critical)
- [webpack-plugin-critical](https://github.com/nrwl/webpack-plugin-critical)
- [html-critical-webpack-plugin](https://github.com/anthonygore/html-critical-webpack-plugin)
- [react-snap](https://github.com/stereobooster/react-snap)
## License
[Apache 2.0](LICENSE)
This is not an official Google product.
[beasties-webpack-plugin]: https://github.com/danielroe/beasties/tree/main/packages/beasties-webpack-plugin
[critical css]: https://www.smashingmagazine.com/2015/08/understanding-critical-css/
[html-webpack-plugin]: https://github.com/jantimon/html-webpack-plugin

982
node_modules/beasties/dist/index.cjs generated vendored Normal file
View File

@@ -0,0 +1,982 @@
'use strict';
const node_fs = require('node:fs');
const path = require('node:path');
const postcss = require('postcss');
const mediaParser = require('postcss-media-query-parser');
const cssSelect = require('css-select');
const cssWhat = require('css-what');
const render = require('dom-serializer');
const domhandler = require('domhandler');
const htmlparser2 = require('htmlparser2');
const pc = require('picocolors');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
const mediaParser__default = /*#__PURE__*/_interopDefaultCompat(mediaParser);
const render__default = /*#__PURE__*/_interopDefaultCompat(render);
const pc__default = /*#__PURE__*/_interopDefaultCompat(pc);
function parseStylesheet(stylesheet) {
return postcss.parse(stylesheet);
}
function serializeStylesheet(ast, options) {
const cssParts = [];
postcss.stringify(ast, (result, node, type) => {
if (node?.type === "decl" && node.value.includes("</style>")) {
return;
}
if (!options.compress) {
cssParts.push(result);
return;
}
if (node?.type === "comment")
return;
if (node?.type === "decl") {
const prefix = node.prop + node.raws.between;
cssParts.push(result.replace(prefix, prefix.trim()));
return;
}
if (type === "start") {
if (node?.type === "rule" && node.selectors) {
if (node.selectors.length === 1) {
cssParts.push(node.selectors[0] ?? "", "{");
} else {
cssParts.push(node.selectors.join(","), "{");
}
} else {
cssParts.push(result.trim());
}
return;
}
if (type === "end" && result === "}" && node?.raws?.semicolon) {
const lastItemIdx = cssParts.length - 2;
if (lastItemIdx >= 0 && cssParts[lastItemIdx]) {
cssParts[lastItemIdx] = cssParts[lastItemIdx].slice(0, -1);
}
}
cssParts.push(result.trim());
});
return cssParts.join("");
}
function markOnly(predicate) {
return (rule) => {
const sel = "selectors" in rule ? rule.selectors : void 0;
if (predicate(rule) === false) {
rule.$$remove = true;
}
if ("selectors" in rule) {
rule.$$markedSelectors = rule.selectors;
rule.selectors = sel;
}
if (rule._other) {
rule._other.$$markedSelectors = rule._other.selectors;
}
};
}
function applyMarkedSelectors(rule) {
if (rule.$$markedSelectors) {
rule.selectors = rule.$$markedSelectors;
}
if (rule._other) {
applyMarkedSelectors(rule._other);
}
}
function walkStyleRules(node, iterator) {
if (!("nodes" in node)) {
return;
}
node.nodes = node.nodes?.filter((rule) => {
if (hasNestedRules(rule)) {
walkStyleRules(rule, iterator);
}
rule._other = void 0;
rule.filterSelectors = filterSelectors;
return iterator(rule) !== false;
});
}
function walkStyleRulesWithReverseMirror(node, node2, iterator) {
if (!node2)
return walkStyleRules(node, iterator);
[node.nodes, node2.nodes] = splitFilter(
node.nodes,
node2.nodes,
(rule, index, _rules, rules2) => {
const rule2 = rules2?.[index];
if (hasNestedRules(rule)) {
walkStyleRulesWithReverseMirror(rule, rule2, iterator);
}
rule._other = rule2;
rule.filterSelectors = filterSelectors;
return iterator(rule) !== false;
}
);
}
function hasNestedRules(rule) {
return "nodes" in rule && !!rule.nodes?.length && (!("name" in rule) || rule.name !== "keyframes" && rule.name !== "-webkit-keyframes") && rule.nodes.some((n) => n.type === "rule" || n.type === "atrule");
}
function splitFilter(a, b, predicate) {
const aOut = [];
const bOut = [];
for (let index = 0; index < a.length; index++) {
const item = a[index];
if (predicate(item, index, a, b)) {
aOut.push(item);
} else {
bOut.push(item);
}
}
return [aOut, bOut];
}
function filterSelectors(predicate) {
if (this._other) {
const [a, b] = splitFilter(
this.selectors,
this._other.selectors,
predicate
);
this.selectors = a;
this._other.selectors = b;
} else {
this.selectors = this.selectors.filter(predicate);
}
}
const MEDIA_TYPES = /* @__PURE__ */ new Set(["all", "print", "screen", "speech"]);
const MEDIA_KEYWORDS = /* @__PURE__ */ new Set(["and", "not", ","]);
const MEDIA_FEATURES = new Set(
[
"width",
"aspect-ratio",
"color",
"color-index",
"grid",
"height",
"monochrome",
"orientation",
"resolution",
"scan"
].flatMap((feature) => [feature, `min-${feature}`, `max-${feature}`])
);
function validateMediaType(node) {
const { type: nodeType, value: nodeValue } = node;
if (nodeType === "media-type") {
return MEDIA_TYPES.has(nodeValue);
} else if (nodeType === "keyword") {
return MEDIA_KEYWORDS.has(nodeValue);
} else if (nodeType === "media-feature") {
return MEDIA_FEATURES.has(nodeValue);
}
}
function validateMediaQuery(query) {
const mediaParserFn = "default" in mediaParser__default ? mediaParser__default.default : mediaParser__default;
const mediaTree = mediaParserFn(query);
const nodeTypes = /* @__PURE__ */ new Set(["media-type", "keyword", "media-feature"]);
const stack = [mediaTree];
while (stack.length > 0) {
const node = stack.pop();
if (nodeTypes.has(node.type) && !validateMediaType(node)) {
return false;
}
if (node.nodes) {
stack.push(...node.nodes);
}
}
return true;
}
let classCache = null;
let idCache = null;
function buildCache(container) {
classCache = /* @__PURE__ */ new Set();
idCache = /* @__PURE__ */ new Set();
const queue = [container];
while (queue.length) {
const node = queue.shift();
if (node.hasAttribute?.("class")) {
const classList = node.getAttribute("class").trim().split(" ");
classList.forEach((cls) => {
classCache.add(cls);
});
}
if (node.hasAttribute?.("id")) {
const id = node.getAttribute("id").trim();
idCache.add(id);
}
if ("children" in node) {
queue.push(...node.children.filter((child) => child.type === "tag"));
}
}
}
function createDocument(html) {
const document = htmlparser2.parseDocument(html, { decodeEntities: false });
extendDocument(document);
extendElement(domhandler.Element.prototype);
let beastiesContainer = document.querySelector("[data-beasties-container]");
if (!beastiesContainer) {
document.documentElement?.setAttribute("data-beasties-container", "");
beastiesContainer = document.documentElement || document;
}
document.beastiesContainer = beastiesContainer;
buildCache(beastiesContainer);
return document;
}
function serializeDocument(document) {
return render__default(document, { decodeEntities: false });
}
let extended = false;
function extendElement(element) {
if (extended) {
return;
}
extended = true;
Object.defineProperties(element, {
nodeName: {
get() {
return this.tagName.toUpperCase();
}
},
id: {
get() {
return this.getAttribute("id");
},
set(value) {
this.setAttribute("id", value);
}
},
className: {
get() {
return this.getAttribute("class");
},
set(value) {
this.setAttribute("class", value);
}
},
insertBefore: {
value(child, referenceNode) {
if (!referenceNode)
return this.appendChild(child);
htmlparser2.DomUtils.prepend(referenceNode, child);
return child;
}
},
appendChild: {
value(child) {
htmlparser2.DomUtils.appendChild(this, child);
return child;
}
},
removeChild: {
value(child) {
htmlparser2.DomUtils.removeElement(child);
}
},
remove: {
value() {
htmlparser2.DomUtils.removeElement(this);
}
},
textContent: {
get() {
return htmlparser2.DomUtils.getText(this);
},
set(text) {
this.children = [];
htmlparser2.DomUtils.appendChild(this, new domhandler.Text(text));
}
},
setAttribute: {
value(name, value) {
if (this.attribs == null)
this.attribs = {};
if (value == null)
value = "";
this.attribs[name] = value;
}
},
removeAttribute: {
value(name) {
if (this.attribs != null) {
delete this.attribs[name];
}
}
},
getAttribute: {
value(name) {
return this.attribs != null && this.attribs[name];
}
},
hasAttribute: {
value(name) {
return this.attribs != null && this.attribs[name] != null;
}
},
getAttributeNode: {
value(name) {
const value = this.getAttribute(name);
if (value != null)
return { specified: true, value };
}
},
exists: {
value(sel) {
return cachedQuerySelector(sel, this);
}
},
querySelector: {
value(sel) {
return cssSelect.selectOne(sel, this);
}
},
querySelectorAll: {
value(sel) {
return cssSelect.selectAll(sel, this);
}
}
});
}
function extendDocument(document) {
Object.defineProperties(document, {
// document is just an Element in htmlparser2, giving it a nodeType of ELEMENT_NODE.
// TODO: verify if these are needed for css-select
nodeType: {
get() {
return 9;
}
},
contentType: {
get() {
return "text/html";
}
},
nodeName: {
get() {
return "#document";
}
},
documentElement: {
get() {
return this.children.find(
(child) => "tagName" in child && String(child.tagName).toLowerCase() === "html"
);
}
},
head: {
get() {
return this.querySelector("head");
}
},
body: {
get() {
return this.querySelector("body");
}
},
createElement: {
value(name) {
return new domhandler.Element(name, {});
}
},
createTextNode: {
value(text) {
return new domhandler.Text(text);
}
},
exists: {
value(sel) {
return cachedQuerySelector(sel, this);
}
},
querySelector: {
value(sel) {
return cssSelect.selectOne(sel, this);
}
},
querySelectorAll: {
value(sel) {
if (sel === ":root") {
return this;
}
return cssSelect.selectAll(sel, this);
}
}
});
}
const selectorTokensCache = /* @__PURE__ */ new Map();
function cachedQuerySelector(sel, node) {
let selectorTokens = selectorTokensCache.get(sel);
if (selectorTokens === void 0) {
selectorTokens = parseRelevantSelectors(sel);
selectorTokensCache.set(sel, selectorTokens);
}
if (selectorTokens) {
for (const token of selectorTokens) {
if (token.name === "class") {
return classCache.has(token.value);
}
if (token.name === "id") {
return idCache.has(token.value);
}
}
}
return !!cssSelect.selectOne(sel, node);
}
function parseRelevantSelectors(sel) {
const tokens = cssWhat.parse(sel);
const relevantTokens = [];
for (let i = 0; i < tokens.length; i++) {
const tokenGroup = tokens[i];
if (tokenGroup?.length !== 1) {
continue;
}
const token = tokenGroup[0];
if (token?.type === "attribute" && (token.name === "class" || token.name === "id")) {
relevantTokens.push(token);
}
}
return relevantTokens.length > 0 ? relevantTokens : null;
}
const LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "silent"];
const defaultLogger = {
trace(msg) {
console.trace(msg);
},
debug(msg) {
console.debug(msg);
},
warn(msg) {
console.warn(pc__default.yellow(msg));
},
error(msg) {
console.error(pc__default.bold(pc__default.red(msg)));
},
info(msg) {
console.info(pc__default.bold(pc__default.blue(msg)));
},
silent() {
}
};
function createLogger(logLevel) {
const logLevelIdx = LOG_LEVELS.indexOf(logLevel);
return LOG_LEVELS.reduce((logger, type, index) => {
if (index >= logLevelIdx) {
logger[type] = defaultLogger[type];
} else {
logger[type] = defaultLogger.silent;
}
return logger;
}, {});
}
function isSubpath(basePath, currentPath) {
return !path__default.relative(basePath, currentPath).startsWith("..");
}
const removePseudoClassesAndElementsPattern = /(?<!\\)::?[a-z-]+(?:\(.+\))?/gi;
const implicitUniversalPattern = /([>+~])\s*(?!\1)([>+~])/g;
const emptyCombinatorPattern = /([>+~])\s*(?=\1|$)/g;
const removeTrailingCommasPattern = /\(\s*,|,\s*\)/g;
class Beasties {
#selectorCache = /* @__PURE__ */ new Map();
options;
logger;
fs;
constructor(options = {}) {
this.options = Object.assign({
logLevel: "info",
path: "",
publicPath: "",
reduceInlineStyles: true,
pruneSource: false,
additionalStylesheets: [],
allowRules: []
}, options);
this.logger = this.options.logger || createLogger(this.options.logLevel);
}
/**
* Read the contents of a file from the specified filesystem or disk
*/
readFile(filename) {
const fs = this.fs;
return new Promise((resolve, reject) => {
const callback = (err, data) => {
if (err)
reject(err);
else resolve(data.toString());
};
if (fs && fs.readFile) {
fs.readFile(filename, callback);
} else {
node_fs.readFile(filename, "utf-8", callback);
}
});
}
/**
* Write content to a file
*/
writeFile(filename, data) {
const fs = this.fs;
return new Promise((resolve, reject) => {
const callback = (err) => {
if (err)
reject(err);
else resolve();
};
if (fs && fs.writeFile) {
fs.writeFile(filename, data, callback);
} else {
node_fs.writeFile(filename, data, callback);
}
});
}
/**
* Apply critical CSS processing to the html
*/
async process(html) {
const start = Date.now();
const document = createDocument(html);
if (this.options.additionalStylesheets.length > 0) {
await this.embedAdditionalStylesheet(document);
}
if (this.options.external !== false) {
const externalSheets = [...document.querySelectorAll('link[rel="stylesheet"]')];
await Promise.all(
externalSheets.map((link) => this.embedLinkedStylesheet(link, document))
);
}
const styles = this.getAffectedStyleTags(document);
for (const style of styles) {
this.processStyle(style, document);
}
if (this.options.mergeStylesheets !== false && styles.length !== 0) {
this.mergeStylesheets(document);
}
const output = serializeDocument(document);
const end = Date.now();
this.logger.info?.(`Time ${end - start}ms`);
return output;
}
/**
* Get the style tags that need processing
*/
getAffectedStyleTags(document) {
const styles = [...document.querySelectorAll("style")];
if (this.options.reduceInlineStyles === false) {
return styles.filter((style) => style.$$external);
}
return styles;
}
mergeStylesheets(document) {
const styles = this.getAffectedStyleTags(document);
if (styles.length === 0) {
this.logger.warn?.(
"Merging inline stylesheets into a single <style> tag skipped, no inline stylesheets to merge"
);
return;
}
const first = styles[0];
let sheet = first.textContent;
for (let i = 1; i < styles.length; i++) {
const node = styles[i];
sheet += node.textContent;
node.remove();
}
first.textContent = sheet;
}
/**
* Given href, find the corresponding CSS asset
*/
async getCssAsset(href, _style) {
const outputPath = this.options.path;
const publicPath = this.options.publicPath;
let normalizedPath = href.replace(/^\//, "");
const pathPrefix = `${(publicPath || "").replace(/(^\/|\/$)/g, "")}/`;
if (normalizedPath.startsWith(pathPrefix)) {
normalizedPath = normalizedPath.substring(pathPrefix.length).replace(/^\//, "");
}
if (/^https?:\/\//.test(normalizedPath) || href.startsWith("//")) {
return void 0;
}
const filename = path__default.resolve(outputPath, normalizedPath);
if (!isSubpath(outputPath, filename)) {
return void 0;
}
let sheet;
try {
sheet = await this.readFile(filename);
} catch {
this.logger.warn?.(`Unable to locate stylesheet: ${filename}`);
}
return sheet;
}
checkInlineThreshold(link, style, sheet) {
if (this.options.inlineThreshold && sheet.length < this.options.inlineThreshold) {
const href = style.$$name;
style.$$reduce = false;
this.logger.info?.(
`\x1B[32mInlined all of ${href} (${sheet.length} was below the threshold of ${this.options.inlineThreshold})\x1B[39m`
);
link.remove();
return true;
}
return false;
}
/**
* Inline the stylesheets from options.additionalStylesheets (assuming it passes `options.filter`)
*/
async embedAdditionalStylesheet(document) {
const styleSheetsIncluded = [];
const sources = await Promise.all(
this.options.additionalStylesheets.map((cssFile) => {
if (styleSheetsIncluded.includes(cssFile)) {
return [];
}
styleSheetsIncluded.push(cssFile);
const style = document.createElement("style");
style.$$external = true;
return this.getCssAsset(cssFile, style).then((sheet) => [sheet, style]);
})
);
for (const [sheet, style] of sources) {
if (sheet) {
style.textContent = sheet;
document.head.appendChild(style);
}
}
}
/**
* Inline the target stylesheet referred to by a <link rel="stylesheet"> (assuming it passes `options.filter`)
*/
async embedLinkedStylesheet(link, document) {
const href = link.getAttribute("href");
if (!href?.endsWith(".css")) {
return void 0;
}
const style = document.createElement("style");
style.$$external = true;
const sheet = await this.getCssAsset(href, style);
if (!sheet) {
return;
}
style.textContent = sheet;
style.$$name = href;
style.$$links = [link];
link.parentNode?.insertBefore(style, link);
if (this.checkInlineThreshold(link, style, sheet)) {
return;
}
let media = link.getAttribute("media");
if (media && !validateMediaQuery(media)) {
media = void 0;
}
const preloadMode = this.options.preload;
let cssLoaderPreamble = "function $loadcss(u,m,l){(l=document.createElement('link')).rel='stylesheet';l.href=u;document.head.appendChild(l)}";
const lazy = preloadMode === "js-lazy";
if (lazy) {
cssLoaderPreamble = cssLoaderPreamble.replace(
"l.href",
"l.media='print';l.onload=function(){l.media=m};l.href"
);
}
if (preloadMode === false)
return;
let noscriptFallback = false;
let updateLinkToPreload = false;
const noscriptLink = link.cloneNode(false);
if (preloadMode === "body") {
document.body.appendChild(link);
} else {
if (preloadMode === "js" || preloadMode === "js-lazy") {
const script = document.createElement("script");
script.setAttribute("data-href", href);
script.setAttribute("data-media", media || "all");
const js = `${cssLoaderPreamble}$loadcss(document.currentScript.dataset.href,document.currentScript.dataset.media)`;
script.textContent = js;
link.parentNode.insertBefore(script, link.nextSibling);
style.$$links.push(script);
cssLoaderPreamble = "";
noscriptFallback = true;
updateLinkToPreload = true;
} else if (preloadMode === "media") {
link.setAttribute("media", "print");
link.setAttribute("onload", `this.media='${media || "all"}'`);
noscriptFallback = true;
} else if (preloadMode === "swap-high") {
link.setAttribute("rel", "alternate stylesheet preload");
link.setAttribute("title", "styles");
link.setAttribute("onload", `this.title='';this.rel='stylesheet'`);
noscriptFallback = true;
} else if (preloadMode === "swap-low") {
link.setAttribute("rel", "alternate stylesheet");
link.setAttribute("title", "styles");
link.setAttribute("onload", `this.title='';this.rel='stylesheet'`);
noscriptFallback = true;
} else if (preloadMode === "swap") {
link.setAttribute("onload", "this.rel='stylesheet'");
updateLinkToPreload = true;
noscriptFallback = true;
} else {
const bodyLink = link.cloneNode(false);
bodyLink.removeAttribute("id");
document.body.appendChild(bodyLink);
style.$$links.push(bodyLink);
updateLinkToPreload = true;
}
}
if (this.options.noscriptFallback !== false && noscriptFallback && !href.includes("</noscript>")) {
const noscript = document.createElement("noscript");
noscriptLink.removeAttribute("id");
noscript.appendChild(noscriptLink);
link.parentNode.insertBefore(noscript, link.nextSibling);
style.$$links.push(noscript);
}
if (updateLinkToPreload) {
link.setAttribute("rel", "preload");
link.setAttribute("as", "style");
}
}
/**
* Prune the source CSS files
*/
pruneSource(style, before, sheetInverse) {
const minSize = this.options.minimumExternalSize;
const name = style.$$name;
const shouldInline = minSize && sheetInverse.length < minSize;
if (shouldInline) {
this.logger.info?.(
`\x1B[32mInlined all of ${name} (non-critical external stylesheet would have been ${sheetInverse.length}b, which was below the threshold of ${minSize})\x1B[39m`
);
}
if (shouldInline || !sheetInverse) {
style.textContent = before;
if (style.$$links) {
for (const link of style.$$links) {
const parent = link.parentNode;
parent?.removeChild(link);
}
}
}
return !!shouldInline;
}
/**
* Parse the stylesheet within a <style> element, then reduce it to contain only rules used by the document.
*/
processStyle(style, document) {
if (style.$$reduce === false)
return;
const name = style.$$name ? style.$$name.replace(/^\//, "") : "inline CSS";
const options = this.options;
const beastiesContainer = document.beastiesContainer;
let keyframesMode = options.keyframes ?? "critical";
if (keyframesMode === true)
keyframesMode = "all";
if (keyframesMode === false)
keyframesMode = "none";
let sheet = style.textContent;
const before = sheet;
if (!sheet)
return;
const ast = parseStylesheet(sheet);
const astInverse = options.pruneSource ? parseStylesheet(sheet) : null;
let criticalFonts = "";
const failedSelectors = [];
const criticalKeyframeNames = /* @__PURE__ */ new Set();
let includeNext = false;
let includeAll = false;
let excludeNext = false;
let excludeAll = false;
const shouldPreloadFonts = options.fonts === true || options.preloadFonts === true;
const shouldInlineFonts = options.fonts !== false && options.inlineFonts === true;
walkStyleRules(
ast,
markOnly((rule) => {
if (rule.type === "comment") {
const beastiesComment = rule.text.match(/^(?<!! )beasties:(.*)/);
const command = beastiesComment && beastiesComment[1];
if (command) {
switch (command) {
case "include":
includeNext = true;
break;
case "exclude":
excludeNext = true;
break;
case "include start":
includeAll = true;
break;
case "include end":
includeAll = false;
break;
case "exclude start":
excludeAll = true;
break;
case "exclude end":
excludeAll = false;
break;
}
}
}
if (rule.type === "rule") {
if (includeNext) {
includeNext = false;
return true;
}
if (excludeNext) {
excludeNext = false;
return false;
}
if (includeAll) {
return true;
}
if (excludeAll) {
return false;
}
rule.filterSelectors?.((sel) => {
const isAllowedRule = options.allowRules.some((exp) => {
if (exp instanceof RegExp) {
return exp.test(sel);
}
return exp === sel;
});
if (isAllowedRule)
return true;
if (sel === ":root" || sel === "html" || sel === "body" || sel[0] === ":" && /^::?(?:before|after)$/.test(sel)) {
return true;
}
sel = this.normalizeCssSelector(sel);
if (!sel)
return false;
try {
return beastiesContainer.exists(sel);
} catch (e) {
failedSelectors.push(`${sel} -> ${e.message || e.toString()}`);
return false;
}
});
if (!rule.selector) {
return false;
}
if (rule.nodes) {
for (const decl of rule.nodes) {
if (!("prop" in decl)) {
continue;
}
if (shouldInlineFonts && /\bfont(?:-family)?\b/i.test(decl.prop)) {
criticalFonts += ` ${decl.value}`;
}
if (decl.prop === "animation" || decl.prop === "animation-name") {
for (const name2 of decl.value.split(/\s+/)) {
const nameTrimmed = name2.trim();
if (nameTrimmed)
criticalKeyframeNames.add(nameTrimmed);
}
}
}
}
}
if (rule.type === "atrule" && (rule.name === "font-face" || rule.name === "layer"))
return;
const hasRemainingRules = ("nodes" in rule && rule.nodes?.some((rule2) => !rule2.$$remove)) ?? true;
return hasRemainingRules;
})
);
if (failedSelectors.length !== 0) {
this.logger.warn?.(
`${failedSelectors.length} rules skipped due to selector errors:
${failedSelectors.join("\n ")}`
);
}
const preloadedFonts = /* @__PURE__ */ new Set();
walkStyleRulesWithReverseMirror(ast, astInverse, (rule) => {
if (rule.$$remove === true)
return false;
if ("selectors" in rule) {
applyMarkedSelectors(rule);
}
if (rule.type === "atrule" && rule.name === "keyframes") {
if (keyframesMode === "none")
return false;
if (keyframesMode === "all")
return true;
return criticalKeyframeNames.has(rule.params);
}
if (rule.type === "atrule" && rule.name === "font-face") {
let family, src;
if (rule.nodes) {
for (const decl of rule.nodes) {
if (!("prop" in decl)) {
continue;
}
if (decl.prop === "src") {
src = (decl.value.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/) || [])[2];
} else if (decl.prop === "font-family") {
family = decl.value;
}
}
if (src && shouldPreloadFonts && !preloadedFonts.has(src)) {
preloadedFonts.add(src);
const preload = document.createElement("link");
preload.setAttribute("rel", "preload");
preload.setAttribute("as", "font");
preload.setAttribute("crossorigin", "anonymous");
preload.setAttribute("href", src.trim());
document.head.appendChild(preload);
}
}
if (!shouldInlineFonts || !family || !src || !criticalFonts.includes(family)) {
return false;
}
}
});
sheet = serializeStylesheet(ast, {
compress: this.options.compress !== false
});
if (sheet.trim().length === 0) {
if (style.parentNode) {
style.remove();
}
return;
}
let afterText = "";
let styleInlinedCompletely = false;
if (options.pruneSource) {
const sheetInverse = serializeStylesheet(astInverse, {
compress: this.options.compress !== false
});
styleInlinedCompletely = this.pruneSource(style, before, sheetInverse);
if (styleInlinedCompletely) {
const percent2 = sheetInverse.length / before.length * 100;
afterText = `, reducing non-inlined size ${percent2 | 0}% to ${formatSize(sheetInverse.length)}`;
}
const cssFilePath = path__default.resolve(this.options.path, name);
this.writeFile(cssFilePath, sheetInverse).then(() => this.logger.info?.(`${name} was successfully updated`)).catch((err) => this.logger.error?.(err));
}
if (!styleInlinedCompletely) {
style.textContent = sheet;
}
const percent = sheet.length / before.length * 100 | 0;
this.logger.info?.(
`\x1B[32mInlined ${formatSize(sheet.length)} (${percent}% of original ${formatSize(before.length)}) of ${name}${afterText}.\x1B[39m`
);
}
normalizeCssSelector(sel) {
let normalizedSelector = this.#selectorCache.get(sel);
if (normalizedSelector !== void 0) {
return normalizedSelector;
}
normalizedSelector = sel.replace(removePseudoClassesAndElementsPattern, "").replace(removeTrailingCommasPattern, (match) => match.includes("(") ? "(" : ")").replace(implicitUniversalPattern, "$1 * $2").replace(emptyCombinatorPattern, "$1 *").trim();
this.#selectorCache.set(sel, normalizedSelector);
return normalizedSelector;
}
}
function formatSize(size) {
if (size <= 0) {
return "0 bytes";
}
const abbreviations = ["bytes", "kB", "MB", "GB"];
const index = Math.floor(Math.log(size) / Math.log(1024));
const roundedSize = size / 1024 ** index;
const fractionDigits = index === 0 ? 0 : 2;
return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`;
}
module.exports = Beasties;

80
node_modules/beasties/dist/index.d.cts generated vendored Normal file
View File

@@ -0,0 +1,80 @@
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
declare class Beasties {
/**
* Create an instance of Beasties with custom options.
* The `.process()` method can be called repeatedly to re-use this instance and its cache.
*/
constructor(options?: Options)
/**
* Process an HTML document to inline critical CSS from its stylesheets.
* @param html String containing a full HTML document to be parsed.
* @returns A modified copy of the provided HTML with critical CSS inlined.
*/
process(html: string): Promise<string>
/**
* Read the contents of a file from the specified filesystem or disk.
* Override this method to customize how stylesheets are loaded.
*/
readFile(filename: string): Promise<string> | string
/**
* Given a stylesheet URL, returns the corresponding CSS asset.
* Overriding this method requires doing your own URL normalization, so it's generally better to override `readFile()`.
*/
getCssAsset(href: string): Promise<string | undefined> | string | undefined
/**
* Override this method to customise how beasties prunes the content of source files.
*/
pruneSource(style: Node, before: string, sheetInverse: string): boolean
/**
* Override this method to customise how beasties prunes the content of source files.
*/
checkInlineThreshold(link: Node, style: Node, sheet: string): boolean
}
interface Options {
path?: string
publicPath?: string
external?: boolean
inlineThreshold?: number
minimumExternalSize?: number
pruneSource?: boolean
mergeStylesheets?: boolean
additionalStylesheets?: string[]
preload?: 'body' | 'media' | 'swap' | 'swap-high' | 'swap-low' | 'js' | 'js-lazy'
noscriptFallback?: boolean
inlineFonts?: boolean
preloadFonts?: boolean
allowRules?: Array<string | RegExp>
fonts?: boolean
keyframes?: string
compress?: boolean
logLevel?: 'info' | 'warn' | 'error' | 'trace' | 'debug' | 'silent'
reduceInlineStyles?: boolean
logger?: Logger
}
interface Logger {
trace?: (message: string) => void
debug?: (message: string) => void
info?: (message: string) => void
warn?: (message: string) => void
error?: (message: string) => void
}
export = Beasties;
export type { Logger, Options };

80
node_modules/beasties/dist/index.d.mts generated vendored Normal file
View File

@@ -0,0 +1,80 @@
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
declare class Beasties {
/**
* Create an instance of Beasties with custom options.
* The `.process()` method can be called repeatedly to re-use this instance and its cache.
*/
constructor(options?: Options)
/**
* Process an HTML document to inline critical CSS from its stylesheets.
* @param html String containing a full HTML document to be parsed.
* @returns A modified copy of the provided HTML with critical CSS inlined.
*/
process(html: string): Promise<string>
/**
* Read the contents of a file from the specified filesystem or disk.
* Override this method to customize how stylesheets are loaded.
*/
readFile(filename: string): Promise<string> | string
/**
* Given a stylesheet URL, returns the corresponding CSS asset.
* Overriding this method requires doing your own URL normalization, so it's generally better to override `readFile()`.
*/
getCssAsset(href: string): Promise<string | undefined> | string | undefined
/**
* Override this method to customise how beasties prunes the content of source files.
*/
pruneSource(style: Node, before: string, sheetInverse: string): boolean
/**
* Override this method to customise how beasties prunes the content of source files.
*/
checkInlineThreshold(link: Node, style: Node, sheet: string): boolean
}
interface Options {
path?: string
publicPath?: string
external?: boolean
inlineThreshold?: number
minimumExternalSize?: number
pruneSource?: boolean
mergeStylesheets?: boolean
additionalStylesheets?: string[]
preload?: 'body' | 'media' | 'swap' | 'swap-high' | 'swap-low' | 'js' | 'js-lazy'
noscriptFallback?: boolean
inlineFonts?: boolean
preloadFonts?: boolean
allowRules?: Array<string | RegExp>
fonts?: boolean
keyframes?: string
compress?: boolean
logLevel?: 'info' | 'warn' | 'error' | 'trace' | 'debug' | 'silent'
reduceInlineStyles?: boolean
logger?: Logger
}
interface Logger {
trace?: (message: string) => void
debug?: (message: string) => void
info?: (message: string) => void
warn?: (message: string) => void
error?: (message: string) => void
}
export { Beasties as default };
export type { Logger, Options };

77
node_modules/beasties/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,77 @@
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
export default class Beasties {
/**
* Create an instance of Beasties with custom options.
* The `.process()` method can be called repeatedly to re-use this instance and its cache.
*/
constructor(options?: Options)
/**
* Process an HTML document to inline critical CSS from its stylesheets.
* @param html String containing a full HTML document to be parsed.
* @returns A modified copy of the provided HTML with critical CSS inlined.
*/
process(html: string): Promise<string>
/**
* Read the contents of a file from the specified filesystem or disk.
* Override this method to customize how stylesheets are loaded.
*/
readFile(filename: string): Promise<string> | string
/**
* Given a stylesheet URL, returns the corresponding CSS asset.
* Overriding this method requires doing your own URL normalization, so it's generally better to override `readFile()`.
*/
getCssAsset(href: string): Promise<string | undefined> | string | undefined
/**
* Override this method to customise how beasties prunes the content of source files.
*/
pruneSource(style: Node, before: string, sheetInverse: string): boolean
/**
* Override this method to customise how beasties prunes the content of source files.
*/
checkInlineThreshold(link: Node, style: Node, sheet: string): boolean
}
export interface Options {
path?: string
publicPath?: string
external?: boolean
inlineThreshold?: number
minimumExternalSize?: number
pruneSource?: boolean
mergeStylesheets?: boolean
additionalStylesheets?: string[]
preload?: 'body' | 'media' | 'swap' | 'swap-high' | 'swap-low' | 'js' | 'js-lazy'
noscriptFallback?: boolean
inlineFonts?: boolean
preloadFonts?: boolean
allowRules?: Array<string | RegExp>
fonts?: boolean
keyframes?: string
compress?: boolean
logLevel?: 'info' | 'warn' | 'error' | 'trace' | 'debug' | 'silent'
reduceInlineStyles?: boolean
logger?: Logger
}
export interface Logger {
trace?: (message: string) => void
debug?: (message: string) => void
info?: (message: string) => void
warn?: (message: string) => void
error?: (message: string) => void
}

973
node_modules/beasties/dist/index.mjs generated vendored Normal file
View File

@@ -0,0 +1,973 @@
import { readFile, writeFile } from 'node:fs';
import path from 'node:path';
import { parse, stringify } from 'postcss';
import mediaParser from 'postcss-media-query-parser';
import { selectAll, selectOne } from 'css-select';
import { parse as parse$1 } from 'css-what';
import render from 'dom-serializer';
import { Element, Text } from 'domhandler';
import { parseDocument, DomUtils } from 'htmlparser2';
import pc from 'picocolors';
function parseStylesheet(stylesheet) {
return parse(stylesheet);
}
function serializeStylesheet(ast, options) {
const cssParts = [];
stringify(ast, (result, node, type) => {
if (node?.type === "decl" && node.value.includes("</style>")) {
return;
}
if (!options.compress) {
cssParts.push(result);
return;
}
if (node?.type === "comment")
return;
if (node?.type === "decl") {
const prefix = node.prop + node.raws.between;
cssParts.push(result.replace(prefix, prefix.trim()));
return;
}
if (type === "start") {
if (node?.type === "rule" && node.selectors) {
if (node.selectors.length === 1) {
cssParts.push(node.selectors[0] ?? "", "{");
} else {
cssParts.push(node.selectors.join(","), "{");
}
} else {
cssParts.push(result.trim());
}
return;
}
if (type === "end" && result === "}" && node?.raws?.semicolon) {
const lastItemIdx = cssParts.length - 2;
if (lastItemIdx >= 0 && cssParts[lastItemIdx]) {
cssParts[lastItemIdx] = cssParts[lastItemIdx].slice(0, -1);
}
}
cssParts.push(result.trim());
});
return cssParts.join("");
}
function markOnly(predicate) {
return (rule) => {
const sel = "selectors" in rule ? rule.selectors : void 0;
if (predicate(rule) === false) {
rule.$$remove = true;
}
if ("selectors" in rule) {
rule.$$markedSelectors = rule.selectors;
rule.selectors = sel;
}
if (rule._other) {
rule._other.$$markedSelectors = rule._other.selectors;
}
};
}
function applyMarkedSelectors(rule) {
if (rule.$$markedSelectors) {
rule.selectors = rule.$$markedSelectors;
}
if (rule._other) {
applyMarkedSelectors(rule._other);
}
}
function walkStyleRules(node, iterator) {
if (!("nodes" in node)) {
return;
}
node.nodes = node.nodes?.filter((rule) => {
if (hasNestedRules(rule)) {
walkStyleRules(rule, iterator);
}
rule._other = void 0;
rule.filterSelectors = filterSelectors;
return iterator(rule) !== false;
});
}
function walkStyleRulesWithReverseMirror(node, node2, iterator) {
if (!node2)
return walkStyleRules(node, iterator);
[node.nodes, node2.nodes] = splitFilter(
node.nodes,
node2.nodes,
(rule, index, _rules, rules2) => {
const rule2 = rules2?.[index];
if (hasNestedRules(rule)) {
walkStyleRulesWithReverseMirror(rule, rule2, iterator);
}
rule._other = rule2;
rule.filterSelectors = filterSelectors;
return iterator(rule) !== false;
}
);
}
function hasNestedRules(rule) {
return "nodes" in rule && !!rule.nodes?.length && (!("name" in rule) || rule.name !== "keyframes" && rule.name !== "-webkit-keyframes") && rule.nodes.some((n) => n.type === "rule" || n.type === "atrule");
}
function splitFilter(a, b, predicate) {
const aOut = [];
const bOut = [];
for (let index = 0; index < a.length; index++) {
const item = a[index];
if (predicate(item, index, a, b)) {
aOut.push(item);
} else {
bOut.push(item);
}
}
return [aOut, bOut];
}
function filterSelectors(predicate) {
if (this._other) {
const [a, b] = splitFilter(
this.selectors,
this._other.selectors,
predicate
);
this.selectors = a;
this._other.selectors = b;
} else {
this.selectors = this.selectors.filter(predicate);
}
}
const MEDIA_TYPES = /* @__PURE__ */ new Set(["all", "print", "screen", "speech"]);
const MEDIA_KEYWORDS = /* @__PURE__ */ new Set(["and", "not", ","]);
const MEDIA_FEATURES = new Set(
[
"width",
"aspect-ratio",
"color",
"color-index",
"grid",
"height",
"monochrome",
"orientation",
"resolution",
"scan"
].flatMap((feature) => [feature, `min-${feature}`, `max-${feature}`])
);
function validateMediaType(node) {
const { type: nodeType, value: nodeValue } = node;
if (nodeType === "media-type") {
return MEDIA_TYPES.has(nodeValue);
} else if (nodeType === "keyword") {
return MEDIA_KEYWORDS.has(nodeValue);
} else if (nodeType === "media-feature") {
return MEDIA_FEATURES.has(nodeValue);
}
}
function validateMediaQuery(query) {
const mediaParserFn = "default" in mediaParser ? mediaParser.default : mediaParser;
const mediaTree = mediaParserFn(query);
const nodeTypes = /* @__PURE__ */ new Set(["media-type", "keyword", "media-feature"]);
const stack = [mediaTree];
while (stack.length > 0) {
const node = stack.pop();
if (nodeTypes.has(node.type) && !validateMediaType(node)) {
return false;
}
if (node.nodes) {
stack.push(...node.nodes);
}
}
return true;
}
let classCache = null;
let idCache = null;
function buildCache(container) {
classCache = /* @__PURE__ */ new Set();
idCache = /* @__PURE__ */ new Set();
const queue = [container];
while (queue.length) {
const node = queue.shift();
if (node.hasAttribute?.("class")) {
const classList = node.getAttribute("class").trim().split(" ");
classList.forEach((cls) => {
classCache.add(cls);
});
}
if (node.hasAttribute?.("id")) {
const id = node.getAttribute("id").trim();
idCache.add(id);
}
if ("children" in node) {
queue.push(...node.children.filter((child) => child.type === "tag"));
}
}
}
function createDocument(html) {
const document = parseDocument(html, { decodeEntities: false });
extendDocument(document);
extendElement(Element.prototype);
let beastiesContainer = document.querySelector("[data-beasties-container]");
if (!beastiesContainer) {
document.documentElement?.setAttribute("data-beasties-container", "");
beastiesContainer = document.documentElement || document;
}
document.beastiesContainer = beastiesContainer;
buildCache(beastiesContainer);
return document;
}
function serializeDocument(document) {
return render(document, { decodeEntities: false });
}
let extended = false;
function extendElement(element) {
if (extended) {
return;
}
extended = true;
Object.defineProperties(element, {
nodeName: {
get() {
return this.tagName.toUpperCase();
}
},
id: {
get() {
return this.getAttribute("id");
},
set(value) {
this.setAttribute("id", value);
}
},
className: {
get() {
return this.getAttribute("class");
},
set(value) {
this.setAttribute("class", value);
}
},
insertBefore: {
value(child, referenceNode) {
if (!referenceNode)
return this.appendChild(child);
DomUtils.prepend(referenceNode, child);
return child;
}
},
appendChild: {
value(child) {
DomUtils.appendChild(this, child);
return child;
}
},
removeChild: {
value(child) {
DomUtils.removeElement(child);
}
},
remove: {
value() {
DomUtils.removeElement(this);
}
},
textContent: {
get() {
return DomUtils.getText(this);
},
set(text) {
this.children = [];
DomUtils.appendChild(this, new Text(text));
}
},
setAttribute: {
value(name, value) {
if (this.attribs == null)
this.attribs = {};
if (value == null)
value = "";
this.attribs[name] = value;
}
},
removeAttribute: {
value(name) {
if (this.attribs != null) {
delete this.attribs[name];
}
}
},
getAttribute: {
value(name) {
return this.attribs != null && this.attribs[name];
}
},
hasAttribute: {
value(name) {
return this.attribs != null && this.attribs[name] != null;
}
},
getAttributeNode: {
value(name) {
const value = this.getAttribute(name);
if (value != null)
return { specified: true, value };
}
},
exists: {
value(sel) {
return cachedQuerySelector(sel, this);
}
},
querySelector: {
value(sel) {
return selectOne(sel, this);
}
},
querySelectorAll: {
value(sel) {
return selectAll(sel, this);
}
}
});
}
function extendDocument(document) {
Object.defineProperties(document, {
// document is just an Element in htmlparser2, giving it a nodeType of ELEMENT_NODE.
// TODO: verify if these are needed for css-select
nodeType: {
get() {
return 9;
}
},
contentType: {
get() {
return "text/html";
}
},
nodeName: {
get() {
return "#document";
}
},
documentElement: {
get() {
return this.children.find(
(child) => "tagName" in child && String(child.tagName).toLowerCase() === "html"
);
}
},
head: {
get() {
return this.querySelector("head");
}
},
body: {
get() {
return this.querySelector("body");
}
},
createElement: {
value(name) {
return new Element(name, {});
}
},
createTextNode: {
value(text) {
return new Text(text);
}
},
exists: {
value(sel) {
return cachedQuerySelector(sel, this);
}
},
querySelector: {
value(sel) {
return selectOne(sel, this);
}
},
querySelectorAll: {
value(sel) {
if (sel === ":root") {
return this;
}
return selectAll(sel, this);
}
}
});
}
const selectorTokensCache = /* @__PURE__ */ new Map();
function cachedQuerySelector(sel, node) {
let selectorTokens = selectorTokensCache.get(sel);
if (selectorTokens === void 0) {
selectorTokens = parseRelevantSelectors(sel);
selectorTokensCache.set(sel, selectorTokens);
}
if (selectorTokens) {
for (const token of selectorTokens) {
if (token.name === "class") {
return classCache.has(token.value);
}
if (token.name === "id") {
return idCache.has(token.value);
}
}
}
return !!selectOne(sel, node);
}
function parseRelevantSelectors(sel) {
const tokens = parse$1(sel);
const relevantTokens = [];
for (let i = 0; i < tokens.length; i++) {
const tokenGroup = tokens[i];
if (tokenGroup?.length !== 1) {
continue;
}
const token = tokenGroup[0];
if (token?.type === "attribute" && (token.name === "class" || token.name === "id")) {
relevantTokens.push(token);
}
}
return relevantTokens.length > 0 ? relevantTokens : null;
}
const LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "silent"];
const defaultLogger = {
trace(msg) {
console.trace(msg);
},
debug(msg) {
console.debug(msg);
},
warn(msg) {
console.warn(pc.yellow(msg));
},
error(msg) {
console.error(pc.bold(pc.red(msg)));
},
info(msg) {
console.info(pc.bold(pc.blue(msg)));
},
silent() {
}
};
function createLogger(logLevel) {
const logLevelIdx = LOG_LEVELS.indexOf(logLevel);
return LOG_LEVELS.reduce((logger, type, index) => {
if (index >= logLevelIdx) {
logger[type] = defaultLogger[type];
} else {
logger[type] = defaultLogger.silent;
}
return logger;
}, {});
}
function isSubpath(basePath, currentPath) {
return !path.relative(basePath, currentPath).startsWith("..");
}
const removePseudoClassesAndElementsPattern = /(?<!\\)::?[a-z-]+(?:\(.+\))?/gi;
const implicitUniversalPattern = /([>+~])\s*(?!\1)([>+~])/g;
const emptyCombinatorPattern = /([>+~])\s*(?=\1|$)/g;
const removeTrailingCommasPattern = /\(\s*,|,\s*\)/g;
class Beasties {
#selectorCache = /* @__PURE__ */ new Map();
options;
logger;
fs;
constructor(options = {}) {
this.options = Object.assign({
logLevel: "info",
path: "",
publicPath: "",
reduceInlineStyles: true,
pruneSource: false,
additionalStylesheets: [],
allowRules: []
}, options);
this.logger = this.options.logger || createLogger(this.options.logLevel);
}
/**
* Read the contents of a file from the specified filesystem or disk
*/
readFile(filename) {
const fs = this.fs;
return new Promise((resolve, reject) => {
const callback = (err, data) => {
if (err)
reject(err);
else resolve(data.toString());
};
if (fs && fs.readFile) {
fs.readFile(filename, callback);
} else {
readFile(filename, "utf-8", callback);
}
});
}
/**
* Write content to a file
*/
writeFile(filename, data) {
const fs = this.fs;
return new Promise((resolve, reject) => {
const callback = (err) => {
if (err)
reject(err);
else resolve();
};
if (fs && fs.writeFile) {
fs.writeFile(filename, data, callback);
} else {
writeFile(filename, data, callback);
}
});
}
/**
* Apply critical CSS processing to the html
*/
async process(html) {
const start = Date.now();
const document = createDocument(html);
if (this.options.additionalStylesheets.length > 0) {
await this.embedAdditionalStylesheet(document);
}
if (this.options.external !== false) {
const externalSheets = [...document.querySelectorAll('link[rel="stylesheet"]')];
await Promise.all(
externalSheets.map((link) => this.embedLinkedStylesheet(link, document))
);
}
const styles = this.getAffectedStyleTags(document);
for (const style of styles) {
this.processStyle(style, document);
}
if (this.options.mergeStylesheets !== false && styles.length !== 0) {
this.mergeStylesheets(document);
}
const output = serializeDocument(document);
const end = Date.now();
this.logger.info?.(`Time ${end - start}ms`);
return output;
}
/**
* Get the style tags that need processing
*/
getAffectedStyleTags(document) {
const styles = [...document.querySelectorAll("style")];
if (this.options.reduceInlineStyles === false) {
return styles.filter((style) => style.$$external);
}
return styles;
}
mergeStylesheets(document) {
const styles = this.getAffectedStyleTags(document);
if (styles.length === 0) {
this.logger.warn?.(
"Merging inline stylesheets into a single <style> tag skipped, no inline stylesheets to merge"
);
return;
}
const first = styles[0];
let sheet = first.textContent;
for (let i = 1; i < styles.length; i++) {
const node = styles[i];
sheet += node.textContent;
node.remove();
}
first.textContent = sheet;
}
/**
* Given href, find the corresponding CSS asset
*/
async getCssAsset(href, _style) {
const outputPath = this.options.path;
const publicPath = this.options.publicPath;
let normalizedPath = href.replace(/^\//, "");
const pathPrefix = `${(publicPath || "").replace(/(^\/|\/$)/g, "")}/`;
if (normalizedPath.startsWith(pathPrefix)) {
normalizedPath = normalizedPath.substring(pathPrefix.length).replace(/^\//, "");
}
if (/^https?:\/\//.test(normalizedPath) || href.startsWith("//")) {
return void 0;
}
const filename = path.resolve(outputPath, normalizedPath);
if (!isSubpath(outputPath, filename)) {
return void 0;
}
let sheet;
try {
sheet = await this.readFile(filename);
} catch {
this.logger.warn?.(`Unable to locate stylesheet: ${filename}`);
}
return sheet;
}
checkInlineThreshold(link, style, sheet) {
if (this.options.inlineThreshold && sheet.length < this.options.inlineThreshold) {
const href = style.$$name;
style.$$reduce = false;
this.logger.info?.(
`\x1B[32mInlined all of ${href} (${sheet.length} was below the threshold of ${this.options.inlineThreshold})\x1B[39m`
);
link.remove();
return true;
}
return false;
}
/**
* Inline the stylesheets from options.additionalStylesheets (assuming it passes `options.filter`)
*/
async embedAdditionalStylesheet(document) {
const styleSheetsIncluded = [];
const sources = await Promise.all(
this.options.additionalStylesheets.map((cssFile) => {
if (styleSheetsIncluded.includes(cssFile)) {
return [];
}
styleSheetsIncluded.push(cssFile);
const style = document.createElement("style");
style.$$external = true;
return this.getCssAsset(cssFile, style).then((sheet) => [sheet, style]);
})
);
for (const [sheet, style] of sources) {
if (sheet) {
style.textContent = sheet;
document.head.appendChild(style);
}
}
}
/**
* Inline the target stylesheet referred to by a <link rel="stylesheet"> (assuming it passes `options.filter`)
*/
async embedLinkedStylesheet(link, document) {
const href = link.getAttribute("href");
if (!href?.endsWith(".css")) {
return void 0;
}
const style = document.createElement("style");
style.$$external = true;
const sheet = await this.getCssAsset(href, style);
if (!sheet) {
return;
}
style.textContent = sheet;
style.$$name = href;
style.$$links = [link];
link.parentNode?.insertBefore(style, link);
if (this.checkInlineThreshold(link, style, sheet)) {
return;
}
let media = link.getAttribute("media");
if (media && !validateMediaQuery(media)) {
media = void 0;
}
const preloadMode = this.options.preload;
let cssLoaderPreamble = "function $loadcss(u,m,l){(l=document.createElement('link')).rel='stylesheet';l.href=u;document.head.appendChild(l)}";
const lazy = preloadMode === "js-lazy";
if (lazy) {
cssLoaderPreamble = cssLoaderPreamble.replace(
"l.href",
"l.media='print';l.onload=function(){l.media=m};l.href"
);
}
if (preloadMode === false)
return;
let noscriptFallback = false;
let updateLinkToPreload = false;
const noscriptLink = link.cloneNode(false);
if (preloadMode === "body") {
document.body.appendChild(link);
} else {
if (preloadMode === "js" || preloadMode === "js-lazy") {
const script = document.createElement("script");
script.setAttribute("data-href", href);
script.setAttribute("data-media", media || "all");
const js = `${cssLoaderPreamble}$loadcss(document.currentScript.dataset.href,document.currentScript.dataset.media)`;
script.textContent = js;
link.parentNode.insertBefore(script, link.nextSibling);
style.$$links.push(script);
cssLoaderPreamble = "";
noscriptFallback = true;
updateLinkToPreload = true;
} else if (preloadMode === "media") {
link.setAttribute("media", "print");
link.setAttribute("onload", `this.media='${media || "all"}'`);
noscriptFallback = true;
} else if (preloadMode === "swap-high") {
link.setAttribute("rel", "alternate stylesheet preload");
link.setAttribute("title", "styles");
link.setAttribute("onload", `this.title='';this.rel='stylesheet'`);
noscriptFallback = true;
} else if (preloadMode === "swap-low") {
link.setAttribute("rel", "alternate stylesheet");
link.setAttribute("title", "styles");
link.setAttribute("onload", `this.title='';this.rel='stylesheet'`);
noscriptFallback = true;
} else if (preloadMode === "swap") {
link.setAttribute("onload", "this.rel='stylesheet'");
updateLinkToPreload = true;
noscriptFallback = true;
} else {
const bodyLink = link.cloneNode(false);
bodyLink.removeAttribute("id");
document.body.appendChild(bodyLink);
style.$$links.push(bodyLink);
updateLinkToPreload = true;
}
}
if (this.options.noscriptFallback !== false && noscriptFallback && !href.includes("</noscript>")) {
const noscript = document.createElement("noscript");
noscriptLink.removeAttribute("id");
noscript.appendChild(noscriptLink);
link.parentNode.insertBefore(noscript, link.nextSibling);
style.$$links.push(noscript);
}
if (updateLinkToPreload) {
link.setAttribute("rel", "preload");
link.setAttribute("as", "style");
}
}
/**
* Prune the source CSS files
*/
pruneSource(style, before, sheetInverse) {
const minSize = this.options.minimumExternalSize;
const name = style.$$name;
const shouldInline = minSize && sheetInverse.length < minSize;
if (shouldInline) {
this.logger.info?.(
`\x1B[32mInlined all of ${name} (non-critical external stylesheet would have been ${sheetInverse.length}b, which was below the threshold of ${minSize})\x1B[39m`
);
}
if (shouldInline || !sheetInverse) {
style.textContent = before;
if (style.$$links) {
for (const link of style.$$links) {
const parent = link.parentNode;
parent?.removeChild(link);
}
}
}
return !!shouldInline;
}
/**
* Parse the stylesheet within a <style> element, then reduce it to contain only rules used by the document.
*/
processStyle(style, document) {
if (style.$$reduce === false)
return;
const name = style.$$name ? style.$$name.replace(/^\//, "") : "inline CSS";
const options = this.options;
const beastiesContainer = document.beastiesContainer;
let keyframesMode = options.keyframes ?? "critical";
if (keyframesMode === true)
keyframesMode = "all";
if (keyframesMode === false)
keyframesMode = "none";
let sheet = style.textContent;
const before = sheet;
if (!sheet)
return;
const ast = parseStylesheet(sheet);
const astInverse = options.pruneSource ? parseStylesheet(sheet) : null;
let criticalFonts = "";
const failedSelectors = [];
const criticalKeyframeNames = /* @__PURE__ */ new Set();
let includeNext = false;
let includeAll = false;
let excludeNext = false;
let excludeAll = false;
const shouldPreloadFonts = options.fonts === true || options.preloadFonts === true;
const shouldInlineFonts = options.fonts !== false && options.inlineFonts === true;
walkStyleRules(
ast,
markOnly((rule) => {
if (rule.type === "comment") {
const beastiesComment = rule.text.match(/^(?<!! )beasties:(.*)/);
const command = beastiesComment && beastiesComment[1];
if (command) {
switch (command) {
case "include":
includeNext = true;
break;
case "exclude":
excludeNext = true;
break;
case "include start":
includeAll = true;
break;
case "include end":
includeAll = false;
break;
case "exclude start":
excludeAll = true;
break;
case "exclude end":
excludeAll = false;
break;
}
}
}
if (rule.type === "rule") {
if (includeNext) {
includeNext = false;
return true;
}
if (excludeNext) {
excludeNext = false;
return false;
}
if (includeAll) {
return true;
}
if (excludeAll) {
return false;
}
rule.filterSelectors?.((sel) => {
const isAllowedRule = options.allowRules.some((exp) => {
if (exp instanceof RegExp) {
return exp.test(sel);
}
return exp === sel;
});
if (isAllowedRule)
return true;
if (sel === ":root" || sel === "html" || sel === "body" || sel[0] === ":" && /^::?(?:before|after)$/.test(sel)) {
return true;
}
sel = this.normalizeCssSelector(sel);
if (!sel)
return false;
try {
return beastiesContainer.exists(sel);
} catch (e) {
failedSelectors.push(`${sel} -> ${e.message || e.toString()}`);
return false;
}
});
if (!rule.selector) {
return false;
}
if (rule.nodes) {
for (const decl of rule.nodes) {
if (!("prop" in decl)) {
continue;
}
if (shouldInlineFonts && /\bfont(?:-family)?\b/i.test(decl.prop)) {
criticalFonts += ` ${decl.value}`;
}
if (decl.prop === "animation" || decl.prop === "animation-name") {
for (const name2 of decl.value.split(/\s+/)) {
const nameTrimmed = name2.trim();
if (nameTrimmed)
criticalKeyframeNames.add(nameTrimmed);
}
}
}
}
}
if (rule.type === "atrule" && (rule.name === "font-face" || rule.name === "layer"))
return;
const hasRemainingRules = ("nodes" in rule && rule.nodes?.some((rule2) => !rule2.$$remove)) ?? true;
return hasRemainingRules;
})
);
if (failedSelectors.length !== 0) {
this.logger.warn?.(
`${failedSelectors.length} rules skipped due to selector errors:
${failedSelectors.join("\n ")}`
);
}
const preloadedFonts = /* @__PURE__ */ new Set();
walkStyleRulesWithReverseMirror(ast, astInverse, (rule) => {
if (rule.$$remove === true)
return false;
if ("selectors" in rule) {
applyMarkedSelectors(rule);
}
if (rule.type === "atrule" && rule.name === "keyframes") {
if (keyframesMode === "none")
return false;
if (keyframesMode === "all")
return true;
return criticalKeyframeNames.has(rule.params);
}
if (rule.type === "atrule" && rule.name === "font-face") {
let family, src;
if (rule.nodes) {
for (const decl of rule.nodes) {
if (!("prop" in decl)) {
continue;
}
if (decl.prop === "src") {
src = (decl.value.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/) || [])[2];
} else if (decl.prop === "font-family") {
family = decl.value;
}
}
if (src && shouldPreloadFonts && !preloadedFonts.has(src)) {
preloadedFonts.add(src);
const preload = document.createElement("link");
preload.setAttribute("rel", "preload");
preload.setAttribute("as", "font");
preload.setAttribute("crossorigin", "anonymous");
preload.setAttribute("href", src.trim());
document.head.appendChild(preload);
}
}
if (!shouldInlineFonts || !family || !src || !criticalFonts.includes(family)) {
return false;
}
}
});
sheet = serializeStylesheet(ast, {
compress: this.options.compress !== false
});
if (sheet.trim().length === 0) {
if (style.parentNode) {
style.remove();
}
return;
}
let afterText = "";
let styleInlinedCompletely = false;
if (options.pruneSource) {
const sheetInverse = serializeStylesheet(astInverse, {
compress: this.options.compress !== false
});
styleInlinedCompletely = this.pruneSource(style, before, sheetInverse);
if (styleInlinedCompletely) {
const percent2 = sheetInverse.length / before.length * 100;
afterText = `, reducing non-inlined size ${percent2 | 0}% to ${formatSize(sheetInverse.length)}`;
}
const cssFilePath = path.resolve(this.options.path, name);
this.writeFile(cssFilePath, sheetInverse).then(() => this.logger.info?.(`${name} was successfully updated`)).catch((err) => this.logger.error?.(err));
}
if (!styleInlinedCompletely) {
style.textContent = sheet;
}
const percent = sheet.length / before.length * 100 | 0;
this.logger.info?.(
`\x1B[32mInlined ${formatSize(sheet.length)} (${percent}% of original ${formatSize(before.length)}) of ${name}${afterText}.\x1B[39m`
);
}
normalizeCssSelector(sel) {
let normalizedSelector = this.#selectorCache.get(sel);
if (normalizedSelector !== void 0) {
return normalizedSelector;
}
normalizedSelector = sel.replace(removePseudoClassesAndElementsPattern, "").replace(removeTrailingCommasPattern, (match) => match.includes("(") ? "(" : ")").replace(implicitUniversalPattern, "$1 * $2").replace(emptyCombinatorPattern, "$1 *").trim();
this.#selectorCache.set(sel, normalizedSelector);
return normalizedSelector;
}
}
function formatSize(size) {
if (size <= 0) {
return "0 bytes";
}
const abbreviations = ["bytes", "kB", "MB", "GB"];
const index = Math.floor(Math.log(size) / Math.log(1024));
const roundedSize = size / 1024 ** index;
const fractionDigits = index === 0 ? 0 : 2;
return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`;
}
export { Beasties as default };

67
node_modules/beasties/package.json generated vendored Normal file
View File

@@ -0,0 +1,67 @@
{
"name": "beasties",
"version": "0.3.5",
"description": "Inline critical CSS and lazy-load the rest.",
"author": "The Chromium Authors",
"contributors": [
{
"name": "Jason Miller",
"email": "developit@google.com"
},
{
"name": "Janicklas Ralph",
"email": "janicklas@google.com"
}
],
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/danielroe/beasties",
"directory": "packages/beasties"
},
"keywords": [
"critical css",
"inline css",
"critical",
"beasties",
"webpack plugin",
"performance"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"default": "./dist/index.mjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=14.0.0"
},
"dependencies": {
"css-select": "^6.0.0",
"css-what": "^7.0.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"htmlparser2": "^10.0.0",
"picocolors": "^1.1.1",
"postcss": "^8.4.49",
"postcss-media-query-parser": "^0.2.3"
},
"devDependencies": {
"@types/postcss-media-query-parser": "0.2.4",
"documentation": "14.0.3",
"unbuild": "3.5.0"
},
"scripts": {
"build": "unbuild && node -e \"require('fs/promises').cp('src/index.d.ts', 'dist/index.d.ts')\"",
"build:stub": "unbuild --stub && node -e \"require('fs/promises').cp('src/index.d.ts', 'dist/index.d.ts')\"",
"docs": "documentation readme src -q --no-markdown-toc -a public -s Usage --sort-order alpha"
}
}