README.md
[![npm version](https://img.shields.io/npm/v/postcss-params.svg)](https://www.npmjs.com/package/postcss-params)
[![Build Status](https://img.shields.io/travis/joeframbach/postcss-params.svg)](https://travis-ci.org/joeframbach/postcss-params)
[![Coverage Status](https://img.shields.io/coveralls/joeframbach/postcss-params.svg)](https://coveralls.io/github/joeframbach/postcss-params?branch=master)
[![Code Climate](https://img.shields.io/codeclimate/github/joeframbach/postcss-params.svg)](https://codeclimate.com/github/joeframbach/postcss-params)
[![dependencies Status](https://img.shields.io/david/joeframbach/postcss-params.svg)](https://david-dm.org/joeframbach/postcss-params)
[![devDependencies Status](https://img.shields.io/david/dev/joeframbach/postcss-params.svg)](https://david-dm.org/joeframbach/postcss-params?type=dev)
# PostCSS Params
`postcss-params` has two usage modes:
1. Target devices/clients based on a build configuration, much like media queries.
2. Pass strings from css to your PostCSS plugin, using a familiar syntax.
Some sites serve different assets to different clients.
For example, you may have some IE-specific css hacks that you only want to serve to IE browsers.
Or you want to load certain fonts for certain countries.
PostCSS is a good way to keep all your code in one file, then generate separate assets.
```scss
@my-plugin (browser: ie) {
button {
background-color: red;
}
}
@my-plugin not (browser: ie) {
button {
background-color: green;
}
}
```
`postcss-params` helps you write a plugin which reads the `(browser: ie)` parameter string, and keep or discard the block accordingly.
Build two assets, with configurations `{browser: ie}` and `{}`.
PostCSS will generate two assets. One you can serve to your locked-in
customers browsing from their lunch breaks at BigCorp. The other asset
you can serve to the rest of the civilized world.
---
## `buildComparator`
`buildComparator` accepts a param string and returns a function.
The resulting *comparator* function accepts a configuration object,
and returns `true` if the params match the configuration, and
`false` otherwise.
See the tests in `tests/buildComparator` for more examples.
CSS:
```scss
@my-plugin (region: cn) {
body {
font-family: ".PingFang-SC-Regular", sans-serif;
}
}
@my-plugin not (region: cn) {
body {
font-family: "Helvetica Neue", Arial, sans-serif !default;
}
}
```
Plugin:
```js
const { buildComparator } = require('postcss-params');
postcss.plugin('my-plugin', (configuration) => (root) => {
root.walkAtRules('my-plugin', (atRule) => {
const comparator = buildComparator(atRule.params);
if (comparator(configuration)) {
atRule.replaceWith(atRule.nodes);
} else {
atRule.remove();
}
});
});
```
Running PostCSS with various configuration objects will result in css assets
suitable for separate intended audiences. For example, you may serve a different font-family in China, but not want to load this asset for all countries.
`configuration` is provided to PostCSS through your build tool.
Example configuration:
```js
{
debug: true,
region: "us",
theme: "blue"
}
```
---
## `buildAst`
`buildAst` gives you finer control and access to the params written in css.
Given this simple rule:
```scss
@my-plugin (theme: red) {
body {
background-color: theme-color;
}
}
```
and this plugin:
```js
const { buildAst } = require('postcss-params');
postcss.plugin('my-plugin', (configuration) => (root) => {
root.walkAtRules('my-plugin', (atRule) => {
const ast = buildAst(atRule.params);
console.log(ast);
});
});
```
This AST is generated:
```js
{ feature: "theme", value: "red" }
```
---
Given this more complicated rule:
```scss
@my-plugin (debug),
(region: cn) and (theme: red),
(region: us) and (theme: blue),
not (production) and (staging) {
body {
background-color: red;
}
}
```
Plugin:
```js
const { buildAst } = require('postcss-params');
postcss.plugin('my-plugin', (configuration) => (root) => {
root.walkAtRules('my-plugin', (atRule) => {
const ast = buildAst(atRule.params);
console.log(ast);
});
});
```
This AST is generated:
any
├─ { feature: debug, value: true }
├─ all
│ ├─ { feature: region, value: cn }
│ └─ { feature: theme, value: red }
├─ all
│ ├─ { feature: region, value: us }
│ └─ { feature: theme, value: blue }
└─ all
├─ { feature: production, not: true }
└─ { feature: staging, not: true }
Now your PostCSS plugin can make use of this AST to pull values into variables,
or decide to keep or discard the block.
Limitations:
* Feature names and values must NOT contain characters `(),:`
* Errors are fairly opaque (`'Expected L_PAREN'`)
* Feature values are optional, so comparators must expect String or Undefined
* Feature can only have one value. `(f: a), (f: b)` can't be `(f: a, b)`
- As a hack, `(f: a|b)` is legal, and the comparator can split on `|`
---
## AST Object Reference
The resulting AST from `buildAst` is a tree structure. There are three possible
nodes in this tree:
- `any`: if any item resolves true, return true.
- `all`: if all items resolve true, return true.
- `feature`: compare the param value with the config value.
- `not`: if feature returns true, return false. And vice-versa.
Given this contrived rule:
```scss
@my-plugin (debug),
(region: cn) and (theme: red),
(region: us) and (theme: blue),
not (production) and (staging) {
body {
background-color: red;
}
}
```
This AST is generated:
any
├─ { feature: debug, value: true }
├─ all
│ ├─ { feature: region, value: cn }
│ └─ { feature: theme, value: red }
├─ all
│ ├─ { feature: region, value: us }
│ └─ { feature: theme, value: blue }
└─ all
├─ { feature: production, not: true }
└─ { feature: staging, not: true }
See the tests in `tests/buildAst` for more examples.
---
## About the syntax
All PostCSS at-rules follow the structure `@plugin-name params { body }`.
Plugins are given the `params` as a string, with no provisions for
parsing, or even a standard format for clients to write params.
CSS already has an analogous structure for media queries:
- `@media media-query [, media-query]* { rule-list }`
where `media-query` can take either form:
- `[NOT|ONLY]? media-type [AND (media-feature[: value]?)]*`
- `(media-feature[: value]?) [AND (media-feature[: value]?)]*`
However, this has some limitations:
1. `media-type` is not meant for general-purpose use. It accepts specific values, e.g., `all`, `screen`, `print`. We are only interested in `media-feature` usage.
2. `NOT` must be used with `media-type`. (`NOT (media-feature)` is illegal).
3. `ONLY` was a hack for older browsers.
So we have defined a similar grammar which:
1. removes `media-type` entirely.
2. allows `NOT` to be juxtaposed with `media-feature`.
3. removes `ONLY`.
---
## ASTs are automatically flattened
`any` and `all` nodes with a single child are replaced
with that child node.
any
└─ all
└─ { feature: debug, value: true }
is the same as
{ feature: debug, value: true }
---
## LL(1) Grammar Reference - Please do not LL(2+)
CommaSeparatedList
: MediaQuery [ COMMA MediaQuery ]*
MediaQuery
: [NOT]? Feature [ AND Feature ]*
Feature
: L_PAREN IDENT [ COLON IDENT ]? R_PAREN