diff --git a/.github/ISSUE_TEMPLATE/css-issue.yml b/.github/ISSUE_TEMPLATE/css-issue.yml
index 6700d9358..f28afbfb2 100644
--- a/.github/ISSUE_TEMPLATE/css-issue.yml
+++ b/.github/ISSUE_TEMPLATE/css-issue.yml
@@ -111,6 +111,7 @@ body:
- PostCSS Replace Overflow Wrap
- PostCSS Scope Pseudo Class
- PostCSS Selector Not
+ - PostCSS Slow Plugins
- PostCSS Stepped Value Functions
- PostCSS System Ui Font Family
- PostCSS Text Decoration Shorthand
diff --git a/.github/ISSUE_TEMPLATE/plugin-issue.yml b/.github/ISSUE_TEMPLATE/plugin-issue.yml
index 5dc15842c..90ddf33b6 100644
--- a/.github/ISSUE_TEMPLATE/plugin-issue.yml
+++ b/.github/ISSUE_TEMPLATE/plugin-issue.yml
@@ -113,6 +113,7 @@ body:
- PostCSS Replace Overflow Wrap
- PostCSS Scope Pseudo Class
- PostCSS Selector Not
+ - PostCSS Slow Plugins
- PostCSS Stepped Value Functions
- PostCSS System Ui Font Family
- PostCSS Text Decoration Shorthand
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 872846e0a..1101a1e23 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -249,6 +249,10 @@
- plugins/postcss-selector-not/**
- experimental/postcss-selector-not/**
+"plugins/postcss-slow-plugins":
+ - plugins/postcss-slow-plugins/**
+ - experimental/postcss-slow-plugins/**
+
"plugins/postcss-system-ui-font-family":
- plugins/postcss-system-ui-font-family/**
- experimental/postcss-system-ui-font-family/**
diff --git a/package-lock.json b/package-lock.json
index 4bdf32bce..473ba134a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2179,6 +2179,10 @@
"resolved": "plugins/postcss-scope-pseudo-class",
"link": true
},
+ "node_modules/@csstools/postcss-slow-plugins": {
+ "resolved": "plugins/postcss-slow-plugins",
+ "link": true
+ },
"node_modules/@csstools/postcss-stepped-value-functions": {
"resolved": "plugins/postcss-stepped-value-functions",
"link": true
@@ -14258,6 +14262,31 @@
"postcss": "^8.4"
}
},
+ "plugins/postcss-slow-plugins": {
+ "name": "@csstools/postcss-slow-plugins",
+ "version": "0.0.0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "devDependencies": {
+ "@csstools/postcss-tape": "*",
+ "postcss-preset-env": "^9.1.3"
+ },
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ }
+ },
"plugins/postcss-stepped-value-functions": {
"name": "@csstools/postcss-stepped-value-functions",
"version": "3.0.1",
diff --git a/plugins/postcss-slow-plugins/.gitignore b/plugins/postcss-slow-plugins/.gitignore
new file mode 100644
index 000000000..e5b28db4a
--- /dev/null
+++ b/plugins/postcss-slow-plugins/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+package-lock.json
+yarn.lock
+*.result.css
+*.result.css.map
+*.result.html
diff --git a/plugins/postcss-slow-plugins/.nvmrc b/plugins/postcss-slow-plugins/.nvmrc
new file mode 100644
index 000000000..6ed5da955
--- /dev/null
+++ b/plugins/postcss-slow-plugins/.nvmrc
@@ -0,0 +1 @@
+v20.2.0
diff --git a/plugins/postcss-slow-plugins/.tape.mjs b/plugins/postcss-slow-plugins/.tape.mjs
new file mode 100644
index 000000000..305f1363a
--- /dev/null
+++ b/plugins/postcss-slow-plugins/.tape.mjs
@@ -0,0 +1,13 @@
+import { postcssTape } from '@csstools/postcss-tape';
+import plugin from '@csstools/postcss-slow-plugins';
+import postcssPresetEnv from 'postcss-preset-env';
+
+await postcssTape(plugin)({
+ basic: {
+ message: "supports basic usage",
+ plugins: [
+ plugin({ ignore: ['postcss-oklab-function', 'autoprefixer'] }),
+ postcssPresetEnv({ stage: 0, browsers: 'Chrome 90' }),
+ ],
+ },
+});
diff --git a/plugins/postcss-slow-plugins/CHANGELOG.md b/plugins/postcss-slow-plugins/CHANGELOG.md
new file mode 100644
index 000000000..0c0d6bf36
--- /dev/null
+++ b/plugins/postcss-slow-plugins/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changes to PostCSS Slow Plugins
+
+### Unreleased (major)
+
+- Initial version
diff --git a/plugins/postcss-slow-plugins/INSTALL.md b/plugins/postcss-slow-plugins/INSTALL.md
new file mode 100644
index 000000000..925e9cdaa
--- /dev/null
+++ b/plugins/postcss-slow-plugins/INSTALL.md
@@ -0,0 +1,235 @@
+# Installing PostCSS Slow Plugins
+
+[PostCSS Slow Plugins] runs in all Node environments, with special instructions for:
+
+- [Node](#node)
+- [PostCSS CLI](#postcss-cli)
+- [PostCSS Load Config](#postcss-load-config)
+- [Webpack](#webpack)
+- [Next.js](#nextjs)
+- [Gulp](#gulp)
+- [Grunt](#grunt)
+
+
+
+## Node
+
+Add [PostCSS Slow Plugins] to your project:
+
+```bash
+npm install postcss @csstools/postcss-slow-plugins --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+// commonjs
+const postcss = require('postcss');
+const postcssSlowPlugins = require('@csstools/postcss-slow-plugins');
+
+postcss([
+ postcssSlowPlugins(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+```js
+// esm
+import postcss from 'postcss';
+import postcssSlowPlugins from '@csstools/postcss-slow-plugins';
+
+postcss([
+ postcssSlowPlugins(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+## PostCSS CLI
+
+Add [PostCSS CLI] to your project:
+
+```bash
+npm install postcss-cli @csstools/postcss-slow-plugins --save-dev
+```
+
+Use [PostCSS Slow Plugins] in your `postcss.config.js` configuration file:
+
+```js
+const postcssSlowPlugins = require('@csstools/postcss-slow-plugins');
+
+module.exports = {
+ plugins: [
+ postcssSlowPlugins(/* pluginOptions */)
+ ]
+}
+```
+
+## PostCSS Load Config
+
+If your framework/CLI supports [`postcss-load-config`](https://github.com/postcss/postcss-load-config).
+
+```bash
+npm install @csstools/postcss-slow-plugins --save-dev
+```
+
+`package.json`:
+
+```json
+{
+ "postcss": {
+ "plugins": {
+ "@csstools/postcss-slow-plugins": {}
+ }
+ }
+}
+```
+
+`.postcssrc.json`:
+
+```json
+{
+ "plugins": {
+ "@csstools/postcss-slow-plugins": {}
+ }
+}
+```
+
+_See the [README of `postcss-load-config`](https://github.com/postcss/postcss-load-config#usage) for more usage options._
+
+## Webpack
+
+_Webpack version 5_
+
+Add [PostCSS Loader] to your project:
+
+```bash
+npm install postcss-loader @csstools/postcss-slow-plugins --save-dev
+```
+
+Use [PostCSS Slow Plugins] in your Webpack configuration:
+
+```js
+module.exports = {
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: [
+ "style-loader",
+ {
+ loader: "css-loader",
+ options: { importLoaders: 1 },
+ },
+ {
+ loader: "postcss-loader",
+ options: {
+ postcssOptions: {
+ plugins: [
+ // Other plugins,
+ [
+ "@csstools/postcss-slow-plugins",
+ {
+ // Options
+ },
+ ],
+ ],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
+```
+
+## Next.js
+
+Read the instructions on how to [customize the PostCSS configuration in Next.js](https://nextjs.org/docs/advanced-features/customizing-postcss-config)
+
+```bash
+npm install @csstools/postcss-slow-plugins --save-dev
+```
+
+Use [PostCSS Slow Plugins] in your `postcss.config.json` file:
+
+```json
+{
+ "plugins": [
+ "@csstools/postcss-slow-plugins"
+ ]
+}
+```
+
+```json5
+{
+ "plugins": [
+ [
+ "@csstools/postcss-slow-plugins",
+ {
+ // Optionally add plugin options
+ }
+ ]
+ ]
+}
+```
+
+## Gulp
+
+Add [Gulp PostCSS] to your project:
+
+```bash
+npm install gulp-postcss @csstools/postcss-slow-plugins --save-dev
+```
+
+Use [PostCSS Slow Plugins] in your Gulpfile:
+
+```js
+const postcss = require('gulp-postcss');
+const postcssSlowPlugins = require('@csstools/postcss-slow-plugins');
+
+gulp.task('css', function () {
+ var plugins = [
+ postcssSlowPlugins(/* pluginOptions */)
+ ];
+
+ return gulp.src('./src/*.css')
+ .pipe(postcss(plugins))
+ .pipe(gulp.dest('.'));
+});
+```
+
+## Grunt
+
+Add [Grunt PostCSS] to your project:
+
+```bash
+npm install grunt-postcss @csstools/postcss-slow-plugins --save-dev
+```
+
+Use [PostCSS Slow Plugins] in your Gruntfile:
+
+```js
+const postcssSlowPlugins = require('@csstools/postcss-slow-plugins');
+
+grunt.loadNpmTasks('grunt-postcss');
+
+grunt.initConfig({
+ postcss: {
+ options: {
+ processors: [
+ postcssSlowPlugins(/* pluginOptions */)
+ ]
+ },
+ dist: {
+ src: '*.css'
+ }
+ }
+});
+```
+
+[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
+[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS CLI]: https://github.com/postcss/postcss-cli
+[PostCSS Loader]: https://github.com/postcss/postcss-loader
+[PostCSS Slow Plugins]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-slow-plugins
+[Next.js]: https://nextjs.org
diff --git a/plugins/postcss-slow-plugins/LICENSE.md b/plugins/postcss-slow-plugins/LICENSE.md
new file mode 100644
index 000000000..e8ae93b9f
--- /dev/null
+++ b/plugins/postcss-slow-plugins/LICENSE.md
@@ -0,0 +1,18 @@
+MIT No Attribution (MIT-0)
+
+Copyright © CSSTools Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the “Software”), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/plugins/postcss-slow-plugins/README.md b/plugins/postcss-slow-plugins/README.md
new file mode 100644
index 000000000..30993af50
--- /dev/null
+++ b/plugins/postcss-slow-plugins/README.md
@@ -0,0 +1,137 @@
+# PostCSS Slow Plugins [
][PostCSS]
+
+[
][npm-url] [
][cli-url] [
][discord]
+
+[PostCSS Slow Plugins] lets you easily check which plugins have the most impact on your build duration.
+
+PostCSS is incredibly fast but adding too many plugins or a few slow ones can still cause issues.
+
+Typical causes of slow PostCSS setups are :
+- you have a lot of CSS
+- you have a lot of plugins
+- a specific plugin is very slow
+
+This plugin is intended to make it visible which plugins are the most impactful to remove/disable.
+
+We also welcome anyone to report performance issues so that we can improve the performance of popular plugins.
+However we ask that everyone is respectful when doing so.
+No one is required to fix your performance issue.
+
+We strongly believe that PostCSS setups can and should be fast and efficient.
+
+
+## How to measure
+
+[PostCSS Slow Plugins] itself is also just a PostCSS plugin.
+Simply add it to your config as you would any other plugin and run your build.
+
+This will take a long time.
+
+Seriously, a very long time.
+
+It will profile your build twice for each plugin :
+- once with only that plugin disabled
+- once with only that plugin enabled
+
+When profiling it will run your build 20 times to get a mean duration.
+
+So expect this to take `N * 20 * 2` times longer than your normal build.
+
+
+## Some tips to improve performance
+
+- make sure your `browserslist` config is accurate and up to date
+- make sure `caniuse-lite` is up to date
+- add a dev specific config with minimal plugins
+- remove plugins that do trivial things you could do by hand
+- remove plugins for static values that could be generated once
+
+
+## Example output
+
+```
+Analyzing with file:
+ plugins/postcss-slow-plugins/test/basic.css
+
+Most impactful to remove, ordered by drop in duration when excluded:
+┌─────────┬────────────┬──────────────┬───────────┬──────────────────────────────────────────┬───────────────────────┐
+│ (index) │ duration │ kb's per ms │ drop │ name │ index in plugins list │
+├─────────┼────────────┼──────────────┼───────────┼──────────────────────────────────────────┼───────────────────────┤
+│ 0 │ '10.692ms' │ '0.937kb/ms' │ '--' │ '-- all plugins --' │ '--' │
+│ 1 │ '6.884ms' │ '1.455kb/ms' │ '3.808ms' │ 'postcss-gradients-interpolation-method' │ 9 │
+│ 2 │ '7.128ms' │ '1.405kb/ms' │ '3.564ms' │ 'postcss-progressive-custom-properties' │ 25 │
+│ 3 │ '7.138ms' │ '1.403kb/ms' │ '3.554ms' │ 'postcss-normalize-display-values' │ 18 │
+│ 4 │ '7.281ms' │ '1.376kb/ms' │ '3.411ms' │ 'postcss-color-mix-function' │ 10 │
+│ 5 │ '7.286ms' │ '1.375kb/ms' │ '3.406ms' │ 'postcss-stepped-value-functions' │ 20 │
+│ 6 │ '7.299ms' │ '1.372kb/ms' │ '3.393ms' │ 'postcss-cascade-layers' │ 23 │
+│ 7 │ '7.327ms' │ '1.367kb/ms' │ '3.366ms' │ 'postcss-trigonometric-functions' │ 21 │
+│ 8 │ '7.329ms' │ '1.367kb/ms' │ '3.363ms' │ 'postcss-color-function' │ 14 │
+│ 9 │ '7.332ms' │ '1.366kb/ms' │ '3.361ms' │ 'postcss-ic-unit' │ 19 │
+│ 10 │ '7.342ms' │ '1.364kb/ms' │ '3.350ms' │ 'postcss-lab-function' │ 12 │
+└─────────┴────────────┴──────────────┴───────────┴──────────────────────────────────────────┴───────────────────────┘
+Most impactful to remove, ordered by increase in duration when running alone:
+┌─────────┬───────────┬──────────────┬──────────────────────────────────────────┬───────────────────────┐
+│ (index) │ duration │ kb's per ms │ name │ index in plugins list │
+├─────────┼───────────┼──────────────┼──────────────────────────────────────────┼───────────────────────┤
+│ 0 │ '2.171ms' │ '4.613kb/ms' │ 'postcss-gradients-interpolation-method' │ 9 │
+│ 1 │ '2.000ms' │ '5.007kb/ms' │ 'postcss-relative-color-syntax' │ 11 │
+│ 2 │ '1.965ms' │ '5.097kb/ms' │ 'postcss-lab-function' │ 12 │
+│ 3 │ '1.948ms' │ '5.142kb/ms' │ 'postcss-color-mix-function' │ 10 │
+│ 4 │ '1.833ms' │ '5.465kb/ms' │ 'postcss-nesting' │ 3 │
+│ 5 │ '1.821ms' │ '5.500kb/ms' │ 'postcss-custom-media' │ 0 │
+│ 6 │ '1.819ms' │ '5.507kb/ms' │ 'postcss-color-function' │ 14 │
+│ 7 │ '1.762ms' │ '5.684kb/ms' │ 'postcss-normalize-display-values' │ 18 │
+│ 8 │ '1.750ms' │ '5.724kb/ms' │ 'postcss-custom-selectors' │ 4 │
+│ 9 │ '1.734ms' │ '5.777kb/ms' │ 'postcss-stepped-value-functions' │ 20 │
+│ 10 │ '1.729ms' │ '5.792kb/ms' │ 'postcss-trigonometric-functions' │ 21 │
+└─────────┴───────────┴──────────────┴──────────────────────────────────────────┴───────────────────────┘
+```
+
+## Usage
+
+Add [PostCSS Slow Plugins] to your project:
+
+```bash
+npm install postcss @csstools/postcss-slow-plugins --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+const postcss = require('postcss');
+const postcssSlowPlugins = require('@csstools/postcss-slow-plugins');
+
+postcss([
+ postcssSlowPlugins(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+[PostCSS Slow Plugins] runs in all Node environments, with special
+instructions for:
+
+- [Node](INSTALL.md#node)
+- [PostCSS CLI](INSTALL.md#postcss-cli)
+- [PostCSS Load Config](INSTALL.md#postcss-load-config)
+- [Webpack](INSTALL.md#webpack)
+- [Next.js](INSTALL.md#nextjs)
+- [Gulp](INSTALL.md#gulp)
+- [Grunt](INSTALL.md#grunt)
+
+## Options
+
+### ignore
+
+The `ignore` option allows you to skip profiling specific plugins.
+This is useful to exclude those plugins that are critical anyway.
+
+```js
+postcssSlowPlugins({ ignore: ['postcss-oklab-function'] })
+```
+
+[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
+
+[discord]: https://discord.gg/bUadyRwkJS
+[npm-url]: https://www.npmjs.com/package/@csstools/postcss-slow-plugins
+
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS Slow Plugins]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-slow-plugins
diff --git a/plugins/postcss-slow-plugins/dist/index.cjs b/plugins/postcss-slow-plugins/dist/index.cjs
new file mode 100644
index 000000000..cf2cd7420
--- /dev/null
+++ b/plugins/postcss-slow-plugins/dist/index.cjs
@@ -0,0 +1 @@
+"use strict";const creator=s=>{const o=(null==s?void 0:s.ignore)??[];return{postcssPlugin:"postcss-slow-plugins",Once:async(s,{result:n,postcss:t})=>{var e,i;console.log("Analyzing with file:\n "+(null==(e=s.source)?void 0:e.input.from)+"\n");const r=(null==(i=s.source)?void 0:i.input.css)??"",l=[...n.processor.plugins.filter((s=>"postcssPlugin"in s&&"postcss-slow-plugins"!==s.postcssPlugin))],c=(await t(l).process(r??"",n.opts)).css.length/1024,medianDuration=async s=>{const o=[];for(let n=0;n<21;n++){const n=performance.now();await s(),o.push(performance.now()-n)}return o.sort(((s,o)=>s-o)),o[10]},p=await medianDuration((async()=>{(await t(l).process(r??"",n.opts)).css}));{const s=[];for(let e=0;es!==l[e])),a=await medianDuration((async()=>{(await t(u).process(r??"",n.opts)).css})),d=p-a;s.push({duration:`${a.toFixed(3)}ms`,"kb's per ms":`${(c/a).toFixed(3)}kb/ms`,drop:d,name:i,"index in plugins list":e})}s.sort(((s,o)=>Number(o.drop)-Number(s.drop))),s.splice(0,0,{duration:`${p.toFixed(3)}ms`,"kb's per ms":`${(c/p).toFixed(3)}kb/ms`,drop:"--",name:"-- all plugins --","index in plugins list":"--"}),s.map((s=>("string"==typeof s.drop||(s.drop=`${s.drop.toFixed(3)}ms`),s))),console.log("Most impactful to remove, ordered by drop in duration when excluded:"),console.table(s.slice(0,11))}{const s=[];for(let e=0;e{(await t(u).process(r??"",n.opts)).css})),d=p-a;s.push({duration:`${a.toFixed(3)}ms`,"kb's per ms":`${(c/a).toFixed(3)}kb/ms`,drop:d,name:i,"index in plugins list":e})}s.sort(((s,o)=>Number(s.drop)-Number(o.drop))),s.map((s=>(delete s.drop,s))),console.log("Most impactful to remove, ordered by increase in duration when running alone:"),console.table(s.slice(0,11))}}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-slow-plugins/dist/index.d.ts b/plugins/postcss-slow-plugins/dist/index.d.ts
new file mode 100644
index 000000000..753b96144
--- /dev/null
+++ b/plugins/postcss-slow-plugins/dist/index.d.ts
@@ -0,0 +1,8 @@
+import type { PluginCreator } from 'postcss';
+/** postcss-slow-plugins plugin options */
+export type pluginOptions = {
+ /** Plugins to ignore when reporting the results */
+ ignore?: Array;
+};
+declare const creator: PluginCreator;
+export default creator;
diff --git a/plugins/postcss-slow-plugins/dist/index.mjs b/plugins/postcss-slow-plugins/dist/index.mjs
new file mode 100644
index 000000000..52259a3a1
--- /dev/null
+++ b/plugins/postcss-slow-plugins/dist/index.mjs
@@ -0,0 +1 @@
+const creator=s=>{const o=(null==s?void 0:s.ignore)??[];return{postcssPlugin:"postcss-slow-plugins",Once:async(s,{result:n,postcss:t})=>{var e,i;console.log("Analyzing with file:\n "+(null==(e=s.source)?void 0:e.input.from)+"\n");const r=(null==(i=s.source)?void 0:i.input.css)??"",l=[...n.processor.plugins.filter((s=>"postcssPlugin"in s&&"postcss-slow-plugins"!==s.postcssPlugin))],c=(await t(l).process(r??"",n.opts)).css.length/1024,medianDuration=async s=>{const o=[];for(let n=0;n<21;n++){const n=performance.now();await s(),o.push(performance.now()-n)}return o.sort(((s,o)=>s-o)),o[10]},p=await medianDuration((async()=>{(await t(l).process(r??"",n.opts)).css}));{const s=[];for(let e=0;es!==l[e])),a=await medianDuration((async()=>{(await t(u).process(r??"",n.opts)).css})),d=p-a;s.push({duration:`${a.toFixed(3)}ms`,"kb's per ms":`${(c/a).toFixed(3)}kb/ms`,drop:d,name:i,"index in plugins list":e})}s.sort(((s,o)=>Number(o.drop)-Number(s.drop))),s.splice(0,0,{duration:`${p.toFixed(3)}ms`,"kb's per ms":`${(c/p).toFixed(3)}kb/ms`,drop:"--",name:"-- all plugins --","index in plugins list":"--"}),s.map((s=>("string"==typeof s.drop||(s.drop=`${s.drop.toFixed(3)}ms`),s))),console.log("Most impactful to remove, ordered by drop in duration when excluded:"),console.table(s.slice(0,11))}{const s=[];for(let e=0;e{(await t(u).process(r??"",n.opts)).css})),d=p-a;s.push({duration:`${a.toFixed(3)}ms`,"kb's per ms":`${(c/a).toFixed(3)}kb/ms`,drop:d,name:i,"index in plugins list":e})}s.sort(((s,o)=>Number(s.drop)-Number(o.drop))),s.map((s=>(delete s.drop,s))),console.log("Most impactful to remove, ordered by increase in duration when running alone:"),console.table(s.slice(0,11))}}}};creator.postcss=!0;export{creator as default};
diff --git a/plugins/postcss-slow-plugins/docs/README.md b/plugins/postcss-slow-plugins/docs/README.md
new file mode 100644
index 000000000..bbb46817f
--- /dev/null
+++ b/plugins/postcss-slow-plugins/docs/README.md
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[] lets you easily check which plugins have the most impact on your build duration.
+
+PostCSS is incredibly fast but adding too many plugins or a few slow ones can still cause issues.
+
+Typical causes of slow PostCSS setups are :
+- you have a lot of CSS
+- you have a lot of plugins
+- a specific plugin is very slow
+
+This plugin is intended to make it visible which plugins are the most impactful to remove/disable.
+
+We also welcome anyone to report performance issues so that we can improve the performance of popular plugins.
+However we ask that everyone is respectful when doing so.
+No one is required to fix your performance issue.
+
+We strongly believe that PostCSS setups can and should be fast and efficient.
+
+
+## How to measure
+
+[] itself is also just a PostCSS plugin.
+Simply add it to your config as you would any other plugin and run your build.
+
+This will take a long time.
+
+Seriously, a very long time.
+
+It will profile your build twice for each plugin :
+- once with only that plugin disabled
+- once with only that plugin enabled
+
+When profiling it will run your build 20 times to get a mean duration.
+
+So expect this to take `N * 20 * 2` times longer than your normal build.
+
+
+## Some tips to improve performance
+
+- make sure your `browserslist` config is accurate and up to date
+- make sure `caniuse-lite` is up to date
+- add a dev specific config with minimal plugins
+- remove plugins that do trivial things you could do by hand
+- remove plugins for static values that could be generated once
+
+
+## Example output
+
+```
+Analyzing with file:
+ plugins/postcss-slow-plugins/test/basic.css
+
+Most impactful to remove, ordered by drop in duration when excluded:
+┌─────────┬────────────┬──────────────┬───────────┬──────────────────────────────────────────┬───────────────────────┐
+│ (index) │ duration │ kb's per ms │ drop │ name │ index in plugins list │
+├─────────┼────────────┼──────────────┼───────────┼──────────────────────────────────────────┼───────────────────────┤
+│ 0 │ '10.692ms' │ '0.937kb/ms' │ '--' │ '-- all plugins --' │ '--' │
+│ 1 │ '6.884ms' │ '1.455kb/ms' │ '3.808ms' │ 'postcss-gradients-interpolation-method' │ 9 │
+│ 2 │ '7.128ms' │ '1.405kb/ms' │ '3.564ms' │ 'postcss-progressive-custom-properties' │ 25 │
+│ 3 │ '7.138ms' │ '1.403kb/ms' │ '3.554ms' │ 'postcss-normalize-display-values' │ 18 │
+│ 4 │ '7.281ms' │ '1.376kb/ms' │ '3.411ms' │ 'postcss-color-mix-function' │ 10 │
+│ 5 │ '7.286ms' │ '1.375kb/ms' │ '3.406ms' │ 'postcss-stepped-value-functions' │ 20 │
+│ 6 │ '7.299ms' │ '1.372kb/ms' │ '3.393ms' │ 'postcss-cascade-layers' │ 23 │
+│ 7 │ '7.327ms' │ '1.367kb/ms' │ '3.366ms' │ 'postcss-trigonometric-functions' │ 21 │
+│ 8 │ '7.329ms' │ '1.367kb/ms' │ '3.363ms' │ 'postcss-color-function' │ 14 │
+│ 9 │ '7.332ms' │ '1.366kb/ms' │ '3.361ms' │ 'postcss-ic-unit' │ 19 │
+│ 10 │ '7.342ms' │ '1.364kb/ms' │ '3.350ms' │ 'postcss-lab-function' │ 12 │
+└─────────┴────────────┴──────────────┴───────────┴──────────────────────────────────────────┴───────────────────────┘
+Most impactful to remove, ordered by increase in duration when running alone:
+┌─────────┬───────────┬──────────────┬──────────────────────────────────────────┬───────────────────────┐
+│ (index) │ duration │ kb's per ms │ name │ index in plugins list │
+├─────────┼───────────┼──────────────┼──────────────────────────────────────────┼───────────────────────┤
+│ 0 │ '2.171ms' │ '4.613kb/ms' │ 'postcss-gradients-interpolation-method' │ 9 │
+│ 1 │ '2.000ms' │ '5.007kb/ms' │ 'postcss-relative-color-syntax' │ 11 │
+│ 2 │ '1.965ms' │ '5.097kb/ms' │ 'postcss-lab-function' │ 12 │
+│ 3 │ '1.948ms' │ '5.142kb/ms' │ 'postcss-color-mix-function' │ 10 │
+│ 4 │ '1.833ms' │ '5.465kb/ms' │ 'postcss-nesting' │ 3 │
+│ 5 │ '1.821ms' │ '5.500kb/ms' │ 'postcss-custom-media' │ 0 │
+│ 6 │ '1.819ms' │ '5.507kb/ms' │ 'postcss-color-function' │ 14 │
+│ 7 │ '1.762ms' │ '5.684kb/ms' │ 'postcss-normalize-display-values' │ 18 │
+│ 8 │ '1.750ms' │ '5.724kb/ms' │ 'postcss-custom-selectors' │ 4 │
+│ 9 │ '1.734ms' │ '5.777kb/ms' │ 'postcss-stepped-value-functions' │ 20 │
+│ 10 │ '1.729ms' │ '5.792kb/ms' │ 'postcss-trigonometric-functions' │ 21 │
+└─────────┴───────────┴──────────────┴──────────────────────────────────────────┴───────────────────────┘
+```
+
+
+
+
+
+## Options
+
+### ignore
+
+The `ignore` option allows you to skip profiling specific plugins.
+This is useful to exclude those plugins that are critical anyway.
+
+```js
+({ ignore: ['postcss-oklab-function'] })
+```
+
+
diff --git a/plugins/postcss-slow-plugins/package.json b/plugins/postcss-slow-plugins/package.json
new file mode 100644
index 000000000..40f1ac726
--- /dev/null
+++ b/plugins/postcss-slow-plugins/package.json
@@ -0,0 +1,84 @@
+{
+ "name": "@csstools/postcss-slow-plugins",
+ "description": "Identify slow plugins in your PostCSS config",
+ "version": "0.0.0",
+ "contributors": [
+ {
+ "name": "Antonio Laguna",
+ "email": "antonio@laguna.es",
+ "url": "https://antonio.laguna.es"
+ },
+ {
+ "name": "Romain Menke",
+ "email": "romainmenke@gmail.com"
+ }
+ ],
+ "license": "MIT-0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "main": "dist/index.cjs",
+ "module": "dist/index.mjs",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "default": "./dist/index.mjs"
+ }
+ },
+ "files": [
+ "CHANGELOG.md",
+ "LICENSE.md",
+ "README.md",
+ "dist"
+ ],
+ "peerDependencies": {
+ "postcss": "^8.4"
+ },
+ "devDependencies": {
+ "@csstools/postcss-tape": "*",
+ "postcss-preset-env": "^9.1.3"
+ },
+ "scripts": {
+ "build": "rollup -c ../../rollup/default.mjs",
+ "docs": "node ../../.github/bin/generate-docs/install.mjs && node ../../.github/bin/generate-docs/readme.mjs",
+ "lint": "node ../../.github/bin/format-package-json.mjs",
+ "prepublishOnly": "npm run build && npm run test",
+ "test": "node .tape.mjs && node ./test/_import.mjs && node ./test/_require.cjs",
+ "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs"
+ },
+ "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-slow-plugins#readme",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/csstools/postcss-plugins.git",
+ "directory": "plugins/postcss-slow-plugins"
+ },
+ "bugs": "https://github.com/csstools/postcss-plugins/issues",
+ "keywords": [
+ "debug",
+ "performance",
+ "postcss-plugin",
+ "profiling",
+ "slow",
+ "utility"
+ ],
+ "csstools": {
+ "exportName": "postcssSlowPlugins",
+ "humanReadableName": "PostCSS Slow Plugins"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/plugins/postcss-slow-plugins/src/index.ts b/plugins/postcss-slow-plugins/src/index.ts
new file mode 100644
index 000000000..bec6f9e4a
--- /dev/null
+++ b/plugins/postcss-slow-plugins/src/index.ts
@@ -0,0 +1,143 @@
+import type { Plugin, PluginCreator, Transformer } from 'postcss';
+
+/** postcss-slow-plugins plugin options */
+export type pluginOptions = {
+ /** Plugins to ignore when reporting the results */
+ ignore?: Array
+};
+
+const creator: PluginCreator = (opts?: pluginOptions) => {
+ const ignore = opts?.ignore ?? [];
+
+ return {
+ postcssPlugin: 'postcss-slow-plugins',
+ Once: async (root, { result, postcss }) => {
+ console.log('Analyzing with file:\n ' + root.source?.input.from + '\n');
+
+ const inputCSS = root.source?.input.css ?? '';
+
+ const plugins = [
+ ...result.processor.plugins.filter((x) => 'postcssPlugin' in x && x.postcssPlugin !== 'postcss-slow-plugins'),
+ ];
+
+ const outputCSS_KB = (await postcss(plugins).process(inputCSS ?? '', result.opts)).css.length / 1024;
+
+ const medianDuration = async (cb: () => Promise) => {
+ const durations = [];
+ for (let i = 0; i < 21; i++) {
+ const start = performance.now();
+ await cb();
+ durations.push(performance.now() - start);
+ }
+ durations.sort((a, b) => a - b);
+ return durations[10];
+ };
+
+ const baseline = await medianDuration(async () => {
+ (await postcss(plugins).process(inputCSS ?? '', result.opts)).css;
+ });
+
+ {
+ const results: Array<{
+ duration: string,
+ 'kb\'s per ms'?: string,
+ drop: number | string,
+ name: string,
+ 'index in plugins list': number | string,
+ }> = [];
+
+ for (let i = 0; i < plugins.length; i++) {
+ const name = 'postcssPlugin' in plugins[i] ? (plugins[i] as Plugin | Transformer).postcssPlugin : 'unknown plugin';
+ if (ignore.includes(name)) {
+ continue;
+ }
+
+ const pluginsWithoutCurrent = plugins.filter((x) => x !== plugins[i]);
+
+ const durationWithoutPlugin = await medianDuration(async () => {
+ (await postcss(pluginsWithoutCurrent).process(inputCSS ?? '', result.opts)).css;
+ });
+
+ const durationDrop = baseline - durationWithoutPlugin;
+
+ results.push({
+ duration: `${durationWithoutPlugin.toFixed(3)}ms`,
+ 'kb\'s per ms': `${(outputCSS_KB / durationWithoutPlugin).toFixed(3)}kb/ms`,
+ drop: durationDrop,
+ name: name,
+ 'index in plugins list': i,
+ });
+ }
+
+ results.sort((a, b) => Number(b.drop) - Number(a.drop));
+
+ results.splice(0, 0, {
+ duration: `${baseline.toFixed(3)}ms`,
+ 'kb\'s per ms': `${(outputCSS_KB / baseline).toFixed(3)}kb/ms`,
+ drop: '--',
+ name: '-- all plugins --',
+ 'index in plugins list': '--',
+ });
+
+ results.map((x) => {
+ if (typeof x.drop === 'string') {
+ return x;
+ }
+
+ x.drop = `${x.drop.toFixed(3)}ms`;
+ return x;
+ });
+
+ console.log('Most impactful to remove, ordered by drop in duration when excluded:');
+ console.table(results.slice(0, 11));
+ }
+
+ {
+ const results: Array<{
+ duration: string,
+ 'kb\'s per ms'?: string,
+ drop?: number | string,
+ name: string,
+ 'index in plugins list': number | string,
+ }> = [];
+
+ for (let i = 0; i < plugins.length; i++) {
+ const name = 'postcssPlugin' in plugins[i] ? (plugins[i] as Plugin | Transformer).postcssPlugin : 'unknown plugin';
+ if (ignore.includes(name)) {
+ continue;
+ }
+
+ const pluginsWithOnlyCurrent = [plugins[i]];
+
+ const durationWithOnlyPlugin = await medianDuration(async () => {
+ (await postcss(pluginsWithOnlyCurrent).process(inputCSS ?? '', result.opts)).css;
+ });
+
+ const durationDrop = baseline - durationWithOnlyPlugin;
+
+ results.push({
+ duration: `${durationWithOnlyPlugin.toFixed(3)}ms`,
+ 'kb\'s per ms': `${(outputCSS_KB / durationWithOnlyPlugin).toFixed(3)}kb/ms`,
+ drop: durationDrop,
+ name: name,
+ 'index in plugins list': i,
+ });
+ }
+
+ results.sort((a, b) => Number(a.drop) - Number(b.drop));
+
+ results.map((x) => {
+ delete x.drop;
+ return x;
+ });
+
+ console.log('Most impactful to remove, ordered by increase in duration when running alone:');
+ console.table(results.slice(0, 11));
+ }
+ },
+ };
+};
+
+creator.postcss = true;
+
+export default creator;
diff --git a/plugins/postcss-slow-plugins/test/_import.mjs b/plugins/postcss-slow-plugins/test/_import.mjs
new file mode 100644
index 000000000..4d850b340
--- /dev/null
+++ b/plugins/postcss-slow-plugins/test/_import.mjs
@@ -0,0 +1,6 @@
+import assert from 'assert';
+import plugin from '@csstools/postcss-slow-plugins';
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugins/postcss-slow-plugins/test/_require.cjs b/plugins/postcss-slow-plugins/test/_require.cjs
new file mode 100644
index 000000000..60ae10ff4
--- /dev/null
+++ b/plugins/postcss-slow-plugins/test/_require.cjs
@@ -0,0 +1,6 @@
+const assert = require('assert');
+const plugin = require('@csstools/postcss-slow-plugins');
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugins/postcss-slow-plugins/test/basic.css b/plugins/postcss-slow-plugins/test/basic.css
new file mode 100644
index 000000000..7bc2b4819
--- /dev/null
+++ b/plugins/postcss-slow-plugins/test/basic.css
@@ -0,0 +1,476 @@
+:root {
+ --order: 1;
+}
+
+.test-custom-property-fallbacks {
+ --firebrick: lab(40% 56.6 39);
+}
+
+.test-custom-properties {
+ order: var(--order);
+}
+
+.test-image-set-function {
+ background-image: image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x);
+ order: 2;
+}
+
+.test-logical-properties-and-values {
+ margin-inline-start: 1px;
+ margin-inline-end: 2px;
+ order: 3;
+ padding-block: 4px;
+}
+
+.test-logical-resize {
+ resize: inline;
+}
+
+.test-logical-viewport-units {
+ width: calc(10vi + 5px);
+}
+
+.test-nesting-rules {
+ order: 4;
+
+ & p {
+ order: 5;
+ }
+
+ order: 6;
+}
+
+.test-nesting-rules,
+#test-is-pseudo {
+ order: 7;
+
+ & + p {
+ order: 8;
+ }
+
+ order: 9;
+}
+
+@custom-media --narrow-window (max-width: 30em);
+
+@media (--narrow-window) {
+ .test-custom-media-queries {
+ order: 10;
+ }
+}
+
+@media (480px <= width < 768px) {
+ .test-media-query-ranges {
+ order: 11;
+ }
+}
+
+@custom-media --dark-mode (prefers-color-scheme: dark);
+
+@media (--dark-mode) {
+ body {
+ background-color: black;
+ color: white;
+ }
+}
+
+@custom-selector :--heading h1, h2, h3, h4, h5, h6, .heading-7;
+
+.test-custom-selectors:--heading {
+ order:12;
+}
+
+.test-case-insensitive-attributes[frame=hsides i] {
+ order: 13;
+}
+
+.test-rebeccapurple-color {
+ color: rebeccapurple;
+ order: 14;
+}
+
+.test-hexadecimal-alpha-notation {
+ background-color: #f3f3f3f3;
+ color: #0003;
+ order: 15;
+}
+
+.test-color-functional-notation {
+ color: rgb(70% 13.5% 13.5% / 50%);
+ order: 16;
+}
+
+.test-lab-function {
+ background-color: lab(40% 56.6 39);
+ color: lch(40% 68.8 34.5 / 50%);
+ order: 17;
+}
+
+.test-system-ui-font-family {
+ font-family: system-ui;
+ order: 18;
+}
+
+.test-font-variant-property {
+ font-variant-caps: small-caps;
+ order: 19;
+}
+
+.test-all-property {
+ all: initial;
+ order: 20;
+}
+
+.test-matches-pseudo-class:matches(:first-child, .special) {
+ order: 21;
+}
+
+.test-not-pseudo-class:not(:first-child, .special) {
+ order: 22;
+}
+
+.test-any-link-pseudo-class:any-link {
+ order: 23;
+}
+
+.test-dir-pseudo-class:dir(rtl) {
+ order: 24;
+}
+
+.test-overflow-wrap-property {
+ order: 25;
+ overflow-wrap: break-word;
+}
+
+.test-focus-visible-pseudo-class:focus-visible {
+ order: 26;
+}
+
+.test-double-position-gradients {
+ background-image: conic-gradient(yellowgreen 40%, gold 0deg 75%, #f06 0deg);
+}
+
+.test-blank-pseudo-class:blank {
+ background-color: yellow;
+}
+
+.test-has-pseudo-class:has(.inner-class) {
+ background-color: yellow;
+}
+
+:is(.a, .b):is(:focus, :hover) {
+ order: 27;
+}
+
+:is(.a > .b) + :is(.c > .d) {
+ order: 28;
+}
+
+.test-hwb-function {
+ background-color: hwb(194 0% 0% / .5);
+}
+
+.test-opacity-percent {
+ opacity: 42%;
+}
+
+.clamp-same-unit {
+ width: clamp(10px, 64px, 80px);
+}
+
+.complex-clamp {
+ width: clamp(calc(100% - 10px), min(10px, 100%), max(40px, 4em));
+}
+
+.clamp-different-units {
+ width: clamp(10%, 2px, 4rem);
+}
+
+.mixed-clamp {
+ grid-template-columns: clamp(22rem, 40%, 32rem) minmax(0, 1fr);
+ margin: clamp(1rem, 2%, 3rem) 4vh;
+}
+
+.calc-clamp {
+ margin: 0 40px 0 calc(-1 * clamp(32px, 16vw, 64px));
+}
+
+.multiple-calc-clamp {
+ margin: calc(-1 * clamp(1px, 2vw, 3px)) calc(-1 * clamp(4px, 5vw, 6px));
+}
+
+.nested-clamp {
+ font-size: clamp(clamp(1rem, 2vw, 3rem), 4vw, 5rem);
+}
+
+@font-face {
+ font-family: 'A';
+ font-style: normal;
+ font-weight: 300;
+ font-display: swap;
+ src: url(a) format(woff2);
+}
+
+.block-flow {
+ display: block flow;
+}
+
+.block-flow-root {
+ display: block flow-root;
+}
+
+.inline-flow {
+ display: inline flow;
+}
+
+.inline-flow-root {
+ display: inline flow-root;
+}
+
+.run-in-flow {
+ display: run-in flow;
+}
+
+.list-item-block-flow {
+ display: list-item block flow;
+}
+
+.inline-flow-list-item {
+ display: inline flow list-item;
+}
+
+.block-flex {
+ display: block flex;
+}
+
+.inline-flex {
+ display: inline flex;
+}
+
+.block-grid {
+ display: block grid;
+}
+
+.inline-grid {
+ display: inline grid;
+}
+
+.inline-ruby {
+ display: inline ruby;
+}
+
+.block-table {
+ display: block table;
+}
+
+.inline-table {
+ display: inline table;
+}
+
+.table-cell-flow {
+ display: table-cell flow;
+}
+
+.table-caption-flow {
+ display: table-caption flow;
+}
+
+.ruby-base-flow {
+ display: ruby-base flow;
+}
+
+.ruby-text-flow {
+ display: ruby-text flow;
+}
+
+.logical-float {
+ float: inline-start;
+}
+
+.color-function {
+ prop-1: color(display-p3 0.00000 0.51872 0.36985);
+ prop-2: 'color(display-p3 0.02472 0.01150 0.00574 / 1)';
+ prop-3: color(display-p3 0.02472 0.01150 0.00574 / 1);
+ prop-4: color(display-p3 0.02472 0.01150 0.00574 / calc(33 / 22));
+ prop-5: color(display-p3 1 1 1 1);
+}
+
+.oklab {
+ color-1: oklab(40% 0.001236 0.0039);
+ color-2: oklab(40% 0.1236 0.0039 / 1);
+ color-3: oklab(40% 0.1236 0.0039 / .5);
+ color-4: oklab(40% 0.1236 0.0039 / 100%);
+ color-5: oklab(40% 0.1236 0.0039 / 50%);
+ color-6: oklab(60% 0.1 0);
+ color-7: oklab(60% 0.1 0 foo);
+ color-8: oklab(40.101% 0.1147 0.0453);
+ color-9: oklab(59.686% 0.1009 0.1192);
+ color-10: oklab(65.125% -0.0320 0.1274);
+ color-11: oklab(66.016% -0.1084 0.1114);
+ color-12: oklab(72.322% -0.0465 -0.1150);
+}
+
+.oklch {
+ color-1: oklch(40% 0.1268735435 34.568626);
+ color-2: oklch(40% 0.1268735435 34.568626 / 1);
+ color-3: oklch(40% 0.1268735435 34.568626 / .5);
+ color-4: oklch(40% 0.1268735435 34.568626 / 100%);
+ color-5: oklch(40% 0.1268735435 34.568626 / 50%);
+ color-6: oklch(60% 0.150 0);
+
+ color-7: oklch(60% 0.1250 180);
+ color-8: oklch(60% 0.1250 180deg);
+ color-9: oklch(60% 0.1250 0.5turn);
+ color-10: oklch(60% 0.1250 200grad);
+ color-11: oklch(60% 0.1250 3.14159rad);
+
+ color-12: oklch(60% 0.1250 45);
+ color-13: oklch(60% 0.1250 45deg);
+ color-14: oklch(60% 0.1250 0.125turn);
+ color-15: oklch(60% 0.1250 50grad);
+ color-16: oklch(60% 0.1250 0.785398rad);
+ color-17: oklch(60% 0.1250 0.785398unknown);
+}
+
+.ic-unit {
+ --value-2ic: initial;
+ text-indent: 2ic;
+ content: var(--value-2ic);
+ left: var(--non-existing, 2ic);
+ width: calc(8ic + 20px);
+ height: 10px;
+ margin: 0.5ic 1ic .2ic;
+ padding: 2 ic;
+}
+
+.unset {
+ clip: unset;
+}
+
+.mod {
+ padding: 8px mod(18px, 5px) 1px calc(mod(15px, 6px) + 50%);
+ transform: rotate(mod(-140deg, -90deg));
+ width: mod(mod(-18px, 5px), 5px);
+}
+
+.rem {
+ padding: 8px rem(18px, 5px) 1px calc(rem(15px, 6px) + 50%);
+ transform: rotate(rem(-140deg, -90deg));
+}
+
+.round {
+ top: round(2.5px, 1px);
+ right: round(nearest, 2.5px, 1px);
+ bottom: round(up, 2.5px, 1px);
+ left: round(down, 2.5px, 1px);
+ padding-left: round(to-zero, 2.5px, 1px);
+}
+
+.trigonometric {
+ left: sin(45deg);
+ left: cos(45deg);
+ left: tan(45deg);
+ left: asin(0.5);
+ left: acos(0.5);
+ left: atan(10);
+ left: atan2(-1, 1);
+ left: asin(sin(pi / 2));
+ left: atan2(infinity, -infinity);
+ left: sin((1deg * 3) + .25turn);
+}
+
+.trigonometric-ignore-not-a-function {
+ left: sin;
+ left: cos;
+ left: tan;
+ left: asin;
+ left: acos;
+ left: atan;
+ left: atan2;
+}
+
+.trigonometric-ignore-no-arguments {
+ left: sin();
+ left: cos();
+ left: tan();
+ left: asin();
+ left: acos();
+ left: atan();
+ left: atan2();
+}
+
+@custom-selector :--view-m [data-view-size=m];
+
+.view {
+ :--view-m & {
+ background: red;
+ }
+}
+
+.nested-calc {
+ order: calc(1 * calc(8 / 3 + calc(5 * 10)));
+}
+
+.text-decoration-shorthand {
+ text-decoration: 3px wavy pink overline;
+}
+
+.stage__container {
+ inset-inline-start: var(--size, 1rem);
+}
+
+:scope {
+ content: "plain :scope";
+}
+
+@scope (.foo) {
+ :scope {
+ content: ":scope in @scope";
+ }
+}
+
+:scope {
+ @scope (.foo) {
+ content: ":scope in @scope, but with nesting";
+ }
+}
+
+@media (min-aspect-ratio: 1.77) {
+ /* media query aspect ratio : */
+}
+
+.color-mix {
+ color-1: color-mix(in srgb, rgb(255 0 0 / 0), blue 65%);
+ color-2: color-mix(in lab, rgb(255 0 0 / 0), blue 65%);
+ color-3: color-mix(in xyz, rgb(255 0 0 / 0), blue 65%);
+ color-4: color-mix(in oklch, rgb(255 0 0 / 0), blue 65%);
+ color-5: color-mix(in lch, rgb(255 0 0 / 0), blue 65%);
+ color-6: color-mix(in hsl, rgb(255 0 0 / 0), blue 65%);
+}
+
+.gradients-interpolation-method {
+ --background-image: linear-gradient(in oklch decreasing hue, hsl(0deg 85% 75%) 0%, hsl(180deg 80% 65%) 100%);
+
+ background-image: radial-gradient(farthest-corner circle at 50% 115% in oklch, oklch(80% .3 34) 0%, oklch(90% .3 200) 100%);
+}
+
+.test-css-color-5-interop {
+ color-1: color(from color(a98-rgb 0.7 0.5 0.3) a98-rgb r g b / alpha);
+ color-2: color(from color(a98-rgb 0.7 0.5 0.3) a98-rgb r g b / none);
+ color-3: color(from color(prophoto-rgb 0.7 0.5 0.3) prophoto-rgb r g b);
+ color-4: color(from color(prophoto-rgb 0.7 0.5 0.3) prophoto-rgb r g none);
+ color-5: hwb(from hwb(50deg 20% 30%) h w b);
+ color-6: lab(from lab(40% 56.6 39) l a b);
+ color-7: lch(from lch(40% 56.6 39) l c h);
+ color-8: oklab(from oklab(40% 0.566 0.39) l a b);
+ color-9: oklch(from oklch(40% 0.566 39deg) l c h);
+}
+
+.test-property-with-var {
+ --opacity: 1;
+ color: rgba(87 107 149 / var(--opacity));
+}
+
+.exponential-functions {
+ width: calc(1px * pow(2, 3));
+}
diff --git a/plugins/postcss-slow-plugins/test/basic.expect.css b/plugins/postcss-slow-plugins/test/basic.expect.css
new file mode 100644
index 000000000..364178f61
--- /dev/null
+++ b/plugins/postcss-slow-plugins/test/basic.expect.css
@@ -0,0 +1,513 @@
+:root {
+ --order: 1;
+}
+
+.test-custom-property-fallbacks {
+ --firebrick: rgb(179, 35, 35);
+}
+
+.test-custom-properties {
+ order: var(--order);
+}
+
+.test-image-set-function {
+ background-image: -webkit-image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x);
+ background-image: image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x);
+ order: 2;
+}
+
+.test-logical-properties-and-values {
+ margin-inline-start: 1px;
+ margin-inline-end: 2px;
+ order: 3;
+ padding-block: 4px;
+}
+
+.test-logical-resize {
+ resize: horizontal;
+}
+
+.test-logical-viewport-units {
+ width: calc(10vw + 5px);
+ width: calc(10vi + 5px);
+}
+
+.test-nesting-rules {
+ order: 4;
+
+ order: 6;
+}
+
+.test-nesting-rules p {
+ order: 5;
+ }
+
+.test-nesting-rules,
+#test-is-pseudo {
+ order: 7;
+
+ order: 9;
+}
+
+:is(.test-nesting-rules,#test-is-pseudo) + p {
+ order: 8;
+ }
+
+@media (max-width: 30em) {
+ .test-custom-media-queries {
+ order: 10;
+ }
+}
+
+@media (min-width: 480px) and (max-width: 767.98px) {
+ .test-media-query-ranges {
+ order: 11;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: black;
+ color: white;
+ }
+}
+
+.test-custom-selectors:is(h1, h2, h3, h4, h5, h6, .heading-7) {
+ order:12;
+}
+
+.test-case-insensitive-attributes[frame=hsides i] {
+ order: 13;
+}
+
+.test-rebeccapurple-color {
+ color: rebeccapurple;
+ order: 14;
+}
+
+.test-hexadecimal-alpha-notation {
+ background-color: #f3f3f3f3;
+ color: #0003;
+ order: 15;
+}
+
+.test-color-functional-notation {
+ color: rgb(70% 13.5% 13.5% / 50%);
+ order: 16;
+}
+
+.test-lab-function {
+ background-color: rgb(179, 35, 35);
+ color: rgba(179, 34, 35, 0.5);
+ order: 17;
+}
+
+.test-system-ui-font-family {
+ font-family: system-ui;
+ order: 18;
+}
+
+.test-font-variant-property {
+ font-feature-settings: "smcp";
+ font-variant-caps: small-caps;
+ order: 19;
+}
+
+.test-all-property {
+ all: initial;
+ order: 20;
+}
+
+.test-matches-pseudo-class:matches(:first-child, .special) {
+ order: 21;
+}
+
+.test-not-pseudo-class:not(:first-child, .special) {
+ order: 22;
+}
+
+.test-any-link-pseudo-class:any-link {
+ order: 23;
+}
+
+[dir="rtl"] .test-dir-pseudo-class {
+ order: 24;
+}
+
+.test-overflow-wrap-property {
+ order: 25;
+ overflow-wrap: break-word;
+}
+
+.test-focus-visible-pseudo-class:focus-visible {
+ order: 26;
+}
+
+.test-double-position-gradients {
+ background-image: conic-gradient(yellowgreen 40%, gold 0deg 75%, #f06 0deg);
+}
+
+.test-blank-pseudo-class:blank {
+ background-color: yellow;
+}
+
+.test-has-pseudo-class:has(.inner-class) {
+ background-color: yellow;
+}
+
+:is(.a, .b):is(:focus, :hover) {
+ order: 27;
+}
+
+:is(.a > .b) + :is(.c > .d) {
+ order: 28;
+}
+
+.test-hwb-function {
+ background-color: rgba(0, 196, 255, 0.5);
+}
+
+.test-opacity-percent {
+ opacity: 42%;
+}
+
+.clamp-same-unit {
+ width: clamp(10px, 64px, 80px);
+}
+
+.complex-clamp {
+ width: clamp(calc(100% - 10px), min(10px, 100%), max(40px, 4em));
+}
+
+.clamp-different-units {
+ width: clamp(10%, 2px, 4rem);
+}
+
+.mixed-clamp {
+ grid-template-columns: clamp(22rem, 40%, 32rem) minmax(0, 1fr);
+ margin: clamp(1rem, 2%, 3rem) 4vh;
+}
+
+.calc-clamp {
+ margin: 0 40px 0 calc(-1 * clamp(32px, 16vw, 64px));
+}
+
+.multiple-calc-clamp {
+ margin: calc(-1 * clamp(1px, 2vw, 3px)) calc(-1 * clamp(4px, 5vw, 6px));
+}
+
+.nested-clamp {
+ font-size: clamp(clamp(1rem, 2vw, 3rem), 4vw, 5rem);
+}
+
+@font-face {
+ font-family: 'A';
+ font-style: normal;
+ font-weight: 300;
+ font-display: swap;
+ src: url(a) format("woff2");
+}
+
+.block-flow {
+ display: block;
+ display: block flow;
+}
+
+.block-flow-root {
+ display: flow-root;
+ display: block flow-root;
+}
+
+.inline-flow {
+ display: inline;
+ display: inline flow;
+}
+
+.inline-flow-root {
+ display: inline-block;
+ display: inline flow-root;
+}
+
+.run-in-flow {
+ display: run-in;
+ display: run-in flow;
+}
+
+.list-item-block-flow {
+ display: list-item;
+ display: list-item block flow;
+}
+
+.inline-flow-list-item {
+ display: inline list-item;
+ display: inline flow list-item;
+}
+
+.block-flex {
+ display: flex;
+ display: block flex;
+}
+
+.inline-flex {
+ display: inline-flex;
+ display: inline flex;
+}
+
+.block-grid {
+ display: grid;
+ display: block grid;
+}
+
+.inline-grid {
+ display: inline-grid;
+ display: inline grid;
+}
+
+.inline-ruby {
+ display: ruby;
+ display: inline ruby;
+}
+
+.block-table {
+ display: table;
+ display: block table;
+}
+
+.inline-table {
+ display: inline-table;
+ display: inline table;
+}
+
+.table-cell-flow {
+ display: table-cell;
+ display: table-cell flow;
+}
+
+.table-caption-flow {
+ display: table-caption;
+ display: table-caption flow;
+}
+
+.ruby-base-flow {
+ display: ruby-base;
+ display: ruby-base flow;
+}
+
+.ruby-text-flow {
+ display: ruby-text;
+ display: ruby-text flow;
+}
+
+.logical-float {
+ float: left;
+}
+
+.color-function {
+ prop-1: rgb(0, 129, 96);
+ prop-2: 'color(display-p3 0.02472 0.01150 0.00574 / 1)';
+ prop-3: rgb(7, 3, 1);
+ prop-4: rgb(7, 3, 1);
+ prop-5: color(display-p3 1 1 1 1);
+}
+
+.oklab {
+ color-1: rgb(73, 71, 69);
+ color-2: rgb(121, 34, 67);
+ color-3: rgba(121, 34, 67, 0.5);
+ color-4: rgb(121, 34, 67);
+ color-5: rgba(121, 34, 67, 0.5);
+ color-6: rgb(177, 102, 126);
+ color-7: oklab(60% 0.1 0 foo);
+ color-8: rgb(125, 35, 41);
+ color-9: rgb(198, 93, 7);
+ color-10: rgb(157, 147, 24);
+ color-11: rgb(104, 166, 57);
+ color-12: rgb(98, 172, 239);
+}
+
+.oklch {
+ color-1: rgb(126, 37, 15);
+ color-2: rgb(126, 37, 15);
+ color-3: rgba(126, 37, 15, 0.5);
+ color-4: rgb(126, 37, 15);
+ color-5: rgba(126, 37, 15, 0.5);
+ color-6: rgb(197, 84, 124);
+
+ color-7: rgb(0, 149, 131);
+
+ color-7: color(display-p3 0.19244 0.58461 0.51559);
+ color-8: rgb(0, 149, 131);
+ color-8: color(display-p3 0.19244 0.58461 0.51559);
+ color-9: rgb(0, 149, 131);
+ color-9: color(display-p3 0.19244 0.58461 0.51559);
+ color-10: rgb(0, 149, 131);
+ color-10: color(display-p3 0.19244 0.58461 0.51559);
+ color-11: rgb(0, 149, 131);
+ color-11: color(display-p3 0.19244 0.58461 0.51559);
+
+ color-12: rgb(188, 101, 59);
+ color-13: rgb(188, 101, 59);
+ color-14: rgb(188, 101, 59);
+ color-15: rgb(188, 101, 59);
+ color-16: rgb(188, 101, 59);
+ color-17: oklch(60% 0.1250 0.785398unknown);
+}
+
+.ic-unit {
+ --value-2ic: initial;
+ text-indent: 2em;
+ content: var(--value-2ic);
+ left: var(--non-existing, 2em);
+ width: calc(8em + 20px);
+ height: 10px;
+ margin: 0.5em 1em .2em;
+ padding: 2 ic;
+}
+
+.unset {
+ clip: unset;
+}
+
+.mod {
+ padding: 8px 3px 1px calc(3px + 50%);
+ transform: rotate(-50deg);
+ width: 2px;
+}
+
+.rem {
+ padding: 8px 3px 1px calc(3px + 50%);
+ transform: rotate(-50deg);
+}
+
+.round {
+ top: 3px;
+ right: 3px;
+ bottom: 3px;
+ left: 2px;
+ padding-left: 2px;
+}
+
+.trigonometric {
+ left: 0.70711;
+ left: 0.70711;
+ left: 1;
+ left: 30deg;
+ left: 60deg;
+ left: 84.28941deg;
+ left: -45deg;
+ left: 90deg;
+ left: 135deg;
+ left: 0.99863;
+}
+
+.trigonometric-ignore-not-a-function {
+ left: sin;
+ left: cos;
+ left: tan;
+ left: asin;
+ left: acos;
+ left: atan;
+ left: atan2;
+}
+
+.trigonometric-ignore-no-arguments {
+ left: sin();
+ left: cos();
+ left: tan();
+ left: asin();
+ left: acos();
+ left: atan();
+ left: atan2();
+}
+
+:is([data-view-size=m]) .view {
+ background: red;
+ }
+
+.nested-calc {
+ order: calc(1 * calc(8 / 3 + calc(5 * 10)));
+}
+
+.text-decoration-shorthand {
+ text-decoration: 3px wavy pink overline;
+}
+
+.stage__container {
+ inset-inline-start: var(--size, 1rem);
+}
+
+:scope {
+ content: "plain :scope";
+}
+
+@scope (.foo) {
+ :scope {
+ content: ":scope in @scope";
+ }
+}
+
+:scope {
+ @scope (.foo) {
+ content: ":scope in @scope, but with nesting";
+ }
+}
+
+@media (min-aspect-ratio: 177/100) {
+ /* media query aspect ratio : */
+}
+
+.color-mix {
+ color-1: rgba(0, 0, 255, 0.65);
+ color-2: rgba(0, 0, 255, 0.65);
+ color-3: rgba(0, 0, 255, 0.65);
+ color-4: rgba(122, 0, 182, 0.65);
+ color-4: color(display-p3 0.44471 0 0.71665 / 0.65);
+ color-5: rgba(149, 0, 122, 0.65);
+ color-5: color(display-p3 0.55417 0 0.48083 / 0.65);
+ color-6: rgba(179, 0, 255, 0.65);
+}
+
+.gradients-interpolation-method {
+ --background-image: linear-gradient(rgb(245, 137, 137) 0%, rgb(245, 140, 170), rgb(238, 146, 202), rgb(227, 155, 230), rgb(210, 166, 253), rgb(188, 179, 255), rgb(164, 192, 255), rgb(137, 206, 255), rgb(111, 218, 255), rgb(93, 229, 255), rgb(94, 237, 237) 100%);
+
+ background-image: radial-gradient(farthest-corner circle at 50% 115%, rgb(255, 71, 0) 0%, rgb(255, 103, 0), rgb(255, 137, 0), rgb(255, 169, 0), rgb(250, 199, 0), rgb(197, 225, 0), rgb(116, 246, 0), rgb(0, 255, 86), rgb(0, 255, 171), rgb(0, 255, 239), rgb(0, 255, 255) 100%);
+
+ background-image: radial-gradient(farthest-corner circle at 50% 115%, color(display-p3 1.2198 0.38716 0.16814) 0%, color(display-p3 1.20289 0.47522 -0.3004), color(display-p3 1.15167 0.57912 -0.3946), color(display-p3 1.06645 0.68611 -0.4296), color(display-p3 0.94738 0.78766 -0.4263), color(display-p3 0.79233 0.87807 -0.3707), color(display-p3 0.59026 0.95319 -0.1467), color(display-p3 0.27082 1.00972 0.43521), color(display-p3 -0.3983 1.04501 0.69996), color(display-p3 -0.5392 1.05733 0.93686), color(display-p3 -0.5777 1.04644 1.15396) 100%);
+
+ background-image: radial-gradient(farthest-corner circle at 50% 115% in oklch, oklch(80% .3 34) 0%, oklch(90% .3 200) 100%);
+}
+
+@supports (color: color(display-p3 0 0 0)) {
+.gradients-interpolation-method {
+ --background-image: linear-gradient(rgb(245, 137, 137) 0%, rgb(245, 140, 170), rgb(238, 146, 202), rgb(227, 155, 230), rgb(210, 166, 253), color(display-p3 0.73255 0.7035 1.03328), color(display-p3 0.66377 0.75145 1.06652), color(display-p3 0.59643 0.80011 1.0696), color(display-p3 0.54198 0.8462 1.04382), color(display-p3 0.51578 0.88643 0.99318), rgb(94, 237, 237) 100%);
+}
+}
+
+@supports (background: linear-gradient(in oklch, red 0%, red 0% 1%, red 2%)) and (color: hsl(0 0% 0% / 0)) {
+.gradients-interpolation-method {
+ --background-image: linear-gradient(in oklch decreasing hue, hsl(0deg 85% 75%) 0%, hsl(180deg 80% 65%) 100%);
+}
+}
+
+.test-css-color-5-interop {
+ color-1: rgb(196, 129, 72);
+ color-2: color(from rgb(196, 129, 72) a98-rgb r g b / none);
+ color-3: rgb(234, 133, 82);
+ color-4: color(from rgb(234, 133, 82) prophoto-rgb r g none);
+ color-5: rgb(179, 157, 51);
+ color-6: rgb(179, 35, 35);
+ color-7: rgb(163, 57, 39);
+ color-8: rgb(141, 0, 0);
+ color-8: color(display-p3 0.50566 0.0781 0);
+ color-9: rgb(130, 31, 0);
+ color-9: color(display-p3 0.48896 0.1211 0);
+}
+
+.test-property-with-var {
+ --opacity: 1;
+ color: rgba(87 107 149 / var(--opacity));
+}
+
+.exponential-functions {
+ width: 8px;
+}
diff --git a/plugins/postcss-slow-plugins/tsconfig.json b/plugins/postcss-slow-plugins/tsconfig.json
new file mode 100644
index 000000000..500af6d26
--- /dev/null
+++ b/plugins/postcss-slow-plugins/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "declarationDir": ".",
+ "strict": true
+ },
+ "include": ["./src/**/*"],
+ "exclude": ["dist"]
+}