Drupal 8モジュールへのスタイルシート(CSS)とJavaScript(JS)の追加
このドキュメントはモジュール向けです。テーマについては、Drupal 8テーマへのスタイルシート(CSS)とJavaScript(JS)の追加セクションを参照してください。
Drupal 8では、スタイルシート(CSS)とJavaScript(JS)は、モジュール(コード)とテーマの両方で同じシステム(アセットライブラリ)を通じて読み込まれます。アセットライブラリには、1つ以上のCSSアセット、1つ以上のJSアセット、および1つ以上のJS設定を含めることができます。
Drupalは高レベルの原則を使用します:アセット(CSSまたはJS)は、Drupalに読み込む必要があると伝えた場合にのみ読み込まれます。Drupalはすべてのページですべてのアセット(CSS/JS)を読み込みません。これはフロントエンドのパフォーマンスに悪影響を与えるためです。
Drupal 7との違い
開発者向けに、Drupal 7との重要な違いが2つあります:
1. 特定のページに必要なJavaScriptのみがそのページに追加されます。特に、デフォルトでは、匿名ユーザーが見るほとんどのページでDrupalはJavaScriptを必要としません。つまり、jQueryはすべてのページに自動的に読み込まれなくなりました。
したがって、テーマがjQueryまたは他のJavaScript(アセットライブラリにも定義されている)を必要とする場合は、必要なアセットライブラリへの依存関係を宣言して、Drupalにそのことを伝える必要があります。
2. JavascriptオブジェクトのDrupal.settingsは、drupalSettingsに置き換えられました。
プロセス
アセット(CSS / JS)の読み込みの主要な手順:
1. CSSまたはJSをファイルに保存します。
2. CSSファイルとJSファイルを含めることができる「ライブラリ」を定義します。
3. フック内のレンダー配列にライブラリを「添付」します。
ただし、テーマの場合はステップ3の代替があります:テーマはすべてのページで任意の数のアセットライブラリを読み込むことができます。
ライブラリの定義
1つ以上のライブラリ(アセット)を定義するには、モジュールのフォルダーのルート(.info.ymlファイルの隣)に*.libraries.ymlファイルを追加します。(モジュール名がfluffinessの場合、ファイル名はfluffiness.libraries.ymlでなければなりません)。ファイル内の各「ライブラリ」は、CSSファイルとJSファイル(アセット)を詳述するエントリです。例:
フランス人への特別な注意:.libraries.ymlのスペルを正しく書くように注意してください!.librairies.ymlではありません!!(フランス語ではlibrairieは書店を意味します...)そうしないと、存在しないライブラリを添付してもDrupalがエラーを出さないため、何が悪いのかを長時間探すことになるかもしれません...;)
cuddly-slider:
version: 1.x
css:
layout:
css/cuddly-slider-layout.css: {}
theme:
css/cuddly-slider-theme.css: {}
js:
js/cuddly-slider.js: {}
cssに'layout'と'theme'のキーがあることに気づくでしょうが、jsにはありません。これは、CSSファイルが属するスタイルの種類を示しています。
CSSには5つの異なるスタイルレベルでウェイトを設定できます:
- base:CSSリセット/ノーマライズとHTML要素のスタイリング。キーはCSS_BASE = -200のウェイトを割り当てます
- layout:グリッドシステムを含むWebページのマクロな順序。キーはCSS_LAYOUT = -100のウェイトを割り当てます
- component:個別の再利用可能なUI要素。キーはCSS_COMPONENT = 0のウェイトを割り当てます
- state:クライアント側のコンポーネントの変更に関連するスタイル。キーはCSS_STATE = 100のウェイトを割り当てます
- theme:コンポーネントの純粋に視覚的なスタイル(「外観」)。キーはCSS_THEME = 200のウェイトを割り当てます
これはSMACSS標準によって定義されています。したがって、themeを指定した場合、CSSファイルに純粋な外観であるテーマ関連のスタイルが含まれていることを意味します。詳細はこちら。他のキーは使用できません。厳密な警告が発生するためです。
この例では、実際のJavaScriptのcuddly-slider.jsがモジュールのjsサブフォルダーにあることを前提としています。JSを外部URLから取得したり、CSSファイルを含めたりするなど、他のオプションもあります。詳細はCDN / 外部ライブラリを参照してください。
ただし、Drupal 8はデフォルトですべてのページにjQueryを読み込まなくなったことを忘れないでください。Drupal 8は必要なものだけを読み込みます。したがって、モジュールのcuddly-sliderライブラリがjQueryを含むライブラリへの依存関係を宣言する必要があります。jQueryを提供するのはモジュールやテーマではなく、Drupalコアです:core/jqueryが宣言したい依存関係です。(これは、拡張子名、その後にスラッシュ、その後にライブラリ名です。したがって、他のライブラリがcuddly-sliderライブラリに依存したい場合、モジュール名がfluffinessであるため、fluffiness/cuddly-sliderへの依存関係を宣言する必要があります。)
したがって、js/cuddly-slider.jsでjQueryを使用できるようにするには、上記を次のように更新します:
cuddly-slider:
version: 1.x
css:
theme:
css/cuddly-slider.css: {}
js:
js/cuddly-slider.js: {}
dependencies:
- core/jquery
予想どおり、CSSおよびJSアセットがリストされる順序が、それらの読み込み順序でもあります。
デフォルトでは、DrupalはJSアセットをページの下部に添付して、DOMコンテンツの読み込みをブロックする、jQueryコードから準備ができていないDOM要素にアクセスするなどの一般的な問題を回避します。何らかの理由でJSアセットを<head>セクションに含める必要がある場合は、headerオプションを次のように使用できます:
cuddly-slider:
version: 1.x
header: true
js:
js/cuddly-slider.js: {}
したがって、js/cuddly-slider.jsはページの上部に添付されます。
ライブラリをページに添付する
読み込む必要があるアセットに応じて、対応するアセットライブラリをさまざまな方法で添付できます。結局のところ、一部のアセットライブラリはすべてのページで必要であり、他のライブラリは非常にまれにしか必要とされず、また他のライブラリはほとんどのページで必要ですが、すべてではありません。
しかし最も重要なのは、ライブラリを添付するかどうかを、どのページにいるか(つまり、どのURLまたはルートか)に基づいて決定するのではなく、ページに表示されるものに基づいて決定することです:ページに'#type' => 'table'、'#type' => 'dropbutton'、'#type' => 'foobar'が含まれている場合、これらの各#typeに関連するライブラリのみを読み込みます。
ただし、「#type」だけに制限されません:特定の「#type」インスタンスにのみ特定のアセットライブラリを読み込みたい場合もあります。その場合は、そのインスタンスのレンダー配列に添付するだけです。
もちろん、ページの「もの」に関係なく、特定のアセットをすべてのページで実際に読み込む正当な理由(たとえば、ページ読み込みを追跡する分析JavaScriptなど)があることは非常にまれです。
ここでのサブセクションは、これらのことを行う方法の例を示しています。
特定の「#type」への添付(そのすべてのインスタンスに対して)
特定の既存の「#type」のすべてのインスタンスにライブラリを添付するには、hook_element_info_alter()を使用します:
function yourmodule_element_info_alter(array &$types) {
if (isset($types['table'])) {
$types['table']['#attached']['library'][] = 'your_module/library_name';
}
}
次にキャッシュをクリアして、追加した新しいフック実装をDrupalに認識させます。
レンダー配列への添付
レンダー配列(および特定の'#type'の特定のインスタンス)にライブラリを添付するには、そのレンダー配列にアクセスできる必要があります。レンダー配列を定義しているかもしれません。フックで変更しているかもしれません。いずれにしても、次のようになります:
$build['the_element_that_needs_the_asset_library']['#attached']['library'][] = 'your_module/library_name';
常に数値キーを使用してください!
Drupalを助けたいと思って、非数値キーを使用してライブラリの重複を避けたくなるかもしれません:
$build['the_element_that_needs_the_asset_library']['#attached']['library']['your_module/library_name'] = 'your_module/library_name';
これはしないでください。Drupalの配列のマージ方法により、無効なネストされた配列が発生します。次のエラー(googlefood)に注意してください:
Warning: explode() expects parameter 2 to be string, array given in Drupal\Core\Asset\LibraryDependencyResolver->doGetDependencies()
Notice: Array to string conversion in system_js_settings_alter()
Notice: Array to string conversion in Drupal\Core\Asset\AttachedAssets->setLibraries()
ブロックプラグインのレンダー配列への添付
ライブラリをレンダー配列に添付する別の例を示します。モジュールでブロックプラグインを作成する場合、BlockBaseクラスを拡張するクラスのbuild()関数で、レンダー配列にライブラリを添付できます(Drupal 8ベータ6以降)。
return [
'#theme' => 'your_module_theme_id',
'#someVariable' => $some_variable,
'#attached' => [
'library' => [
'your_module/library_name',
],
],
];
フォームへのライブラリの添付
フォームは単なるレンダー配列であるため、ライブラリの添付はまったく同じように機能します:
/**
* Implements hook_form_alter().
*/
function yourmodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
/* @var Drupal\Core\Entity\FieldableEntityInterface $entity */
$formObject = $form_state->getFormObject();
if ($formObject instanceof \Drupal\Core\Entity\EntityFormInterface) {
$entity = $formObject->getEntity();
if (
$entity->getEntityTypeId() === 'node'
&& in_array($entity->bundle(), ['organisation', 'location', 'event', 'article'])
) {
$form['#attached']['library'][] = 'yourmodule/yourlibrary';
}
}
}
すべての(または一部の)ページへのライブラリの添付
場合によっては、アセットライブラリがページの特定の部分に関連付けられておらず、ページ全体に関連付けられています。この場合のためにhook_page_attachments()が存在します。代表的な例は「Contextual Links」モジュールにあります:
// From core/modules/contextual/contextual.module.
function contextual_page_attachments(array &$page) {
if (!\Drupal::currentUser()->hasPermission('access contextual links')) {
return;
}
$page['#attached']['library'][] = 'contextual/drupal.contextual-links';
}
プリプロセス関数でのライブラリの添付
プリプロセス関数では、特別なキー'#attached'を使用してライブラリを添付できます:
function yourmodule_preprocess_maintenance_page(&$variables) {
$variables['#attached']['library'][] = 'your_module/library_name';
}
Twigテンプレートでのライブラリの添付
Twigテンプレートでは、attach_library() Twig関数を使用してライブラリを添付することもできます。任意の*.html.twigで:
{{ attach_library('your_module/library_name') }}
<div>Some markup {{ message }}</div>
トークン置換中のライブラリの添付
独自のトークンがフィルタリングされたテキストに存在する場合、hook_tokens()の置換中にBubbleableMetadataオブジェクトにライブラリを追加して、ライブラリを添付することもできます:
/**
* Implements hook_tokens().
*/
function your_module_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {
$replacements = [];
if ($type == 'your_module') {
foreach ($tokens as $name => $original) {
switch ($name) {
case 'your-token':
$your_render_array = your_module_build_your_renderable_thing();
$replacements[$original] = \Drupal::service('renderer')->render($your_render_array);
// LOOK HERE! WE CAN ADD LIBRARIES TOO!
$bubbleable_metadata->addAttachments(['library' => ['your_module/library_name'] ]);
break;
}
}
}
return $replacements;
}
この例では、置換中にライブラリを添付する方法のみを示していることに注意してください。カスタムトークンを完全に実装するには、hook_token_info()も実装する必要があります。
フィルタープラグインでのライブラリの添付
モジュールがテキストフィルターを提供する場合、FilterProcessResultクラスのsetAttachments()メソッドまたはaddAttachments()メソッドを使用できます。たとえば、filter_captionフィルターは次のように行います:
if (...) { ...
$result->setProcessedText(Html::serialize($dom))
->addAttachments([
'library' => [
'filter/caption',
],
]);
}
return $result;
カスタムJavaScriptの添付
場合によっては、PHPで計算された情報に依存するJavaScriptをページに追加したいことがあります。これは、Drupal 7のDrupal.settingsの後継であるdrupalSettingsを使用して行うことができます。これはPHPスクリプトで定義された設定の配列で、JavaScriptで設定オブジェクトとしてアクセスできます。
ライブラリでdrupalSettingsを使用するには、まずライブラリ定義でcore / drupalSettingsへの依存関係を宣言する必要があります。
したがって、前の例のライブラリ定義は次のようになります:
cuddly-slider:
version: 1.x
js:
js/cuddly-slider.js: {}
dependencies:
- core/jquery
- core/drupalSettings
PHPファイルでは、ライブラリと一緒に必要なdrupalSettingsを渡すことができます。慣例として、設定のキーにはlowerCamelCaseのモジュール名を使用し、lowerCamelCaseのライブラリ名を追加のキーとして追加します。
例のJavaScriptにPHPから計算された値'foo'と'baz'を渡したい場合は、次のようにします:
$computed_settings = [
'foo' => 'bar',
'baz' => 'qux',
];
$build['#attached']['library'][] = 'your_module/library_name';
$build['#attached']['drupalSettings']['fluffiness']['cuddlySlider'] = $computed_settings;
すると、cuddly-slider.jsはdrupalSettings.fluffiness.cuddlySlider.fooとdrupalSettings.fluffiness.cuddlySlider.bazにアクセスでき、それぞれ「bar」と「qux」の値を持ちます。
レンダー配列はキャッシュされます。計算された値の性質とdrupalSettingsを添付するコンポーネントに応じて、キャッシュ可能性メタデータを適宜変更する必要がある場合があります。
スクリプト要素への属性の追加
スクリプトタグに属性を追加する場合は、スクリプトのURLの後、JSONにattributesキーを追加する必要があります。attributesキーの後のオブジェクト内に、スクリプトに表示したい属性名を新しいキーとして追加します。このキーの値が属性の値になります。この値がtrueに設定されている場合、属性は要素の値なしで単独で表示されます。
例:
https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap: {type: external, attributes: { defer: true, async: true, data-test: map-link } }
これにより、次のマークアップが生成されます:
<script src="https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap" async defer data-test="map-link"></script>
集約の無効化
デフォルトでは、可能な場合に複数のローカルファイルが結合されます。ファイルに対してこれを無効にするには、'preprocess'フラグをfalseに設定します。
cuddly-slider:
version: 1.x
js:
js/cuddly-slider.js: {preprocess: false}
dependencies:
- core/jquery
- core/drupalSettings
CDN / 外部ライブラリ
ページの読み込み速度を向上させるために、CDN(コンテンツ配信ネットワーク)にあるJavaScriptを使用したい場合があります。これは、ライブラリを「外部」として宣言することで行えます。定義に外部ライブラリに関する情報を含めることも役立ちます。
angular.angularjs:
remote: https://github.com/angular/angular.js
version: 1.4.4
license:
name: MIT
url: https://github.com/angular/angular.js/blob/master/LICENSE
gpl-compatible: true
js:
https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }
インラインJavaScript
インラインJavaScriptは強く推奨されません。インラインで使用したいJSはファイルに配置することをお勧めします。クライアント側でJavaScriptをキャッシュできるようになるためです。また、JavaScriptコードのレビューと記述も可能になります。インラインJSは多くのサイトのコンテンツセキュリティポリシーにも抵触し、それらのサイトでモジュールを使用できなくします。
マークアップを生成するインラインJavaScript
これはお勧めしません。代わりにJavaScriptをファイルに配置してください。例としては、広告、ソーシャル共有ボタン、ソーシャルフィードウィジェットがあります。これらはインラインJavaScriptを使用します。しかし、これらはサイトのコンテンツの装飾やインタラクティブ性を目的としておらず、JavaScriptを介して外部コンテンツを取得することを目的としているため、単なる特別な種類のコンテンツ/マークアップです。
これらはカスタムブロックまたはTwigテンプレートに直接配置する必要があります。
例:
<script type="text/javascript"><!--
ad_client_id = "some identifier"
ad_width = 160;
ad_height = 90;
//--></script>
<script type="text/javascript" src="http://adserver.com/ad.js"></script>
<a class="twitter-timeline" href="https://twitter.com/wimleers" data-widget-id="307116909013368833">Tweets by @wimleers</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
ページ全体に影響を与えるインラインJavaScript
インラインJavaScriptは強く推奨されません。ページ全体に影響を与えるインラインJavaScriptの例には、アナリティクス(Google Analyticsなど)やホスト型フォントサービスがあります。ページ全体に影響を与えるインラインJavaScriptは、フロントエンド/スタイリングまたはロジックの2つのカテゴリのいずれかに分類できます。これらのケースのほとんどは、ファイル内の固定JavaScriptと追加設定で満たせます。
フロントエンド/スタイルの場合(ホスト型フォントサービスなど)はテーマに属するため、その場合は「Drupal 8テーマへのスタイルシート(CSS)とJavaScript(JS)の追加」を参照してください。
もう1つの場合、JSはモジュールに属します。適切なフック(おそらくhook_page_attachments())で、#attachedプロパティの'html_head'キーを使用して、ネストされたHTML <HEAD>データを定義します:
function fluffiness_page_attachments(array &$attachments) {
$attachments['#attached']['html_head'][] = [
// The data.
[
'#type' => 'html_tag',
// The HTML tag to add, in this case a tag.
'#tag' => 'script',
// The value of the HTML tag, here we want to end up with
// alert("Hello world!");.
'#value' => 'alert("Hello world!");',
// Set attributes like src to load a file.
'#attributes' => array('src' => ''),
],
// A key, to make it possible to recognize this HTML element when altering.
'hello-world',
];
}
動的に生成されるCSSとJS
非常にまれで複雑なケースでは、CSSとJSを動的に生成する必要があるかもしれません。「動的」には2つのカテゴリがあります:
1. 動的に構築されるが、複数のリクエストで使用される
2. リクエストごとに動的に構築される
動的CSS / JSが複数のリクエストで使用される場合、hook_library_info_alter()を使用して、動的に/自動的に生成されたCSS / JSを含めるようにライブラリを変更できます。Drupal 8コアの例はcolor_library_info_alter()です。hook_library_info_build()またはhook_library_info_alter()を使用してライブラリを追加するだけでは、ライブラリがページに自動的に表示されないことを理解してください。上記の方法のいずれかを使用して、アタッチメントとして定義する必要があります(ページまたは特定の要素に対して)。
動的CSS / JSがリクエストごとに作成される場合、これは本当に高度な領域に入ります。これは難しいことですが、それには十分な理由があります:リクエストごとの動的アセットは、各リクエストで構築する必要があり、Drupalを遅くします。Drupalを遅くすることを困難にしたいため、これを行うための優れたAPIは提供していません。そうしてほしくないからです。
ただし、これは可能です。動的JSの場合:代わりにカスタムJavaScriptの使用を検討してください。ほぼ常に非常に優れた選択肢です。ロジックはファイルに保存され(レビュー、印刷、クライアント側でのキャッシュが可能)、各リクエストではそのファイル内のロジックを設定するパラメーターのみを作成する必要があります。実際、これは動的CSSにも使用できます:動的CSSをdrupalSettingsとして添付し、JSファイルにページへの追加を任せます。
drupalSettingsとJavaScriptファイルの使用がオプションでない場合、まだ1つのオプションが残っています:hook_page_attachments()を使用し、$page['#attached']['html_head']に新しい値を追加します。これは上記の「ページ全体に影響を与えるインラインJavaScript」セクションで既に示したように、<script>タグまたは<style>タグを含みます。
動的ライブラリ定義のためのhook_library_info_build()が追加されました
いくつかの高度なユースケース(手動で読み込む必要があるサードパーティライブラリを検出し、それらをDrupalアセットライブラリとして提示するなど、たとえばLibraries APIモジュール)では、PHPコードを使用して追加のロジックでライブラリを登録できるようにしたい場合があります。これがhook_library_info_build()が追加された理由です。
「動的」は「実行時」(つまりリクエストごと)を意味しないことに注意してください。それはパフォーマンスにひどい影響を与えるでしょう。動的に追加されたライブラリは、YMLファイルで定義されたライブラリと同様にキャッシュされます。つまり、上記の方法のいずれかを使用して、ライブラリをページまたは要素に添付する必要があります。ライブラリの添付を制御するためにロジックを使用できるため「動的」なのです。
Drupal 7との違い
- Drupal 7では、ライブラリはhook_library_info()で定義する必要がありました。これは*.libraries.ymlファイルに置き換えられました。
- Drupal 8では、drupal_add_css()、drupal_add_js()、drupal_add_library()が削除され、#attachedが採用されました
- Drupal 8では、必要なライブラリがcore/drupalSettingsライブラリに依存している場合にのみ、設定がページに追加されます。