Drupal8ではHTML5対応やレスポンシブデザインへのサポート機能、そのほかにもモダンな技術が数多く取り込まれていて、WordPressやMovableTypeなどほかのCMSを利用している方から見ても魅力的なものになりました。
今回はおもにフロントエンド周りのとくにテーマ制作について、Drupal7から8へと乗り換えたときのチェックポイントを紹介したいと思います。
(この記事は Drupal Advent Calendar 2016 のために書いたものです)
#Drupal7のテーマ制作を振り返る
D7では PHPTemplate と呼ばれるテーマエンジンがあり、Drupal4からデフォルトで採用されて移行、扱いやすさからD7以前から長らく標準搭載されて利用されてきました。
PHPTemplateは拡張子がphpになっていて、テンプレートファイル上で純粋なphpコードを利用することができて Drupal API やモジュール上で定義したクラス・関数を呼び出したりと、書式に制限がなく柔軟に扱うことができていました。
ただ、PHPTemplateエンジンは実行速度がとても遅くて、そのことでページ表示が重いことがネックになっていました。
#Drupal8のテーマ制作はどう変わったか
D8ではHTML5がサポートされて、同梱テーマやモジュールがHTML5対応になってことが大きな変化です。
PHPTemplateエンジンに代わって、Twigがデフォルトエンジンになりました。これによってPHPTemplate エンジンは削除されています。
つまり、* .tpl.phpファイルのすべてが使用できなくなりました。
D8のテンプレートファイルは、新しいTwigの拡張子を利用します。
* .html.twig
##Twigとは
D8はフレームワーク「symfony2」を採用したことで、デフォルトテーマエンジンがTwigに置き換えられました。
Twigは独自の記法によりテンプレートを記述することが前提ですが、テンプレートがキャッシュされた状態だと非常に高速にレンダリングされます。
ただ、PHPTemplateエンジンのときのようにテンプレートファイル上にHTMLソースとphpコードを混ぜて書くことができません。
D7のphpコードが自由に書けたときと比べると自由度は落ちますが、シンプルで可読性が高く、Webデザイナーさんでも編集しやすいように設計されています。
これは、モデル、ビュー、コントローラという3つの処理に分割(※)されていることにより、テンプレートの品質向上が保たれます。
※Twigはモデル、ビュー、コントローラ(MVCパターン)のビューの部分に当たります。
そして人によってテーマ設計がブレにくいなどのメリットがあります。
###Drupal7 + PHPTemplateエンジン
例)views-view-unformatted.tpl.php ファイルの中身
<?php if (!empty($title)): ?>
<h3><?php print $title; ?></h3>
<?php endif; ?>
<?php foreach ($rows as $delta => $row): ?>
<div class="<?php echo $classes_array[$delta]; ?>">
<?php print $row; ?>
</div>
<?php endforeach; ?>
###Drupal8 + Twigエンジン
例)views-view-unformatted.html.twig ファイルの中身
{% if title %}
<h3>{{ title }}</h3>
{% endif %}
{% for row in rows %}
{% set parity = cycle(['odd', 'even'], loop.index0) %}
{%
set row_classes = [
default_row_class ? 'views-row ' ~ parity,
]
%}
<div{{ row.attributes.addClass(row_classes) }}>
{{ row.content }}
</div>
{% endfor %}
以上、構文の違いが見て取れると思います。
Twig の基本的な構文です。
- {{ 〜〜 }} : 変数や計算式の結果を出力します。
- {% 〜〜 %} :if文やfor文などのロジックを制御するタグです。
- {# 〜〜 #} : コメントを残します。HTMLには出力されません。
テンプレートファイルをincludeすることも用可能です。
{% include directory ~ '/templates/inc/header.html.twig' %}
参考)https://www.drupal.org/docs/8/theming-drupal-8/including-part-template
なお、includeは相対パスを指定できないのでご注意ください。
Twig フィルターを利用すれば、変数に対して簡単な処理をかけることもできます。
{{ variable|filter }}.
フィルターにはphp関数ではおなじみの striptags や nl2br、 trim などが用意されています。
その他、フィルターがいろいろ用意されていますので公式ドキュメントでご確認ください。
Twig Filters - Documentation
http://twig.sensiolabs.org/doc/filters/index.html
また、変数をデバッグするときは Twigが提供するデバッグ関数 dump() が利用できます。
{{ dump(var) }}
引数に何も指定しなければすべての変数が確認できます。
{{ dump() }}
DrupalのDevelサブモジュール「Kint」を有効化している場合は、D7のdpm関数と同じようなアコーディオン形式で変数を表示させることができます。
{{ kint(var) }}
引数に何も指定しなければすべての変数が確認できます。
{{ kint() }}
kintの描画結果は表示に時間がかかるので、アコーディオンが展開可能な状態になるまでに数十秒ほど待たされることがあります。
以上、デバッグ方法の紹介でした。
##Drupal8でテーマ制作をしやすくするための環境設定
###Twig debugging options
Drupalテーマは読み込まれているテンプレートファイル名を表示することができます。
さらにどのようなテンプレートファイル名でファイルを設置すればいいのかを教えてくれる、いわゆるサジェスチョンという機能ですね。
D7のときは sites/default/settings.php ファイル上で以下1行を追加していました。
$conf['theme_debug'] = 1;
一方、D8からは sites/default/services.yml ファイル上の twig.config セクションにて管理されるようになりました。
(もし services.yml がまだ設置されていない場合は default.services.yml をコピーして services.yml へリネームしてください)
以下の通りに値を書き換えれば有効化されます。
debug: true (デフォルト:false)
これにより、HTMLソース上にテンプレートファイル名のサジェスチョンが表示されるようになります。
×印の行が今現在ロードされているテンプレートファイルです。
###Twig auto-reload
Twigはパフォーマンス向上のためにコンパイルされた上でキャッシュされて、次の画面表示からはキャッシュが読み込まれます。
テーマ制作中はキャッシュの内容ではなく編集内容をすぐに確認できるようにしたいので auto reload を有効化します。
auto_reload: true (デフォルト:null)
trueにしておけば、テンプレートが変更されるたびに再コンパイルされます。
ここをtrueにしておかないと、Drupalキャッシュクリアするまでテンプレートの編集内容が確認できなくなってしまうので制作効率が下がってしまいます。
###Twig cache
cache: false (デフォルト:true)
Twigテンプレートのコンパイル結果をディスクにキャッシュ保存することで高速化を行っています。
falseを指定すればディスクへのキャッシュ保存を無効化できますが、通常は auto_reload: true だけでも足りるので基本的にはデフォルトのtrueのままにしておきましょう。
###Drupal render cache
D8は高速化のためにデフォルトでブロックとエンティティをキャッシュするので、それらの変更内容はキャッシュクリアするまでページ反映されません。
キャッシュを無効化するには sites/default/settings.php の次の2行を探してコメントアウトします。
if (file_exists(__DIR__ . '/settings.local.php')) {
include __DIR__ . '/settings.local.php';
ファイルをコピーします。
コピー元)/sites/example.settings.local.php
コピー先)/sites/default/settings.local.php
あとはDrupalキャッシュクリアを行えば設定が反映されます。
なお、これらの設定はパフォーマンスのために本番サイト上ではデフォルトの状態に戻すことをオススメします。
参考)https://www.drupal.org/docs/8/theming/twig/debugging-compiled-twig-templates
##Twigテンプレート上で出力する変数をあらかじめ加工するには
Twigフィルターを利用すれば、変数に対してちょっとした加工などの操作は行えますがやはり限界があります。テンプレート上にPHPコードが書けたらと思うことがあると思います。
ですが、Twigテンプレート上ではPHPコードは使えませんので、あらかじめフックを利用して変数を操作する必要があります。
フックを記述する場所はテーマフォルダ配下の *.theme になります。
拡張子がphpではありませんが問題なくPHPコードが書けますのでご安心ください。
エンティティオブジェクトやフィールド値の操作を行う上で、よく利用するフックは例えば次のものが挙げられます。
- templete_preprocess_breadcrumb(&$vars)
- templete_preprocess_block(&$vars)
- templete_preprocess_node(&$vars)
- templete_preprocess_field(&$vars)
Viewsの出力内容を操作するには以下のフックなども利用可能です。
- templete_preprocess_views_view_unformatted(&$vars)
- templete_preprocess_views_view_field(&$vars)
フィールド値を加工する場合、Nodeなどのエンティティが対象の場合、templete_preprocess_fieldフックを利用しますが、Viewsが出力するフィールド値を加工するには templete_preprocess_views_view_fieldフックを利用します。
なお、フックの先頭の templete の部分はテーマ名に置き換えてご利用ください。
小ネタとして、例えば templete_preprocess_views_view_fieldフックを特定のViewsでのみ利用する場合は以下の命名をすると限定的に利用可能です。
templete_views_view_field__[Views name]__[Display name](&$vars)
利用例:
function example_theme_views_view_field__article_list__title(&$vars) {
preg_match("|<a href=\"(.*?)\".*?>(.*?)</a>|mis", $vars['field']->original_value, $matches);
$url = $matches[1];
$name = $matches[2];
return '' . $name . '<a href="' . $url . '" class="category-link"><span>記事一覧へ</span></a>';
}
すごく適当なコードで恐縮ですが、こんな感じでTwigテンプレートに変数が渡される前に値を加工することができます。
##CSSとJavascriptを動的にロードする方法
###Drupal7で動的にロードする書き方
D7では、CSSとJavascriptを動的にロードするときは drupal_add_css(), drupal_add_js() が利用できました。
####drupal_add_css関数
$options = array('group' => CSS_THEME, //CSS_SYSTEM or CSS_DEFAULT or CSS_THEME
'type' => 'file', // file or inline or external
'weight' => 0);
drupal_add_css(drupal_get_path('theme', 'any_theme_name') . '/css/style.2nd.css', $css_option);
####drupal_add_js関数
$options = array('group' => JS_THEME, //JS_LIBRARY or JS_DEFAULT or JS_THEME
'type' => 'file', // file or inline or external
'scope' => 'header', // header or footer
'weight' => 0);
drupal_add_js(drupal_get_path('theme', 'any_theme_name') . '/js/custom.js', $options);
これらコードをモジュール上のプリプロセス系フックに書いていましたね。
###D8で動的にロードする書き方
一方、D8ではあらかじめライブラリを定義しておきます。
ライブラリには、CSSとJSファイルの両方を含むことができて、ほかのライブラリとの依存関係も明記することができます。
以下、テーマ名を example_theme と仮定して説明していきます。
ライブラリを定義するには、テーマフォルダ配下に example_theme.libraries.yml ファイルを設置して、次のように記述します。
気をつける点は、ライブラリを記述するときのインデントが重要だということです。
ライブラリ名は一番左端にくるよう、タブやスペースが入らないようにしてください。
そのほか、css、jsセクションのインデントもしっかりしておく必要があります。
CSSを定義している箇所で、theme: とありますね。
css:
theme:
style/css/jquery.bxslider.css: {}
theme: 以外にもほかに用意されたグループを指定すればheadタグ上での出力順序を調整できます。
- base key assigns a weight of CSS_BASE = -200
- layout key assigns a weight of CSS_LAYOUT = -100
- component key assigns a weight of CSS_COMPONENT = 0;
- state key assigns a weight of CSS_STATE = 100
- theme key assigns a weight of CSS_THEME = 200
あと、D7のときはJSファイルをheadタグ内に出力されていましたが、D8からはHTMLソースのフッター部分に出力されるようになりました。
もし、D7のときのようにheadタグ内にJSファイルを出力させたい場合は明示的に記述する必要があります。
js-header:
header: true
js:
js/jquery.bxslider.js: {}
js/jquery.easing.1.3.js: {}
js/jquery.fitvids.js: {}
js-footer:
js:
footer.js: {}
参考)https://www.drupal.org/node/2274843
そして、特定ページで動的にライブラリをロードするにはテーマフォルダ配下の example_theme.themeファイル上で次のように実装します。
上のコードを読むと分かると思いますが、トップページでのみjquery_bxsliderライブラリをロードされるようになります。
もし、すべてのページで常にライブラリをロードしたい場合は、example_theme.info.yml ファイル上の libraries セクションに追加します。
libraries セクションに追加されたライブラリはすべてのページでロードされます。
ただ、パフォーマンス向上のためにはやはり特定ページでのみロードされるように実装するべきかと思います。
#Drupal8で人気のテーマ「OMEGA5」を導入する
D8の人気テーマとして Bootstrap や Adminimal、 AdaptiveTheme が人気です。
Adminimal、 AdaptiveTheme は導入した初期状態でそれなりのデザインなのでお手頃感がありますが、やはり、業務で利用するならなるべくベースがシンプルで汎用性の高い OMEGA5 テーマを導入したいです。
(D7ではカスタマイズし易いテーマとして OMEGA と双璧を成す Zen テーマがありましたが、D8版はまだstable版がリリースされていないようなので検討からは外しました)
##OMEGA5をインストールしてサブテーマを設置する
OMEGA5テーマはここからダウンロードできます。
https://www.drupal.org/project/omega
ダウンロードしたテーマは themes フォルダ配下にコピーします。
そしてDrupal管理画面のテーマ一覧 /admin/appearance にアクセスして、
OMEGA5テーマの install リンクをクリックすればテーマを有効化できます。
D7のときはOMEGAサブテーマを作るときはdrushコマンドから行う必要がありましたが、管理画面上の Create Subtheme リンクをクリックするだけで作成できてしまうので便利になりました。
基本的にOMEGA5テーマを利用するときはサブテーマを作成して設定を進めていきます。
##OMEGA5サブテーマの設定を行う
まず、Create Subtheme リンクをクリックします。
Theme name を入力します。
このとき、既存モジュールと同じ名前をテーマに付けると、テーマが読み込まれなくなるようなのでご注意ください。
プロジェクト名をそのままテーマ名に付けて、モジュールのとりあえずプロジェクト名を付けたものを設置するなんてことはよくやりがちです。D7ではそれでも支障なく動作していましたがD8ではダメみたいです。
Description と Version は任意の内容を入力しておきます。
SUB-THEME OPTIONS は Clone を選択します。
SCSSを利用する場合は、Enable SCSS Customizations にチェックを入れます。
最後に Export Subtheme ボタンをクリックしましょう。
テーマ一覧の Uninstalled themes セクションに作成したサブテーマが追加されます。
Install and set as default リンクをクリックして有効化します。
有効化したら Settings リンクをクリックして設定画面を開きます。
Default Optionsタブを見てみましょう。
OMEGA5では SCSSファイルを管理画面からコンパイル可能になりました。
Compile SCSS Directly にチェックを入れた状態で Save & Update Styles ボタンをクリックするとSCSSコンパイルが実行されます。
これは scssphp というコンパイラが組み込まれていて、わざわざSCSSコンパイル環境を構築しなくてもいいのでとくにWebデザイナーさんには優しい機能だと思います。
CSS/JS Libraries タブでは example_theme.libraries.yml ファイルに記述しておいたライブラリが表示されます。
デフォルトでチェックが入った状態になっていますが、このままだとライブラリが常時ロードされてしまうようです。
templete_preprocess_pageフックで動的にロードしたい場合はチェックを外しましょう。
OMEGA5以外のほかのテーマだと *.info.yml の libraries:セクションに書かない限りはライブラリが常時ロードされないと思うので、OMEGA5固有の気をつけるポイントですね。
Layout Builder タブでは example_theme.breakpoints.yml に記述されているレスポンシブにおけるブレークポイントの数値を編集できるようです。
ただし、新たなブレークポイントを追加するときは YAMLファイル上で手動で追記する必要があるようです。
##OMEGA5サブテーマの公開画面
参考までにOMEGA5の表画面はこのような感じです。
デモモードを有効化している場合はリージョンが表示されるところはOMEGAテーマおなじみの機能です。
OMEGA5からは画面の横幅を表示させる機能が追加されて、レスポンシブデザイン対応する上で心強い機能です。
管理画面の Debugging & Development タブで有効化しておけば機能を利用できます。
以上、OMEGA5テーマを利用する上での最低限のポイントの紹介でした。
#最後に
D8のテーマ周りを簡単に紹介しましたが、如何でしたでしょうか。
この辺の内容ならD7を利用していた方なら比較的スムーズに移行できるかと思います。
これらに加えてPanelsモジュール や Display Suiteモジュール を導入すればさらに高度にテーマ開発が可能です。
ブロックもエンティティ化されたことでフィールド追加できるようになり、D7のときのイマイチ感が払拭されました。一度定義したブロックを何度も使い回しできるようになのは便利だなと感じました。
あとは、もっと複雑なシステムを構築しようとすればモジュール開発が必要ですが、D8からはオブジェクト指向になっていたり、ネームスペースってなんだ!?みないな話になると思いますが、その辺はまた次の機会に書きたいと思います。