【詳解】wp_enqueue_scriptでjavascriptファイルを読み込む

今日はwordpressスクリプトファイルをインクルードする方法について紹介します。

wp_enqueue_scriptの使い方やwp_register_scriptとの違い、wordpressでよくあるjsまわりのトラブルシューティングなどについてお伝えできればと思います。

【事前知識】htmlにjavascriptの外部ファイルを読み込む方法

インクルード vs インライン

Google Analyticsのトラッキングコードを自身のブログに張り付けたことがある人も多いと思いますが、中身はただのjavascriptです。 実際に見てみましょう。

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=************"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', '************');
</script>

このトラッキングコードには二つの<script></script>タグがあります。

一つ目は、scriptタグのsrc属性にurlが書かれていてタグの中身は空っぽです。これがいわゆるインクルードと呼ばれる方法で、src属性の先にあるjavascriptファイルを読み込んで実行します。(asyncについてはここでは触れません。)

二つ目はインラインと呼ばれる方法で、scriptタグの中に直接javascriptを書いて動かすことができます。

どっちを使うのがいいんだろう?と悩む方もいるかと思いますが、基本的にjavascriptはインラインで記述せず、jsファイルに書いてインクルードする方法が好まれます。

理由はいっぱいあるかと思いますが、ここでは三つほど挙げます。

まず、htmlはhtmlファイルに、javascriptはjsファイルに分けて書くことで、複数人での分業がやりやすくなります。あと、管理もしやすくなります。どういうことかと言うと、元の開発者と別の人がソースのメンテナンスを任された時に、ファイルを分けて書いておけば、javascriptの修正が必要になった時にjsファイルから該当の処理を探せば済みます。実は探していた処理がインラインで書かれていた、というオチは実際しばしばあるのですが、発見までにかなり時間がかかることもあり担当者泣かせだったりします。

次に、外部ファイルをインクルードする場合、ブラウザキャッシュのメリットを享受できます。最初にウェブページを読み込み、同一ページを2回目以降見ると表示が早くなる感覚ありますよね?あれはブラウザがページ表示に利用するcssやjsファイルなどのリソースをローカルに一定期間保存してくれていて、そいつがあればローカルのものを読み込んでくれるようになるためです。一々サーバへリクエストする必要がなくなる分、ページの表示が早くなります。一方でインラインの場合には通常このキャッシュが使用できません。

最後に、これは副次的な効果ですが、外部ファイルにjavascriptを書くと(インラインで場当たり的に書くよりは)機能ごとにモジュール化を意識した書き方になりやすいです。

以上のとおり書きましたが、実際の開発現場では、例えばGoogle Analyticsのトラッキングコードのように、外部サービスによってインラインでの挿入を指示されることもありますし、このページだけちょっとだけjavascript使いたいんだよな、みたいな時にはインラインで書いちゃうこともあります。あまり固く考えすぎずにケースバイケースで運用しましょう。

ヘッダーで読み込むか、フッターで読み込むか

javascriptファイルを<head></head>タグの中と</body>の直前のどちらで読み込むかはよく議論されますが、一般的には</body>の直前にで読み込むことが推奨されます。本題ではないので詳しくは書きませんが、<head>で読み込むと、jsファイルの転送が完了するまでページの表示を遅延させてしまうためです。</body>の直前に書けば、以後に続くDOMがないのでページ表示は遅延しません。

どういう書き方が遅延を引き起こすかについて詳しくはgoogleサイトに初回ペイントの遅延を引き起こすリソースを使用しないとかを読んでみてください。

wordpressスクリプトファイルをインクルードするには

wordpressでは、テンプレートファイル(header.phpとかfooter.phpとか)に直接<script src="****"></script>を書くことは推奨されていません。代わりにwp_enqueue_script()を使います。

使い方

wp_enqueue_script( string $handle, string $src = '', array $deps = array(), string|bool|null $ver = false, bool $in_footer = false );

<?php 
// 使用例
wp_enqueue_script( 'my-script',  get_stylesheet_directory_uri() . '/my-script.js', array(), false, false);

第一引数のみが必須の引数になります。

第一引数$handleは、スクリプトファイルを識別するためのハンドル名で、任意の名前を付けることができます。ただ、wordpressが予約しているハンドル名や、有効化されたプラグインで使用されているハンドル名と重複すると不具合の原因になるので、my-などのように接頭辞を付けることをおすすめします。

第二引数には、スクリプトファイルのurlかパスを与えられます。これを省略する場合、事前にwp_register_script()で登録しておく必要があります。

第三引数には、依存するスクリプトファイルのハンドル名を配列で与えられます。配列に含まれるスクリプトファイルは、ここで指定したスクリプトファイルよりも前に読み込まれます。例えばmy-script.jsがjQueryを使用したスクリプトである場合、このファイルよりも前にjQuery本体を読み込んでおかないと動きません。そうした時に、第三引数にarray('jquery')という配列を与えてあげるとそれが保証されます。ちなみに、ここでの'jquery'というのはwordpressが定義済みjQueryのハンドル名です。

第四引数はファイルパスにクエリストリングとして追加される文字列です。javascriptファイルに変更を加えた時に、キャッシュされた古いファイルが呼ばれないように、ファイルパスを新しくしたい時に使用します。

第五引数はこのスクリプトファイルをヘッダーで読み込むか、フッターで読み込むかを指定します。デフォルトではヘッダーでのインクルードですが、引数にtrueを与えるとフッターでインクルードされるようになります。

wp_enqueue_scriptとwp_register_scriptの違い

上の使用例は、実は以下のように書いても同義です。

<?php 
//  元の例
wp_enqueue_script( 'my-script',  get_stylesheet_directory_uri() . '/my-script.js', array(), false, false);

// こう書いても同じ
wp_register_script( 'my-script',  get_stylesheet_directory_uri() . '/my-script.js', array(), false, false);
wp_enqueue_script('my-script');

wp_register_script()でハンドル名を登録するだけしておき、wp_enqueue_script()が登録済みのハンドル名を使ってエンキューします。

こう見るとwp_register_script()いらないじゃん、と思うかもしれませんし、実際テーマ開発していてこれを使う機会って少ないんですが、もし自分で依存関係があるスクリプトファイルを複数作った場合、wp_register_script()を使えば明示的に読み込み順序を指定出来て便利です。

<?php 
//  child.jsはparent.jsに依存する
wp_register_script( 'my-parent',  get_stylesheet_directory_uri() . '/parent.js', array(), false, false);
wp_enqueue_script( 'my-child',  get_stylesheet_directory_uri() . '/child.js', array('my-parent'), false, false);

wp_enqueue_scriptはどこで実行するか

'wp_enqueue_scripts'というアクションフックが用意されているので、特に理由がない限りここで実行しましょう。

<?php
// テーマ開発の場合、functions.phpとかに書く
add_action( 'wp_enqueue_scripts', 'enqueue_my_scripts' );
function enqueue_my_scripts() {
    wp_enqueue_script( 'my-script',  get_stylesheet_directory_uri() . '/my-script.js', array(), false, false);    
}

トラブルシューティング

最後に、wordpressでよくあるjsまわりのトラブルシューティングについてまとめておきます。

$ is not defined

jQueryを使う時に、通常$という名の変数でjQueryオブジェクトにアクセスしますが、wordpressでインクルードされるjQuerynoConflict()が有効化されていて、$という変数を使えません。

このエラーを回避するには、以下のようにするのが一般的です。

jQuery(document).ready(function($) {
 // この関数の中では、$()をjQuery()のエイリアスとして利用できます。
 // DOM readyのタイミングでこの無名関数が実行されます。 
});

jQuery(function($) {
    // このような書き方をしても上と同じです。
});

(function($) {
 // DOM readyを待たずに処理を実行させたい場合には、このように即時実行関数を使う方法があります。
 // この関数の中では、$()をjQuery()のエイリアスとして利用できます。
})(jQuery);

jQuery本体をフッターにインクルードしたいのにヘッダーに出てきちゃう!

冒頭でスクリプトファイルはフッター読み込みの方が好ましいと言いました。

ふむふむ、じゃあこうすればいいんだな、と思って以下のようなコードを書いても、jQueryがヘッダーでインクルードされてしまうと思います。

<?php
// 第三引数で依存先にjqueryを指定
// 第五引数でフッター読み込みを指定
wp_enqueue_script( 'my-script',  get_stylesheet_directory_uri() . '/script.js', array('jquery'), false, true);

これは、wordpressが登録済みのjQueryを使っているためです。このjQuery$in_footer == falsewp_register_script()されているので依存元をフッター読み込みにしても、jQueryはヘッダーに来てしまいます。

これを解決するためにはwp_deregister_script()で登録を外し、新しくwp_register_script()で登録し直します。

<?php
add_action('wp_enqueue_scripts', 'enqueue_my_scripts');
function enqueue_my_scripts() {
 //登録済みのjQueryを一回解除する
 wp_deregister_script( 'jquery' );

 // 同じハンドル名で、フッター読み込みに変えて再登録する
 wp_register_script( 'jquery', includes_url( '/js/jquery/jquery.js' ), array(), false, true );
 
 // 続けて使用したいスクリプトファイルをエンキューしていく
}

もしこれをやっても相変わらずjQueryがヘッダーに来てしまう場合、考えられる原因として、プラグインが独自のスクリプトファイルをjquery依存でヘッダーにインクルードしている可能性があります。こうなると、プラグインがどこまでアクションフックを用意してくれているか次第です。もし上手くプラグインの設定を変えられない場合、諦めましょう

jQuery is not defined

jQueryを使用したスクリプトファイルをインクルードする前に、jQuery本体がインクルードされていないようです。まずはwp_enqueue_script()の第三引数にarray('jquery')を与えて問題が解決するか確認してみてください。

見落としがちなのがヘッダー読み込みとフッター読み込みの混在による問題です。例えば以下のケース。

<?php
//登録済みのjQueryを一回解除する
wp_deregister_script( 'jquery' );

// 同じハンドル名で、フッター読み込みに変えて再登録する
wp_register_script( 'jquery', includes_url( '/js/jquery/jquery.js' ), array(), false, true );

// foo.jsとbar.jsはともにjQueryに依存していることとする
// foo.jsはフッターで読み込まれるように指定したが、bar.jsはうっかり指定を忘れてヘッダー(デフォルト)で読み込まれてた!
wp_enqueue_script( 'my-foo',  get_stylesheet_directory_uri() . '/foo.js', array('jquery'), false, true);
wp_enqueue_script( 'my-bar',  get_stylesheet_directory_uri() . '/bar.js');

こう書いた場合、jQuery本体とfoo.jsは</body>タグの直前でインクルードされますが、bar.jsは<head></head>の中でインクルードされます。jQueryより先にbar.jsが読み込まれることになるので、jQuery is not definedとなってしまいます。

このようなケアレスミスは意外とあるので、引数は省略せずに全て書くことをおすすめします。

jQueryが重複して読み込まれるんですけど!

これは特異なケースですが起こり得ます。以下の例を見てください。

<?php
add_action('wp_enqueue_scripts', 'enqueue_my_scripts');
function enqueue_my_scripts() {
 // cdnでjQueryを読み込んだ!ハンドル名は'jQuery', wordpressの内蔵jQueryのハンドル名は'jquery'なので微妙に異なります。
 wp_enqueue_script('jQuery', 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js', array(), false, true);

 // 続けて使用したいスクリプトファイルをエンキューしていく
}

CDNjQuery'jQuery'というハンドル名でエンキューしました。

これだけでは特に問題ないのですが、この条件に加えて、例えばContact Form 7という有名なプラグインを有効化しているとどうでしょうか?wordpress内蔵のjQueryと、CDNjQueryが二つインクルードされてしまうと思います。

これは、Contact Form 7のプラグイン内でスクリプトファイルがwp_enqueue_script()されていて、そこで'jquery'というハンドル名、つまりwordpress内蔵のjQueryを依存先に指定しているためです。

'jquery''jQuery'、ハンドル名の取り扱いはケースセンシティブのようで、今回別々のハンドル名で依存先をそれぞれ指定している状況となったため、jQueryが二つ読み込まれてしまったということになります。

では、CDN版のハンドル名を'jquery'にすればいいのかということで実験してみたのですが、この場合jQueryが重複してインクルードされる現象は解消されたものの、同じハンドル名でスクリプトファイルを登録した場合、先にContact Form 7が依存先として指定しているwordpress内蔵のjQueryが優先されるようで、CDNjQueryを読み込んでくれませんでした...

<?php
add_action('wp_enqueue_scripts', 'enqueue_my_scripts');
function enqueue_my_scripts() {
 // Contact Form 7を有効化している状況を仮定します
 // Contact Form 7は内部で、ハンドル名'jquery'と'jquery-form'に依存するスクリプトファイルをwp_enqueue_scriptしてます
 // 今回は'jquery-form'については触れません

 // wordpressで予約されている'jquery'というハンドル名でエンキューすれば、ソースをCDNに上書きできるのではと期待する
 wp_enqueue_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js', array(), false, true);

 // 結果、Contact Form 7の依存先で指定されているwordpress内蔵のjQueryが優先され、上書きは出来なかった
}

ならばということで試してみる価値があるのが、一旦wordpressの内蔵jQuerywp_deregister_script()して、CDNの方を再登録するという方法です。Contact Form 7の場合これで上手くいきました。

<?php
add_action('wp_enqueue_scripts', 'enqueue_my_scripts');
function enqueue_my_scripts() {
 // Contact Form 7を有効化している状況を仮定します
 // Contact Form 7は内部で、ハンドル名'jquery'と'jquery-form'に依存するスクリプトファイルをwp_enqueue_scriptしてます
 // 今回は'jquery-form'については触れません

 // 一旦wordpress内蔵のjQueryをderegisterする
 wp_deregister_script('jquery');

 // wordpressで予約されている'jquery'というハンドル名で再度登録する
 wp_register_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js', array(), false, true);

 // 結果、CDNのjQueryがフッターでインクルードされた!
}

読んでいただきありがとうございました!よきwordpressライフを!