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

最新のツールでウェブアプリの骨組みを作成

Addy Osmani
Addy Osmani

はじめに

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

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

Yeoman

Yo、Grunt、Bower の概要

Yeoman は、生産性を高めるための 3 つのツールを備えた帽子をかぶった男性です。

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

Yeoman では、1、2 つのコマンドで、アプリのボイラープレート コード(またはモデルなどの個々の部分)を記述し、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 Generator

前述のように、Polymer は、最新のブラウザで Web コンポーネントを使用できるようにするポリフィルとサガーのライブラリです。このプロジェクトにより、デベロッパーは未来のプラットフォームを使用してアプリを構築し、飛行中の仕様をさらに改善できる部分を W3C に報告できます。

Polymer ジェネレータのホームページ

generator-polymer は、Yeoman を使用して Polymer アプリの骨組みを作成するための新しいジェネレータです。コマンドラインから Polymer(カスタム)要素を簡単に作成、カスタマイズし、HTML インポートを使用してインポートできます。これにより、ボイラープレート コードを自動的に作成して時間を節約できます。

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

$ npm install generator-polymer -g

これで完了です。これで、アプリにウェブ コンポーネントのスーパーパワーが備わりました。

新しくインストールされたジェネレータには、以下の機能が用意されています。

  • 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 サーバー

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

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

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

Yeoman から、コンストラクタを含めるか、HTML インポートを使用して index.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 Spreadsheets API を使用します。これにより、Google ドキュメントを使用して作成されたスプレッドシートのコンテンツを簡単に読み取ることができます。

設定方法は次のとおりです。

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

    • ID
    • タイトル
    • 著者
    • コンテンツ
    • 日付
    • キーワード
    • メールアドレス(作成者)
    • スラグ(投稿のスラグ URL 用)
  2. [ファイル] メニューに移動し、[コピーを作成] を選択して、スプレッドシートのコピーを作成します。コンテンツは自由に編集でき、投稿を追加または削除できます。

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

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

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

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

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

ブラウザに表示される JSON 出力は少し複雑に見えますが、ご安心ください。投稿のデータのみが対象となります。

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

新しくスキャフォールドされた投稿要素を編集して、マークアップの一部をスプレッドシート内のデータにバインドできます。そのために、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 Imports を使用して 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 インポート(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>

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

<!-- 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 属性は、投稿の配列コレクション内のすべての要素に [[ バインディング ]] を持つインスタンスを作成して維持します(要素が指定されている場合)。

Polymer アプリ

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

幸い、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 要素を使用するシンプルなブログが完成しました。

サードパーティ要素の操作

ウェブ コンポーネントをめぐる要素エコシステムは最近成長しており、customelements.io などのコンポーネント ギャラリー サイトが登場し始めています。コミュニティによって作成された要素を調べたところ、gravatar プロフィールを取得するための要素を見つけました。この要素を取得してブログサイトに追加することもできます。

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

gravatar 要素のソースを /elements ディレクトリにコピーし、HTML インポートを介して post.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 という別のオープンソース プロジェクトが含まれています。これは、(Gruntfile で定義された)さまざまなビルド固有のタスクを実行して、アプリケーションの最適化バージョンを生成できるタスクランナーです。grunt を単独で実行すると、ジェネレータがリンティング、テスト、ビルド用に設定した default タスクが実行されます。

grunt.registerTask('default', [

    'jshint',

    'test',

    'build'

]);

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

test タスクは次のようになります。このタスクを使用すると、おすすめのテスト フレームワークである 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 ビルド

完了

行き詰まった場合は、https://github.com/addyosmani/polymer-blog で polymer-blog のビルド済みバージョンをチェックアウトできます。

その他の機能

ウェブ コンポーネントはまだ進化の途上にあり、関連するツールも同様です。

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

これらの質問に明確な回答が得られ次第、お知らせいたします。今後も、Google はさまざまな取り組みを進めていく予定です。

Bower を使用して Polymer スタンドアロンをインストールする

軽量な方法で Polymer を開始したい場合は、次のコマンドを実行して Bower から直接スタンドアロンでインストールできます。

bower install polymer

これで、bower_components ディレクトリに追加されます。その後、アプリケーション インデックスで手動で参照して、未来を切り開くことができます。

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

これで、Yeoman でウェブ コンポーネントを使用して Polymer アプリをスキャフォールドする方法について理解できました。ジェネレータに関するフィードバックがある場合は、コメントで、またはバグを報告するか、Yeoman の問題トラッカーに投稿してください。生成ツールの改善点などございましたら、ぜひお知らせください。改善は皆様のご利用とフィードバックによってのみ行われます。