読者です 読者をやめる 読者になる 読者になる

アラタナエンジニアブログ

aratana Engineer's Blog

え!JavaScriptライブラリのUnderscore.jsを紐解けばWordPressで激モテって本当!?

f:id:miiitaka:20150204190717p:plain

アラタナエンジニアブログをご覧の皆様、お初にお目にかかります。
アラタナの生きる化石、高見@フロントエンドエンジニアです。普段はECサイトの表側をJavaScriptCSSなんかでせっせと作っています。昔はパンも作ってました。パン。

オープンソースってすてきですよね。
私は世界中で最も使用されているCMSWordPressがとても好きです。functions.phpに美しいソースを書くだけで、WordPress内に無数に存在するフックポイントを利用して柔軟にカスタマイズができる、けど中身(DB)はシンプルな所が好き。フックポイントを利用したプラグイン作りもホント楽しい(^o^)。地球に生まれて良かったです。
WordPressは数あるCMSの中でシェア率約60%ですよ、すごくないですか?(2015年2月現在)

今日はそんなWordPressに同梱されているJavaScriptライブラリのお話。

WordPressには多くのオープンソースJavaScriptライブラリが含まれています。

f:id:miiitaka:20150205070615p:plain
/wp-includes/js/ディレクトリに同梱されているJavaScriptライブラリ群

その中でも今日はWordPressに同梱されていますJavaScriptライブラリ「Underscore.js」のお話をします。WordPressのバージョン3.5から導入されたライブラリですね。

Underscore.jsってどんなライブラリ?

Underscore.jsは便利関数の集合体で、2015年2月現在の version1.7.0では100を超える関数が準備されています。
jQueryのようにDOM操作ができるわけでは無いのですが、普通に記述すると面倒な処理(配列やオブジェクトの操作、ソートなどなど)をUnderscore.jsの関数でシンプルに実装することができます。可読性が良くなる上に、ライブラリ化されているのでバグが少なくなるのはプログラマーにとってありがたいことですね。

Underscore.jsの基本

まずは基本。
Underscore.jsの公式サイトからダウンロードします。ダウンロードしたら普通のJavaScriptを読み込むのと同じようにファイルを読み込みます。とりあえずUnderscore.jsのバージョンの表示をしてみます。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Underscore.js Sample</title>
  <script src="underscore.js"></script>
</head>
<body>
  <script>
    console.log(_.VERSION);
  </script>
</body>
</html>

Underscore.jsでは、その名の通り「 _(アンダースコア)」を使用します。 VERSION関数で、現在のバージョン( _.VERSION )を表示してみました。このように「_.xxxxxx」の形式で様々な関数が準備されています。今回はその中でも配列・オブジェクト操作を少し紐解いてみます。

配列・オブジェクト操作 _.each(list, iteratee, [context])

配列やオブジェクトを繰り返し処理する場面は沢山出てきます。その度にfor文など用いてせっせと処理を書くのですが、Underscore.jsの_.each()を使用することで処理をシンプルに記述することができます。

- 通常の配列ループ処理

var d = ['桃太郎', '犬', '猿', '雉', '鬼のような嫁'];

for (var i = 0, len = d.length; i < len; i++) {
  console.log(d[i]);
}

配列ループなら、forEachを使わなくもないのですが、まだ対応しなければいけない古いブラウザ(IE8とかの事です。主のIE。)で動作しなかったりするわけで、配列の数取得して、1個1個回すのが現実的です。 しかし、これをUnderscore.jsで置き換えると...

- Underscore.jsの配列ループ処理

var d = ['桃太郎', '犬', '猿', '雉', '鬼のような嫁'];

_.each(d, function(v) {
  console.log(v);
});

超シンプル!\(^o^)/
これは配列の例ですので、「あんまり変わらないような...」と思われるかもしれませんが、これがObjectになると大変で、Object.keysでループを組むとなると、案の定言う事を聞いてくれないブラウザがいるわけで(IEとかね...)、フロントエンドエンジニアがムキーってなる所です。 賢い人達が、互換性を持たせるように、Object.keysをネイティブにサポートしていない環境でも動作するコードを準備してくれているのですが、「これ使う度に毎回書くのか...」とうなだれます。 Underscore.jsではこういった処理をよりシンプルにしてくれます。

感動したのは、配列のシャッフル。JavaScriptは標準ではそういった機能がありません。私が普段触れることの多いPHPだと、shuffle()関数が準備されています。サーバー側でできるなら良いのですが、最近はAjaxでデータを取得するケースも増えているので、JavaScript側で配列操作を行うことが多いのでそういったケースの場合、配列操作の関数群があるととても便利(^o^)

- 出力する前にシャッフルしてみる

// 配列
var d = _.shuffle(['桃太郎', '犬', '猿', '雉', '鬼のような嫁']);

_.each(d, function(v) {
  console.log(_.escape(v));
});

シャッフルされた!\(^o^)/

f:id:miiitaka:20150204185857p:plain

出力前にエスケープしてあげると安全で良いですね。この例は配列にデータをベタ打ちしているので脆弱性は発生しませんが、内部・外部から動的にデータを取得するケースの場合は有りえますので。それもUnderscore.jsの関数で。

WordPressでの実装例

3.5以降からメディアメニューやリビジョン更新などで使用されています。WordPressのバージョンも4.1になり、2015年度のテーマ「Twenty Fifteen」も新しく追加されました。そのテーマの中でUnderscore.jsが組込まれています。
WordPressの管理画面からテーマをTwenty Fifteenを有効化にしていると、カスタマイズメニューで色設定ができます。このカラースキーム選択で使用されている例を紹介します。

f:id:miiitaka:20150205070126p:plain

JavaScriptは、/wp-content/themes/twentyfifteen/js/color-scheme-control.jsです。

( function( api ) {
  var cssTemplate = wp.template( 'twentyfifteen-color-scheme' ),
    colorSchemeKeys = [
      'background_color',
      'header_background_color',
      'box_background_color',
      'textcolor',
      'sidebar_textcolor',
      'meta_box_background_color'
    ],
    colorSettings = [
      'background_color',
      'header_background_color',
      'sidebar_textcolor'
    ];

  api.controlConstructor.select = api.Control.extend( {
    ready: function() {
      if ( 'color_scheme' === this.id ) {
        this.setting.bind( 'change', function( value ) {
          // Update Background Color.
          api( 'background_color' ).set( colorScheme[value].colors[0] );
          api.control( 'background_color' ).container.find( '.color-picker-hex' )
            .data( 'data-default-color', colorScheme[value].colors[0] )
            .wpColorPicker( 'defaultColor', colorScheme[value].colors[0] );

          // Update Header/Sidebar Background Color.
          api( 'header_background_color' ).set( colorScheme[value].colors[1] );
          api.control( 'header_background_color' ).container.find( '.color-picker-hex' )
            .data( 'data-default-color', colorScheme[value].colors[1] )
            .wpColorPicker( 'defaultColor', colorScheme[value].colors[1] );

          // Update Header/Sidebar Text Color.
          api( 'sidebar_textcolor' ).set( colorScheme[value].colors[4] );
          api.control( 'sidebar_textcolor' ).container.find( '.color-picker-hex' )
            .data( 'data-default-color', colorScheme[value].colors[4] )
            .wpColorPicker( 'defaultColor', colorScheme[value].colors[4] );
        } );
      }
    }
  } );

  // Generate the CSS for the current Color Scheme.
  function updateCSS() {
    var scheme = api( 'color_scheme' )(), css,
      colors = _.object( colorSchemeKeys, colorScheme[ scheme ].colors );

    // Merge in color scheme overrides.
    _.each( colorSettings, function( setting ) {
      colors[ setting ] = api( setting )();
    });

    // Add additional colors.
    colors.secondary_textcolor = Color( colors.textcolor ).toCSS( 'rgba', 0.7 );
    colors.border_color = Color( colors.textcolor ).toCSS( 'rgba', 0.1 );
    colors.border_focus_color = Color( colors.textcolor ).toCSS( 'rgba', 0.3 );
    colors.secondary_sidebar_textcolor = Color( colors.sidebar_textcolor ).toCSS( 'rgba', 0.7 );
    colors.sidebar_border_color = Color( colors.sidebar_textcolor ).toCSS( 'rgba', 0.1 );
    colors.sidebar_border_focus_color = Color( colors.sidebar_textcolor ).toCSS( 'rgba', 0.3 );

    css = cssTemplate( colors );

    api.previewer.send( 'update-color-scheme-css', css );
  }

  // Update the CSS whenever a color setting is changed.
  _.each( colorSettings, function( setting ) {
    api( setting, function( setting ) {
      setting.bind( updateCSS );
    } );
  } );
} )( wp.customize );

処理の流れとしては、ロード時に_.each()で定義配列colorSettingsの内容を1つずつbindしてupdateCSS()内で、_.object()で配列をオブジェクトに変換し、さらに_.each()で,定義配列colorSchemeKeysの内容を設定みたいな処理が走っています。 一見難しそうに見えますが、Underscore.js無しでこの一連の処理を書くとかなり冗長なソースになってしまいます。jQueryが最初に発表された時もそうでしたが、最初は違和感を感じます。しかし今、jQuery無しでJavaScriptのプログラミングをするのはつらいと思う方も多いですよね?要は慣れですね。慣れ。

テンプレートエンジンとしての機能

便利関数もそうなのですが、実はテンプレートエンジンとしての機能も実装されています。 _.tempalte()

var compiled = _.template("hello: <%= name %>");
compiled({name: 'momotaro'});

=> "hello: momotaro"

最近沢山のJavaScriptテンプレートエンジンが出ています。私もEC-CUBEのプラグインを作成した時に、Handlebarsを使いました。Undersocre.jsのテンプレートエンジンは、Handlebarsのようにテンプレートに特化してロジックが組み込めるほどはありませんが、通常使用する分には機能として遜色ありませんし、実際WordPress内でも実装されています。プリコンパイル形式でないのが欠点かなぁ。

JavaScriptテンプレートエンジンの今後

JavaScriptの非同期取得データも検索エンジンが認識できるようになってきたよとGoogleのマット・カッツ氏も言ってます。Ajaxデータを認識するので、SEO的にOKとなりますから、ますますフロント側で実装する処理が増えそうです。Ajaxでデータ取得後、テンプレートエンジンにのせてDOM構築、レンダリング。その際に如何にパフォーマンス高くプログラムが組めるか!腕の見せどころですね。うーん、腕が鳴るぅ!\(^o^)/
Ajaxを利用したページですよと検索エンジンに教えてあげる必要はありますけど。(参考:Making AJAX Applications Crawlable

今回は触れませんでしたが、これまたWordPressのバージョン3.5で導入されたライブラリ「Backborn.js」がUnderscore.js依存のライブラリでして、今までごちゃっとなりがちだったJavaScriptMVCアーキテクチャの思想のもと、美しいソースへ変身を遂げるのです。マーベラス!キャプテン・マーベラス!

あれ?あんまりWordPressの話っぽくない。
この二つのライブラリを利用してWordPressのテーマ作成にチャレンジしてみようかと思いました。投稿記事の一覧(アーカイブ)ページとかで「もっと見る」ボタンの設置して、URLをプライマリーになるように実装とか。カテゴリーページにジャンプせずに該当カテゴリの記事を表示とか。

そんなすてきCMSWordPressの勉強会がWordBenchという名で各地で行われていまして、私も宮崎県の勉強会の主催の一人として活動させてもらっています。2ヶ月に1度定期開催していますので興味のある方は是非(^-^)

それではまた次回~^^