Fix Syntax docs

This commit is contained in:
Andrey Sitnik
2015-08-17 16:20:42 +03:00
parent 7256982ba0
commit 947b23d2a8
+76 -65
View File
@@ -1,16 +1,16 @@
# How to Write Custom Syntax
PostCSS can transform not only CSS. You can write a custom syntax
and transform styles in other format.
PostCSS can transforms styles in any syntax, not only in CSS.
You can write a custom syntax and transform styles in any format.
Writing a custom syntax is much harder rather then writing just
a PostCSS plugin. But it is a awesome adventure.
Writing a custom syntax is much harder then writing just a PostCSS plugin.
But it is a awesome adventure.
You can provide 3 types of package:
There are 3 types of PostCSS syntax packages:
* **Parser** to parse input string to node’s tree.
* **Stringifier** to generate output string by node’s tree.
* **Syntax** contains parser and stringifier.
* **Syntax** contains both parser and stringifier.
## Syntax
@@ -31,28 +31,30 @@ module.exports = {
## Parser
Main example of parse is [Safe Parser], to parse broken CSS. There is not sense
to generate string with broken CSS, so package should provide only parser.
Main example of parse is [Safe Parser], which parses broken CSS.
There is no sense to generate string with broken CSS,
so package provides only parser.
Parser API is a function, that receives string and returns `Root` node.
The second function argument will be a object with PostCSS options.
Parser API is a function, that receives string and returns [`Root`] node.
In second argument this function receives a object with PostCSS options.
```js
var postcss = require('postcss');
module.exports = function (css, opts) {
var root = postcss.root();
// Some magic with css
// Add other nodes to root
return root;
};
```
[Safe Parser]: https://github.com/postcss/postcss-safe-parser
[`Root`]: https://github.com/postcss/postcss/blob/master/docs/api.md#root-node
### Main Theory
There are many books about parses. But do not scary, CSS syntax is very easy,
so parser will be much simpler than usual.
There are many books about parsers. But do not scary, CSS syntax is very easy,
so parser will be much simpler than programming language parsers.
The default PostCSS parser contains two steps:
@@ -61,64 +63,65 @@ The default PostCSS parser contains two steps:
or detect strings to `['string', '"\"{"']`.
* [Parser] to read tokens array, create node instances and build tree.
Look at this parts for a parser example.
[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 a longest task in CSS usual processor. So it is very important
Parsing input is a longest task in CSS processors. So it is very important
to have fast parser.
The main rule of optimization: there is no performance without benchmark.
You can look at [PostCSS benchmarks] as example.
You can look at [PostCSS benchmarks] to build your own.
If we will look deeper, the tokenize step will be a longest part of parsing.
So you should focus only on tokenizer performance. Other parts could be
well written maintainable code.
Unfortunately, classes, functions and high level structures can slow down
1. Unfortunately, classes, functions and high level structures can slow down
your tokenizer. Be ready to write dirty code with code repeating.
This is why it is difficult to extend default tokenizer.
This is why it is difficult to extend default [PostCSS tokenizer].
Copy paste will be a necessary evil.
Second optimization is using char codes instead of string.
2. Second optimization is using char codes instead of string.
```js
// Slow
string[i] === '{';
```js
// Slow
string[i] === '{';
// Fast
const OPEN_CURLY = 123; // `{'
string.charCodeAt(i) === OPEN_CURLY;
```
// Fast
const OPEN_CURLY = 123; // `{'
string.charCodeAt(i) === OPEN_CURLY;
```
Third optimization is “fast jumps”. If you find open quote, you can find
closed quote much faster by `indexOf`:
3. Third optimization is “fast jumps”. If you find open quote, you can find
next close quote much faster by `indexOf`:
```js
// Simple jump
next = string.indexOf('"', currentPosition + 1);
```js
// Simple jump
next = string.indexOf('"', currentPosition + 1);
// Complicated jump
regexp.lastIndex = currentPosion + 1;
regexp.text(string);
next = regexp.lastIndex;
```
// Jump by RegExp
regexp.lastIndex = currentPosion + 1;
regexp.text(string);
next = regexp.lastIndex;
```
Parser can be well written class. So there is no need in copy-paste and
hardcore optimization there. You can safely extend default class.
Parser can be well written class. There is no need in copy-paste and
hardcore optimization there. You can extend default [PostCSS parser].
[PostCSS benchmarks]: https://github.com/postcss/benchmark
[PostCSS tokenizer]: https://github.com/postcss/postcss/blob/master/lib/tokenize.es6
[PostCSS parser]: https://github.com/postcss/postcss/blob/master/lib/parser.es6
### Node Source
To generate source map, every node instance should have `source` property.
It should have `{ line, column }` object in `start` and `end` properties
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 [`Input`] instance.
So your tokenizer should provide token type, content and positions.
So your tokenizer should save position to every token to use set it to nodes
in parser.
[`Input`]: https://github.com/postcss/postcss/blob/master/lib/input.es6
@@ -129,35 +132,38 @@ to generate byte-to-byte equal output. It is not so difficult, but respectful
for user input and allow integration smoke tests.
Parser should save all addition symbols to [`node.raws`] object.
It is a open structure to you, you can add addition keys.
For example, [SCSS parser] saves comment types there (`/* */` or `//` comment).
It is a open structure for you, you can add addition keys.
For example, [SCSS parser] saves comment types (`/* */` or `//`)
in `node.raws.inline`.
Also, default parser clean some CSS values from comments and spaces.
It save origin value with comments to `node.raws.value.raw` and use it,
if node values were not changed.
Default parser cleans CSS values from comments and spaces.
It saves origin value with comments to `node.raws.value.raw` and use it,
if node value was not changed.
[SCSS parser]: https://github.com/postcss/postcss-scss
[`node.raws`]: https://github.com/postcss/postcss/blob/master/docs/api.md#node-raws
### Tests
Of course, all parsers must have a tests.
Of course, all parsers in PostCSS ecosystem must have tests.
If your parser just extend CSS syntax (like [SCSS] or [Safe Parser]),
you should use [PostCSS Parser Tests]. It contains unit and integration tests.
you can use [PostCSS Parser Tests]. It contains unit and integration tests.
[PostCSS Parser Tests]: https://github.com/postcss/postcss-parser-tests
## Stringifier
Style guide generator is good example of stringifier. It generates output HTML
with usage examples by component styles.
with usage examples by component CSS. So there is no sense for parser
and package can contain only stringifier.
Stringifier API is little bit more complicated, that parser API.
PostCSS generates source map. So stringifier can’t just return a string.
It must link every string with source node.
It must link every substring with source node.
Stringifier is a function, that receives `Root` node and builder callback.
Then it calls builder with every node’s string and this node.
Stringifier is a function, that receives [`Root`] node and builder callback.
Then it calls builder with every node’s string and node instance.
```js
module.export = function (root, builder) {
@@ -171,8 +177,7 @@ module.export = function (root, builder) {
### Main Theory
PostCSS [default stringifier] is just a class with method for each node type
and many methods to detect raw properties (if node was built manually)
by other nodes.
and many methods to detect raw properties.
In most cases it will be enough just to extent this class,
like in [SCSS stringifier].
@@ -183,26 +188,32 @@ like in [SCSS stringifier].
### Builder Function
Builder function will be pass to `stringify` function as second argument.
Stringifier class saves it to `this.builder` property.
For example, default PostCSS stringifier class saves it
to `this.builder` property.
It receive output substring and source node and accept this substring
Builder receives output substring and source node to append this substring
to final output.
Some nodes string can contains other nodes in the middle.
For example, rule has `a {` beginning, many declarations inside
and `}` at the end.
Some nodes contains other nodes in the middle. For example, rule has `a {`
beginning, many declarations inside and `}` at the end.
For this cases, you should pass a third argument to builder function:
`'start'` or `'end'` string.
`'start'` or `'end'` string:
```js
this.builder(rule.selector + '{', rule, 'start');
// Stringify declarations inside
this.builder('}', rule, 'end');
```
### Raw Values
Good PostCSS syntax saves all symbols and provide byte-to-byte equal output
if there were not AST changes.
if there were no changes.
This is why every node has [`node.raws`] object to store space symbol, etc.
But be ready, that any of this `raws` properties can be missed. Some nodes
But be ready, that any of this raw properties can be missed. Some nodes
can be built manually. Some nodes can loose indent on moving between parents.
This is why default stringifier has `raw()` method to autodetect raw property
@@ -215,7 +226,7 @@ and them multiply it with current node depth.
Stringifier must have a tests too.
You can use unit and integration cases from [PostCSS Parser Tests]
in smoke test. Just compare input CSS with CSS after you parser and stringifier.
You can use unit and integration cases from [PostCSS Parser Tests].
Just compare input CSS with CSS after you parser and stringifier.
[PostCSS Parser Tests]: https://github.com/postcss/postcss-parser-tests