はじめに
弊社のとあるシステムでは(超)マイナーテンプレートエンジンを使って開発を行っていたのだが、
- 機能がとにかく少ない
- ドキュメントがほぼ無い
- バグがそこかしこに埋まっている
- 拡張性に乏しい
などの様々な問題を抱えていたため、数か月間かけてTwigへのリプレイスを行った。
特殊過ぎてどこにも需要は無さそうだけど、万一こんな謎作業に従事することになった人がいたら少しでも参考になればと思う。
元のテンプレートエンジンはDjangoのTemplateを参考に作られていたので、本記事では勝手にTELD(Template engine like Django's one)と呼称する。1
実際にやったこと
一度のリリースで完全に移行するのは非常にデメリットが大きかった2ため、
以下のように手順を細かく分けて作業を行った。
- TELDとTwigの相違点調査
- TELDの書式をTwigに寄せる
- 【TELDにあってTwigに無い機能】をTELDから削減
- 【両方にあるけど微妙に挙動が違う機能】についてTELDを修正
- 【TELDにあってTwigに無い機能】をTwig拡張として実装
- TELDとTwigをプロダクト環境で同時に動かして差分チェック
- プロダクト環境をTwigへ完全に切り替え
TELDを修正する場合、万全を期すため以下のようにさらに細かく段階を踏んで作業を行った。
- TELDをTwigと同じ書き方で動くようにする
- TELD本来の書き方を非推奨化(ログを出力させる)してリリース
- Twigの書き方でのみ動くように修正して再度リリース
ゆえにリプレイス完了までに実際は10回以上リリースを行っている。
1. TELDとTwigの相違点調査
DjangoのTemplateとTwigが微妙に似ているおかげで、タグや変数の基本的な書き方については問題なかった。3
ただやはり完全に一緒ということは無く、以下のように細かい部分で違いが多数あった。
対象項目 | TELD | Twig |
---|---|---|
フィルタ引数の書式 | {{ my_date|date:"Y-m-d" }} |
{{ my_date|date("Y-m-d") }} |
不正なフィルタの扱い 例) {{ var||escape }}
|
なぜか動く(無視される) | エラーになる |
変数名として使える文字 |
- や~ などの記号も使える |
- や~ は演算子として解釈される |
フィルタ名称 例) URLエンコード |
urlencode | url_encode |
フィルタ挙動 例) {{ 20230102|date("Y/m/d") }}
|
人間的な解釈(2023年1月2日 ) |
UNIXタイムスタンプとして解釈 |
if系タグの種類 | if, ifequal, ifnotequal等 | ifのみ |
forタグ内のループ変数 | forloop.counter等 | loop.index等 |
コメントアウト方法 |
{# ~ #} と{% comment %}~{% endcomment %}
|
{# ~ #} のみ |
タグ直後の改行文字の扱い | 削除されない | 削除される4 |
includeタグの参照パス | includeタグが記載されたテンプレートからの相対パス | ルートパスを基準とした絶対パス |
2. TELDの書式をTwigに寄せる
フィルタ引数の書式
や変数名として使える文字
等の差異はTwigの拡張で対応できないため、
Twig同様の書式でも動くようにTELDを修正し、テンプレート内の該当箇所は正規表現で一括置換した。
ようするに力業で解決。
3. 【TELDにあってTwigに無い機能】をTELDから削減
【TELDにあってTwigに無い機能】は、テンプレート内での使用頻度によって対応を分けた。
-
テンプレート内で全く使われていないタグ、フィルタ、関数
→TELDから削除し、新規利用を抑止 -
テンプレート内で少しだけ使われているタグ、フィルタ、関数
→もし本当に必要ならTwigと同じ書き方ができるようにTELDを修正。無くても何とかなるなら削除
例)ifforwardmatchとかいう謎タグをif ~ starts with
に置き換えた、等 -
テンプレート内で多数使われているタグ、フィルタ、関数
→Twigの拡張で対応することにしたので後述
例)ifequalタグ、commentタグ等
4. 【両方にあるけど微妙に挙動が違う機能】についてTELDを修正
-
forタグ内のループ変数
/フィルタ名称
の差異
→Twigと同じ書き方ができるようTELDを修正し、テンプレート内の該当箇所を一括置換 -
フィルタ挙動
の差異
→問題となるような使い方をしていたらログを出すように修正。該当箇所が見つかったら適宜修正 -
includeタグの参照パス
の差異
→こちらもTwigの拡張で対応することにしたので後述
5. 【TELDにあってTwigに無い機能】をTwig拡張として実装
-
ifequalタグ、ifnotequalタグ等
→組み込みのIfTokenParserをほぼコピペして専用のTokenParserを実装 -
commentタグ
→本来ならLexerで何とかすべきなのかもしれないが、TokenParserとして無理やり実装 -
相対パス対応のincludeタグ
→専用のTokenParser、Node、Loaderを実装して無理やり実現
6. TELDとTwigをプロダクト環境で同時に動かして差分チェック
TELDの裏側でTwigによるレンダリングも行えるように魔改造を施し、
- レンダリング結果に差分があったらログ出力
- いずれかがエラーになった場合はもう片方のレンダリング結果を表示
とすることで、普通の調査では分からなかった細かい違いをあぶり出した。
TELDのforタグにarray以外を渡すと勝手にarrayでラップしてくれる(文字列とかもループできる)仕様になってたけど、こんなの実際に差分取らなきゃ気付けなるわけないよね。
7. プロダクト環境をTwigへ完全に切り替え
TELDの実行関数をTwigの呼び出しにそっくり挿げ替えて終わり!
今のところ特にエラーもお問い合わせも来ていないのでセーフ!!
おわりに
Twigになってコーディングが格段にやりやすくなったと思うので、今はがんばった自分をほめてあげたい。