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

Drupal 8 のテーマにスタイルシート(CSS)とJavaScript(JS)を追加する
この記事の目次

最終更新日 2018年5月3日

このページは不完全です。詳細情報を追加してください

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

Drupalは、高度なルールで運用されています:
アセット(CSSまたはJS)は必要がある場合にのみ読み込まれます。フロントエンドのパフォーマンスを低下させないために、すべてのアセットをすべてのページで読み込みません。

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. Drupal8では、drupal_add_css()、drupal_add_js()d、rupal_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 8のデフォルト設定ではすべてのページにjQueryがロードされません。たとえば、cuddly-sliderライブラリで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

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

ほとんどのテーマでは、アクティブなすべてのページに読み込むスタイルシート(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アセットごとに適用されます。

browsers CSSを読み込ませるブラウザの条件を指定 { 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 scriptタグに属性を追加 { type: external, attributes: { async: true } }
browsers JSを読み込ませるブラウザの条件を指定 { browsers: { IE: 'lte IE 9', '!IE': false } }
preprocess CSSをアグリゲートするかどうか。
デフォルト:true
{ preprocess: false }
type JSアセットのソース
デフォルト:file
{ type: external, minified: true }
weight グループ内の他のJSとの相対的な順序を調整します。
(設定、ライブラリ、デフォルト、テーマ)
{ 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

libraries-extend

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

テーマ内の特定のコンポーネントのスタイル設定に適しています。global 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ファイルを作成し、これまでの説明のようにライブラリとして定義します。また、drupalSettings(Drupalの7のDrupal.settings後継ファイル)経由で、JavaScript設定と読み込むJavaScriptファイルを指定します。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に以下のように設定すると、drupalSettings.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から外部コンテンツを取り込む特殊なコードになります。

これらをカスタムブロックに入れたい場合や、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 analyticsなどの分析ツールなど)」の2種類に分けられます。

フロントエンド/スタイリング型の場合、JSはテーマに属します。html.html.twigファイルに直接JSを書いてください。このようなフォントの場合は、フォントを読み込んでいる間のFOUT(Flash Of Unstyled Text)を防ぐことが可能になり、フォントを最も有効に使用できる場所にコードを記載します。(JSから読み込まれるフォントは、HTMLの

内でCSSの前に記載する必要があります)(詳細は、「Async Typekit & the Micro-FOUT」という素晴らしい記事を参照してください)

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

モジュール内のインライン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オブジェクトにストアされた値は、キャッシュされるメタデータを変更した際、レンダリングシステムに、要素のレンダリングキャッシュが適切にクリア/期限切れになるように設定する必要があります。
    例: MODULES.module
     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 }

詳しくは

 

 
フッターの採用情報
 
Kentaro Inoueの写真

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

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

関連コンテンツ