{% partial %} 文法リファレンス ― 名前・属性・型解決・スコープの全仕様
このブログは @nano-cms/core の上で動いています。Liquid 連載では、共通パーツを配る {% partial %} タグを、使う側・なぜ拡げたか・作り方の 3 回で見てきました。本記事は、その補遺となる文法リファレンスです(連載 3 回に続くリファレンス編)。名前・属性・型解決・スコープ・版選択・エラーまで、{% partial %} の文法を端から端まで引けるようにまとめます。挙動はすべて実際のエンジンで実行して確認しました。手元で書き方に迷ったら、この記事を引いてください。
1. 全体像 ― 「名前」+「任意の属性」の 2 部構成
{% partial %} は 2 つの部分でできています。先頭の名前(必須)と、その後ろに続く属性(任意・0 個以上)です。
3 つだけ先に押さえます。第一に、これは閉じタグの無いインラインタグです。{% endpartial %} のようなものは存在せず、1 つのタグで完結します。第二に、Liquid の空白制御が使えます。{%- partial "x" -%} と書いてもよく、このハイフンはタグの区切りの一部として扱われ、引数の解析には影響しません({%-partial "x"-%} のようにハイフンを詰めても同じです)。第三に、名前と属性は文法が別物です(名前は静的なリテラルしか書けない/属性値は変数や数値も書ける)。この違いは後の節で表にします。
2. パーシャル名の文法
名前は、引数の先頭に置くクォート文字列リテラルです。取り出しは次の正規表現で行われます。
/^"([^"]+)"|^'([^']+)'/
ここから読み取れる規則はこうです。
- クォートは必須。ダブルクォート
"..."とシングルクォート'...'のどちらでも書け、両者は等価です。 - 中身は 1 文字以上。
[^"]+が 1 文字以上を要求するため、空名""は名前として認められません。 - 中身の文字種は自由。クォート以外なら、日本語・空白・スラッシュなども名前に使えます(例
"記事 一覧")。ただし実際にその名前の共通パーツが管理画面に登録されている必要があります。名前はファイルのパスではなく、登録名を指します。 - エスケープは無い。同じ種類のクォートが出てきた時点で文字列が閉じます。
"a"b"は名前aとして解釈され、残りb"は属性側に回りますが、key=valueの形ではないので無視されます。 - 名前は静的リテラルのみ。名前に変数や式は使えません。クォート無しの
{% partial headerVar %}は、先頭がクォート文字列でないため名前が取れず無音になります。一方、先頭にクォート名さえあれば、その後ろの「属性ではない断片」は黙って捨てられます。たとえば{% partial "pre" | append: y %}は、名前preが確定し、| append: yはフィルタとして働かず無視され、preがそのまま描画されます(エラーにも無音にもなりません)。名前はタグの構文解析時に 1 回だけ確定し、描画ごとに変わることはありません。
3. 名前が無効なときは「黙って何も出さない」
名前が取り出せないと、{% partial %} はエラーを出さずに何も描画しません(早期 return します)。共通パーツの読み込みも、属性の処理も行いません。「タグを書いたのに効かない、けれどエラーも出ない」という状態は、たいていこの無音スキップです。
| 書き方 | 結果 | 理由 |
|---|---|---|
{% partial "site-header" %} | 読み込む | 正しい形 |
{% partial site-header %} | 無音 | クォートが無い |
{% partial "" %} | 無音 | 空名(1 文字以上が必要) |
{% partial headerVar %} | 無音 | 変数は名前に使えない |
{% partial "abc %} | 無音 | 閉じクォート忘れ |
4. 属性(key=value)の字句文法
名前の後ろには、ローカル変数として渡す属性を key=value の形で並べられます。属性列は次の正規表現で 1 つずつ取り出されます。
/(\w+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g
- キーは
\w+。使えるのは ASCII の英数字とアンダースコア([A-Za-z0-9_])だけです。ハイフン・ドット・コロン・日本語はキーに使えません。data-xやa-bのようなキーは、その文字より後ろだけが拾われます(a-b=1はキーbとして解釈されます)。日本=x ok=yはokだけが採られます。数字始まり・全桁数字のキーは有効です(2024=xはキー2024)。 - 値は 3 形態。ダブルクォート
"..."、シングルクォート'...'、クォート無し(次の空白までの一塊\S+)。クォート付きの値には空白を含められます(label="新 着")。クォート付きの値は空にもでき、k=""は空文字列として渡ります。 - 区切りは空白のみ。これが要注意点です。カンマは区切りになりません。
a=1,b=2と書くと、クォート無しの値が空白まで一塊なので、aの値が1,b=2になってしまいます。属性は必ず空白で区切ってください。 =の無い裸トークンは無視。キーと=が揃わないものは、エラーにならず単に読み飛ばされます。- 同じキーは後勝ち。
x=1 x=2ならxは2になります。 - 属性が無ければ空。属性を書かなければ何も渡らず、もちろんエラーも出ません。
- クォートの閉じ忘れは、クォート無しの形にフォールバックします。
k="abcは値が"abc(先頭のクォートを含んだ文字列)として渡ります。
5. 属性値の型 ― リテラル / 数値 / 変数参照
取り出した属性値は、描画の直前に型へ解決されます。判定は「クォートの有無」と「全桁が数字か」の 2 軸だけです。
クォート付きは原則リテラルですが、全桁が数字(正規表現 /^\d+$/ =非負整数)なら数値に変換されます。クォート無しは、全桁数字なら数値、そうでなければ文脈変数として解決され、見つからなければ元の文字列がそのまま渡ります。変数参照で辿れるのは a.b のようなドット記法だけで、配列添字 arr[0] や式は解決されず元の文字列のまま渡ります。実際に走らせて確認した結果を表にします。
| 書き方 | クォート | 渡る値 | 型 |
|---|---|---|---|
k="募集要項" | 有 | 募集要項 | 文字列 |
k="3" | 有 | 3 | 数値 ⚠ |
k="007" | 有 | 7 | 数値(先頭ゼロ消失)⚠ |
k=3 | 無 | 3 | 数値 |
k=-3 / k=3.5 | 無 | -3 / 3.5 | 文字列(負数・小数は数値化されない) |
k=2024 | 無 | 2024 | 数値(変数 2024 は引かれない) |
k=user.name | 無 | 変数 user.name の値 | その値の型 |
k=missing | 無 | missing(元の文字列) | 文字列(未定義のフォールバック) |
k=true | 無 | true(元の文字列) | 文字列(真偽リテラルは無い) |
"3" も 3 も数値 3 になります ― クォートで囲んでも「文字列の "3"」は渡せません。ゼロ詰め("007"→7)も同じく消えるので、郵便番号やゼロ詰め ID を素の数字で渡すことはできません。true / false / null は専用のリテラルが無く、クォート無しなら変数として解決され、未定義なら文字列 "true" 等になります。Liquid 側で {% if flag %} と書くと、文字列 "false" は真として扱われるので注意してください。6. スコープ ― 属性ありで一時フレーム、なしは親そのまま
パーシャルは、呼び出し元(親テンプレート)の変数を引き継いで描画されます。属性の有無で、変数の見え方が変わります。
属性なしのときは、親の変数をそのまま全部見ます。何も積みません。属性ありのときは、解決した値を一時フレームとして積み、そのパーシャルを描画する間だけ追加の変数として見えるようにし、描画が終わると取り除きます。だから、ある呼び出しで渡した値が別の呼び出しに漏れることはありません。同じ名前の親変数があれば、その間だけ引数のほうが優先され、描画後に元へ戻ります。なお、引数の右辺に変数を書いた場合(title=post.title)、その右辺はフレームを積む前=親スコープで評価されてから渡されます。
7. 名前と属性は文法が違う(非対称)
ここまでで見たとおり、名前と属性値は別の文法に従います。引きやすいように対比でまとめます。
| パーシャル名 | 属性値 | |
|---|---|---|
| クォート | 必須 | 任意(無しなら変数/数値) |
| 空文字 | 不可(無音) | 可(k="") |
| 変数・式 | 不可(静的リテラルのみ) | 可(クォート無しで変数参照) |
| 数値化 | されない(常に文字列の名前) | される(全桁数字なら数値) |
8. パーシャル内で参照できる変数
パーシャルの中身も Liquid テンプレートなので、{% if %} や {{ 変数 }} が使えます。そこから参照できるのは次のものです。
| 参照できるもの | 内容 |
|---|---|
| 親から引き継いだ全変数 | 呼び出し元が持つ変数(記事データや、後述のサイト共通の値など)すべて |
| 属性で渡したローカル変数 | key=value で渡したもの(描画中のみ有効) |
request.path | リクエストのパス(静的生成では query / params は空) |
site.basePath | 配信のベースパス(/published など) |
DEPLOY_ENV | デプロイ環境。{% if DEPLOY_ENV == "production" %} で本番だけ計測タグを出す、といった分岐に使える |
エンジンは寛容な設定で動いているため、未定義の変数を参照してもエラーにはならず、空(偽値)として扱われます。
9. 版の選択 ― 公開版 / ドラフト / 予約(応用)
同じ {% partial "site-header" %} でも、文脈によって読み込む版が変わります。文法というより動作の応用ですが、リファレンスとして要点を載せます。
- サイトのスコープ: どのサイトのページを描いているか(数値のサイトID)でサイト別に解決されます。このサイトIDが数値として渡っていないと、公開版であっても何も出ません。スタンドアロンで試して「何も出ない」ときの典型がこれです。
- 通常配信: 公開された版を読みます。公開版は更新日時で照合するキャッシュに載り、変わっていなければ解析済みを使い回します。
- プレビュー対象: 編集中のパーシャルを指定してプレビューすると(プレビュー対象のパーシャル名と厳密一致したとき)、まだ公開していない下書きを優先して読み、前後にハイライトの目印を付けます。
- 予約プレビュー: 表示したい時刻を渡すと、その時刻までに公開予定の版を先取りして読みます。
- キャッシュに載るのは公開版だけ: 下書きやプレビューの読み込みはキャッシュを使わず毎回データベースから取得するため、編集中の内容はすぐ反映されます。
この「読む版が切り替わる」体験の詳しい説明は 第 1 回(使う側)にあります。
10. 入れ子・再帰とエラー隔離
パーシャルの中身にさらに {% partial %} を書けます(入れ子)。各タグはそれぞれ独立に版を選び、属性のフレームを積み下ろしします。ただし循環参照の検出や深さ制限はありません。パーシャルが自分自身を直接・間接に読み込むと止まらなくなりうるので、自己参照は避けてください。
各タグの描画はエラーを隔離する囲みの中で行われます。1 つのパーシャルが描画に失敗しても、ページ全体は壊れず、その範囲が空になるだけで残りは描画され続けます(失敗はログに残ります)。なお、{% raw %} や {% comment %} の中に書いた {% partial %} は、Liquid の標準どおり評価されません(そのまま文字列になる/無視される)。
11. ハマりどころ早見表
| やりがち | 結果 | 正しくは |
|---|---|---|
| 名前にクォートを付けない | 無音 | 必ず "..." か '...' |
| 属性をカンマ区切り | 値に飲まれる | 空白で区切る |
キーに data-x・ハイフン・日本語 | 拾われない | 英数字と _ のみ(snake_case) |
n="007" で文字列のつもり | 7 になる | ゼロ詰めは素の数字では渡せない |
flag=false | 文字列 "false" → {% if %} で真 | 真偽は渡さず、有無で分ける等 |
名前に変数 {% partial name %} | 無音 | 名前は静的リテラルのみ |
| 単体で試して何も出ない | サイトID(数値)が未注入 | 実ページ/プレビューで確認 |
属性値に arr[0] | 解決されない | ドット記法(a.b)のみ |
名前で迷ったら §2・§3、属性の書き方は §4、値が数値になる/変数が効かないは §5、変数の見え方は §6、名前と属性の違いは §7、パーシャル内で使える変数は §8、効かないとき(無音・空出力)は §3 と §11 を見てください。
{% partial %} の文法は、結局のところ「名前(先頭の静的なクォート文字列リテラル)」+「属性(key=value を空白区切り、値はクォートの有無と数字かどうかで型が決まる)」+「スコープ(属性は描画中だけの一時フレーム)」に尽きます。実際の使い方は 第 1 回(使う側)、なぜこのタグを足したのかは 第 2 回(設計)、タグ自体の作り方は 第 3 回(作り方)にまとまっています。

