mirror of
https://github.com/hanzoai/postcss.git
synced 2026-09-24 17:01:56 +00:00
Fix Syntax docs
This commit is contained in:
+76
-65
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user