1 # How to Write Custom Syntax
3 PostCSS can transform styles in any syntax, and is not limited to just CSS.
4 By writing a custom syntax, you can transform styles in any desired format.
6 Writing a custom syntax is much harder than writing a PostCSS plugin, but
7 it is an awesome adventure.
9 There are 3 types of PostCSS syntax packages:
11 * **Parser** to parse input string to node’s tree.
12 * **Stringifier** to generate output string by node’s tree.
13 * **Syntax** contains both parser and stringifier.
17 A good example of a custom syntax is [SCSS]. Some users may want to transform
18 SCSS sources with PostCSS plugins, for example if they need to add vendor
19 prefixes or change the property order. So this syntax should output SCSS from
22 The syntax API is a very simple plain object, with `parse` & `stringify`
27 parse: require('./parse'),
28 stringify: require('./stringify')
32 [SCSS]: https://github.com/postcss/postcss-scss
36 A good example of a parser is [Safe Parser], which parses malformed/broken CSS.
37 Because there is no point to generate broken output, this package only provides
40 The parser API is a function which receives a string & returns a [`Root`] node.
41 The second argument is a function which receives an object with PostCSS options.
44 var postcss = require('postcss');
46 module.exports = function (css, opts) {
47 var root = postcss.root();
48 // Add other nodes to root
53 [Safe Parser]: https://github.com/postcss/postcss-safe-parser
54 [`Root`]: https://github.com/postcss/postcss/blob/master/docs/api.md#root-node
58 There are many books about parsers; but do not worry because CSS syntax is
59 very easy, and so the parser will be much simpler than a programming language
62 The default PostCSS parser contains two steps:
64 1. [Tokenizer] which reads input string character by character and builds a
65 tokens array. For example, it joins space symbols to a `['space', '\n ']`
66 token, and detects strings to a `['string', '"\"{"']` token.
67 2. [Parser] which reads the tokens array, creates node instances and
70 [Tokenizer]: https://github.com/postcss/postcss/blob/master/lib/tokenize.es6
71 [Parser]: https://github.com/postcss/postcss/blob/master/lib/parser.es6
75 Parsing input is often the most time consuming task in CSS processors. So it
76 is very important to have a fast parser.
78 The main rule of optimization is that there is no performance without a
79 benchmark. You can look at [PostCSS benchmarks] to build your own.
81 Of parsing tasks, the tokenize step will often take the most time, so its
82 performance should be prioritized. Unfortunately, classes, functions and
83 high level structures can slow down your tokenizer. Be ready to write dirty
84 code with repeated statements. This is why it is difficult to extend the
85 default [PostCSS tokenizer]; copy & paste will be a necessary evil.
87 Second optimization is using character codes instead of strings.
94 const OPEN_CURLY = 123; // `{'
95 string.charCodeAt(i) === OPEN_CURLY;
98 Third optimization is “fast jumps”. If you find open quotes, you can find
99 next closing quote much faster by `indexOf`:
103 next = string.indexOf('"', currentPosition + 1);
106 regexp.lastIndex = currentPosion + 1;
108 next = regexp.lastIndex;
111 The parser can be a well written class. There is no need in copy-paste and
112 hardcore optimization there. You can extend the default [PostCSS parser].
114 [PostCSS benchmarks]: https://github.com/postcss/benchmark
115 [PostCSS tokenizer]: https://github.com/postcss/postcss/blob/master/lib/tokenize.es6
116 [PostCSS parser]: https://github.com/postcss/postcss/blob/master/lib/parser.es6
120 Every node should have `source` property to generate correct source map.
121 This property contains `start` and `end` properties with `{ line, column }`,
122 and `input` property with an [`Input`] instance.
124 Your tokenizer should save the original position so that you can propagate
125 the values to the parser, to ensure that the source map is correctly updated.
127 [`Input`]: https://github.com/postcss/postcss/blob/master/lib/input.es6
131 A good PostCSS parser should provide all information (including spaces symbols)
132 to generate byte-to-byte equal output. It is not so difficult, but respectful
133 for user input and allow integration smoke tests.
135 A parser should save all additional symbols to [`node.raws`] object.
136 It is an open structure for you, you can add additional keys.
137 For example, [SCSS parser] saves comment types (`/* */` or `//`)
138 in `node.raws.inline`.
140 The default parser cleans CSS values from comments and spaces.
141 It saves the original value with comments to `node.raws.value.raw` and uses it,
142 if the node value was not changed.
144 [SCSS parser]: https://github.com/postcss/postcss-scss
145 [`node.raws`]: https://github.com/postcss/postcss/blob/master/docs/api.md#node-raws
149 Of course, all parsers in the PostCSS ecosystem must have tests.
151 If your parser just extends CSS syntax (like [SCSS] or [Safe Parser]),
152 you can use the [PostCSS Parser Tests]. It contains unit & integration tests.
154 [PostCSS Parser Tests]: https://github.com/postcss/postcss-parser-tests
158 A style guide generator is a good example of a stringifier. It generates output
159 HTML which contains CSS components. For this use case, a parser isn't necessary,
160 so the package should just contain a stringifier.
162 The Stringifier API is little bit more complicated, than the parser API.
163 PostCSS generates a source map, so a stringifier can’t just return a string.
164 It must link every substring with its source node.
166 A Stringifier is a function which receives [`Root`] node and builder callback.
167 Then it calls builder with every node’s string and node instance.
170 module.exports = function (root, builder) {
172 var string = decl.prop + ':' + decl.value + ';';
173 builder(string, decl);
180 PostCSS [default stringifier] is just a class with a method for each node type
181 and many methods to detect raw properties.
183 In most cases it will be enough just to extend this class,
184 like in [SCSS stringifier].
186 [default stringifier]: https://github.com/postcss/postcss/blob/master/lib/stringifier.es6
187 [SCSS stringifier]: https://github.com/postcss/postcss-scss/blob/master/lib/scss-stringifier.es6
191 A builder function will be passed to `stringify` function as second argument.
192 For example, the default PostCSS stringifier class saves it
193 to `this.builder` property.
195 Builder receives output substring and source node to append this substring
198 Some nodes contain other nodes in the middle. For example, a rule has a `{`
199 at the beginning, many declarations inside and a closing `}`.
201 For these cases, you should pass a third argument to builder function:
202 `'start'` or `'end'` string:
205 this.builder(rule.selector + '{', rule, 'start');
206 // Stringify declarations inside
207 this.builder('}', rule, 'end');
212 A good PostCSS custom syntax saves all symbols and provide byte-to-byte equal
213 output if there were no changes.
215 This is why every node has [`node.raws`] object to store space symbol, etc.
217 Be careful, because sometimes these raw properties will not be present; some
218 nodes may be built manually, or may lose their indentation when they are moved
219 to another parent node.
221 This is why the default stringifier has a `raw()` method to autodetect raw
222 properties by other nodes. For example, it will look at other nodes to detect
223 indent size and them multiply it with the current node depth.
225 [`node.raws`]: https://github.com/postcss/postcss/blob/master/docs/api.md#node-raws
229 A stringifier must have tests too.
231 You can use unit and integration test cases from [PostCSS Parser Tests].
232 Just compare input CSS with CSS after your parser and stringifier.
234 [PostCSS Parser Tests]: https://github.com/postcss/postcss-parser-tests