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 Logo][PostCSS] + +[npm version][npm-url] [Build Status][cli-url] [Discord][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"] +}