カスタム構文の書き方
PostCSSは、CSSだけでなく、あらゆる構文のスタイルを変換できます。カスタム構文を作成することで、任意の形式のスタイルを変換できます。
カスタム構文の作成は、PostCSSプラグインの作成よりもはるかに難しいですが、素晴らしい冒険です。
PostCSS構文パッケージには3つのタイプがあります。
- パーサー:入力文字列をノードツリーに解析します。
- 文字列化:ノードツリーから出力文字列を生成します。
- 構文:パーサーと文字列化の両方を含みます。
構文
カスタム構文の良い例は、SCSSです。一部のユーザーは、ベンダープレフィックスを追加したり、プロパティの順序を変更したりする必要がある場合など、PostCSSプラグインを使用してSCSSソースを変換したい場合があります。そのため、この構文はSCSS入力からSCSSを出力する必要があります。
構文APIは、parse
関数とstringify
関数を持つ非常にシンプルなプレーンオブジェクトです。
module.exports = {
parse: require('./parse'),
stringify: require('./stringify')
}
パーサー
パーサーの良い例は、不正な形式/壊れたCSSを解析するSafe Parserです。壊れた出力を生成しても意味がないため、このパッケージはパーサーのみを提供します。
パーサーAPIは、文字列を受け取り、Root
ノードまたはDocument
ノードを返す関数です。2番目の引数は、PostCSSオプションを含むオブジェクトを受け取る関数です。
const postcss = require('postcss')
module.exports = function parse (css, opts) {
const root = postcss.root()
// Add other nodes to root
return root
}
オープンソースのパーサーnpmパッケージでは、postcss
は直接のdependencies
ではなく、peerDependencies
に含める必要があります。
主要な理論
パーサーに関する書籍はたくさんありますが、CSS構文は非常に簡単なので、パーサーはプログラミング言語のパーサーよりもはるかにシンプルになります。心配しないでください。
デフォルトのPostCSSパーサーには、2つのステップがあります。
- 入力文字列を1文字ずつ読み取り、トークン配列を構築するトークナイザー。たとえば、スペース記号を
['space', '\n ']
トークンに結合し、文字列を['string', '"\"{"']
トークンとして検出します。 - トークン配列を読み取り、ノードインスタンスを作成し、ツリーを構築するパーサー。
パフォーマンス
入力の解析は、多くの場合、CSSプロセッサで最も時間のかかるタスクです。そのため、高速なパーサーを用意することが非常に重要です。
最適化の主なルールは、ベンチマークがなければパフォーマンスはないということです。独自のベンチマークを作成するには、PostCSSベンチマークを参照してください。
解析タスクの中で、トークン化ステップは多くの場合、最も時間がかかるため、そのパフォーマンスを優先する必要があります。残念ながら、クラス、関数、および高レベルの構造は、トークナイザーの速度を低下させる可能性があります。繰り返しのステートメントを含む汚いコードを書く準備をしてください。そのため、デフォルトのPostCSSトークナイザーを拡張することは困難です。コピー&ペーストが必要悪となります。
2番目の最適化は、文字列の代わりに文字コードを使用することです。
// Slow
string[i] === '{'
// Fast
const OPEN_CURLY = 123 // `{'
string.charCodeAt(i) === OPEN_CURLY
3番目の最適化は「高速ジャンプ」です。開始引用符が見つかった場合、indexOf
を使用して次の終了引用符をはるかに高速に見つけることができます。
// Simple jump
next = string.indexOf('"', currentPosition + 1)
// Jump by RegExp
regexp.lastIndex = currentPosion + 1
regexp.test(string)
next = regexp.lastIndex
パーサーは、よく書かれたクラスにすることができます。コピーペーストやハードコアな最適化は必要ありません。デフォルトのPostCSSパーサーを拡張できます。
ノードソース
すべてのノードには、正しいソースマップを生成するためのsource
プロパティが必要です。このプロパティには、{ line, column }
を持つstart
プロパティとend
プロパティ、およびInput
インスタンスを持つinput
プロパティが含まれています。
トークナイザーは元の位置を保存して、パーサーに値を伝達し、ソースマップが正しく更新されるようにする必要があります。
生の値
優れたPostCSSパーサーは、バイト単位で等しい出力を生成するために、すべての情報(スペース記号を含む)を提供する必要があります。それほど難しくはありませんが、ユーザー入力に敬意を払い、統合スモークテストを可能にします。
パーサーは、すべての追加記号をnode.raws
オブジェクトに保存する必要があります。これはオープンな構造であり、追加のキーを追加できます。たとえば、SCSSパーサーは、コメントタイプ(/* */
または//
)をnode.raws.inline
に保存します。
デフォルトのパーサーは、CSS値からコメントとスペースを削除します。元の値とコメントをnode.raws.value.raw
に保存し、ノード値が変更されていない場合はそれを使用します。
テスト
もちろん、PostCSSエコシステムのすべてのパーサーにはテストが必要です。
パーサーがCSS構文を拡張するだけの場合(SCSSやSafe Parserなど)、PostCSS Parser Testsを使用できます。これには、ユニットテストと統合テストが含まれています。
文字列化
スタイルガイドジェネレーターは、文字列化の良い例です。CSSコンポーネントを含む出力HTMLを生成します。このユースケースでは、パーサーは必要ないため、パッケージには文字列化のみを含める必要があります。
文字列化APIは、パーサーAPIよりも少し複雑です。PostCSSはソースマップを生成するため、文字列化は文字列を返すだけでは不十分です。すべての部分文字列をソースノードにリンクする必要があります。
文字列化は、Root
ノードまたはDocument
ノードとビルダーコールバックを受け取る関数です。次に、各ノードの文字列とノードインスタンスを使用してビルダーを呼び出します。
module.exports = function stringify (root, builder) {
// Some magic
const string = decl.prop + ':' + decl.value + ';'
builder(string, decl)
// Some science
};
主要な理論
PostCSSのデフォルトの文字列化は、各ノードタイプに対応するメソッドと、生のプロパティを検出するための多くのメソッドを持つクラスです。
SCSS文字列化のように、このクラスを拡張するだけで十分な場合がほとんどです。
ビルダー関数
ビルダー関数は、2番目の引数としてstringify
関数に渡されます。たとえば、デフォルトのPostCSS文字列化クラスは、それをthis.builder
プロパティに保存します。
ビルダーは、出力部分文字列とソースノードを受け取り、この部分文字列を最終出力に追加します。
一部のノードには、途中に他のノードが含まれています。たとえば、ルールには、先頭に{
、内部に多くの宣言、および末尾に}
があります。
このような場合は、3番目の引数として'start'
または'end'
文字列をビルダー関数に渡す必要があります。
this.builder(rule.selector + '{', rule, 'start')
// Stringify declarations inside
this.builder('}', rule, 'end')
生の値
優れたPostCSSカスタム構文は、すべての記号を保存し、変更がなかった場合はバイト単位で等しい出力を提供します。
そのため、すべてのノードには、スペース記号などを格納するためのnode.raws
オブジェクトがあります。
ソースコードに関連し、CSS構造に関連しないすべてのデータは、Node#raws
に配置する必要があります。たとえば、postcss-scss
は、インラインコメント(/* comment */
ではなく// comment
)のブールマーカーをComment#raws.inline
に保持します。
これらの生のプロパティが存在しない場合があるため、注意してください。一部のノードは手動で構築されているか、別の親ノードに移動されたときにインデントを失っている可能性があります。
そのため、デフォルトの文字列化には、他のノードによって生のプロパティを自動検出するためのraw()
メソッドがあります。たとえば、他のノードを見てインデントサイズを検出し、それを現在のノードの深さで乗算します。
テスト
文字列化にもテストが必要です。
PostCSS Parser Testsのユニットテストと統合テストケースを使用できます。入力CSSと、パーサーと文字列化後のCSSを比較するだけです。