Syntax
Syntax
PostCSS can transform styles in any syntax, and is not limited to just CSS.
By writing a custom syntax, you can transform styles in any desired format.
Writing a custom syntax is much harder than writing a PostCSS plugin, but
it is an awesome adventure.
## Syntax
A good example of a custom syntax is [SCSS]. Some users may want to transform
SCSS sources with PostCSS plugins, for example if they need to add vendor
prefixes or change the property order. So this syntax should output SCSS from
an SCSS input.
The syntax API is a very simple plain object, with `parse` & `stringify`
functions:
```js
module.exports = {
parse: require('./parse'),
stringify: require('./stringify')
};
```
[SCSS]: https://github.com/postcss/postcss-scss
## Parser
The parser API is a function which receives a string & returns a [`Root`] node.
The second argument is a function which receives an object with PostCSS options.
```js
var postcss = require('postcss');
There are many books about parsers; but do not worry because CSS syntax is
very easy, and so the parser will be much simpler than a programming language
parser.
[Tokenizer]: https://github.com/postcss/postcss/blob/master/lib/tokenize.es6
[Parser]: https://github.com/postcss/postcss/blob/master/lib/parser.es6
### Performance
Parsing input is often the most time consuming task in CSS processors. So it
is very important to have a fast parser.
Of parsing tasks, the tokenize step will often take the most time, so its
performance should be prioritized. Unfortunately, classes, functions and
high level structures can slow down your tokenizer. Be ready to write dirty
code with repeated statements. This is why it is difficult to extend the
default [PostCSS tokenizer]; copy & paste will be a necessary evil.
```js
// Slow
string[i] === '{';
// Fast
const OPEN_CURLY = 123; // `{'
string.charCodeAt(i) === OPEN_CURLY;
```
Third optimization is “fast jumps”. If you find open quotes, you can find
next closing quote much faster by `indexOf`:
```js
// Simple jump
next = string.indexOf('"', currentPosition + 1);
// Jump by RegExp
regexp.lastIndex = currentPosion + 1;
regexp.text(string);
next = regexp.lastIndex;
```
The parser can be a well written class. There is no need in copy-paste and
hardcore optimization there. You can extend the default [PostCSS parser].
Every node should have `source` property to generate correct source map.
This property contains `start` and `end` properties with `{ line, column }`,
and `input` property with an [`Input`] instance.
Your tokenizer should save the original position so that you can propagate
the values to the parser, to ensure that the source map is correctly updated.
[`Input`]: https://github.com/postcss/postcss/blob/master/lib/input.es6
A good PostCSS parser should provide all information (including spaces symbols)
to generate byte-to-byte equal output. It is not so difficult, but respectful
for user input and allow integration smoke tests.
The default parser cleans CSS values from comments and spaces.
It saves the original value with comments to `node.raws.value.raw` and uses it,
if the node value was not changed.
### Tests
If your parser just extends CSS syntax (like [SCSS] or [Safe Parser]),
you can use the [PostCSS Parser Tests]. It contains unit & integration tests.
## Stringifier
The Stringifier API is little bit more complicated, than the parser API.
PostCSS generates a source map, so a stringifier can’t just return a string.
It must link every substring with its source node.
```js
module.exports = function (root, builder) {
// Some magic
var string = decl.prop + ':' + decl.value + ';';
builder(string, decl);
// Some science
};
```
### Main Theory
PostCSS [default stringifier] is just a class with a method for each node type
and many methods to detect raw properties.
[default stringifier]:
https://github.com/postcss/postcss/blob/master/lib/stringifier.es6
[SCSS stringifier]:
https://github.com/postcss/postcss-scss/blob/master/lib/scss-stringifier.es6
Builder receives output substring and source node to append this substring
to the final output.
Some nodes contain other nodes in the middle. For example, a rule has a `{`
at the beginning, many declarations inside and a closing `}`.
For these cases, you should pass a third argument to builder function:
`'start'` or `'end'` string:
```js
this.builder(rule.selector + '{', rule, 'start');
// Stringify declarations inside
this.builder('}', rule, 'end');
```
A good PostCSS custom syntax saves all symbols and provide byte-to-byte equal
output if there were no changes.
This is why every node has `node.raws` object to store space symbol, etc.
Be careful, because sometimes these raw properties will not be present; some
nodes may be built manually, or may lose their indentation when they are moved
to another parent node.
This is why the default stringifier has a `raw()` method to autodetect raw
properties by other nodes. For example, it will look at other nodes to detect
indent size and them multiply it with the current node depth.
### Tests
You can use unit and integration test cases from [PostCSS Parser Tests].
Just compare input CSS with CSS after your parser and stringifier.