blob: 9946543166ea72cb71883399bab355931004b5ee [file] [view]
# PostCSS Calc [<img src="https://postcss.github.io/postcss/logo.svg" alt="PostCSS" width="90" height="90" align="right">][PostCSS]
[![NPM Version][npm-img]][npm-url]
[![Support Chat][git-img]][git-url]
[PostCSS Calc] lets you reduce `calc()` references whenever it's possible.
When multiple units are mixed together in the same expression, the `calc()`
statement is left as is, to fallback to the [W3C calc() implementation].
## Installation
```bash
npm install postcss-calc
```
## PostCSS usage
```js
// dependencies
var fs = require('fs');
var postcss = require('postcss');
var calc = require('postcss-calc');
// css to be processed
var css = fs.readFileSync('input.css', 'utf8');
// process css
var output = postcss().use(calc()).process(css).css;
```
Using this `input.css`:
```css
h1 {
font-size: calc(16px * 2);
height: calc(100px - 2em);
width: calc(2 * var(--base-width));
margin-bottom: calc(16px * 1.5);
}
```
you will get:
```css
h1 {
font-size: 32px;
height: calc(100px - 2em);
width: calc(2 * var(--base-width));
margin-bottom: 24px;
}
```
Checkout [tests] for more examples.
## Use the reducer without PostCSS
For a single CSS component-value string, import the dedicated reducer entry
point. It reduces `calc()` and the supported CSS math functions it finds while
leaving all other text untouched.
```js
import reduceCalc from 'postcss-calc/reduce';
reduceCalc('calc(1in + 10px)');
// => '1.10417in'
reduceCalc('min(50px, calc(2 * 40px))');
// => '50px'
```
It accepts `precision`, `unwrapSingleNegativeNumber`, `warnWhenCannotResolve`, `onParseError`,
and `onWarn`:
```js
const result = reduceCalc('calc(100% + var(--gap))', {
precision: false,
warnWhenCannotResolve: true,
onWarn: console.warn,
onParseError(error, input) {
console.error(`Invalid calculation: ${input}`, error);
},
});
```
Unlike the PostCSS plugin, the standalone reducer does not show warnings
by default; provide `onParseError` and/or `onWarn` if you want diagnostics.
### Standalone reducer options
#### `unwrapSingleNegativeNumber` (default: `false`)
Controls whether a finite negative result is serialized as a bare value or
wrapped in `calc()`. Keep the default when reducing declaration values; set it
to `true` when the surrounding CSS context requires a bare negative value, such
as a selector:
```js
reduceCalc('calc(5px - 10px)');
// => 'calc(-5px)'
reduceCalc('calc(5px - 10px)', { unwrapNegativeNumbers: true });
// => '-5px'
```
### PostCSS plugin options
These options apply when using the PostCSS plugin:
```js
postcss().use(calc({ precision: 10 }));
```
#### `precision` (default: `5`)
Allows you to define the precision for decimal numbers. Set it to `false` to
disable rounding.
```js
var out = postcss()
.use(calc({ precision: 10 }))
.process(css).css;
```
#### `warnWhenCannotResolve` (default: `false`)
Adds warnings when calc() are not reduced to a single value.
```js
var out = postcss()
.use(calc({ warnWhenCannotResolve: true }))
.process(css).css;
```
#### `mediaQueries` (default: `false`)
Allows calc() usage in media query parameters.
```js
var out = postcss()
.use(calc({ mediaQueries: true }))
.process(css).css;
```
Example:
```css
@media (min-width: calc(100px + 100px)) {
div {
width: 100px;
}
}
```
With `mediaQueries: true`, this becomes:
```css
@media (min-width: 200px) {
div {
width: 100px;
}
}
```
#### `selectors` (default: `false`)
Reduces `calc()` functions found in selectors. Selectors do not accept
`calc()` functions, so the plugin replaces them with their reduced values.
Finite negative results are serialized as bare values because a selector cannot
contain a `calc()` function; the plugin enables `unwrapSingleNegativeNumber` automatically
for selectors.
```js
var out = postcss()
.use(calc({ selectors: true }))
.process(css).css;
```
Example:
```css
div:nth-child(calc(1 + 2)) {
width: 100px;
}
```
With `selectors: true`, this becomes `div:nth-child(3)`.
#### `onParseError`
Callback invoked when a `calc()` body fails to parse or simplify. Matches
[`@csstools/css-calc`][csstools-css-calc]'s shape:
```js
postcss().use(
calc({
onParseError: (err, input) => {
throw err; // or log, route to a different channel, etc.
},
})
);
```
When omitted, errors are reported via PostCSS `result.warn()` so the
plugin never throws at the postcss level.
### Behavior differences from the legacy parser
The legacy [jison][jison]-generated parser was replaced by a hand-written
Pratt parser whose simplifier follows [CSS Values 4][css-values-4]. Most
inputs reduce to identical output; the differences are spec-aligned or
canonical-form decisions:
- **Strict whitespace (§10.1).** `calc(2px+3px)` is invalid CSS (binary
`+` / `-` require surrounding whitespace) and is preserved with a
warning instead of reduced.
- **Canonical operand order.** Commutative operands serialize
numeric-first, matching [`@csstools/css-calc`][csstools-css-calc]:
`calc(var(--foo) + 10px)` → `calc(10px + var(--foo))`.
- **Zero buckets are kept.** `calc(100px - (100px - 100%))` →
`calc(0px + 100%)`, not `100%` — [WPT calc-serialization-002][wpt-calc-serialization]
requires the zero term because it carries the length-percentage type.
- **Constant folding.** `calc(43 + pi)` now folds to `46.14159` (§10.7.1).
Previously `pi` / `e` stayed symbolic.
- **Reciprocal conversion.** `calc(var(--x) / 2)` becomes
`calc(var(--x) * 0.5)`. The two are mathematically equivalent;
previously the division shape was kept.
- **Distributive multiplication.** `calc(0.5 * (100vw - 10px))` becomes
`calc(50vw - 5px)`.
- **Unit case normalization.** `2PX` becomes `2px` (CSS units are case-
insensitive; lowercase is conventional).
- **Calc unwrap (§10.6).** `calc(var(--foo))` becomes `var(--foo)` — a
`calc()` containing a single value is replaced by that value.
- **Spec-style spaced operators.** `2px*var(--x)` is serialized as
`2px * var(--x)`. The tokenizer is unaffected; only output spacing
differs.
- **Division by zero / by a unit.** `calc(500px/0)` reduces to
`calc(infinity * 1px)` (§10.13) instead of throwing. Use `onParseError`
if you want validation behavior.
[css-values-4]: https://www.w3.org/TR/css-values-4/
[csstools-css-calc]: https://www.npmjs.com/package/@csstools/css-calc
[wpt-calc-serialization]: https://github.com/web-platform-tests/wpt/blob/master/css/css-values/calc-serialization-002.html
[jison]: https://github.com/zaach/jison
---
## Related PostCSS plugins
To replace the value of CSS custom properties at build time, try [PostCSS Custom Properties].
## Contributing
Work on a branch, install dev-dependencies, respect coding style & run tests
before submitting a bug fix or a feature.
```bash
git clone git@github.com:postcss/postcss-calc.git
git checkout -b patch-1
npm install
npm test
```
The normal test run uses a deterministic structural sample of the harvested
real-world corpus. Run the complete differential corpus before releases or
when changing parsing/simplification behavior:
```bash
pnpm test:corpus:full
```
Profile long arithmetic parser chains with `pnpm benchmark:arithmetic-chains`.
## [Changelog](CHANGELOG.md)
## [License](LICENSE)
[git-img]: https://img.shields.io/badge/support-chat-blue.svg
[git-url]: https://gitter.im/postcss/postcss
[npm-img]: https://img.shields.io/npm/v/postcss-calc.svg
[npm-url]: https://www.npmjs.com/package/postcss-calc
[PostCSS]: https://github.com/postcss
[PostCSS Calc]: https://github.com/postcss/postcss-calc
[PostCSS Custom Properties]: https://github.com/postcss/postcss-custom-properties
[tests]: test/index.js
[W3C calc() implementation]: https://www.w3.org/TR/css3-values/#calc-notation