Skip to content

Commit dbcc33e

Browse files
committed
Deploying to gh-pages from @ 8bb1a4c 🚀
1 parent 4891c5b commit dbcc33e

File tree

7 files changed

+1151
-0
lines changed

7 files changed

+1151
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta property="og:type" content="article"><meta property="og:url" content="http://postcss.org/"><meta name="twitter:card" content="summary"><meta name="twitter:site" content="@postcss"><meta name="twitter:creator" content="@postcss"><meta name="description" content="Transform CSS with the power of JavaScript. Auto-prefixing, future CSS syntaxes, modules, linting and more are possible with hundreds of PostCSS plugins."><link rel="icon" type="image/x-icon" sizes="any" href="/docs/assets/favicon-DbGqNhKa.ico"><link rel="icon" href="/docs/assets/logo-C2ryQugb.svg" type="image/svg+xml"><meta property="og:image" content="./base/og.jpg"><title>How to Write Custom Syntax</title> <script type="module" crossorigin src="/docs/assets/docs-dwWlJbYc.js"></script>
2+
<link rel="stylesheet" crossorigin href="/docs/assets/docs-hQTQJUj-.css">
3+
</head><body><main><div class="hero is-small"><a class="hero_home" rel="home" href="/"><img class="hero_logo" src="/docs/assets/postcss-CsElRNOW.svg" alt="PostCSS logo"></a></div><nav class="nav"><ul class="nav_items"><li class="nav_item"><a class="nav_link" href="https://github.com/postcss/postcss#usage">Setup</a></li><li class="nav_item"><a class="nav_link" href="/docs/">Docs</a></li><li class="nav_item"><a class="nav_link" href="/docs/postcss-plugins">Plugins</a></li><li class="nav_item"><a class="nav_link" href="/api/">API</a></li><li class="nav_item"><a class="nav_link" href="https://github.com/postcss/brand">Logo</a></li></ul></nav><nav class="sidemenu"><ul><a href="how-to-write-custom-syntax#syntax" class="sidemenu_section">Syntax</a><li class="sidemenu_item"><div class="sidemenu_bar"><a href="how-to-write-custom-syntax#parser" class="sidemenu_section">Parser</a><button class="sidemenu_controller"></button></div><ul class="sidemenu_children"><li><a href="how-to-write-custom-syntax#main-theory" class="sidemenu_child">Main Theory</a></li><li><a href="how-to-write-custom-syntax#performance" class="sidemenu_child">Performance</a></li><li><a href="how-to-write-custom-syntax#node-source" class="sidemenu_child">Node Source</a></li><li><a href="how-to-write-custom-syntax#raw-values" class="sidemenu_child">Raw Values</a></li><li><a href="how-to-write-custom-syntax#tests" class="sidemenu_child">Tests</a></li></ul></li><li class="sidemenu_item"><div class="sidemenu_bar"><a href="how-to-write-custom-syntax#stringifier" class="sidemenu_section">Stringifier</a><button class="sidemenu_controller"></button></div><ul class="sidemenu_children"><li><a href="how-to-write-custom-syntax#main-theory" class="sidemenu_child">Main Theory</a></li><li><a href="how-to-write-custom-syntax#builder-function" class="sidemenu_child">Builder Function</a></li><li><a href="how-to-write-custom-syntax#raw-values" class="sidemenu_child">Raw Values</a></li><li><a href="how-to-write-custom-syntax#tests" class="sidemenu_child">Tests</a></li></ul></li></ul></nav><article class="doc"><h1 class="doc_title">How to Write Custom Syntax</h1><p>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.</p><p>Writing a custom syntax is much harder than writing a PostCSS plugin, but
5+
it is an awesome adventure.</p><p>There are 3 types of PostCSS syntax packages:</p><ul>
6+
<li><strong>Parser</strong> to parse input string to node’s tree.</li>
7+
<li><strong>Stringifier</strong> to generate output string by node’s tree.</li>
8+
<li><strong>Syntax</strong> contains both parser and stringifier.</li>
9+
</ul><h2 class="doc_subtitle" id="syntax">Syntax</h2><p>A good example of a custom syntax is <a href="https://github.com/postcss/postcss-scss">SCSS</a>. Some users may want to transform
10+
SCSS sources with PostCSS plugins, for example if they need to add vendor
11+
prefixes or change the property order. So this syntax should output SCSS from
12+
an SCSS input.</p><p>The syntax API is a very simple plain object, with <code>parse</code> &#x26; <code>stringify</code>
13+
functions:</p><pre><code class="code language-js"><span class="code-variable language_">module</span>.<span class="code-property">exports</span> = {
14+
<span class="code-attr">parse</span>: <span class="code-built_in">require</span>(<span class="code-string">'./parse'</span>),
15+
<span class="code-attr">stringify</span>: <span class="code-built_in">require</span>(<span class="code-string">'./stringify'</span>)
16+
}
17+
</code></pre><h2 class="doc_subtitle" id="parser">Parser</h2><p>A good example of a parser is <a href="https://github.com/postcss/postcss-safe-parser">Safe Parser</a>, which parses malformed/broken CSS.
18+
Because there is no point to generate broken output, this package only provides
19+
a parser.</p><p>The parser API is a function which receives a string &#x26; returns a <a href="https://postcss.org/api/#root"><code>Root</code></a>
20+
or <a href="https://postcss.org/api/#document"><code>Document</code></a> node. The second argument is a function which receives
21+
an object with PostCSS options.</p><pre><code class="code language-js"><span class="code-keyword">const</span> postcss = <span class="code-built_in">require</span>(<span class="code-string">'postcss'</span>)
22+
23+
<span class="code-variable language_">module</span>.<span class="code-property">exports</span> = <span class="code-keyword">function</span> <span class="code-title function_">parse</span> (css, opts) {
24+
<span class="code-keyword">const</span> root = postcss.<span class="code-title function_">root</span>()
25+
<span class="code-comment">// Add other nodes to root</span>
26+
<span class="code-keyword">return</span> root
27+
}
28+
</code></pre><p>For open source parser npm package must have <code>postcss</code> in <code>peerDependencies</code>,
29+
not in direct <code>dependencies</code>.</p><h3 class="doc_subtitle" id="main-theory">Main Theory</h3><p>There are many books about parsers; but do not worry because CSS syntax is
30+
very easy, and so the parser will be much simpler than a programming language
31+
parser.</p><p>The default PostCSS parser contains two steps:</p><ol>
32+
<li><a href="https://github.com/postcss/postcss/blob/main/lib/tokenize.js">Tokenizer</a> which reads input string character by character and builds a
33+
tokens array. For example, it joins space symbols to a <code>['space', '\n ']</code>
34+
token, and detects strings to a <code>['string', '"\"{"']</code> token.</li>
35+
<li><a href="https://github.com/postcss/postcss/blob/main/lib/parser.js">Parser</a> which reads the tokens array, creates node instances and
36+
builds a tree.</li>
37+
</ol><h3 class="doc_subtitle" id="performance">Performance</h3><p>Parsing input is often the most time consuming task in CSS processors. So it
38+
is very important to have a fast parser.</p><p>The main rule of optimization is that there is no performance without a
39+
benchmark. You can look at <a href="https://github.com/postcss/benchmark">PostCSS benchmarks</a> to build your own.</p><p>Of parsing tasks, the tokenize step will often take the most time, so its
40+
performance should be prioritized. Unfortunately, classes, functions and
41+
high level structures can slow down your tokenizer. Be ready to write dirty
42+
code with repeated statements. This is why it is difficult to extend the
43+
default <a href="https://github.com/postcss/postcss/blob/main/lib/tokenize.js">PostCSS tokenizer</a>; copy &#x26; paste will be a necessary evil.</p><p>Second optimization is using character codes instead of strings.</p><pre><code class="code language-js"><span class="code-comment">// Slow</span>
44+
string[i] === <span class="code-string">'{'</span>
45+
46+
<span class="code-comment">// Fast</span>
47+
<span class="code-keyword">const</span> <span class="code-variable constant_">OPEN_CURLY</span> = <span class="code-number">123</span> <span class="code-comment">// `{'</span>
48+
string.<span class="code-title function_">charCodeAt</span>(i) === <span class="code-variable constant_">OPEN_CURLY</span>
49+
</code></pre><p>Third optimization is “fast jumps”. If you find open quotes, you can find
50+
next closing quote much faster by <code>indexOf</code>:</p><pre><code class="code language-js"><span class="code-comment">// Simple jump</span>
51+
next = string.<span class="code-title function_">indexOf</span>(<span class="code-string">'"'</span>, currentPosition + <span class="code-number">1</span>)
52+
53+
<span class="code-comment">// Jump by RegExp</span>
54+
regexp.<span class="code-property">lastIndex</span> = currentPosion + <span class="code-number">1</span>
55+
regexp.<span class="code-title function_">test</span>(string)
56+
next = regexp.<span class="code-property">lastIndex</span>
57+
</code></pre><p>The parser can be a well written class. There is no need in copy-paste and
58+
hardcore optimization there. You can extend the default <a href="https://github.com/postcss/postcss/blob/main/lib/parser.js">PostCSS parser</a>.</p><h3 class="doc_subtitle" id="node-source">Node Source</h3><p>Every node should have <code>source</code> property to generate correct source map.
59+
This property contains <code>start</code> and <code>end</code> properties with <code>{ line, column }</code>,
60+
and <code>input</code> property with an <a href="https://github.com/postcss/postcss/blob/main/lib/input.js"><code>Input</code></a> instance.</p><p>Your tokenizer should save the original position so that you can propagate
61+
the values to the parser, to ensure that the source map is correctly updated.</p><h3 class="doc_subtitle" id="raw-values">Raw Values</h3><p>A good PostCSS parser should provide all information (including spaces symbols)
62+
to generate byte-to-byte equal output. It is not so difficult, but respectful
63+
for user input and allow integration smoke tests.</p><p>A parser should save all additional symbols to <code>node.raws</code> object.
64+
It is an open structure for you, you can add additional keys.
65+
For example, <a href="https://github.com/postcss/postcss-scss">SCSS parser</a> saves comment types (<code>/* */</code> or <code>//</code>)
66+
in <code>node.raws.inline</code>.</p><p>The default parser cleans CSS values from comments and spaces.
67+
It saves the original value with comments to <code>node.raws.value.raw</code> and uses it,
68+
if the node value was not changed.</p><h3 class="doc_subtitle" id="tests">Tests</h3><p>Of course, all parsers in the PostCSS ecosystem must have tests.</p><p>If your parser just extends CSS syntax (like <a href="https://github.com/postcss/postcss-scss">SCSS</a> or <a href="https://github.com/postcss/postcss-safe-parser">Safe Parser</a>),
69+
you can use the <a href="https://github.com/postcss/postcss-parser-tests">PostCSS Parser Tests</a>. It contains unit &#x26; integration tests.</p><h2 class="doc_subtitle" id="stringifier">Stringifier</h2><p>A style guide generator is a good example of a stringifier. It generates output
70+
HTML which contains CSS components. For this use case, a parser isn't necessary,
71+
so the package should just contain a stringifier.</p><p>The Stringifier API is little bit more complicated, than the parser API.
72+
PostCSS generates a source map, so a stringifier can’t just return a string.
73+
It must link every substring with its source node.</p><p>A Stringifier is a function which receives <a href="https://postcss.org/api/#root"><code>Root</code></a> or <a href="https://postcss.org/api/#document"><code>Document</code></a> node and builder callback.
74+
Then it calls builder with every node’s string and node instance.</p><pre><code class="code language-js"><span class="code-variable language_">module</span>.<span class="code-property">exports</span> = <span class="code-keyword">function</span> <span class="code-title function_">stringify</span> (root, builder) {
75+
<span class="code-comment">// Some magic</span>
76+
<span class="code-keyword">const</span> string = decl.<span class="code-property">prop</span> + <span class="code-string">':'</span> + decl.<span class="code-property">value</span> + <span class="code-string">';'</span>
77+
<span class="code-title function_">builder</span>(string, decl)
78+
<span class="code-comment">// Some science</span>
79+
};
80+
</code></pre><h3 class="doc_subtitle" id="main-theory-1">Main Theory</h3><p>PostCSS <a href="https://github.com/postcss/postcss/blob/main/lib/stringifier.js">default stringifier</a> is just a class with a method for each node type
81+
and many methods to detect raw properties.</p><p>In most cases it will be enough just to extend this class,
82+
like in <a href="https://github.com/postcss/postcss-scss/blob/main/lib/scss-stringifier.js">SCSS stringifier</a>.</p><h3 class="doc_subtitle" id="builder-function">Builder Function</h3><p>A builder function will be passed to <code>stringify</code> function as second argument.
83+
For example, the default PostCSS stringifier class saves it
84+
to <code>this.builder</code> property.</p><p>Builder receives output substring and source node to append this substring
85+
to the final output.</p><p>Some nodes contain other nodes in the middle. For example, a rule has a <code>{</code>
86+
at the beginning, many declarations inside and a closing <code>}</code>.</p><p>For these cases, you should pass a third argument to builder function:
87+
<code>'start'</code> or <code>'end'</code> string:</p><pre><code class="code language-js"><span class="code-variable language_">this</span>.<span class="code-title function_">builder</span>(rule.<span class="code-property">selector</span> + <span class="code-string">'{'</span>, rule, <span class="code-string">'start'</span>)
88+
<span class="code-comment">// Stringify declarations inside</span>
89+
<span class="code-variable language_">this</span>.<span class="code-title function_">builder</span>(<span class="code-string">'}'</span>, rule, <span class="code-string">'end'</span>)
90+
</code></pre><h3 class="doc_subtitle" id="raw-values">Raw Values</h3><p>A good PostCSS custom syntax saves all symbols and provide byte-to-byte equal
91+
output if there were no changes.</p><p>This is why every node has <code>node.raws</code> object to store space symbol, etc.</p><p>All data related to source code and not CSS structure, should be in <code>Node#raws</code>. For instance, <code>postcss-scss</code> keep in <code>Comment#raws.inline</code> boolean marker of inline comment (<code>// comment</code> instead of <code>/* comment */</code>).</p><p>Be careful, because sometimes these raw properties will not be present; some
92+
nodes may be built manually, or may lose their indentation when they are moved
93+
to another parent node.</p><p>This is why the default stringifier has a <code>raw()</code> method to autodetect raw
94+
properties by other nodes. For example, it will look at other nodes to detect
95+
indent size and them multiply it with the current node depth.</p><h3 class="doc_subtitle" id="tests">Tests</h3><p>A stringifier must have tests too.</p><p>You can use unit and integration test cases from <a href="https://github.com/postcss/postcss-parser-tests">PostCSS Parser Tests</a>.
96+
Just compare input CSS with CSS after your parser and stringifier.</p></article><aside class="socials"><ul class="socials_items"><li class="socials_item"><a class="socials_link is-open-collective" href="https://opencollective.com/postcss/" rel="me">Open Collective</a></li><li class="socials_item"><a class="socials_link is-twitter" href="https://twitter.com/postcss" rel="me">Twitter</a></li><li class="socials_item"><a class="socials_link is-github" href="https://github.com/postcss/postcss" rel="me">GitHub</a></li></ul></aside><footer class="footer"><div class="footer_inner"><div class="footer_info"><p class="footer_license">Distributed under the MIT License.</p><p class="footer_issue">Found an issue?<a class="footer_report" href="https://github.com/postcss/postcss.org/issues">Report it!</a></p></div><div><a href="https://evilmartians.com/?utm_source=postcss&amp;utm_campaign=homepage"><img alt="Evil Martians" src="/docs/assets/evilmartians-bzYkprTm.svg"></a></div></div></footer></main></body></html>

docs/index.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta property="og:type" content="article"><meta property="og:url" content="http://postcss.org/"><meta name="twitter:card" content="summary"><meta name="twitter:site" content="@postcss"><meta name="twitter:creator" content="@postcss"><meta name="description" content="Transform CSS with the power of JavaScript. Auto-prefixing, future CSS syntaxes, modules, linting and more are possible with hundreds of PostCSS plugins."><link rel="icon" type="image/x-icon" sizes="any" href="/docs/assets/favicon-DbGqNhKa.ico"><link rel="icon" href="/docs/assets/logo-C2ryQugb.svg" type="image/svg+xml"><meta property="og:image" content="./base/og.jpg"><title>PostCSS Documentation</title> <script type="module" crossorigin src="/docs/assets/docs-dwWlJbYc.js"></script>
2+
<link rel="stylesheet" crossorigin href="/docs/assets/docs-hQTQJUj-.css">
3+
</head><body><main><div class="hero is-small"><a class="hero_home" rel="home" href="/"><img class="hero_logo" src="/docs/assets/postcss-CsElRNOW.svg" alt="PostCSS logo"></a></div><nav class="nav"><ul class="nav_items"><li class="nav_item"><a class="nav_link" href="https://github.com/postcss/postcss#usage">Setup</a></li><li class="nav_item"><a class="nav_link" href="/docs/">Docs</a></li><li class="nav_item"><a class="nav_link" href="/docs/postcss-plugins">Plugins</a></li><li class="nav_item"><a class="nav_link" href="/api/">API</a></li><li class="nav_item"><a class="nav_link" href="https://github.com/postcss/brand">Logo</a></li></ul></nav><article class='doc'><h1 class='doc_title'>Documentation</h1><ul><li><a href="postcss-architecture" class="doc_subtitle">PostCSS Architecture</a></li>
4+
<li><a href="postcss-plugins" class="doc_subtitle">PostCSS Plugins</a></li>
5+
<li><a href="how-to-write-custom-syntax" class="doc_subtitle">How to Write Custom Syntax</a></li>
6+
<li><a href="writing-a-postcss-plugin" class="doc_subtitle">Writing a PostCSS Plugin</a></li>
7+
<li><a href="postcss-plugin-guidelines" class="doc_subtitle">PostCSS Plugin Guidelines</a></li>
8+
<li><a href="postcss-runner-guidelines" class="doc_subtitle">PostCSS Runner Guidelines</a></li></ul></article><aside class="socials"><ul class="socials_items"><li class="socials_item"><a class="socials_link is-open-collective" href="https://opencollective.com/postcss/" rel="me">Open Collective</a></li><li class="socials_item"><a class="socials_link is-twitter" href="https://twitter.com/postcss" rel="me">Twitter</a></li><li class="socials_item"><a class="socials_link is-github" href="https://github.com/postcss/postcss" rel="me">GitHub</a></li></ul></aside><footer class="footer"><div class="footer_inner"><div class="footer_info"><p class="footer_license">Distributed under the MIT License.</p><p class="footer_issue">Found an issue?<a class="footer_report" href="https://github.com/postcss/postcss.org/issues">Report it!</a></p></div><div><a href="https://evilmartians.com/?utm_source=postcss&amp;utm_campaign=homepage"><img alt="Evil Martians" src="/docs/assets/evilmartians-bzYkprTm.svg"></a></div></div></footer></main></body></html>

0 commit comments

Comments
 (0)