critters-webpack-plugin
Advanced tools
Comparing version 2.5.0 to 3.0.0
108
package.json
{ | ||
"name": "critters-webpack-plugin", | ||
"version": "2.5.0", | ||
"version": "3.0.0", | ||
"description": "Webpack plugin to inline critical CSS and lazy-load the rest.", | ||
"main": "dist/critters.js", | ||
"main": "dist/critters-webpack-plugin.js", | ||
"module": "dist/critters-webpack-plugin.mjs", | ||
"source": "src/index.js", | ||
"exports": { | ||
"import": "./dist/critters-webpack-plugin.mjs", | ||
"require": "./dist/critters-webpack-plugin.js", | ||
"default": "./dist/critters-webpack-plugin.mjs" | ||
}, | ||
"files": [ | ||
"src", | ||
"dist" | ||
], | ||
"license": "Apache-2.0", | ||
@@ -13,8 +23,8 @@ "author": "The Chromium Authors", | ||
"email": "developit@google.com" | ||
}, | ||
{ | ||
"name": "Janicklas Ralph", | ||
"email": "janicklas@google.com" | ||
} | ||
], | ||
"files": [ | ||
"src", | ||
"dist" | ||
], | ||
"keywords": [ | ||
@@ -28,81 +38,27 @@ "critical css", | ||
], | ||
"repository": "GoogleChromeLabs/critters", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/GoogleChromeLabs/critters", | ||
"directory": "packages/critters-webpack-plugin" | ||
}, | ||
"scripts": { | ||
"build": "microbundle -f cjs --no-compress --external all", | ||
"build": "microbundle --target node --no-sourcemap -f cjs,esm", | ||
"docs": "documentation readme -q --no-markdown-toc -a public -s Usage --sort-order alpha src", | ||
"prepare": "npm run -s build", | ||
"release": "npm run build -s && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish", | ||
"test": "jest --coverage" | ||
"prepare": "npm run -s build" | ||
}, | ||
"babel": { | ||
"presets": [ | ||
"env" | ||
] | ||
}, | ||
"jest": { | ||
"testEnvironment": "jsdom", | ||
"testURL": "http://localhost", | ||
"coverageReporters": [ | ||
"text" | ||
], | ||
"collectCoverageFrom": [ | ||
"src/**/*" | ||
], | ||
"watchPathIgnorePatterns": [ | ||
"node_modules", | ||
"dist" | ||
] | ||
}, | ||
"devDependencies": { | ||
"babel-core": "^6.26.0", | ||
"babel-jest": "^22.4.3", | ||
"babel-preset-env": "^1.6.1", | ||
"css-loader": "^0.28.11", | ||
"documentation": "^6.3.2", | ||
"eslint": "^4.19.1", | ||
"eslint-config-standard": "^11.0.0", | ||
"eslint-plugin-import": "^2.11.0", | ||
"eslint-plugin-jest": "^21.15.1", | ||
"eslint-plugin-node": "^6.0.1", | ||
"eslint-plugin-promise": "^3.7.0", | ||
"eslint-plugin-standard": "^3.0.1", | ||
"file-loader": "^1.1.11", | ||
"html-webpack-plugin": "^3.2.0", | ||
"jest": "^22.4.3", | ||
"microbundle": "^0.4.4", | ||
"mini-css-extract-plugin": "^0.4.0", | ||
"webpack": "^4.6.0" | ||
"css-loader": "^4.2.1", | ||
"documentation": "^13.0.2", | ||
"file-loader": "^6.0.0", | ||
"html-webpack-plugin": "^4.5.2", | ||
"microbundle": "^0.12.3", | ||
"mini-css-extract-plugin": "^0.10.0", | ||
"webpack": "^4.46.0" | ||
}, | ||
"dependencies": { | ||
"css": "^2.2.1", | ||
"cssnano": "^4.1.7", | ||
"jsdom": "^12.0.0", | ||
"critters": "^0.0.13", | ||
"minimatch": "^3.0.4", | ||
"parse5": "^4.0.0", | ||
"postcss": "^7.0.5", | ||
"pretty-bytes": "^4.0.2", | ||
"webpack-log": "^2.0.0", | ||
"webpack-log": "^3.0.1", | ||
"webpack-sources": "^1.3.0" | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"standard", | ||
"plugin:jest/recommended" | ||
], | ||
"rules": { | ||
"indent": [ | ||
2, | ||
2 | ||
], | ||
"semi": [ | ||
2, | ||
"always" | ||
], | ||
"prefer-const": 1 | ||
}, | ||
"globals": { | ||
"document": 0, | ||
"DOMParser": 1 | ||
} | ||
} | ||
} |
102
README.md
<p align="center"> | ||
<img src="https://i.imgur.com/J0jv1Sz.png" width="240" height="240" alt="critters-webpack-plugin"> | ||
<h1 align="center">Critters</h1> | ||
<h1 align="center">Critters Webpack plugin</h1> | ||
</p> | ||
> Critters is a Webpack plugin that inlines your app's [critical CSS] and lazy-loads the rest. | ||
> critters-webpack-plugin inlines your app's [critical CSS] and lazy-loads the rest. | ||
## critters-webpack-plugin [![npm](https://img.shields.io/npm/v/critters-webpack-plugin.svg?style=flat)](https://www.npmjs.org/package/critters-webpack-plugin) | ||
It's a little different from [other options](#similar-libraries), because it **doesn't use a headless browser** to render content. This tradeoff allows Critters to be very **fast and lightweight**. It also means Critters 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). | ||
It's a little different from [other options](#similar-libraries), because it **doesn't use a headless browser** to render content. This tradeoff allows Critters to be very **fast and lightweight**. It also means Critters 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). | ||
@@ -16,8 +16,8 @@ Critters' 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. | ||
- Fast - no browser, few dependencies | ||
- Integrates with [html-webpack-plugin] | ||
- Works with `webpack-dev-server` / `webpack serve` | ||
- Supports preloading and/or inlining critical fonts | ||
- Prunes unused CSS keyframes and media queries | ||
- Removes inlined CSS rules from lazy-loaded stylesheets | ||
* Fast - no browser, few dependencies | ||
* Integrates with [html-webpack-plugin] | ||
* Works with `webpack-dev-server` / `webpack serve` | ||
* Supports preloading and/or inlining critical fonts | ||
* Prunes unused CSS keyframes and media queries | ||
* Removes inlined CSS rules from lazy-loaded stylesheets | ||
@@ -53,11 +53,13 @@ ## Installation | ||
### Critters | ||
### CrittersWebpackPlugin | ||
**Extends Critters** | ||
Create a Critters plugin instance with the given options. | ||
**Parameters** | ||
#### Parameters | ||
- `options` **Options** Options to control how Critters inlines CSS. | ||
* `options` **Options** Options to control how Critters inlines CSS. See <https://github.com/GoogleChromeLabs/critters#usage> | ||
**Examples** | ||
#### Examples | ||
@@ -79,72 +81,12 @@ ```javascript | ||
### Critters | ||
All optional. Pass them to `new Critters({ ... })`. | ||
**Parameters** | ||
- `options` | ||
**Properties** | ||
- `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: `true`)_ | ||
- `mergeStylesheets` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Merged inlined stylesheets into a | ||
single <style> tag _(default: `true`)_ | ||
- `additionalStylesheets` **[String[]](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Glob for matching other stylesheets which should be used to evaluate critical CSS _(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"`)_ | ||
### 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. | ||
_[JS]_ indicates that a strategy requires JavaScript (falls back to `<noscript>`). | ||
- **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. _[JS]_ | ||
- **"swap":** Convert stylesheet links to preloads that swap to `rel="stylesheet"` once loaded. _[JS]_ | ||
- **"js":** Inject an asynchronous CSS loader similar to [LoadCSS](https://github.com/filamentgroup/loadCSS) and use it to load stylesheets. _[JS]_ | ||
- **"js-lazy":** Like `"js"`, but the stylesheet is disabled until fully loaded. | ||
Type: (default | `"body"` \| `"media"` \| `"swap"` \| `"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: | ||
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) | ||
* [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) | ||
@@ -151,0 +93,0 @@ ## License |
617
src/index.js
@@ -18,10 +18,6 @@ /** | ||
import path from 'path'; | ||
import prettyBytes from 'pretty-bytes'; | ||
import minimatch from 'minimatch'; | ||
import sources from 'webpack-sources'; | ||
import postcss from 'postcss'; | ||
import cssnano from 'cssnano'; | ||
import log from 'webpack-log'; | ||
import minimatch from 'minimatch'; | ||
import { createDocument, serializeDocument, setNodeText } from './dom'; | ||
import { parseStylesheet, serializeStylesheet, walkStyleRules, walkStyleRulesWithReverseMirror, markOnly, applyMarkedSelectors } from './css'; | ||
import Critters from 'critters'; | ||
import { tap } from './util'; | ||
@@ -32,73 +28,8 @@ | ||
/** | ||
* The mechanism to use for lazy-loading stylesheets. | ||
* _[JS]_ indicates that a strategy requires JavaScript (falls back to `<noscript>`). | ||
* | ||
* - **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. _[JS]_ | ||
* - **"swap":** Convert stylesheet links to preloads that swap to `rel="stylesheet"` once loaded. _[JS]_ | ||
* - **"js":** Inject an asynchronous CSS loader similar to [LoadCSS](https://github.com/filamentgroup/loadCSS) and use it to load stylesheets. _[JS]_ | ||
* - **"js-lazy":** Like `"js"`, but the stylesheet is disabled until fully loaded. | ||
* @typedef {(default|'body'|'media'|'swap'|'js'|'js-lazy')} PreloadStrategy | ||
* @public | ||
*/ | ||
/** @typedef {import('critters').Options} Options */ | ||
/** | ||
* Controls which keyframes rules are inlined. | ||
* | ||
* - **"critical":** _(default)_ inline keyframes rules that are used by the critical CSS. | ||
* - **"all":** Inline all keyframes rules. | ||
* - **"none":** Remove all keyframes rules. | ||
* @typedef {('critical'|'all'|'none')} KeyframeStrategy | ||
* @private | ||
* @property {String} keyframes Which {@link KeyframeStrategy keyframe strategy} to use (default: `critical`)_ | ||
*/ | ||
/** | ||
* 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"** | ||
* @typedef {('info'|'warn'|'error'|'trace'|'debug'|'silent')} LogLevel | ||
* @public | ||
*/ | ||
/** | ||
* All optional. Pass them to `new Critters({ ... })`. | ||
* @public | ||
* @typedef Options | ||
* @property {Boolean} external Inline styles from external stylesheets _(default: `true`)_ | ||
* @property {Number} inlineThreshold Inline external stylesheets smaller than a given size _(default: `0`)_ | ||
* @property {Number} minimumExternalSize If the non-critical external stylesheet would be below this size, just inline it _(default: `0`)_ | ||
* @property {Boolean} pruneSource Remove inlined rules from the external stylesheet _(default: `true`)_ | ||
* @property {Boolean} mergeStylesheets Merged inlined stylesheets into a single <style> tag _(default: `true`)_ | ||
* @property {String[]} additionalStylesheets Glob for matching other stylesheets to be used while looking for critical CSS _(default: ``)_. | ||
* @property {String} preload Which {@link PreloadStrategy preload strategy} to use | ||
* @property {Boolean} noscriptFallback Add `<noscript>` fallback to JS-based strategies | ||
* @property {Boolean} inlineFonts Inline critical font-face rules _(default: `false`)_ | ||
* @property {Boolean} preloadFonts Preloads critical fonts _(default: `true`)_ | ||
* @property {Boolean} fonts 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 | ||
* @property {String} keyframes 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 | ||
* @property {Boolean} compress Compress resulting critical CSS _(default: `true`)_ | ||
* @property {String} logLevel Controls {@link LogLevel log level} of the plugin _(default: `"info"`)_ | ||
*/ | ||
/** | ||
* Create a Critters plugin instance with the given options. | ||
* @public | ||
* @param {Options} options Options to control how Critters inlines CSS. | ||
* @param {Options} options Options to control how Critters inlines CSS. See https://github.com/GoogleChromeLabs/critters#usage | ||
* @example | ||
@@ -118,12 +49,12 @@ * // webpack.config.js | ||
*/ | ||
export default class Critters { | ||
/** @private */ | ||
constructor (options) { | ||
this.options = Object.assign({ logLevel: 'info', externalStylesheets: [] }, options || {}); | ||
this.options.pruneSource = this.options.pruneSource !== false; | ||
this.urlFilter = this.options.filter; | ||
if (this.urlFilter instanceof RegExp) { | ||
this.urlFilter = this.urlFilter.test.bind(this.urlFilter); | ||
} | ||
this.logger = log({ name: 'Critters', unique: true, level: this.options.logLevel }); | ||
export default class CrittersWebpackPlugin extends Critters { | ||
constructor(options) { | ||
super(options); | ||
// TODO: Remove webpack-log | ||
this.logger = log({ | ||
name: 'Critters', | ||
unique: true, | ||
level: this.options.logLevel | ||
}); | ||
} | ||
@@ -134,33 +65,73 @@ | ||
*/ | ||
apply (compiler) { | ||
apply(compiler) { | ||
// hook into the compiler to get a Compilation instance... | ||
tap(compiler, 'compilation', PLUGIN_NAME, false, compilation => { | ||
// ... which is how we get an "after" hook into html-webpack-plugin's HTML generation. | ||
if (compilation.hooks && compilation.hooks.htmlWebpackPluginAfterHtmlProcessing) { | ||
tap(compilation, 'html-webpack-plugin-after-html-processing', PLUGIN_NAME, true, (htmlPluginData, callback) => { | ||
this.process(compiler, compilation, htmlPluginData.html) | ||
.then(html => { callback(null, { html }); }) | ||
.catch(callback); | ||
}); | ||
tap(compiler, 'compilation', PLUGIN_NAME, false, (compilation) => { | ||
this.options.path = compiler.options.output.path; | ||
this.options.publicPath = compiler.options.output.publicPath; | ||
const hasHtmlPlugin = compilation.options.plugins.find( | ||
(p) => p.constructor && p.constructor.name === 'HtmlWebpackPlugin' | ||
); | ||
try { | ||
var htmlPluginHooks = require('html-webpack-plugin').getHooks( | ||
compilation | ||
); | ||
} catch (err) {} | ||
const handleHtmlPluginData = (htmlPluginData, callback) => { | ||
this.fs = compilation.outputFileSystem; | ||
this.compilation = compilation; | ||
this.process(htmlPluginData.html) | ||
.then((html) => { | ||
callback(null, { html }); | ||
}) | ||
.catch(callback); | ||
}; | ||
// get an "after" hook into html-webpack-plugin's HTML generation. | ||
if ( | ||
compilation.hooks && | ||
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing | ||
) { | ||
tap( | ||
compilation, | ||
'html-webpack-plugin-after-html-processing', | ||
PLUGIN_NAME, | ||
true, | ||
handleHtmlPluginData | ||
); | ||
} else if (hasHtmlPlugin && htmlPluginHooks) { | ||
htmlPluginHooks.beforeEmit.tapAsync(PLUGIN_NAME, handleHtmlPluginData); | ||
} else { | ||
// If html-webpack-plugin isn't used, process the first HTML asset as an optimize step | ||
tap(compilation, 'optimize-assets', PLUGIN_NAME, true, (assets, callback) => { | ||
let htmlAssetName; | ||
for (const name in assets) { | ||
if (name.match(/\.html$/)) { | ||
htmlAssetName = name; | ||
break; | ||
tap( | ||
compilation, | ||
'optimize-assets', | ||
PLUGIN_NAME, | ||
true, | ||
(assets, callback) => { | ||
this.fs = compilation.outputFileSystem; | ||
this.compilation = compilation; | ||
let htmlAssetName; | ||
for (const name in assets) { | ||
if (name.match(/\.html$/)) { | ||
htmlAssetName = name; | ||
break; | ||
} | ||
} | ||
if (!htmlAssetName) { | ||
return callback(Error('Could not find HTML asset.')); | ||
} | ||
const html = assets[htmlAssetName].source(); | ||
if (!html) return callback(Error('Empty HTML asset.')); | ||
this.process(String(html)) | ||
.then((html) => { | ||
assets[htmlAssetName] = new sources.RawSource(html); | ||
callback(); | ||
}) | ||
.catch(callback); | ||
} | ||
if (!htmlAssetName) return callback(Error('Could not find HTML asset.')); | ||
const html = assets[htmlAssetName].source(); | ||
if (!html) return callback(Error('Empty HTML asset.')); | ||
this.process(compiler, compilation, String(html)) | ||
.then(html => { | ||
assets[htmlAssetName] = new sources.RawSource(html); | ||
callback(); | ||
}) | ||
.catch(callback); | ||
}); | ||
); | ||
} | ||
@@ -171,103 +142,9 @@ }); | ||
/** | ||
* Read the contents of a file from Webpack's input filesystem | ||
* Given href, find the corresponding CSS asset | ||
*/ | ||
readFile (compilation, filename) { | ||
const fs = this.fs || compilation.outputFileSystem; | ||
return new Promise((resolve, reject) => { | ||
const callback = (err, data) => { | ||
if (err) reject(err); | ||
else resolve(data); | ||
}; | ||
if (fs && fs.readFile) { | ||
fs.readFile(filename, callback); | ||
} else { | ||
require('fs').readFile(filename, 'utf8', callback); | ||
} | ||
}); | ||
} | ||
async getCssAsset(href, style) { | ||
const outputPath = this.options.path; | ||
const publicPath = this.options.publicPath; | ||
/** | ||
* Apply critical CSS processing to html-webpack-plugin | ||
*/ | ||
async process (compiler, compilation, html) { | ||
const outputPath = compiler.options.output.path; | ||
const publicPath = compiler.options.output.publicPath; | ||
// Parse the generated HTML in a DOM we can mutate | ||
const document = createDocument(html); | ||
if (this.options.additionalStylesheets) { | ||
const styleSheetsIncluded = []; | ||
(this.options.additionalStylesheets || []).forEach(cssFile => { | ||
if (styleSheetsIncluded.includes(cssFile)) { | ||
return; | ||
} | ||
styleSheetsIncluded.push(cssFile); | ||
const webpackCssAssets = Object.keys(compilation.assets).filter(file => minimatch(file, cssFile)); | ||
webpackCssAssets.map(asset => { | ||
const tag = document.createElement('style'); | ||
tag.innerHTML = compilation.assets[asset].source(); | ||
document.head.appendChild(tag); | ||
}); | ||
}); | ||
} | ||
// `external:false` skips processing of external sheets | ||
if (this.options.external !== false) { | ||
const externalSheets = [].slice.call(document.querySelectorAll('link[rel="stylesheet"]')); | ||
await Promise.all(externalSheets.map( | ||
link => this.embedLinkedStylesheet(link, compilation, outputPath, publicPath) | ||
)); | ||
} | ||
// go through all the style tags in the document and reduce them to only critical CSS | ||
const styles = [].slice.call(document.querySelectorAll('style')); | ||
await Promise.all(styles.map( | ||
style => this.processStyle(style, document) | ||
)); | ||
if (this.options.mergeStylesheets !== false && styles.length !== 0) { | ||
await this.mergeStylesheets(document); | ||
} | ||
// serialize the document back to HTML and we're done | ||
return serializeDocument(document); | ||
} | ||
async mergeStylesheets (document) { | ||
const styles = [].slice.call(document.querySelectorAll('style')); | ||
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(); | ||
} | ||
if (this.options.compress !== false) { | ||
const before = sheet; | ||
const processor = postcss([cssnano()]); | ||
const result = await processor.process(before, { from: undefined }); | ||
// @todo sourcemap support (elsewhere first) | ||
sheet = result.css; | ||
} | ||
setNodeText(first, sheet); | ||
} | ||
/** | ||
* Inline the target stylesheet referred to by a <link rel="stylesheet"> (assuming it passes `options.filter`) | ||
*/ | ||
async embedLinkedStylesheet (link, compilation, outputPath, publicPath) { | ||
const href = link.getAttribute('href'); | ||
const media = link.getAttribute('media'); | ||
const document = link.ownerDocument; | ||
const preloadMode = this.options.preload; | ||
// skip filtered resources, or network resources if no filter is provided | ||
if (this.urlFilter ? this.urlFilter(href) : href.match(/^(https?:)?\/\//)) return Promise.resolve(); | ||
// CHECK - the output path | ||
// path on disk (with output.publicPath removed) | ||
@@ -277,3 +154,5 @@ let normalizedPath = href.replace(/^\//, ''); | ||
if (normalizedPath.indexOf(pathPrefix) === 0) { | ||
normalizedPath = normalizedPath.substring(pathPrefix.length).replace(/^\//, ''); | ||
normalizedPath = normalizedPath | ||
.substring(pathPrefix.length) | ||
.replace(/^\//, ''); | ||
} | ||
@@ -283,11 +162,20 @@ const filename = path.resolve(outputPath, normalizedPath); | ||
// try to find a matching asset by filename in webpack's output (not yet written to disk) | ||
const relativePath = path.relative(outputPath, filename).replace(/^\.\//, ''); | ||
const asset = compilation.assets[relativePath]; | ||
const relativePath = path | ||
.relative(outputPath, filename) | ||
.replace(/^\.\//, ''); | ||
const asset = this.compilation.assets[relativePath]; // compilation.assets[relativePath]; | ||
// Attempt to read from assets, falling back to a disk read | ||
let sheet = asset && asset.source(); | ||
if (!sheet) { | ||
try { | ||
sheet = await this.readFile(compilation, filename); | ||
this.logger.warn(`Stylesheet "${relativePath}" not found in assets, but a file was located on disk.${this.options.pruneSource ? ' This means pruneSource will not be applied.' : ''}`); | ||
sheet = await this.readFile(this.compilation, filename); | ||
this.logger.warn( | ||
`Stylesheet "${relativePath}" not found in assets, but a file was located on disk.${ | ||
this.options.pruneSource | ||
? ' This means pruneSource will not be applied.' | ||
: '' | ||
}` | ||
); | ||
} catch (e) { | ||
@@ -299,269 +187,80 @@ this.logger.warn(`Unable to locate stylesheet: ${relativePath}`); | ||
// CSS loader is only injected for the first sheet, then this becomes an empty string | ||
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='only x';l.onload=function(){l.media=m};l.href`); | ||
} | ||
// the reduced critical CSS gets injected into a new <style> tag | ||
const style = document.createElement('style'); | ||
style.appendChild(document.createTextNode(sheet)); | ||
link.parentNode.insertBefore(style, link); | ||
if (this.options.inlineThreshold && sheet.length < this.options.inlineThreshold) { | ||
style.$$reduce = false; | ||
this.logger.info(`\u001b[32mInlined all of ${href} (${sheet.length} was below the threshold of ${this.options.inlineThreshold})\u001b[39m`); | ||
if (asset) { | ||
delete compilation.assets[relativePath]; | ||
} else { | ||
this.logger.warn(` > ${href} was not found in assets. the resource may still be emitted but will be unreferenced.`); | ||
} | ||
link.parentNode.removeChild(link); | ||
return; | ||
} | ||
// drop references to webpack asset locations onto the tag, used for later reporting and in-place asset updates | ||
style.$$name = href; | ||
style.$$asset = asset; | ||
style.$$assetName = relativePath; | ||
style.$$assets = compilation.assets; | ||
style.$$links = [link]; | ||
// style.$$assets = this.compilation.assets; | ||
// Allow disabling any mutation of the stylesheet link: | ||
if (preloadMode === false) return; | ||
return sheet; | ||
} | ||
let noscriptFallback = false; | ||
checkInlineThreshold(link, style, sheet) { | ||
const inlined = super.checkInlineThreshold(link, style, sheet); | ||
if (preloadMode === 'body') { | ||
document.body.appendChild(link); | ||
} else { | ||
link.setAttribute('rel', 'preload'); | ||
link.setAttribute('as', 'style'); | ||
if (preloadMode === 'js' || preloadMode === 'js-lazy') { | ||
const script = document.createElement('script'); | ||
const js = `${cssLoaderPreamble}$loadcss(${JSON.stringify(href)}${lazy ? (',' + JSON.stringify(media || 'all')) : ''})`; | ||
script.appendChild(document.createTextNode(js)); | ||
link.parentNode.insertBefore(script, link.nextSibling); | ||
style.$$links.push(script); | ||
cssLoaderPreamble = ''; | ||
noscriptFallback = true; | ||
} else if (preloadMode === 'media') { | ||
// @see https://github.com/filamentgroup/loadCSS/blob/af1106cfe0bf70147e22185afa7ead96c01dec48/src/loadCSS.js#L26 | ||
link.setAttribute('rel', 'stylesheet'); | ||
link.removeAttribute('as'); | ||
link.setAttribute('media', 'only x'); | ||
link.setAttribute('onload', `this.media='${media || 'all'}'`); | ||
noscriptFallback = true; | ||
} else if (preloadMode === 'swap') { | ||
link.setAttribute('onload', "this.rel='stylesheet'"); | ||
noscriptFallback = true; | ||
if (inlined) { | ||
const asset = style.$$asset; | ||
if (asset) { | ||
delete this.compilation.assets[style.$$assetName]; | ||
} else { | ||
const bodyLink = document.createElement('link'); | ||
bodyLink.setAttribute('rel', 'stylesheet'); | ||
if (media) bodyLink.setAttribute('media', media); | ||
bodyLink.setAttribute('href', href); | ||
document.body.appendChild(bodyLink); | ||
style.$$links.push(bodyLink); | ||
this.logger.warn( | ||
` > ${style.$$name} was not found in assets. the resource may still be emitted but will be unreferenced.` | ||
); | ||
} | ||
} | ||
if (this.options.noscriptFallback !== false && noscriptFallback) { | ||
const noscript = document.createElement('noscript'); | ||
const noscriptLink = document.createElement('link'); | ||
noscriptLink.setAttribute('rel', 'stylesheet'); | ||
noscriptLink.setAttribute('href', href); | ||
if (media) noscriptLink.setAttribute('media', media); | ||
noscript.appendChild(noscriptLink); | ||
link.parentNode.insertBefore(noscript, link.nextSibling); | ||
style.$$links.push(noscript); | ||
} | ||
return inlined; | ||
} | ||
/** | ||
* Parse the stylesheet within a <style> element, then reduce it to contain only rules used by the document. | ||
* Inline the stylesheets from options.additionalStylesheets (assuming it passes `options.filter`) | ||
*/ | ||
async processStyle (style) { | ||
if (style.$$reduce === false) return; | ||
const name = style.$$name ? style.$$name.replace(/^\//, '') : 'inline CSS'; | ||
const options = this.options; | ||
const document = style.ownerDocument; | ||
const head = document.querySelector('head'); | ||
let keyframesMode = options.keyframes || 'critical'; | ||
// we also accept a boolean value for options.keyframes | ||
if (keyframesMode === true) keyframesMode = 'all'; | ||
if (keyframesMode === false) keyframesMode = 'none'; | ||
// basically `.textContent` | ||
let sheet = style.childNodes.length > 0 && [].map.call(style.childNodes, node => node.nodeValue).join('\n'); | ||
// store a reference to the previous serialized stylesheet for reporting stats | ||
const before = sheet; | ||
// Skip empty stylesheets | ||
if (!sheet) return; | ||
const ast = parseStylesheet(sheet); | ||
const astInverse = options.pruneSource ? parseStylesheet(sheet) : null; | ||
// a string to search for font names (very loose) | ||
let criticalFonts = ''; | ||
const failedSelectors = []; | ||
const criticalKeyframeNames = []; | ||
// Walk all CSS rules, marking unused rules with `.$$remove=true` for removal in the second pass. | ||
// This first pass is also used to collect font and keyframe usage used in the second pass. | ||
walkStyleRules(ast, markOnly(rule => { | ||
if (rule.type === 'rule') { | ||
// Filter the selector list down to only those match | ||
rule.filterSelectors(sel => { | ||
// Strip pseudo-elements and pseudo-classes, since we only care that their associated elements exist. | ||
// This means any selector for a pseudo-element or having a pseudo-class will be inlined if the rest of the selector matches. | ||
if (sel !== ':root') { | ||
sel = sel.replace(/(?:>\s*)?::?[a-z-]+\s*(\{|$)/gi, '$1').trim(); | ||
} | ||
if (!sel) return false; | ||
try { | ||
return document.querySelector(sel) != null; | ||
} catch (e) { | ||
failedSelectors.push(sel + ' -> ' + e.message); | ||
return false; | ||
} | ||
}); | ||
// If there are no matched selectors, remove the rule: | ||
if (rule.selectors.length === 0) { | ||
return false; | ||
} | ||
if (rule.declarations) { | ||
for (let i = 0; i < rule.declarations.length; i++) { | ||
const decl = rule.declarations[i]; | ||
// detect used fonts | ||
if (decl.property && decl.property.match(/\bfont(-family)?\b/i)) { | ||
criticalFonts += ' ' + decl.value; | ||
} | ||
// detect used keyframes | ||
if (decl.property === 'animation' || decl.property === 'animation-name') { | ||
// @todo: parse animation declarations and extract only the name. for now we'll do a lazy match. | ||
const names = decl.value.split(/\s+/); | ||
for (let j = 0; j < names.length; j++) { | ||
const name = names[j].trim(); | ||
if (name) criticalKeyframeNames.push(name); | ||
} | ||
} | ||
} | ||
} | ||
async embedAdditionalStylesheet(document) { | ||
const styleSheetsIncluded = []; | ||
(this.options.additionalStylesheets || []).forEach((cssFile) => { | ||
if (styleSheetsIncluded.includes(cssFile)) { | ||
return; | ||
} | ||
// keep font rules, they're handled in the second pass: | ||
if (rule.type === 'font-face') return; | ||
// If there are no remaining rules, remove the whole rule: | ||
const rules = rule.rules && rule.rules.filter(rule => !rule.$$remove); | ||
return !rules || rules.length !== 0; | ||
})); | ||
if (failedSelectors.length !== 0) { | ||
this.logger.warn(`${failedSelectors.length} rules skipped due to selector errors:\n ${failedSelectors.join('\n ')}`); | ||
} | ||
const shouldPreloadFonts = options.fonts === true || options.preloadFonts === true; | ||
const shouldInlineFonts = options.fonts !== false && options.inlineFonts === true; | ||
const preloadedFonts = []; | ||
// Second pass, using data picked up from the first | ||
walkStyleRulesWithReverseMirror(ast, astInverse, rule => { | ||
// remove any rules marked in the first pass | ||
if (rule.$$remove === true) return false; | ||
applyMarkedSelectors(rule); | ||
// prune @keyframes rules | ||
if (rule.type === 'keyframes') { | ||
if (keyframesMode === 'none') return false; | ||
if (keyframesMode === 'all') return true; | ||
return criticalKeyframeNames.indexOf(rule.name) !== -1; | ||
} | ||
// prune @font-face rules | ||
if (rule.type === 'font-face') { | ||
let family, src; | ||
for (let i = 0; i < rule.declarations.length; i++) { | ||
const decl = rule.declarations[i]; | ||
if (decl.property === 'src') { | ||
// @todo parse this properly and generate multiple preloads with type="font/woff2" etc | ||
src = (decl.value.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/) || [])[2]; | ||
} else if (decl.property === 'font-family') { | ||
family = decl.value; | ||
} | ||
} | ||
if (src && shouldPreloadFonts && preloadedFonts.indexOf(src) === -1) { | ||
preloadedFonts.push(src); | ||
const preload = document.createElement('link'); | ||
preload.setAttribute('rel', 'preload'); | ||
preload.setAttribute('as', 'font'); | ||
preload.setAttribute('crossorigin', 'anonymous'); | ||
preload.setAttribute('href', src.trim()); | ||
head.appendChild(preload); | ||
} | ||
// if we're missing info, if the font is unused, or if critical font inlining is disabled, remove the rule: | ||
if (!family || !src || criticalFonts.indexOf(family) === -1 || !shouldInlineFonts) return false; | ||
} | ||
styleSheetsIncluded.push(cssFile); | ||
const webpackCssAssets = Object.keys(this.compilation.assets).filter( | ||
(file) => minimatch(file, cssFile) | ||
); | ||
webpackCssAssets.map((asset) => { | ||
const style = document.createElement('style'); | ||
style.$$external = true; | ||
style.textContent = this.compilation.assets[asset].source(); | ||
document.head.appendChild(style); | ||
}); | ||
}); | ||
} | ||
sheet = serializeStylesheet(ast, { compress: this.options.compress !== false }).trim(); | ||
/** | ||
* Prune the source CSS files | ||
*/ | ||
pruneSource(style, before, sheetInverse) { | ||
const isStyleInlined = super.pruneSource(style, before, sheetInverse); | ||
const asset = style.$$asset; | ||
const name = style.$$name; | ||
// If all rules were removed, get rid of the style element entirely | ||
if (sheet.trim().length === 0) { | ||
if (style.parentNode) { | ||
style.parentNode.removeChild(style); | ||
if (asset) { | ||
// if external stylesheet would be below minimum size, just inline everything | ||
const minSize = this.options.minimumExternalSize; | ||
if (minSize && sheetInverse.length < minSize) { | ||
// delete the webpack asset: | ||
delete this.compilation.assets[style.$$assetName]; | ||
return true; | ||
} | ||
return; | ||
this.compilation.assets[style.$$assetName] = | ||
new sources.LineToLineMappedSource( | ||
sheetInverse, | ||
style.$$assetName, | ||
before | ||
); | ||
} else { | ||
this.logger.warn( | ||
'pruneSource is enabled, but a style (' + | ||
name + | ||
') has no corresponding Webpack asset.' | ||
); | ||
} | ||
let afterText = ''; | ||
if (options.pruneSource) { | ||
const sheetInverse = serializeStylesheet(astInverse, { compress: this.options.compress !== false }); | ||
const asset = style.$$asset; | ||
if (asset) { | ||
// if external stylesheet would be below minimum size, just inline everything | ||
const minSize = this.options.minimumExternalSize; | ||
if (minSize && sheetInverse.length < minSize) { | ||
this.logger.info(`\u001b[32mInlined all of ${name} (non-critical external stylesheet would have been ${sheetInverse.length}b, which was below the threshold of ${minSize})\u001b[39m`); | ||
setNodeText(style, before); | ||
// remove any associated external resources/loaders: | ||
if (style.$$links) { | ||
for (const link of style.$$links) { | ||
const parent = link.parentNode; | ||
if (parent) parent.removeChild(link); | ||
} | ||
} | ||
// delete the webpack asset: | ||
delete style.$$assets[style.$$assetName]; | ||
return; | ||
} | ||
const percent = sheetInverse.length / before.length * 100; | ||
afterText = `, reducing non-inlined size ${percent | 0}% to ${prettyBytes(sheetInverse.length)}`; | ||
style.$$assets[style.$$assetName] = new sources.LineToLineMappedSource(sheetInverse, style.$$assetName, before); | ||
} else { | ||
this.logger.warn('pruneSource is enabled, but a style (' + name + ') has no corresponding Webpack asset.'); | ||
} | ||
} | ||
// replace the inline stylesheet with its critical'd counterpart | ||
setNodeText(style, sheet); | ||
// output stats | ||
const percent = sheet.length / before.length * 100 | 0; | ||
this.logger.info('\u001b[32mInlined ' + prettyBytes(sheet.length) + ' (' + percent + '% of original ' + prettyBytes(before.length) + ') of ' + name + afterText + '.\u001b[39m'); | ||
return isStyleInlined; | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
export function tap (inst, hook, pluginName, async, callback) { | ||
export function tap(inst, hook, pluginName, async, callback) { | ||
if (inst.hooks) { | ||
@@ -3,0 +3,0 @@ const camel = hook.replace(/-([a-z])/g, (s, i) => i.toUpperCase()); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
4
7
0
29146
6
624
99
1
+ Addedcritters@^0.0.13
+ Addedansi-styles@4.3.0(transitive)
+ Addedchalk@4.1.2(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedcritters@0.0.13(transitive)
+ Addedcss-select@4.3.0(transitive)
+ Addedcss-what@6.1.0(transitive)
+ Addeddom-serializer@1.4.1(transitive)
+ Addeddomhandler@4.3.1(transitive)
+ Addeddomutils@2.8.0(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedloglevelnext@3.0.1(transitive)
+ Addednanoid@2.1.113.3.7(transitive)
+ Addednth-check@2.1.1(transitive)
+ Addedparse5@6.0.1(transitive)
+ Addedparse5-htmlparser2-tree-adapter@6.0.1(transitive)
+ Addedpostcss@8.4.38(transitive)
+ Addedpretty-bytes@5.6.0(transitive)
+ Addedsource-map-js@1.2.0(transitive)
+ Addedsupports-color@7.2.0(transitive)
+ Addedwebpack-log@3.0.2(transitive)
- Removedcss@^2.2.1
- Removedcssnano@^4.1.7
- Removedjsdom@^12.0.0
- Removedparse5@^4.0.0
- Removedpostcss@^7.0.5
- Removedpretty-bytes@^4.0.2
- Removed@types/q@1.5.8(transitive)
- Removedabab@2.0.6(transitive)
- Removedacorn@6.4.2(transitive)
- Removedacorn-globals@4.3.4(transitive)
- Removedacorn-walk@6.2.0(transitive)
- Removedajv@6.12.6(transitive)
- Removedalphanum-sort@1.0.2(transitive)
- Removedansi-colors@3.2.4(transitive)
- Removedargparse@1.0.10(transitive)
- Removedarray-buffer-byte-length@1.0.1(transitive)
- Removedarray-equal@1.0.2(transitive)
- Removedarray.prototype.reduce@1.0.7(transitive)
- Removedarraybuffer.prototype.slice@1.0.3(transitive)
- Removedasn1@0.2.6(transitive)
- Removedassert-plus@1.0.0(transitive)
- Removedasync-limiter@1.0.1(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedatob@2.1.2(transitive)
- Removedavailable-typed-arrays@1.0.7(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.0(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedbrowser-process-hrtime@1.0.0(transitive)
- Removedbrowserslist@4.23.0(transitive)
- Removedcall-bind@1.0.7(transitive)
- Removedcaller-callsite@2.0.0(transitive)
- Removedcaller-path@2.0.0(transitive)
- Removedcallsites@2.0.0(transitive)
- Removedcaniuse-api@3.0.0(transitive)
- Removedcaniuse-lite@1.0.30001621(transitive)
- Removedcaseless@0.12.0(transitive)
- Removedcoa@2.0.2(transitive)
- Removedcolor@3.2.1(transitive)
- Removedcolor-string@1.9.1(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcore-util-is@1.0.2(transitive)
- Removedcosmiconfig@5.2.1(transitive)
- Removedcss@2.2.4(transitive)
- Removedcss-color-names@0.0.4(transitive)
- Removedcss-declaration-sorter@4.0.1(transitive)
- Removedcss-select@2.1.0(transitive)
- Removedcss-select-base-adapter@0.1.1(transitive)
- Removedcss-tree@1.0.0-alpha.371.1.3(transitive)
- Removedcss-what@3.4.2(transitive)
- Removedcssesc@3.0.0(transitive)
- Removedcssnano@4.1.11(transitive)
- Removedcssnano-preset-default@4.0.8(transitive)
- Removedcssnano-util-get-arguments@4.0.0(transitive)
- Removedcssnano-util-get-match@4.0.0(transitive)
- Removedcssnano-util-raw-cache@4.0.1(transitive)
- Removedcssnano-util-same-parent@4.0.1(transitive)
- Removedcsso@4.2.0(transitive)
- Removedcssom@0.3.8(transitive)
- Removedcssstyle@1.4.0(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddata-urls@1.1.0(transitive)
- Removeddata-view-buffer@1.0.1(transitive)
- Removeddata-view-byte-length@1.0.1(transitive)
- Removeddata-view-byte-offset@1.0.0(transitive)
- Removeddecode-uri-component@0.2.2(transitive)
- Removeddeep-is@0.1.4(transitive)
- Removeddefine-data-property@1.1.4(transitive)
- Removeddefine-properties@1.2.1(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removeddom-serializer@0.2.2(transitive)
- Removeddomelementtype@1.3.1(transitive)
- Removeddomexception@1.0.1(transitive)
- Removeddomutils@1.7.0(transitive)
- Removeddot-prop@5.3.0(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removederror-ex@1.3.2(transitive)
- Removedes-abstract@1.23.3(transitive)
- Removedes-array-method-boxes-properly@1.0.0(transitive)
- Removedes-define-property@1.0.0(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.0.0(transitive)
- Removedes-set-tostringtag@2.0.3(transitive)
- Removedes-to-primitive@1.2.1(transitive)
- Removedescalade@3.1.2(transitive)
- Removedescodegen@1.14.3(transitive)
- Removedesprima@4.0.1(transitive)
- Removedestraverse@4.3.0(transitive)
- Removedesutils@2.0.3(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedfast-levenshtein@2.0.6(transitive)
- Removedfor-each@0.3.3(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedfunction.prototype.name@1.1.6(transitive)
- Removedfunctions-have-names@1.2.3(transitive)
- Removedget-intrinsic@1.2.4(transitive)
- Removedget-symbol-description@1.0.2(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedglobalthis@1.0.4(transitive)
- Removedgopd@1.0.1(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhas@1.0.4(transitive)
- Removedhas-bigints@1.0.2(transitive)
- Removedhas-property-descriptors@1.0.2(transitive)
- Removedhas-proto@1.0.3(transitive)
- Removedhas-symbols@1.0.3(transitive)
- Removedhas-tostringtag@1.0.2(transitive)
- Removedhasown@2.0.2(transitive)
- Removedhex-color-regex@1.1.0(transitive)
- Removedhsl-regex@1.0.0(transitive)
- Removedhsla-regex@1.0.0(transitive)
- Removedhtml-encoding-sniffer@1.0.2(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removediconv-lite@0.4.24(transitive)
- Removedimport-fresh@2.0.0(transitive)
- Removedindexes-of@1.0.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedinternal-slot@1.0.7(transitive)
- Removedis-absolute-url@2.1.0(transitive)
- Removedis-array-buffer@3.0.4(transitive)
- Removedis-arrayish@0.2.10.3.2(transitive)
- Removedis-bigint@1.0.4(transitive)
- Removedis-boolean-object@1.1.2(transitive)
- Removedis-callable@1.2.7(transitive)
- Removedis-color-stop@1.1.0(transitive)
- Removedis-data-view@1.0.1(transitive)
- Removedis-date-object@1.0.5(transitive)
- Removedis-directory@0.3.1(transitive)
- Removedis-negative-zero@2.0.3(transitive)
- Removedis-number-object@1.0.7(transitive)
- Removedis-obj@2.0.0(transitive)
- Removedis-regex@1.1.4(transitive)
- Removedis-resolvable@1.1.0(transitive)
- Removedis-shared-array-buffer@1.0.3(transitive)
- Removedis-string@1.0.7(transitive)
- Removedis-symbol@1.0.4(transitive)
- Removedis-typed-array@1.1.13(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedis-weakref@1.0.2(transitive)
- Removedisarray@2.0.5(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjs-yaml@3.14.1(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjsdom@12.2.0(transitive)
- Removedjson-parse-better-errors@1.0.2(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedlevn@0.3.0(transitive)
- Removedlodash@4.17.21(transitive)
- Removedlodash.memoize@4.1.2(transitive)
- Removedlodash.sortby@4.7.0(transitive)
- Removedlodash.uniq@4.5.0(transitive)
- Removedmdn-data@2.0.142.0.4(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedminimist@1.2.8(transitive)
- Removedmkdirp@0.5.6(transitive)
- Removednode-releases@2.0.14(transitive)
- Removednormalize-url@3.3.0(transitive)
- Removednth-check@1.0.2(transitive)
- Removednwsapi@2.2.10(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedobject-inspect@1.13.1(transitive)
- Removedobject-keys@1.1.1(transitive)
- Removedobject.assign@4.1.5(transitive)
- Removedobject.getownpropertydescriptors@2.1.8(transitive)
- Removedobject.values@1.2.0(transitive)
- Removedoptionator@0.8.3(transitive)
- Removedparse-json@4.0.0(transitive)
- Removedparse5@4.0.05.1.0(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedpicocolors@0.2.1(transitive)
- Removedpn@1.1.0(transitive)
- Removedpossible-typed-array-names@1.0.0(transitive)
- Removedpostcss@7.0.39(transitive)
- Removedpostcss-calc@7.0.5(transitive)
- Removedpostcss-colormin@4.0.3(transitive)
- Removedpostcss-convert-values@4.0.1(transitive)
- Removedpostcss-discard-comments@4.0.2(transitive)
- Removedpostcss-discard-duplicates@4.0.2(transitive)
- Removedpostcss-discard-empty@4.0.1(transitive)
- Removedpostcss-discard-overridden@4.0.1(transitive)
- Removedpostcss-merge-longhand@4.0.11(transitive)
- Removedpostcss-merge-rules@4.0.3(transitive)
- Removedpostcss-minify-font-values@4.0.2(transitive)
- Removedpostcss-minify-gradients@4.0.2(transitive)
- Removedpostcss-minify-params@4.0.2(transitive)
- Removedpostcss-minify-selectors@4.0.2(transitive)
- Removedpostcss-normalize-charset@4.0.1(transitive)
- Removedpostcss-normalize-display-values@4.0.2(transitive)
- Removedpostcss-normalize-positions@4.0.2(transitive)
- Removedpostcss-normalize-repeat-style@4.0.2(transitive)
- Removedpostcss-normalize-string@4.0.2(transitive)
- Removedpostcss-normalize-timing-functions@4.0.2(transitive)
- Removedpostcss-normalize-unicode@4.0.1(transitive)
- Removedpostcss-normalize-url@4.0.1(transitive)
- Removedpostcss-normalize-whitespace@4.0.2(transitive)
- Removedpostcss-ordered-values@4.1.2(transitive)
- Removedpostcss-reduce-initial@4.0.3(transitive)
- Removedpostcss-reduce-transforms@4.0.2(transitive)
- Removedpostcss-selector-parser@3.1.26.1.0(transitive)
- Removedpostcss-svgo@4.0.3(transitive)
- Removedpostcss-unique-selectors@4.0.1(transitive)
- Removedpostcss-value-parser@3.3.14.2.0(transitive)
- Removedprelude-ls@1.1.2(transitive)
- Removedpretty-bytes@4.0.2(transitive)
- Removedpsl@1.9.0(transitive)
- Removedpunycode@2.3.1(transitive)
- Removedq@1.5.1(transitive)
- Removedqs@6.5.3(transitive)
- Removedregexp.prototype.flags@1.5.2(transitive)
- Removedrequest@2.88.2(transitive)
- Removedrequest-promise-core@1.1.4(transitive)
- Removedrequest-promise-native@1.0.9(transitive)
- Removedresolve-from@3.0.0(transitive)
- Removedresolve-url@0.2.1(transitive)
- Removedrgb-regex@1.0.1(transitive)
- Removedrgba-regex@1.0.0(transitive)
- Removedsafe-array-concat@1.1.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsafe-regex-test@1.0.3(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsax@1.2.4(transitive)
- Removedsaxes@3.1.11(transitive)
- Removedset-function-length@1.2.2(transitive)
- Removedset-function-name@2.0.2(transitive)
- Removedside-channel@1.0.6(transitive)
- Removedsimple-swizzle@0.2.2(transitive)
- Removedsource-map-resolve@0.5.3(transitive)
- Removedsource-map-url@0.4.1(transitive)
- Removedsprintf-js@1.0.3(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedstable@0.1.8(transitive)
- Removedstealthy-require@1.1.1(transitive)
- Removedstring.prototype.trim@1.2.9(transitive)
- Removedstring.prototype.trimend@1.0.8(transitive)
- Removedstring.prototype.trimstart@1.0.8(transitive)
- Removedstylehacks@4.0.3(transitive)
- Removedsvgo@1.3.2(transitive)
- Removedsymbol-tree@3.2.4(transitive)
- Removedtimsort@0.3.0(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtr46@1.0.1(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removedtype-check@0.3.2(transitive)
- Removedtyped-array-buffer@1.0.2(transitive)
- Removedtyped-array-byte-length@1.0.1(transitive)
- Removedtyped-array-byte-offset@1.0.2(transitive)
- Removedtyped-array-length@1.0.6(transitive)
- Removedunbox-primitive@1.0.2(transitive)
- Removeduniq@1.0.1(transitive)
- Removeduniqs@2.0.0(transitive)
- Removedunquote@1.1.1(transitive)
- Removedupdate-browserslist-db@1.0.16(transitive)
- Removeduri-js@4.4.1(transitive)
- Removedurix@0.1.0(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedutil.promisify@1.0.1(transitive)
- Removeduuid@3.4.0(transitive)
- Removedvendors@1.0.4(transitive)
- Removedverror@1.10.0(transitive)
- Removedw3c-hr-time@1.0.2(transitive)
- Removedwebidl-conversions@4.0.2(transitive)
- Removedwebpack-log@2.0.0(transitive)
- Removedwhatwg-encoding@1.0.5(transitive)
- Removedwhatwg-mimetype@2.3.0(transitive)
- Removedwhatwg-url@7.1.0(transitive)
- Removedwhich-boxed-primitive@1.0.2(transitive)
- Removedwhich-typed-array@1.1.15(transitive)
- Removedword-wrap@1.2.5(transitive)
- Removedws@6.2.2(transitive)
- Removedxml-name-validator@3.0.0(transitive)
- Removedxmlchars@2.2.0(transitive)
Updatedwebpack-log@^3.0.1