最新のツールでウェブアプリの足場を築く
はじめに
ウェブアプリの作成者なら誰でも、生産性を維持することがいかに重要であるかを知っています。適切なボイラープレートの見つけ、開発とテストのワークフローの設定、すべてのソースの圧縮と圧縮といった面倒な作業に悩まなければならない場合、それは大変な作業です。
幸い、最新のフロントエンド ツールを使用すれば、このような作業の多くを自動化できるため、ユーザーはすぐに本格的なアプリの作成に集中できます。この記事では、ウェブアプリ用ツールのワークフローである Yeoman を使用して、ウェブ コンポーネントを使用したアプリを開発するためのポリフィルと糖のライブラリである Polymer を使用してアプリを効率的に作成する方法を説明します。
Yo, Grunt and Bower のご紹介
Yeoman さんは、生産性向上に役立つ 3 つのツールを使いこなしています。
- yo は、フレームワーク固有のスキャフォールド(ジェネレータ)のエコシステムを提供するスキャフォールド ツールです。前述した単調なタスクの一部を実行できるスキャフォールド ツールです。
- grunt は、Yeoman チームと grunt-contrib によってキュレートされたタスクのヘルプを利用し、プロジェクトのビルド、プレビュー、テストに使用されます。
- bower は依存関係の管理に使用されるため、スクリプトを手動でダウンロードして管理する必要がなくなります。
たった 1 つのコマンドで、Yeoman はアプリ(またはモデルなどの個々の部分)のボイラープレート コードを記述し、Sass をコンパイルし、CSS、JS、HTML、画像を最小化して連結し、現在のディレクトリでシンプルなウェブサーバーを起動します。また、単体テストなどを実行することもできます。
ジェネレータは Node Packaged Modules(npm)からインストールでき、現在 220 を超えるジェネレータが利用可能です。その多くはオープンソース コミュニティによって作成されたものです。よく使用されるジェネレータには、generator-angular、generator-backbone、generator-ember があります。
最新バージョンの Node.js をインストールしたら、最も近いターミナルに移動して次のコマンドを実行します。
$ npm install -g yo
これで、これで Yo、Grunt、Bower をコマンドラインから直接実行できるようになりました。yo
を実行すると、次のようになります。
ポリマー発生器
前述したように、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 要素と新しいジェネレータを使用して、シンプルなブログを作成します。
まず、ターミナルに移動し、新しいディレクトリを作成し、mkdir my-new-project && cd $_
を使用してそのディレクトリに移動します。次のコマンドを実行して、Polymer アプリを開始できるようになりました。
$ yo polymer
これにより、Bower から最新バージョンの Polymer が取得され、ワークフローの index.html、ディレクトリ構造、Grunt タスクが土台になります。アプリの準備ができるまでコーヒーを飲んでみませんか?
では、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>
これには以下のものが含まれます。
- カスタム要素のボイラープレート コード。これにより、カスタム DOM 要素タイプ(例:
<post-element>
)をページで使用できます - 「ネイティブ」クライアントサイド テンプレート用のテンプレート タグと、要素のスタイルをカプセル化するためのスコープ スタイルのサンプル
- 要素の登録のボイラープレートとライフサイクル イベント。
実際のデータソースの操作
ブログには、新しい投稿を書いたり読んだりする場所が必要です。ここでは、実際のデータサービスの操作のデモを行うため、Google Apps スプレッドシート API を使用します。これにより、Google ドキュメントを使用して作成したスプレッドシートのコンテンツを簡単に読むことができます。
設定してみましょう。
ブラウザで(この手順では Chrome を推奨)、この Google ドキュメントのスプレッドシートを開きます。次のフィールドにサンプルの投稿データが含まれています。
- ID
- タイトル
- 投稿者
- Content
- 日付
- キーワード
- (作成者の)メール
- スラッグ(投稿のスラッグ URL 用)
スプレッドシートのコピーを作成するには、[ファイル] メニューから [コピーを作成] を選択します。コンテンツの編集はもちろん、投稿の追加や削除もご自由に行うことができます。
もう一度 [ファイル] メニューに移動し、[ウェブに公開] を選択します。
[公開を開始] をクリックします。
[公開データへのリンクを取得] で、最後のテキスト ボックスから、表示された URL の key の部分をコピーします。たとえば、次のようになります。https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0
コピーしたキーを、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
この URL をブラウザに貼り付けて移動すると、JSON バージョンのブログ コンテンツを表示できます。URL をメモして、このデータの形式を再確認し、後で画面に表示するには、URL を反復処理する必要があります。
ブラウザに表示される JSON 出力は少し複雑に見えるかもしれませんが、心配は無用です。本当に必要なのは投稿のデータのみです。
Google spreadsheets API は、ブログ スプレッドシートの各フィールドに特別な接頭辞 post.gsx$
を付けて出力します。たとえば、post.gsx$title.$t
、post.gsx$author.$t
、post.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
をインストールします。
このユーティリティを取得したら、以下を使用して 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 ]] のインスタンスを作成して維持します(存在する場合)。
ここで、現在の [[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>
ボーナス これで、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>
このことから得られたものを見てみましょう。
素晴らしい!
ボイラープレート コードの記述、依存関係の手動ダウンロード、ローカル サーバーやビルド ワークフローの設定について心配することなく、比較的短期間で、複数のウェブ コンポーネントで構成されるシンプルなアプリケーションを作成しました。
アプリケーションを最適化する
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
を実行します。製品版のアプリがビルドされ、リリースできるようになります。試してみましょう。
完了しました。
行き詰まった場合は、事前構築済みの 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 に投稿してください。このジェネレータの機能向上について、ご意見、ご要望などございましたら、ぜひお聞かせください。