Yeoman と Polymer を使用したウェブアプリの構築

最新のツールでウェブアプリの足場を築く

Addy Osmani
Addy Osmani

はじめに

ウェブアプリの作成者なら誰でも、生産性を維持することがいかに重要であるかを知っています。適切なボイラープレートの見つけ、開発とテストのワークフローの設定、すべてのソースの圧縮と圧縮といった面倒な作業に悩まなければならない場合、それは大変な作業です。

幸い、最新のフロントエンド ツールを使用すれば、このような作業の多くを自動化できるため、ユーザーはすぐに本格的なアプリの作成に集中できます。この記事では、ウェブアプリ用ツールのワークフローである Yeoman を使用して、ウェブ コンポーネントを使用したアプリを開発するためのポリフィルと糖のライブラリである Polymer を使用してアプリを効率的に作成する方法を説明します。

Yeoman

Yo, Grunt and Bower のご紹介

Yeoman さんは、生産性向上に役立つ 3 つのツールを使いこなしています。

  • yo は、フレームワーク固有のスキャフォールド(ジェネレータ)のエコシステムを提供するスキャフォールド ツールです。前述した単調なタスクの一部を実行できるスキャフォールド ツールです。
  • grunt は、Yeoman チームと grunt-contrib によってキュレートされたタスクのヘルプを利用し、プロジェクトのビルド、プレビュー、テストに使用されます。
  • bower は依存関係の管理に使用されるため、スクリプトを手動でダウンロードして管理する必要がなくなります。

たった 1 つのコマンドで、Yeoman はアプリ(またはモデルなどの個々の部分)のボイラープレート コードを記述し、Sass をコンパイルし、CSS、JS、HTML、画像を最小化して連結し、現在のディレクトリでシンプルなウェブサーバーを起動します。また、単体テストなどを実行することもできます。

ジェネレータは Node Packaged Modules(npm)からインストールでき、現在 220 を超えるジェネレータが利用可能です。その多くはオープンソース コミュニティによって作成されたものです。よく使用されるジェネレータには、generator-angulargenerator-backbonegenerator-ember があります。

Yeoman ホームページ

最新バージョンの Node.js をインストールしたら、最も近いターミナルに移動して次のコマンドを実行します。

$ npm install -g yo

これで、これで Yo、Grunt、Bower をコマンドラインから直接実行できるようになりました。yo を実行すると、次のようになります。

Yeoman のインストール

ポリマー発生器

前述したように、Polymer はポリフィルと糖のライブラリであり、最新のブラウザで Web Components を使用できるようになります。このプロジェクトにより、デベロッパーは将来のプラットフォームを使用してアプリを構築し、処理中の仕様をさらに改善できる場所を W3C に通知できます。

ポリマー生成器のホームページ

generator-polymer は、Yeoman を使用して Polymer アプリをスキャフォールディングできる新しいジェネレータです。コマンドラインを使用して Polymer(カスタム)要素を簡単に作成してカスタマイズし、HTML Imports を使用してインポートできます。これにより、ボイラープレート コードを記述して時間を節約できます。

次に、次のコマンドを実行して Polymer のジェネレータをインストールします。

$ npm install generator-polymer -g

これで完了です。これで、アプリに Web Component の高度な機能が追加されました。

新しくインストールされたジェネレータには、アクセスできる固有のデータがいくつかあります。

  • polymer:element は、新しい個々の Polymer 要素をスキャフォールドアウトするために使用されます。例: yo polymer:element carousel
  • polymer:app は、最初の index.html、プロジェクトのビルド時構成、Grunt タスク、プロジェクトで推奨されるフォルダ構造を含む Gruntfile.js をスキャフォールディングするために使用されます。プロジェクトのスタイルに Sass Bootstrap を使用することもできます。

Polymer アプリを作成する

カスタムの Polymer 要素と新しいジェネレータを使用して、シンプルなブログを作成します。

Polymer アプリ

まず、ターミナルに移動し、新しいディレクトリを作成し、mkdir my-new-project && cd $_ を使用してそのディレクトリに移動します。次のコマンドを実行して、Polymer アプリを開始できるようになりました。

$ yo polymer
Polymer アプリの構築

これにより、Bower から最新バージョンの Polymer が取得され、ワークフローの index.html、ディレクトリ構造、Grunt タスクが土台になります。アプリの準備ができるまでコーヒーを飲んでみませんか?

では、grunt server を実行して、アプリの外観をプレビューします。

Grunt Server

このサーバーは LiveRetry をサポートしています。つまり、テキスト エディタを起動し、カスタム要素を編集すると、保存時にブラウザが再読み込みされます。これにより、アプリの現在の状態をリアルタイムで把握することができます。

次に、ブログ投稿を表す新しい Polymer 要素を作成します。

$ yo polymer:element post
投稿の要素を作成

Yeoman から、index.html に POST 要素を含めるために、コンストラクタを含めるのか、HTML インポートを使用するのか、といった質問がありました。最初の 2 つの選択肢は「いいえ」にして、3 つ目の選択肢は空白のままにします。

$ yo polymer:element post

[?] Would you like to include constructor=''? No

[?] Import to your index.html using HTML imports? No

[?] Import other elements into this one? (e.g 'another_element.html' or leave blank)

    create app/elements/post.html

これにより、/elements ディレクトリに post.html という名前の新しい Polymer 要素が作成されます。

<polymer-element name="post-element"  attributes="">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

    <span>I'm <b>post-element</b>. This is my Shadow DOM.</span>

    </template>

    <script>

    Polymer('post-element', {

        //applyAuthorStyles: true,

        //resetStyleInheritance: true,

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

これには以下のものが含まれます。

実際のデータソースの操作

ブログには、新しい投稿を書いたり読んだりする場所が必要です。ここでは、実際のデータサービスの操作のデモを行うため、Google Apps スプレッドシート API を使用します。これにより、Google ドキュメントを使用して作成したスプレッドシートのコンテンツを簡単に読むことができます。

設定してみましょう。

  1. ブラウザで(この手順では Chrome を推奨)、この Google ドキュメントのスプレッドシートを開きます。次のフィールドにサンプルの投稿データが含まれています。

    • ID
    • タイトル
    • 投稿者
    • Content
    • 日付
    • キーワード
    • (作成者の)メール
    • スラッグ(投稿のスラッグ URL 用)
  2. スプレッドシートのコピーを作成するには、[ファイル] メニューから [コピーを作成] を選択します。コンテンツの編集はもちろん、投稿の追加や削除もご自由に行うことができます。

  3. もう一度 [ファイル] メニューに移動し、[ウェブに公開] を選択します。

  4. [公開を開始] をクリックします。

  5. [公開データへのリンクを取得] で、最後のテキスト ボックスから、表示された URL の key の部分をコピーします。たとえば、次のようになります。https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0

  6. コピーしたキーを、URL https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback= の URL の your-key-goes-here に貼り付けます。上記のキーを使用した例は、https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script

  7. この URL をブラウザに貼り付けて移動すると、JSON バージョンのブログ コンテンツを表示できます。URL をメモして、このデータの形式を再確認し、後で画面に表示するには、URL を反復処理する必要があります。

ブラウザに表示される JSON 出力は少し複雑に見えるかもしれませんが、心配は無用です。本当に必要なのは投稿のデータのみです。

Google spreadsheets API は、ブログ スプレッドシートの各フィールドに特別な接頭辞 post.gsx$ を付けて出力します。たとえば、post.gsx$title.$tpost.gsx$author.$tpost.gsx$content.$t などです。JSON 出力の各「行」を反復処理するときは、これらのフィールドを参照して各投稿に関連する値を取得します。

新しくスキャフォールドされた POST 要素を編集して、マークアップの一部をスプレッドシートのデータにバインドbindできるようになりました。そのために、属性 post を導入しました。この属性により、前に作成した投稿のタイトル、作成者、コンテンツなどのフィールドを読み取ることができます。selected 属性(後で入力)は、ユーザーが適切なスラッグに移動した場合にのみ投稿を表示するために使用されます。

<polymer-element name="post-element" attributes="post selected">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

        <div class="col-lg-4">

            <template if="[[post.gsx$slug.$t === selected]]">

            <h2>
                <a href="#[[post.gsx$slug.$t]]">
                [[post.gsx$title.$t  ]]
                </a>
            </h2>

            <p>By [[post.gsx$author.$t]]</p>

            <p>[[post.gsx$content.$t]]</p>

            <p>Published on: [[post.gsx$date.$t]]</p>

            <small>Keywords: [[post.gsx$keywords.$t]]</small>

            </template>

        </div>

    </template>

    <script>

    Polymer('post-element', {

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

次に、yo polymer:element blog を実行して、投稿のコレクションとブログのレイアウトの両方を含むブログ要素を作成します。

$ yo polymer:element blog

[?] Would you like to include constructor=''? No

[?] Import to your index.html using HTML imports? Yes

[?] Import other elements into this one? (e.g 'another_element.html' or leave blank) post.html

    create app/elements/blog.html

今度は、HTML import を使用してブログを index.html にインポートしてページに表示させます。具体的には、3 番目のプロンプトでは、含める要素として post.html を指定します。

以前と同様に、新しい要素ファイル(blog.html)が作成され、/elements に追加されます。今回は、post.html をインポートして、テンプレート タグ内に <post-element> を含めます。

<link rel="import" href="post.html">

<polymer-element name="blog-element"  attributes="">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

    <span>I'm <b>blog-element</b>. This is my Shadow DOM.</span>

        <post-element></post-element>

    </template>

    <script>

    Polymer('blog-element', {

        //applyAuthorStyles: true,

        //resetStyleInheritance: true,

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

ブログの要素を HTML import(HTML ドキュメントを他の HTML ドキュメントに含めて再利用する方法)を使用してインデックスにインポートするよう依頼したので、この要素がドキュメント <head> に正しく追加されていることを確認することもできます。

<!doctype html>
    <head>

        <meta charset="utf-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title></title>

        <meta name="description" content="">

        <meta name="viewport" content="width=device-width">

        <link rel="stylesheet" href="styles/main.css">

        <!-- build:js scripts/vendor/modernizr.js -->

        <script src="bower_components/modernizr/modernizr.js"></script>

        <!-- endbuild -->

        <!-- Place your HTML imports here -->

        <link rel="import" href="elements/blog.html">

    </head>

    <body>

        <div class="container">

            <div class="hero-unit" style="width:90%">

                <blog-element></blog-element>

            </div>

        </div>

        <script>
        document.addEventListener('WebComponentsReady', function() {
            // Perform some behaviour
        });
        </script>

        <!-- build:js scripts/vendor.js -->

        <script src="bower_components/polymer/polymer.min.js"></script>

        <!-- endbuild -->

</body>

</html>

いや、素晴らしい。

Bower を使用して依存関係を追加する

次に、Polymer JSONP ユーティリティ要素を使用して posts.json を読み込むように、要素を編集します。アダプターを取得するには、リポジトリの git クローンを作成するか、bower install polymer-elements を実行して Bower を介して polymer-elements をインストールします。

Bower の依存関係

このユーティリティを取得したら、以下を使用して blog.html 要素にインポートとして含める必要があります。

<link rel="import" href="../bower_components/polymer-jsonp/polymer-jsonp.html">

次に、このタグを追加し、先ほどのブログ投稿のスプレッドシートに url を指定して、末尾に &callback= を追加します。

<polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/your-key-value/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>

これにより、スプレッドシートが読み込まれたら、テンプレートを追加してスプレッドシートを反復処理できるようになりました。最初の出力は目次を出力し、投稿のタイトルがリンク先のスラッグを指しています。

<!-- Table of contents -->

<ul>

    <template repeat="[[post in posts.feed.entry]]">

    <li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>

    </template>

</ul>

2 つ目のコードは、見つかったエントリごとに post-element のインスタンスを 1 つレンダリングし、それに応じて投稿のコンテンツを渡します。スプレッドシートの 1 行の投稿コンテンツを表す post 属性と、ルートを入力する selected 属性を渡していることに注意してください。

<!-- Post content -->

<template repeat="[[post in posts.feed.entry]]">

    <post-element post="[[post]]" selected="[[route]]"></post-element>

</template>

テンプレートで使用されている repeat 属性は、投稿の配列コレクション内のすべての要素に対して、[[Bindings ]] のインスタンスを作成して維持します(存在する場合)。

Polymer アプリ

ここで、現在の [[route]] にデータを入力するために、URL ハッシュが変更されるたびに [[route]] にバインドする Flatiron director というライブラリを使用します。

幸いなことに、Polymer 要素more-elements パッケージの一部)を使用してこれを捕捉できます。/elements ディレクトリにコピーしたら、<flatiron-director route="[[route]]" autoHash></flatiron-director> で参照できます。バインドするプロパティとして route を指定し、ハッシュ変更の値を自動的に読み取るように指示できます(autoHash)。

まとめると、次のようになります。

    <link rel="import" href="post.html">

    <link rel="import" href="polymer-jsonp/polymer-jsonp.html">

    <link rel="import" href="flatiron-director/flatiron-director.html">

    <polymer-element name="blog-element"  attributes="">

      <template>

        <style>
          @host { :scope {display: block;} }
        </style>

        <div class="row">

          <h1><a href="/#">My Polymer Blog</a></h1>

          <flatiron-director route="[[route]]" autoHash></flatiron-director>

          <h2>Posts</h2>

          <!-- Table of contents -->

          <ul>

            <template repeat="[[post in posts.feed.entry]]">

              <li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>

            </template>

          </ul>

          <!-- Post content -->

          <template repeat="[[post in posts.feed.entry]]">

            <post-element post="[[post]]" selected="[[route]]"></post-element>

          </template>

        </div>

        <polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdHVQUGd2M2Q0MEZnRms3c3dDQWQ3V1E/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>

      </template>

      <script>

        Polymer('blog-element', {

          created: function() {},

          enteredView: function() { },

          leftView: function() { },

          attributeChanged: function(attrName, oldVal, newVal) { }

        });

      </script>

    </polymer-element>
Polymer アプリ

ボーナス これで、JSON からデータを読み取り、Yeoman でスキャフォールドされた 2 つの Polymer 要素を使用する簡単なブログを作成できました。

サードパーティの要素を使用する

近年、Web Components を中心とした要素のエコシステムが拡大しており、customelements.io などのコンポーネント ギャラリー サイトが登場し始めています。コミュニティによって作成された要素を調べたところ、Gravatar プロフィールを取得できるものが見つかりました。これを実際に取得してブログサイトに追加することもできます。

カスタム要素のホームページ

gravatar 要素のソースを /elements ディレクトリにコピーし、post.html の HTML インポートによってそれを含めます。次に をテンプレートに追加し、ユーザー名のソースとしてスプレッドシートのメール フィールドを渡します。すばらしいです。

<link rel="import" href="gravatar-element/src/gravatar.html">

<polymer-element name="post-element" attributes="post selected">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

        <div class="col-lg-4">

            <template if="[[post.gsx$slug.$t === selected]]">

            <h2><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></h2>

            <p>By [[post.gsx$author.$t]]</p>

            <gravatar-element username="[[post.gsx$email.$t]]" size="100"></gravatar-element>

            <p>[[post.gsx$content.$t]]</p>

            <p>[[post.gsx$date.$t]]</p>

            <small>Keywords: [[post.gsx$keywords.$t]]</small>

            </template>

        </div>

    </template>

    <script>

    Polymer('post-element', {

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

このことから得られたものを見てみましょう。

カスタム要素を使用する Polymer アプリ

素晴らしい!

ボイラープレート コードの記述、依存関係の手動ダウンロード、ローカル サーバーやビルド ワークフローの設定について心配することなく、比較的短期間で、複数のウェブ コンポーネントで構成されるシンプルなアプリケーションを作成しました。

アプリケーションを最適化する

Yeoman のワークフローには、Grunt と呼ばれる別のオープンソース プロジェクトが含まれています。Grunt は、多くのビルド固有のタスク(Gruntfile で定義)を実行して、最適化されたバージョンのアプリケーションを生成するタスクランナーです。grunt を単独で実行すると、ジェネレータが lint チェック、テスト、ビルド用に設定した default タスクが実行されます。

grunt.registerTask('default', [

    'jshint',

    'test',

    'build'

]);

上記の jshint タスクは、.jshintrc ファイルを使用して設定を確認し、プロジェクト内のすべての JavaScript ファイルに対して実行します。JSHint を使用するオプションの詳細については、ドキュメントをご覧ください。

test タスクはこれと少し似ており、Google が推奨するテスト フレームワークである Mocha 用のアプリを作成して提供できます。また、テストも自動的に実行されます。

grunt.registerTask('test', [

    'clean:server',

    'createDefaultTemplate',

    'jst',

    'compass',

    'connect:test',

    'mocha'

]);

今回のアプリはかなり単純であるため、テストの作成は別の演習として行います。ビルドプロセスを処理するために必要なものは他にもいくつかあります。Gruntfile.js で定義した grunt build タスクの処理内容を見てみましょう。

grunt.registerTask('build', [

    'clean:dist',    // Clears out your .tmp/ and dist/ folders

    'compass:dist',  // Compiles your Sassiness

    'useminPrepare', // Looks for <!-- special blocks --> in your HTML

    'imagemin',      // Optimizes your images!

    'htmlmin',       // Minifies your HTML files

    'concat',        // Task used to concatenate your JS and CSS

    'cssmin',        // Minifies your CSS files

    'uglify',        // Task used to minify your JS

    'copy',          // Copies files from .tmp/ and app/ into dist/

    'usemin'         // Updates the references in your HTML with the new files

]);

grunt build を実行します。製品版のアプリがビルドされ、リリースできるようになります。試してみましょう。

Grunt ビルド

完了しました。

行き詰まった場合は、事前構築済みの polymer-blog を https://github.com/addyosmani/polymer-blog から入手できます。

他にもありますか?

Web Components はまだ進化の途上にあり、それに関連するツールも進化を続けています。

現在、Vulcanize(Polymer プロジェクトのツール)などのプロジェクトを介して、HTML インポートを連結して読み込みパフォーマンスを向上させる方法や、コンポーネントのエコシステムが Bower などのパッケージ マネージャーとどのように連携するかを検討しています。

回答が見つかり次第お知らせいたしますが、今後も盛りだくさんの内容をお届けします。

Polymer を Bower とともにスタンドアロンでインストールする

Polymer をすぐに起動する場合は、次のコマンドを実行して Bower から直接スタンドアロンでインストールできます。

bower install polymer

すると、bower_components ディレクトリにそのファイルが追加されます。その後、手動でアプリケーションのインデックスで参照して、将来を見据えることができます。

ご意見をお聞かせください

ここでは、Yeoman で Web Components を使用して Polymer アプリの足場を築く方法を学びました。ジェネレータに関するフィードバックは、コメント欄でお知らせいただくか、バグを報告するか、Yeoman Issue Tracker に投稿してください。このジェネレータの機能向上について、ご意見、ご要望などございましたら、ぜひお聞かせください。