Drupal のテーマにスタイルシート(CSS)とJavaScript(JS)を追加する

Drupal テーマ作成ガイド
この記事の目次

最終更新日 2020年12月1日

これはテーマに関連するドキュメンテーションです。モジュールについては、Adding stylesheets (CSS) and JavaScript (JS) to a Drupal module を参照してください

Drupal では、モジュールとテーマで、スタイルシート(CSS)とJavaScript(JS)がアセットライブラリーと同じシステムで読み込まれます。

誤解のないように繰り返しますが、この説明はテーマにのみ関係するもので、モジュールには適用されません。モジュールについては、Adding stylesheets (CSS) and JavaScript (JS) to a Drupal module を参照してください。

Drupal のテーマは、以下に記すハイレベルのルールに基づいて動作します:
アセット(CSS または JS)は Drupal に明示的な読み込みの指示があった場合にのみ読み込む。すべてのアセットをすべてのページで読み込むことは、フロントエンドのパフォーマンスの低下を招くため行わない。

Drupal 7との相違点

Drupal 7 と比較した場合、テーマには大きな違いが6つあります。

  1. THEME.infoファイルが、THEME.info.ymlファイルに変更になりました。同じデータを設定可能です。
  2. THEME.infoファイルで使用されていた stylesheets(CSSを追加する)プロパティ は削除され、*.libraries.yml ファイルで設定するようになりました('*' はテーマまたはモジュール名に相当)。
  3. THEME.infoファイルで使用されていた scripts(JSを追加する)プロパティ は削除され、*.libraries.yml ファイルで設定するようになりました('*' はテーマまたはモジュール名に相当)。
  4. ページに必要なCSS、JSのみが読み込まれます。たとえば、JQuery は *.libraries.yml ファイルにで設定されていない場合、 自動的にロードされません。すべてのページに jQuery や他のアセットを読み込むことが必要な場合は、それを *.libraries.yml で設定します。
  5. Drupal 7では、 hook_library_info() を使ってライブラリを定義しなければなりませんでした。Drupal 8では、*.libraries.yml ファイルで設定します
  6. Drupal 8 では、drupal_add_css()、drupal_add_js()、drupal_add_library() は廃止され、代わりに #attached が利用されるようになりました。

読み込み方法

CSSまたはJSアセットを読み込むには:

  1. 命名規則やファイル構造ルールにしたがって、CSS または JS を保存します。
  2. これらの CSS / JS ファイルをテーマで使用するライブラリとして定義します。
  3. すべてのページに設定する方法特定の Twig テンプレートにライブラリを設定する方法、もしくは、前処理の過程で特定のページをターゲットとしてレンダリング要素経由で設定する方法があります。

ライブラリ定義

すべてのライブラリアセットをテーマフォルダの *.libraries.yml ファイルに定義します。テーマの名前が「fluffiness」の場合、ファイル名は fluffiness.libraries.yml になります。定義ファイル内の各「ライブラリ」には、CSSファイルとJSファイル(アセット)を詳述する内容を記載します。

例)

# fluffiness.libraries.yml
cuddly-slider:
  version: 1.x
  css:
    theme:
      css/cuddly-slider.css: {}
  js:
    js/cuddly-slider.js: {}

この例では、JavaScript:cuddly-slider.js は、テーマフォルダ内の js フォルダに、CSS:cuddly-slider.css は css フォルダに配置されます。

注意:
この例では、単一の css と js ファイル +  jQuery の追加を示しています。ライブラリ定義には、多くのオプションがあります。詳しくは「ライブラリ定義:オプション・詳細」をご覧ください。

jQueryをライブラリに追加する

Drupal のデフォルト設定ではすべてのページに jQuery がロードされません。たとえば、cuddly-sliderライブラリが jQuery を必要とする場合は、jQuery を含む Drupal コアライブラリーへの依存を宣言します。(Drupalでは、jQuery はモジュールやテーマからではなく、コアシステムから jQuery を提供します。)

スラッシュに続く拡張名やライブラリ名で依存関係を宣言します。この場合は core/jquery となります。それ以外のライブラリで cuddly-slider が必要な場合は、fluffiness/cuddly-slider(テーマ名 / ライブラリ名)の順に宣言します。個々のファイルを宣言することはできません。ライブラリとしてのみ、定義可能です。

jQuery を利用できるようにするためには、上記の cuddly-slider を次のように更新します。

# fluffiness.libraries.yml
cuddly-slider:
  version: 1.x
  css:
    theme:
      css/cuddly-slider.css: {}
  js:
    js/cuddly-slider.js: {}
  dependencies:
    - core/jquery

依存性の宣言

依存を宣言するには、リソース名/ライブラリ名 という形式で宣言します。コアのライブ ラリの場合、リソース名は core になります。その他の場合はモジュールやテーマ名となります。もし new_library がコアの jQuery、my_theme 内で宣言された my_library、また my_module 内で宣言された my_library に依存する場合、依存関係は以下のように宣言します:

# fluffiness.libraries.yml
new_library:
  js:
    js/new_library.js: {}
  dependencies:
    - core/jquery
    - my_module/my_library
    - my_theme/my_library

同名のライブラリに対し、モジュール名とテーマ名が名前空間を提供します。

すべてのページでライブラリを読み込む

ほとんどのテーマでは、アクティブなすべてのページに読み込むスタイルシート(CSS ファイル)を global-styling ライブラリとして使用します。同様に、global-scripts ライブラリとして、JS ファイルも設定可能です。

# fluffiness.libraries.yml (multiple libraries can be added to a libraries.yml file, these would appear below the cuddly-slider libraries added earlier)
global-styling:
  version: 1.x
  css:
    theme:
      css/layout.css: {}
      css/style.css: {}
      css/colors.css: {}
global-scripts:
  version: 1.x
  js: 
    js/navmenu.js: {}  

テーマ内のすべてで global-styling/global-scripts ライブラリを使用するには、.info.yml(fluffiness.info.yml) に下記を追加する必要があります。

#fluffiness.info.yml
name: Fluffiness
type: theme
description: 'A cuddly theme that offers extra fluffiness.'
core: 8.x
# by adding global-styling and global-scripts here, the css/js files in the library become 
# available to every page presented by the theme
libraries:
  - fluffiness/global-styling
  - fluffiness/global-scripts
base theme: classy
regions:
  header: Header
  content: Content
  sidebar_first: 'Sidebar first'
  footer: Footer

Twigテンプレートでのライブラリ読込

任意のTwigテンプレート *.html.twig ファイル内で、attach_library() 関数を使用し、テンプレート内でのみライブラリアセットを読み込むことが可能です。

{{ attach_library('fluffiness/cuddly-slider') }}
<div>Some fluffy markup {{ message }}</div>

一部のページへのライブラリ読み込み

場合によっては、ライブラリをすべてのページで使用せず、一部のページのみで使用する場合があります。たとえば、特定のブロックが表示されている場合や、または特定のノードタイプが表示されている場合などです。

.themeファイルに「THEME_preprocess_HOOK()」関数を実装して設定します。「THEME」をテーマ名に、「HOOK」をテーマフックの機械名に置き換えます。

たとえば、JavaScriptをメンテナンスページで使用する場合、「HOOK」部分は「maintenance_page」となります。関数は次の通りです。

function fluffiness_preprocess_maintenance_page(&$variables) {
  $variables['#attached']['library'][] = 'fluffiness/cuddly-slider';
}

他のテーマフックでも同様のことが可能です。また、関数内に論理式を含めることができます。例えば、"block" フックでは前処理されているブロック、 "node" フックではノードタイプなどを検出できます。

重要な注意点
この場合、条件に対応するキャッシュに関するメタデータを指定する必要があります。上記の例は無条件で動作するため、キャッシュに関するメタデータは必要ありません。現在のルートに基づき、ライブラリアセットを読み込み例は、以下のようになります。

function fluffiness_preprocess_page(&$variables) {
  $variables['page']['#cache']['contexts'][] = 'route';
  $route = "entity.node.preview";
  if (\Drupal::routeMatch()->getRouteName() === $route) {
    $variables['#attached']['library'][] = 'fluffiness/node-preview';
  }
}

ライブラリ定義:オプション・詳細

読み込むCSS / jsにプロパティを追加する

THEMENAME.libraries.yml ファイルに追加した各ファイルの後に波括弧 { } でプロパティを追加可能です。

CSSプロパティ

CSSのオプションのプロパティです。CSSアセットごとに適用されます。

attributes オプションで利用する属性。知られているユースケースとしては Bootstrap CDN が挙げられます { attributes: { crossorigin: anonymous } }
browsersDrupal 9.1.0 で廃止予定 CSSを読み込ませるブラウザの条件を指定。
注意:この方法は条件付きコメントを利用しており、IE9 までしかサポートしていません。
{ browsers: { IE: 'lte IE 9', '!IE': false } }
group CSSはグループでアグリゲート(集約)されます。
デフォルト:SMACSS グループに CSS は集約されます。
めったに使用されない
media メディアタイプを設定します。 { media: print }
minified CSS が圧縮されているかどうか。
デフォルト:false
{ type: external, minified: true }
preprocess CSS をアグリゲートするかどうか。
デフォルト:true
{ preprocess: false }
type CSS アセットのソース
デフォルト:file
{ type: external, minified: true }
weight SMACSS などグループ内で他の CSS も含めて読込順を指定します。
デフォルト:0
(-50〜+50の間の数値を使用)
{ weight: 1 }

JSプロパティ

JSのオプションプロパティです。、JSアセットごとに適用されます。

attributes 付加的なスクリプトの属性 { type: external, attributes: { async: true } }
browsers
Drupal 9.1.0 で廃止予定
JS を読み込ませるブラウザの条件を指定。
注意:この方法は条件付きコメントを利用しており、IE9 までしかサポートしていません。
{ browsers: { IE: 'lte IE 9', '!IE': false } }
preprocess JS をアグリゲートするかどうか。
デフォルト:true
{ preprocess: false }
type JS アセットのソース
デフォルト:file
{ type: external, minified: true }
weight

推奨されません。代わりに dependencies を利用してください。
他のアセットにたいして相対的な順序の調整に使います。
値は必ずマイナスである必要があります。

{ weight: -1 }

ライブラリのオーバーライドと拡張

*.libraries.yml で定義されたライブラリをオーバーライドするためには、*.info.yml に設定が必要です。libraries-override または libraries-extend を使用し、オーバーライドもしくは拡張の設定をします。*.info.ymlに設定したオーバーライドは、サブテーマにより継承されます。

*.info.yml ファイルで使用されていた stylesheets-remove プロパティ は廃止され、Drupalの9.0.x からは削除されます。stylesheets-override プロパティは既に削除されています。

libraries-override

オーバーライドを設定する際のロジックは次のとおりです。

  1. ライブラリ名には元のモジュール(またはコア)の名前空間を使用します。
  2. オーバーライドするファイルの最新のパスをキーとして使用します。
  3. そのパスはファイルへのフルパスでなければなりません。

例:

libraries-override:
  contextual/drupal.contextual-links:
    css:
      component:
        /core/themes/stable/css/contextual/contextual.module.css: false
    

contextual/drupal.contextual-links は、コアライブラリの名前空間です。 /core/themes/stable/css/contextual/contextual.module.css: は、そのライブラリでオーバーライドするファイルの最新のフルパスです。この場合、該当ファイルは、false (使用しない)設定で上書きされます。

ここでの重要なポイントは、最後の部分のみが実際のファイルシステムパスで、他の部分は名前空間を参照していることです。css: とcomponent: 行はオーバーライドされているライブラリの構造を反映します。

これらを使用する際には、ファイルシステムはパスに依存するため、サイトのファイル構造が変更されると、そのパスの設定が破損する可能性があることを覚えておいてください。そのため、ストリームラッパーを使用し、フルパスに依存させない方法が検討されています。

libraries-override を使った、モジュールやテーマから継承されたCSS、JavaScript のアセット、あるいはライブラリ全体の削除や置換する他の方法には、下記のようなものがあります。

libraries-override:
  # Replace an entire library.
  core/drupal.collapse: mytheme/collapse
  
  # Replace an asset with another.
  subtheme/library:
    css:
      theme:
        css/layout.css: css/my-layout.css

  # Replace an override asset from stable.
  contextual/drupal.contextual-toolbar:
    css:
      component:
        core/themes/stable/css/contextual/contextual.toolbar.css: css/contextual.toolbar.css

  # Replace a core module JavaScript asset.
  toolbar/toolbar:
    js:
      js/views/BodyVisualView.js: js/views/BodyVisualView.js

  # Remove an asset.
  drupal/dialog:
    css:
      theme:
        dialog.theme.css: false
  
  # Remove an entire library.
  core/modernizr: false
  
  # Replace very specific assets from a contributed module's library.
  # Note: The module's libraries available for overriding can be found in the module's *.libraries.yml file. In this example, you would find the libraries.yml file at the following location: /modules/contrib/webform/webform.libraries.yml 

  webform/webform.element.location.places:
    css:
      component:
        css/webform.element.location.places.css: css/my-themes-replacement-file.css
    js:
      js/webform.element.location.places.js: js/my-themes-replacement-file.js

libraries-extend

libraries-extend では、テーマ依存のライブラリアセットを追加で設定することで、ライブラリのアセットを変更することができます。 また、複数の他ライブラリを追加し、ライブラリの拡張を設定します。

この方法は、テーマ内の特定のコンポーネントのスタイリングを行いつつ、グローバル CSS には同様の設定をしたくない場合に適しています。つまり、すべてのページで CSS を読み込まずに、特定コンポーネントの外観のカスタマイズを可能にします。

# Extend drupal.user: add assets from classy's user libraries.
libraries-extend:
  core/drupal.user: 
    - classy/user1
    - classy/user2

JavaScriptの追加設定

アセットの読込順序

JS ファイルは、リストで指定した順序で読み込まれます。デフォルトでは、すべての JS アセットがフッターで読み込まれます。必要に応じて、JS 実行後表示される重要な UI 要素は、以下のような設定でヘッダーで読み込むことも可能です。

js-header:
  header: true
  js:
    header.js: {}

js-footer:
  js:
    footer.js: {}

header プロパティを true に設定にすると、そのライブラリアセット内の JavaScript アセットが「クリティカルパス」となり、ヘッダーで読み込まれます。ここで指定したライブラリで直接的・間接的に宣言された依存ライブラリも自動的にヘッダで読み込まれるため、依存ライブラリを個別に宣言する必要はありません。ここでいう「クリティカルパス」とは、ヘッダーで読み込まれるように宣言されたアセットは「クリティカル」と見なされ、それに依存する一連のライブラリは最初に読み込まれることを意味します。

設定変更が可能な JavaScript の設定:

計算された PHP 情報に依存した JavaScript をページに追加したい場合もあると思います。

この場合は、上の説明と同様に JavaScript ファイルを作成し、ライブラリを定義し付加します。同時に JaveScript の設定を付加し、先のファイルからそれらの設定を読み込む必要があります。これは drupalSettings を経由して行います。(Drupal 7 の Drupal.settings の後継となるファイル)drupalSettings を JavaScript ファイルで 使用するためには、jQuery と同じ作業が必要で drupalSettings との依存関係を宣言する必要があります。

実例としては、*.libraries.ymlに以下を設定します

cuddly-slider:
  version: 1.x
  js:
    js/cuddly-slider.js: {}
  dependencies:
    - core/jquery
    - core/drupalSettings

そして、*.theme ファイルに以下を設定します。

function fluffiness_page_attachments_alter(&$page) {
  $page['#attached']['library'][] = 'fluffiness/cuddly-slider';
  $page['#attached']['drupalSettings']['fluffiness']['cuddlySlider']['foo'] = 'bar';
}


上の 'bar' は計算結果にあたります。(キャッシュに関するメタデータはここでも必要です)

そして、cuddly-slider.jsに以下のように設定すると、settings.fluffiness.cuddlySlider.foo へアクセス可能になります(また、これは ==='bar' になります):

(function ($, Drupal, drupalSettings) {

  'use strict';

  Drupal.behaviors.mybehavior = {
    attach: function (context, settings) {
      
      console.log(settings.fluffiness.cuddlySlider.foo);
      
    }
  };

})(jQuery, Drupal, drupalSettings);

script 要素に属性を追加する

script タグに属性を追加する場合は、スクリプト 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>

インラインJavaScript

インライン JavaScript の利用を避けることが強く推奨されます。JS は、コンテンツとしてデータベース上に保存するのではなく、ファイルで配置することをお勧めします。理由は、JavaScript をクライアント側でキャッシュできるようにするためです。また、JavaScript コードのレビューや Lint による分析も可能になります。

マークアップを生成するインラインJavaScript

これは推奨されず、また一般的に不要です。JavaScrpit はファイル中に記載してください。例として、広告、ソーシャルメディア共有ボタン、ソーシャルメディアリストウィジェットなどがあります。これらはインライン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" data-widget-id="307116909013368833" href="https://twitter.com/wimleers">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 アナリティクス)とホスト型フォントサービスなどがあります。このような、インライン JavaScript は「フロントエンド/スタイリング型(ホスト型フォントサービスなど)」と「論理型 (Google アナリティクスをはじめとする分析ツールなど)」の2種類に分けられます。

フロントエンド/スタイリング型の場合、JS はテーマに属します。html.html.twig ファイルに直接 JS を書いてください。こうすることで当該 JS が最良の場所に埋め込まれ、フォントを読み込んでいる間の FOUT(Flash Of Unstyled Text = スタイルが未適用のテキストが一瞬表示される現象)を防ぐことが可能になり、最適(かつ最速の)エンドユーザーエクスペリエンスを実現できます。(JS から読み込まれるフォントは、HTML の内で CSS の前に記載する必要があります)(詳細は、「Async Typekit & the Micro-FOUT」という素晴らしい記事を参照してください)。

上記以外の場合、モジュールに属します。「スタイルシート(CSS)とJavaScript(JS)をDrupal モジュールに追加する」を参照してください。

モジュール内のインラインJavaScript

インライン JavaScript の利用を避けることが強く推奨されます。上に挙げた例が適用できる場合、それらの利用を検討してください。

インライン JavaScript を受け付けるフィールドを提供する際に考慮すべき2つのポイント:

  1. このインライン JavaScript を使用するフィールド、フォーム、またはページに、権限を設定する必要があります。
    例: MODULE.routing.yml
    MODULE.settings:
      path: /admin/config/services/MODULE
      defaults:
        _title: 'MODULE settings'
        _form: \Drupal\MODULE\Form\MODULESettings
      requirements:
        _permission: 'administer site configuration'
    
  2. config オブジェクトに格納された値は、CacheableMetadata を変更した際、レンダリングシステムに、要素のレンダリングキャッシュが適切にクリア/期限切れになるように設定する必要があります。
    例: MODULES.module
    <?php
    
    /**
     * @file
     * Integrates MODULE in a Drupal site.
     */
    
    use Drupal\Core\Render\Markup;
    
    /**
     * Implements hook_page_bottom().
     */
    function MODULE_page_bottom(array &$page_bottom) {
      $settings = \Drupal::config('MODULE.settings');
      $user = \Drupal::currentUser();
      $page_bottom['MODULE'] = [
        '#markup' => Markup::create($settings->get('js_code')),
        '#cache' => [
          'contexts' => ['user'],
          'tags' => ['user:' . $user->id()],
        ],
      ];
      // Add config settings cacheability metadata.
      /** @var Drupal\Core\Render\Renderer $renderer */
      $renderer = \Drupal::service('renderer');
      $renderer->addCacheableDependency($page_bottom['MODULE'], $settings);
    }

CDN /外部ホスティングライブラリ

外部の CDN(コンテンツ配信ネットワーク)にある JavaScript を使用可能です。(例:Webフォントは通常外部URLのみで使用可能)type: external を指定し、ライブラリを外部 URL で宣言することで設定できます。定義に外部ライブラリに関する情報を含めることをお勧めします。

(CDN からのライブラリ読込は一般的には良いことではありません。可能であれば使用しないでください。パフォーマンスやセキュリティ上で多くの障害があり、多くの TCP / IP 接続を必要とし、通常はブラウザにキャッシュされません。しかしながら、サードパーティライブラリは、Drupal.org 内のレポジトリの一部としてホスティングされるべきではありません。サードパーティライブラリに関する方針は、Drupal.orgサードパーティライブラリに関する方針を参照してください。)

angular.angularjs:
  remote: https://github.com/angular
  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 }

ページへのリクエストと同じプロトコルで外部にリクエストしたい場合は、相対プロトコルに基づくURLで指定します。

  js:
    //ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }

CSSを追加したい場合
例:Font Awesome を設定する

font-awesome:
  remote: https://fortawesome.github.io/Font-Awesome/
  version: 4.5.0
  license:
    name: MIT
    url: https://fortawesome.github.io/Font-Awesome/license/
    gpl-compatible: true
  css:
    theme:
      https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css: { type: external, minified: true }

Bootstrap CDN CSS にカスタム属性を追加する場合

bootstrap-cdn:
  remote: getbootstrap.com
  version: 4.0
  license:
    name: MIT
    url: https://github.com/twbs/bootstrap/blob/main/LICENSE
  css:
    theme:
      'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css':
        type: external
        minified: true
        attributes:
          crossorigin: anonymous
          integrity: "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"

参考文献

 

この記事を書いた人 : Kentaro Inoue

ANNAI株式会社
マーケティングマネージャー
サービスの設計・企画、マーケティング、採用戦略の立案などを担当。普段は新潟で猫と一緒に、時々海外からリモートで働いています。好きなモジュールはRulesとFlagです。

関連コンテンツ