Skip to content

Instantly share code, notes, and snippets.

@turusuke
Last active April 14, 2024 06:21
Show Gist options
  • Save turusuke/27dd6cb152d112d3d1bc28442ac3b10c to your computer and use it in GitHub Desktop.
Save turusuke/27dd6cb152d112d3d1bc28442ac3b10c to your computer and use it in GitHub Desktop.
[WIP]Nunjucks - 入門編

Nunjucks - 入門編

  • 入門編のこの記事の内容、サンプルコードは Nunjucks に書いてある内容を参考・一部引用し作成している
  • テンプレート継承周りの話は別に書く予定なので、この記事では関連するテンプレートタグにも触れていない

Nunjucks とは

  • Nunjucks は Python のテンプレートエンジン Jinja2 に影響を受けているテンプレートエンジンで、Mozilla が継続的にメンテナンスをしている
  • 記法は EJS に近く、HTML テンプレートに {% set greeting = "HELLO" %} のように用意されている独自タグを使うことができる
  • EJS よりもテンプレートエンジンとして役にたつ機能が多数用意されているが、記法が JavaScript とも違っている書き方になっているもの多い
  • テンプレートエンジンを触ってこなかった人にとってはとっつきにくいと感じる人が多いかもしれない

シンタックスハイライト

  • エディタで作業する場合、デフォルトでシンタックスハイライト対応しているものはほぼなく、プラグインを利用して対応する
  • (nunjacks のプラグインがないものはもととなっている jinja のシンタックスハイライトを追加すれば対応できるかもしれない) 下記は nunjucks の公式ページ(https://mozilla.github.io/nunjucks/templating.html#syntax-highlighting)より抜粋。

式と文

  • HTML テンプレート中で式や文(テンプレートタグ)を書くことができる
  • 式は {{ username }} のように中括弧2つで囲うことで展開され、テンプレートタグは {% set username %} のように中括弧と % で囲うことで実行される

変数

変数定義の方法

  • set で定義する
{% set username = "joe" %}
{{ username }} {# username が展開される}
  • もちろん文字列だけではなく、配列やオブジェクトも定義できる
{% set arr = [1,2,3,4,5] %}
{% set obj = { a: 1, b: 2, c: [0, 1, 2, {d: 3}]} %}
  • JavaScript のようにプロパティの値にドットや角括弧([]) の記法を使ってアクセスできる
{{ profile.name }}
{{ profile["name"] }}

値が未定義、 null の場合の扱い

  • 値が undefiled または null の場合、何も表示されない
  • undefined または null オブジェクトを参照する場合も同じように何も表示されない
{# profile が未定義だった場合 #} 
{{ profile }} {# 何も表示されない #}
{{ profile.firstName }} {{ profile.lastName }} {# 何も表示されない #}

フィルター

  • 式のあとに縦棒(|)を追加して、 {{ foo | join(",") }} のように書くと foo の値を join という関数へ渡し、その実行結果を表示することができる フィルター という機能が使える
  • フィルターは自分で用意することもできるが、 Nunjacks がビルトインで便利なフィルターを多数用意してくれている
    • そこになければ自作するで良いと思う
  • 以下はビルトインで入っているフィルターを使用した例
{# 文字列を整数へ変換する #}
{{ '100' | int }}
{# => 100 #}

{# 改行コードを `<br>` へ変換する #}
{{ 'こんにちは\n明日は晴れです' | nl2br }}
{# => こんにちは<br>明日は晴れです #}

{# 配列を引数で渡した値で結合する #}
{{ [0, 1, 2, 3, 4] | join('-') }}
{# => 0-1-2-3-4 #}

{# オブジェクトの値を `JSON.stringify` した結果を表示する  #}
{# デバッグしたいときに有用 #}
{% set items = { a: 1, b: [1, 2, 3, c: { d: 123 }] } %}
{{ items | debug }}
{# => {"a":1,"b":[1,2,3,{"c":5}]} #}

{# HTML 文字列(具体的には &, <, >, ‘, ”)をエスケープできる #}
{{ "<html>" | escape }}
{# => &lt;html&gt; #}

{# 特定のキーを基準に配列をまとめ直す #}
{% set items = [
  { name: '田中', type: '赤組' },
  { name: '花澤', type: '白組' },
  { name: '伊藤', type: '赤組' },
  { name: '川田', type: '白組' }
] %}

{% for type, items in items | groupby("type") %}
    <p><strong>{{ type }}</strong> :
    {% for item in items %}
        {{ item.name }}
    {% endfor %}</p>
{% endfor %}

{# =>
<div>
    <p><strong>赤組</strong> :
        田中
        伊藤
    </p>
    <p><strong>白組</strong> :
        花澤
        川田
    </p>
</div>
#}

テンプレートタグ

条件分岐 - if

  • 条件分岐するにはテンプレートタグ if が使える
  • {% if 条件式 %} ~ {% endif %} でブロックを作って HTML テンプレートを囲うことで条件に一致したものが表示される
{% if greeting %} {# もし `greeting` が truthy だったら #}
  こんにちは!
{% else %}
  あいさつ文が登録されていません
{% endif %}
  • elseif を使って条件文をネストさせることも可能
{% if greeting %} {# もし `greeting` が truthy だったら #}
  こんにちは!
{% elif happy %}
  幸せです!
{% else %}
  よろしくおねがいします!
{% endif %}

ループ - for

{# 配列のループ #}
{% set arr = [1, 2, 3] %}
{% for value in arr %}
    <p>{{value}}</p>
{% endfor %}
{# =>
<p>1</p>
<p>2</p>
<p>3</p>
#}

{# 辞書型のループ #}
{% set obj = {a: 1, b: 2, c: 3} %}
{% for key, value in obj %}
    <p>{{key}}, {{value}}</p>
{% endfor %}
{# =>
<p>a, 1</p>
<p>b, 2</p>
<p>c, 3</p>
#}

配列を変数にアンパック

{% for x, y, z in [[0, 1, 2], [3, 4, 5], [6, 7, 8]] %}
  <p>Point: {{ x }}, {{ y }}, {{ z }}</p>
{% endfor %}
{# =>
<p>Point: 0, 1, 2</p>
<p>Point: 3, 4, 5</p>
<p>Point: 6, 7, 8</p>
#}
  • ループの中で loop という変数を使える
    • loop.index: ループ中の現在のインデックス値(1から)
    • loop.index0: ループ中の現在のインデックス値(0から)
    • loop.revindex: ループが終わるまでの残り回数(1まで)
    • loop.revindex0: ループが終わるまでの残り回数(1まで)
    • loop.first: 最初の値かどうかを真偽値(boolean)で返す
    • loop.last: 最後の値かどうかを真偽値(boolean)で返す
    • loop.length: アイテムの数

非同期用ループ

  • カスタムテンプレートローダーを使う場合に使う
  • asyncEach(順序を保持しない), asyncAll(順序を保持する)

関数っぽいもの - macro

  • 再利用可能なテンプレートを macro というタグで定義できる
  • {% macro MacroName %} ~ {% endmacro %} で任意の macro 名を指定し、 テンプレート囲い macro を定義する
{% macro field(name, value='', type='text') %}
<div class="field">
  <input type="{{ type }}" name="{{ name }}"
    value="{{ value | escape }}" />
</div>
{% endmacro %}
  • 定義したテンプレート内で {{ field('user') }} のように実行する
  • 引数を渡したり、デフォルト値をセットすることができる
  • import を使えば外部ファイルに定義した macro を読み込んで再利用することができる
{% import "forms.html" as forms %} {# forms.html に定義している macro へ forms という namespace を通してアクセスできるようにする #}
{{ forms.field('user') }}

現在の namespace への import

  • 現在の namespace 上へ import することもできる
  • 利用するものが限られているのであればこちらの方法を
{% from "forms.html" import field, label as description %}
{# 現在の namespace 上に field macro を import #}
{# 現在の namespace 上に label macro を description という名前で import #}

外部テンプレート読み込み - include

  • include タグを使って外部テンプレートを読み込むことができる
{% include "header.html" %}
  • include は for ループの中でつかうことができたり、文字列だけではなく式を渡すこともできるので、下のように動的なパス指定をすることもできる
{% for item in items %}
{% include item + "item.html" %}
{% endfor %}
  • テンプレートが読み込めなかったときにスルーさせるには ignore missing オプションを追加する
{% include "item.html" ignore missing %}

ブロックの内容をそのまま出力する - raw

  • 中括弧 {{}} などを書いたまま出力させたい場合に使う
{% raw %}
  {{hoge}}
{% endraw %}
{# => {{hoge}} #}

ブロックの内容を filter へ渡す - filter

  • 先述で紹介した filter は式の後に 縦棒(|) を追加して filter へ値を渡していた
  • filter タグを使ってブロックを作ると、中のテキストをまるっと filter に渡すことができる
{% filter replace("いい天気", "豪雨") %}
  今日はいい天気だな。
  明日もいい天気だといいな。
{% endfilter %}
{# => 今日は豪雨だな。 明日も豪雨だといいな。 #}
  • 返ってくるのはあくまで、文字列なので注意
  • タグが含まれていても文字列として返ってくる
  • 中のテキストを外部ファイルで定義していて、全体に何か処理をかけたいときには便利かも
{% filter replace("いい天気", "豪雨") %}
  {% include "./sample.njk" %}
{% endfilter %}

ブロックの内容を macro へ渡す - call

{% macro add(x, y) %}
{{ caller() }}: {{ x + y }}
{% endmacro%}

{% call add(1, 2) -%}
The result is {# このブロックの内容を macro 内で `caller()` を実行して受け取れる #}
{%- endcall %}

{# => The result is: 3 #}

コメント

  • {# -- #}
{# コメントです #}
{{ name }}

ホワイトスペースコントロール

  • デフォルトではスペースは書いた通り表示される
  • 開始ブロックの後ろ、終了タグの前にハイフン( -)を追記することで余白を詰めた状態で出力させることができる
{% for i in [1,2,3,4,5] %}
  {{ i }}
{% endfor %}
{# =>
  1
  2
  3
  4
  5
#}
{% デフォルトではすべての空白をそのまま出力する %}

{% for i in [1,2,3,4,5] -%}
  {{ i }}
{%- endfor %}
{# => 12345 #} 

比較演算子

  • ==
  • ===
  • !=
  • !==
  • >
  • >=
  • <
  • <=
{% if numUsers < 5 %}...{% endif %}
{% if i == 0 %}...{% endif %}

ロジック

  • and
  • or
  • not
  • カッコを使用して式をグループ化する
{% if users and showUsers %}...{% endif %}
{% if i == 0 and not hideFirst %}...{% endif %}
{% if (x < 5 or y < 5) and foo %}...{% endif %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment