Polymer SHOPデモ(#Polymer 2.0 preview)を読んでみた
追記:最新のPolymer CLI(1.3.0)では、Polymer2.0バージョンのShopデモがそのままダウンロードできます。
はじめに
Polymer 2.0のECのデモアプリケーション"SHOP(Polymer 2.0対応)"が公開されていたので、自分のプロジェクトをPolymer 2.0で開発するために空き時間に少しずつソースコードを読んでみました。
SHOPデモ(以下SHOP)とは、Polymerチームが開発者向けに提供している本格的でモダンなECのデモアプリケーションです。SHOPは以下のような特徴があります。:
- ショッピング機能をライブラリに頼らずフル実装
- レスポンシブデザイン
- リソース読み込みタイミングの最適化
- オフライン対応
etc.
いかんせコードそのものがそれなりのボリュームなものですから、読むのにかなり難儀しました。全部読むのはちょっとという方は、記事内の図表部分にざっと目を通していただければ、アプリケーションのアーキテクチャの概略がつかめると思います。拙いメモで恐縮ですが、ソースコードの読解メモとして残します。
なお、チュートリアル〜SHOPデモにかなり難易度の開きがあるので、そもそもPolymer利用経験がないよという方は、Polymerのカンファレンスで例年使われているマテリアルデザインのシンプルなchatアプリのコードから読まれるのがいいと思います。
間違いや不明点があればコメント欄からお寄せください。どんなコメントでもいただけると嬉しいです。(そしていつか勉強会とかしたい…)=> (追記)実現しそうです
準備
実際に、ソースコードを読む前に少し時間をかけて準備をします。
デモを体験
アプリケーションの完成イメージを頭に入れるためにWeb上にホスティングされているアプリケーションにアクセスして購入フローを体験してみました。(擬似サイトなので決済しても問題ありません)
その際、以下のような点に注意して隅々まで使い込みました。フロントエンドの場合、ここの分析に時間をかけるほど後々コードを読むのが楽になるのがわかっていたので、ここは時間を惜しまず。:
- モバイルとデスクトップのデザインの違い
- 2Gや3Gといったネットワーク環境でアプリケーションの使用感を体感(レイジーロードやdom-repeatのchank化はUXにどのような効果をもたらしているか。Developer Toolで遅いネットワーク環境をシュミレートしてみます)
- オフラインに切り替えた際の動作確認
- ウォーターフローやスクリーンキャプチャでリソースの遅延読み込みを確認。(動的にインポートされるリソースが表示されないことやレイジーロードされるリソースがどのタイミングで読み込まれているか)
- 各種ブラウザやデバイスで動作を確認(polyfillがロードされていることやカートアイテムはブラウザを越えて共有されいないこと)
- クライアント側のルーティングがどうなっているかみておく
公式サイトのガイドを読む
一通りアプリケーションの全体像が把握できたら、公式サイトのCase study: the Shop app(未訳)にざっと目を通しておきます。基本的な解説がありますが、残念ながらほとんどコードを読む助けにはあまりなりませんでした。
ソースコードのダウンロード
次に、ソースコードをダウンロードしてローカル環境で起動します。ですが、Polymer CLI(v0.18.1)から直接インストールできるものは、Polymer 1.0版なので、GitHubからPolymer 2.0 previewのブランチをダウンロードする必要があります。
ダウンロードしたらプロジェクトディレクトリに移動して以下のコマンドでbower_componentsを忘れずにインストールします。
bower install
Polymer CLIを使ってローカルで起動
Polymer CLIとは?:Polymerの開発をサポートするためのコマンドラインベースの開発ツールです。アプリケーションの雛型の自動生成やビルド、ヘルプなどがバンドルされています。まだ、インストールがお済みでない方は、公式ドキュメントのPolymer CLIのガイド(未訳)を参考にしてください。
最後にPolymer CLIを使ってローカル上で起動します。
polymer serve
読解手順
読んでいきますが、以下のようにトップダウンの戦略をとり、適時ボトムアップで確認することにしました。
- ディレクトリの構成を確認
- コンポーネントの概要を把握
- ページとルーティングの構成確認
- コンポーネントの詳細(依存関係・イベント・データフローなど)
- 各コンポーネントのファイルを読む
- あとがき
ディレクトリ構成
- ディレクトリの構成を確認
- コンポーネントの概要を把握
- ページとルーティングの構成確認
- コンポーネントの詳細(依存関係・イベント・データフローなど)
- 各コンポーネントのファイルを読む
- あとがき
ディレクトリの構成は、Polymer CLIで生成されるプロジェクトをそのまま利用しています。
shop/
├ app.yaml # Google App Engineの設定ファイル
├ bower_components/ # bowerからインストールしたコンポーネント
├ bower.json # bowerの設定ファイル
├ build/ # ビルドにより生成されるリソース
├ data/ # 商品データ、商品画像、サーバーレスポンスデータ(擬似)
├ images/ # アイコンとカテゴリイメージ
├ index.html # エントリポイント
├ manifest.json # 各種プラットフォーム向けのアイコンやテーマ
├ polymer.json # ビルドコマンド用の設定ファイル
├ README.md # PolymerCLIの使い方
├ service-worker.js # サービスワーカーのコード
├ src/ # 作成したコンポーネントの置き場所
├ sw-precache-config.js # サービスワーカーの設定ファイル
└ test/ # Web Component Tester用のファイル
コンポーネント概要
- ディレクトリの構成を確認
- コンポーネントの概要を把握
- ページとルーティングの構成確認
- コンポーネントの詳細(依存関係・イベント・データフローなど)
- 各コンポーネントのファイルを読む
- あとがき
src/
が作成したコンポーネントの保存先になるので、ここがソースコード読解の中心です。何事もまずは、鳥の目で。Developer toolsやソースコードを利用してSHOPで使われているコンポーネントの概要を掴むようにしました。
まず、コンポーネントを以下のように分類して整理しました。(個人的な分類で、区分に厳密な考えがあるわけではありません。)残りのメモもこの分類に従って書いています。
Entrypoints # アプリケーションの配信のメインドキュメント(ex.index.html)
└App Shell # 各コンポーネントの司令塔(ex.shop-app)
├ Pages # 各ページで表示するビュー(ex.shop-home,shop-listなど)
├ Models # アプリケーションのデータを管理(ex.shop-category-data,shop-cart-data)
├ Blocks # 要素を組み合わせて作られた再利用可能な部品(ex.shop-list-item,shop-cart-item)
├ Elements # ボタンや入力フォームなど最小単位の要素(ex.shop-image,shop-button)
├ Styles # 共有スタイルのリソース(ex.shop-common-styles,shop-checkboxなど)
├ Access # サービス外部(DBやAPIなど)へアクセスを管理(ex.shop-analytics)
└ Utilities # ユーティリティモジュール(ex.lazy-resource)
繰り返しになりますが、後々細い内容は読んでいくので、ざっと目をとして何をしている(っぽい)かをざっと確認してまとめておきます。その際、ブラウザのDeveloper toolでカスタムタグを検索して画面のどの部分に該当するか確認します。(エラー用のコンポーネントはCSSのdisplayやvisibilityをDeveloper tool上で書き換えて表示させます)
まとめると以下の通りです。
コンポーネント | 説明 |
---|---|
App Shell | |
shop-app | ビューやルーティング、イベントなどを管理するメインのコンポーネント |
Pages | |
shop-home | トップページ |
shop-list | 商品一覧ページ |
shop-detail | 商品詳細ページ |
shop-cart | カートページ |
shop-checkout | 注文情報の入力ページ |
shop-404-warning | 404エラーページ(存在しないページへアクセス) |
Blocks | |
shop-cart-item | カート内に表示するアイテムのテンプレート(数量の変更や削除に対応) |
shop-list-item | 商品一覧画面に表示されるアイテムのテンプレート |
shop-network-warning | オフラインや接続に失敗した場合に表示される通知 |
shop-cart-modal | カートに入れた際に表示されるポップアップ |
Elements | |
shop-button | アプリケーションのボタンイメージのスタイル(スタイルモジュールとして実装) |
shop-icons | アプリケーションのアイコンセット |
shop-image | アプリケーションの画像(imgにプレースホルダーやアニメーションなどの機能を付加) |
shop-input | マテリアルデザインのinputフォームのスタイル(スタイルモジュールとして実装) |
shop-checkbox | マテリアルデザインのチェックボックスのスタイル(マテリアルデザイン) |
shop-select | マテリアルデザインのセレクトメニューのスタイル(スタイルモジュールとして実装) |
shop-snackbar | オフラインとオンラインの切り替え時に下部に表示される通知ボックス |
shop-tab | タブメニューのボタン(rippleエフェクト付) |
shop-tabs | タブメニューの選択状態を管理 |
shop-tabs-overlay | タブメニューを移動した際の処理やスタイリング |
Styles | |
shop-common-styles | コンポーネント間に共通するスタイルを定義(ここではcartやcheckoutページのサブヘッダー) |
shop-form-styles | cartやcheckoutの入力フォームで共通のスタイルを定義 |
shop-ripple-container | ユーザーのタッチに反応して波紋が生じるUIエフェクト |
Models | |
shop-category-data | カテゴリデータだけでなく表示する商品情報の管理 |
shop-cart-data | カート内のアイテムを管理するモデル |
Access | |
shop-analytics | Googleアナリティクス用へ送信するためのスクリプト |
Utilities | |
lazy-resource | トップページの初回レンダリング後に、即時には必要ないリソースを遅延ロード |
ルーティング
- ディレクトリの構成を確認
- コンポーネントの概要を把握
- ページとルーティングの構成確認
- コンポーネントの詳細(依存関係・イベント・データフローなど)
- 各コンポーネントのファイルを読む
- あとがき
各コンポーネントの概要と合わせて、アプリケーション全体のルーティング構造も確認します。app-route
を読み解く際に役立ちます。
/ # root(トップーページへ)
│
├ home # トップページ
│
├ list/
│ ├ mens_outerwear # カテゴリの商品一覧ページ
│ ├ ladies_outerwear # 〃
│ ├ mens_tshirts # 〃
│ └ ladies_tshirts # 〃
│
├ detail/
│ ├ mens_outerwear/:商品名 # 商品の詳細ページ
│ ├ ladies_outerwear/:商品名 # 〃
│ ├ mens_tshirts/:商品名 # 〃
│ └ ladies_tshirts/:商品名 # 〃
│
├ cart # カートページ
│
└ checkout/ # 注文の入力フォーム
├ success # 決済完了ページ
└ error # 決済エラーページ
コンポーネント詳細
- ディレクトリの構成を確認
- コンポーネントの概要を把握
- ページとルーティングの構成確認
- コンポーネントの詳細(依存関係・イベント・データフローなど)
- 各コンポーネントのファイルを読む
- あとがき
4.と5.は行ったり来たりするので順番はないのですが、まずソースコードを参照しながら各コンポーネント間の関係の把握につとめます。依存関係・イベント・データフローなどがPolymerアプリケーション開発の鍵になるので、このあたりを重点的に抑えておきたいところです。ドキュメントがあればいいのですが、あいにく簡単な説明が公式サイトにあるだけなので、ドキュメントに相当するものを作りながら整理しました。以下が明らかにしたかったところです。:
- インポートの依存関係
- イベントの発生とそのハンドリング
- データフロー
リソースの依存関係
通常、各コンポーネントに依存するリソースを個々のファイルのヘッダー部分にタグを使って静的に記述します。(index.htmlは絶対パスで指定する必要があります。)”通常”と書いたのは、以下二つのケースのようにリソースが非同期に読み込まれる可能性があるためです。
- 優先度の低いリソースをレイジーリソースとして遅延インポート
- リソースが必要になったタイミングでインポート
今回のSHOPでも二つのテクニックを使い、パフォーマンスを向上させています。具体的にそれぞれ以下のように使っています。
- shop-listやshop-detail、shop-checkoutのように、ページの基本的となるカスタム要素は、ページリンクがクリックされリクエストがあった場合に(その依存関係も含め)インポートします。これによってデフォルトページshop-homeのが素早く表示されます。
2.優先度の低いリソースをlazy.resources.html
にまとめて、Polymer.RenderStatus.afterNextRenderのコールバックに指定することで、初期の描画ですぐに必要のないリソースを遅延読み込みしています。具体的には、タブメニューやポップアップなどがレイジーインポートの対象になります。ブラウザのDeveloperToolでキャプチャをとるなどして確認してください。
これらを踏まえて依存関係を整理したのが、以下のテーブルです。スタイルモジュール(<style include="xxx">
)についてもわかるようにしています。コードを読み進める際に適時参照します。
イベント
アプリケーション内のイベントを追いかけると、各コンポーネントで発生したカスタムイベントをバブリングを活用してアプリケーションシェルが中央集権的に管理しているようなので、カスタムイベントとハンドラを整理しながら確認します。
SHOPのイベント処理機構は大いに参考になる設計です。shop-appは、デザインパータンでいうところのObserverやMediatorに相当する役割を担い、個々のコンポーネントで発生したイベントを受信して処理を実行します。特定の子の観察は行うことがありません。また、各コンポーネントの視点からも、イベントの処理を委譲することで、ハンドラーの重複を回避したり、各コンポーネントに複雑なロジックが散在せずに済むといった利点があります。
ただし、カスタムイベントを使う場合に注意すべき点があります。それは、標準のイベントとは違い、カスタムイベントをバブリングさせてShadow DOMの境界を越えて扱う場合には、composed
というフラグの指定が必要だということです。公式ドキュメント
中規模以上のアプリケーションを開発する際は、この辺りのイベント処理はドキュメントとして管理しておく方がいいと思いました。(iron-components-pageのようなツールでドキュメントでなくても)最低限流れが把握できるようにフローをまとめておくと保守やデバッグの際に大いに助けになるはずです。
データフロー
データフローは最大の難関でした。主にデータバインディングやオブザーバーによってコンポーネント間に依存関係が生じますので、これを読み解いていく必要があります。
まず、データフローはどんな状況で発生するのか復習します。
あとは、主要なアプリケーションデータを列挙し、カーリー({{}}
や[[]]
)を頼りにデータフローの向きや場所を捉えたり、オブザーバーに指定されたパスを頼りに地道に解読しています。
アプリケーション内で中心となるデータは以下の通りです。:
- ルーティング情報
- カテゴリ/商品情報
- カート内の商品
- オンラインステータス
- 情報の取得失敗
以下の図は、私が読む際にまとめたものです。データの変更の契機となるイベントから、波及していく様子を図示したつもりです。データフローが思いの他複雑に入り組んでいる様子がわかると思います。(注意:メモを元にざっと整理しただけなので、厳密なデータの流れは示していない可能性があります。ご自身でコードを追って確認してください。)
このようにデータフローの構造は、アプリケーションの複雑性が増すにつれ依存関係が拡大し把握が困難になります。当然保守やデバッグも困難です。そこで重要なのがFluxやReduxのようなフロントエンドのデータフローを管理する考えです。
この辺りは、Polymerによるアプリケーション開発時のデータフローの問題として機会を改め整理したいと考えています。
ファイルを読む
- ディレクトリの構成を確認
- コンポーネントの概要を把握
- ページとルーティングの構成確認
- コンポーネントの詳細(依存関係・イベント・データフローなど)
- 各コンポーネントのファイルを読む
- あとがき
上記の整理を踏まえて、ブラウザのDeveloper Toolsエディターのジャンプ機能やシェルコマンドを駆使して、上から下に下から上に地道に読んでいきます。不明な点や曖昧な点はドキュメントの該当箇所で確認します。読みながら、キーポイント、主要なプロパティ・メソッド・イベントなどを箇条書きでメモしておきました。
SHOPのソースコードを読み込むことで、ここまで登場したテクニックの他に以下のようなテクニックを身につけることができました。この先、ソースコードの冒険を続けるかどうかの目安にしてください。得られるものとコストはトレードオフ。。。
- Polymer.Deboncerを使った任意のタイミングで非同期タスクを実行する
- 各種コールバックを効果的に使う方法
- iron-layoutを使って高度なレスポンシブレイアウト(画面が小さくなったときに表示するデザインだけでなくコンテンツも切り替え)
- ローカルストレージでセッション情報を管理
- 状態に応じた表示/非表示を効率的に切り替える
- カスタム要素の子の間で親を介してデータをバインド(Mediatorパターン)
- dom-repeatで大規模リストを効率的に描画する方法
- mixinを効果的に使ってカスタム要素に機能を取り入れる
- 外部ライブラリと連携させる方法
etc.
以下、本当に汚くて恐縮ですがソースコードにメモとして残したコメントからの抜粋です。ドキュメントや関連情報へのリファレンスを付けました。
Entrypoint
index.html
- webcomponents-loader.jsを読み込めば、Polymer 1.0のように環境に応じてpolyfillを読み込むようなコードは自分で書く必要なし。
詳細 => 公式ドキュメント - インポートするのは、shop-app(シェル)だけ絶対バスで指定
- bodyに適用したスタイルを子に適用。ドキュメントレベルでカスタム要素を使用した場合は、親要素に適用されるスタイル情報が全て継承される。
詳細 => 公式ドキュメント - unresolved属性へのスタイリングは、polyfillを使用するブラウザ向けのFOUC対策。テキストカラーを赤に変えて実験した。なお、unresolvedは、shop-appのreadyコールバックで削除される。
FOUC問題の詳細 => [HTML5 Rocks](https://www.html5rocks.com/ja/tutorials/webcomponents/customelements/#FOUC prevention using :unresolvedを参照) - User Timing APIの機能検出を行なった上で、ブラウザでパフォーマンスを記録する。機能検出にはModinier.jsのようなライブラリを利用してもよい。
App Shell
shop-app
ビューやルーティング、イベントなどを管理するメインのコンポーネント
-
shop-home.html
はデフォルトページなので先行読み込み - CSS変数を使ってデフォルトカラーを定義。外部ファイルにテーマ関連のスタイルをまとめてもよい。
- hidden属性で表示/非表示を切り替え(カートアイテムのバッジとタブに使用)
- カスタムCSSミックスインを通じてshop-tabsやshop-tabに外部からスタイリング
- カートのバッチは自作しなくても同様のpaper-Elemnetがある
参照 => Polymer Element -
<app-location>
はブラウザのURLのステータスとshop-appを関連づける(内部的にはwindow.location〜iron-location〜app-location〜app-route) - shop-app内の
<app-route>
はトップレベルのルーティングモジュール -
<iron-media-query>
CSSのメディアクエリとデータバインディング可能。画面サイズが767px以下になるとquery-matchesがtrueになる。 - shop-cart-dataからバインドされたデータをshop-cartへ(Mediatorパターン)
- タブメニューは、大きな画面ではレイジーインポートされ、小さな画面ではインスタンス化さえされない。dom-ifで実現している。またdom-ifのおかげで再描画時の負荷を軽減できる。
詳細 => 公式ドキュメント - iron-pagesとiron-selectorでページの切り替えを管理
- shop-appが登録される前にパフォーマンス用のログを残す(あとで差分をとって分析するため)
- constructorはPolymer 1.0のcreatedに相当
- ready内でindex.htmlで設定したFOUC対策としてindex.htmlで設定していたresolvedへのスタイルを解除
- readyのタイミングはワンタイムの初期化に使うことができ、実行のタイミングは以下の通り。プロパティが設定され、ローカルDOMの初期化が完了したとき。
参考 => 公式ドキュメント - 各種カスタムイベントリスナーについては、前のセクションのイベントのフローを参照
- 重要でないリスナーはPolymer.RenderStatus.afterNextRenderを使って登録を遅らせる。
解説 => 公式ドキュメントAPI - shop-home以外はページリクエストのタイミングでリソースを読み込む。pageプロパティはオブザーバーで変更を監視して、各ページにリクエストがあったタミングでimportHrefで動的にリソースをインポートする。
- デフォルトページを最初に表示する際に、すぐに必要のないリソースをlazy-resouecesにまとめてimportHrefで遅延読み込み。
- サービスワーカーは初回描画には関係ないので、あとで遅延させて登録。
- Navigatorオブジェクトからステータスを取得して、オフラインになった時はスナックバーに表示。
- shop-listページでは商品ページへ移動前にスクロール位置を記憶しておく。(カテゴリ移動時にはリセット)
- 算出(computed)バインディングを使って効果的に表示をコントロール
Pages
shop-cart
カートページ
- スペース区切りで複数のスタイルモジュールを読み込んでいる。(スタイル情報が競合する場合は、あとに指定したもので上書きされる)
参考:=> Qiita - カスタムインベントがshop-appにバブリングされるように
composed: true
の指定忘れずに。 - bubblesとcomposedの違いは以下参照
参照 => MDN
shop-checkout
注文情報の入力ページ
- スペース区切りで複数のスタイルモジュールを読み込んでいる。(スタイル情報が競合する場合は、あとに指定したもので上書きされる)
参考=> Qiita - shop-cartからshop-appを経由して[[cart]]がバインドされている(Mediatorパターン)
- プロパティvisibleはiron-pagesのselectedAttributeに設定されており、ビューが選択されたに自動的に付与される。今回はvisibleが付与されたタイミングでオブザーバーメソッド_visibleChangedを実行する。
- サーバーからレスポンスを待つ間paper-spinnerを表示するためにwaitingプロパティを利用
- 複数のパスを監視するためにobserversを利用。routeの変化に応じて_updateStateを実行
- _submitでバリデーションを通過すると、iron-form要素のon-iron-form-presubmitイベントを発火
- Polymer.DebouncerとPolymer.Asyncを使ってプログレスUIのテスト用にタイマーで1秒後に擬似レスポンス
参考 => Qiita - _pushStateメソッドでは、observableな変更を発生させるためにPolymerのsetterを利用して関連するオブザーバーを呼び出す(
this.set('route.path', state);
) - _resetメソッド内でread-onlyのプロパティwaitingを設定したいので専用のsetterを使う
this._setWaiting(false);
- _validateFormメソッドでは、マークアップのpatternで指定した条件でフォームをバリデーション
- ここでもa11yアナウンスの処理をカスタムイベントのバブリングを利用してshop-appに委譲
- カスタムインベントがshop-appにバブリングされるように
composed: true
の指定忘れずに。 - bubblesとcomposedの違いは以下参照
参照 => MDN - _didReceiveResponseメソッドは、サーバーから受信したレスポンスのステータスをチェックして決済完了画面やエラー画面に移行する。iron-formのon-iron-form-responseイベントとして実装されている。
詳細 => Polymer Element
shop-detail
商品詳細ページ
- スペース区切りで複数のスタイルモジュールを読み込んでいる。(スタイル情報が競合する場合は、あとに指定したもので上書きされる)
参考 => Qiita - shop-image::beforeのwidthは%指定なので親要素の”幅”を基準に指定
- セレクタ.detail[has-content]は、商品データがバインドされたときにisDefinedがtureになって適用される
- 一覧ページでは小さい画像を使い、詳細ページでは大きい画像を使う
- 算出バインディングを使って、商品データが読み込まれたタイミングでisDefinedがtrueになる
- ネットワーク接続環境が悪くて描画できない場合にはエラーメッセージを表示するが、のfailureはshop-category-dataからバインドされている
- itemは選択された商品データで、shop-category-dataからバインドされる
- routeはshop-appからバインドされる
- routeDataはshop-category-dataから情報を取得するのに利用されapp-routeからバインドされる
- visibleは、iron-pagesのselectedAttributeに設定されていて、ビューが選択されたに自動的に付与される。
- offlineはapp-shopでブラウザのNavigatorオブジェクトからステータスを取得し、shop-network-warningへ流れていく。またブザーバーでlistページで再リクエスト処理を実装。
- failureはshop-category-dataからバインドされる
- observersを使って複数のプロパティを監視対象とする。今回はitemとvisible
- カスタムインベントがshop-appにバブリングされるように
composed: true
の指定忘れずに。 - bubblesとcomposedの違いは以下参照
参照 => MDN - Polymer.DebouncerとPolymer.Asyncを使って商品データやカテゴリの情報をshop- appにカスタムイベントを使って通知
詳細 => Qiita - _addToCartメソッドはadd to Cartボタンのハンドラ
- _tryReconnectメソッドは、以下の場合にshop-category-dataのrefreshを呼び出す。1)TRY AGAINを押した場合 2)オフラインからオンラインに変化した場合
- _offlineChangedメソッドは、ブラウザのオフテータスがオフラインからオンラインに変化したとき再接続するために実行される
shop-home
トップページ
- dom-repeatのcategoriesは、shop-category-dataからshop-appを経由してバインドされている。(Mediatorパターン)
- テンプレートに指定されたstrip-whiteは以下参照
参考 => Qiita
カテゴリイメージには、ブラーエフェクトのかかった画像を使用 - visibleは、iron-pagesのselectedAttributeに設定されていて、ビューが選択されたに自動的に付与される。
- カスタムインベントがshop-appにバブリングされるように
composed: true
の指定忘れずに。 - bubblesとcomposedの違いは以下参照
参照 => MDN
shop-list
商品一覧ページ
-
iron-layoutを使ってグリッドレイアウト
参考 => Polymer Element -
dom-repeatを使った大きなリスト(今回は20件に満たないが増える可能性がある)は、initial-count(chank化)やtargetFramerateを使って効率的に描画する
参考 => -
ベンダーprefixはauto-prefixerを使ってビルド時にまとめて付与
-
shop-list-itemはaで囲い検索エンジンのクローラーが巡回できるようにする
-
shop-network-warningのfailureは、shop-categoty-dataから受けとたったデータをshop-network-warningにバインド(Mediatorパターン)
-
プロパティvisibleはiron-pagesのselectedAttributeに設定されており、ビューが選択されたに自動的に付与される。
-
observersはページを表示中にカテゴリの変更があった場合に実行
-
connectedCallbackコールバックはPolymer 1.0のattachedメソッドに相当(仕様に合わせて変更された)カスタム要素がドキュメントに追加されたタイミングで実行される。
参照 => Polymer Doc -
disconnectedCallbackはPolymer 1.0のdetachedメソッドに相当(仕様に合わせて変更された)
-
_categoryChangedメソッドでは、カテゴリを正しく指定せずにアクセスした場合は、404ページへ遷移するようにカスタムイベントshow-invalid-url-warningを使ってshop-appへ通知
shop-404-warning
404エラーページ(存在しないページへアクセス)
- URLに../Shoesのように存在しないURLへを指定すると表示される。iron-pagesのfallback-sectionとして実装されている。
Blocks
shop-cart-item
カート内に表示するアイテムのテンプレート(数量の変更や削除に対応)
- テンプレートに指定されたstrip-whiteは以下参照
参考 => Qiita -
.name
に@apply --layout-flex-auto;
を指定してflex-basisをautoにflex-growとflex-shrinkを1に設定。
参考 => Flexbox解説 - ネイティブのselect要素にデータバインディング
参照 => 公式ドキュメント - カートアイテム削除ボタンはon-tapではなく、on-click理由は、以下の通り、「Use on-click instead of on-tap to prevent the next cart item to be focused」
- カスタムインベントがshop-appにバブリングされるように
composed: true
の指定忘れずに。 - bubblesとcomposedの違いは以下参照
参照 => MDN
shop-list-item
商品一覧画面に表示されるアイテムのテンプレート
-
shop-image::before
のpadding-topは%で指定されているが、親要素の”幅”を基準に算定される
shop-network-warning
オフラインや接続に失敗した場合に表示される通知
- shop-common-stylesからshop-detail/shop-listを通じてshop-network-warningへ[hidden]{display:none !important}が継承される
- 再接続にカスタムイベントが使用されている。
shop-cart-modal
カートに入れた際に表示されるポップアップ
- opened属性を使って表示/非表示を切り替え
- iron-overlay-behaviorをミックスインとして実装
参考 => Polymer Elemnet - ワンタイムの初期化にはreadyコールバックを使用
-
this.classList.add('opened');
のように、class
ではなくclassList
を使うこと
参照 => 公式ドキュメント
Elements
shop-button
アプリケーションのボタンイメージのスタイル(スタイルモジュールとして実装)
- prefixer関連は、ビルドのタスクに組み込んで
auto-prefixer
を使って記述する - 小さい画面で下部に幅いっぱいに表示されるボタンは属性セレクタresponsiveを使って実装
shop-checkbox
マテリアルデザインのチェックボックスのスタイル(マテリアルデザイン)
- 自作しなくても要素が使える。
参考 =>Polymer Element - CSSのopacityプロパティを使って表示/非表示を切り替え
shop-icons
アプリケーションのアイコンセット
shop-image
アプリケーションの画像(imgにプレースホルダーやアニメーションなどの機能を付加)
- CSSミックスインを使って、カスタム要素の利用側でミックスインを介してスタイリングできるようにする
- プレースホルダーが設定されている場合は、読み込みが完了した時点でobserverを使って_placeholderImgChangedを呼び出し画像を差し替える
shop-input
マテリアルデザインのinputフォームのスタイル(スタイルモジュールとして実装)
- 自作しなくても要素が使える。
参考 =>Polymer Element
shop-select
マテリアルデザインのセレクトメニューのスタイル(スタイルモジュールとして実装)
shop-snackbar
オフラインとオンラインの切り替え時に下部に表示される通知ボックス
- Polymer.DebouncerとPolymer.Asyncを使って時間が経つと画面外に消えるように
参考 => Qiita - Polymer 2.0のDebouncer.debounce()は呼び出す度に、アクティブなタスクはキャンセルされるため。Polymer.dom.flush();は不要と思われる。
shop-tab
タブメニューのボタン(rippleエフェクト付)
- テンプレートに指定されたstrip-whiteは以下参照
参考 => Qiita - 自作しなくても要素要素が使える。
参考 => Polymer Element
shop-tabs
タブメニューの選択状態を管理
- テンプレートに指定されたstrip-whiteは以下参照
参考 => Qiita - 自作しなくても要素要素が使える。
参考 => [Polymer Element -
<slot>
を使ってカスタム要素に動的にコンテンツを挿入(Polymer 1.0では<content>
だった) - iron-selectable-behaviorをミックスインとして実装
shop-tabs-overlay
タブメニューを移動した際の処理やスタイリング
- テンプレートに指定されたstrip-whiteは以下参照
参考 => Qiita - 自作しなくても要素要素が使える。
参考 => [Polymer Element - _targetChangedメソッドないで、
this._lastTarget.classList
を使うのはclass
が使えないため
参照 => 公式ドキュメント
Styles
shop-common-styles
コンポーネント間に共通するスタイルを定義(ここではcartやcheckoutページのサブヘッダー)
- shop-cartページやshop-checkoutページでは、app-headerの下にサブヘッダーがある。
- スタイルモジュール間のスタイルルールが競合するので要素セレクタ(header)は詳細度的にちょっと心配
参考 => Qiita
shop-form-styles
cartやcheckoutの入力フォームで共通のスタイルを定義
- スタイルモジュール間のスタイルルールが競合するので要素セレクタ(h2)は詳細度的にちょっと心配
参考 => Qiita
shop-ripple-container
ユーザーのタッチに反応して波紋が生じるUIエフェクト
- paper-ripple-behaviorをミックスインとして実装
参考 => Polymer Elemnts - テンプレートに指定されたstrip-whiteは以下参照
参考 => Qiita - readyではワンタイムの初期化を行う
Models
shop-cart-data
カート内のアイテムを管理するモデル
- カート内の商品はapp-localstorage-documentを使いローカルストレージに保存して永続化
参照 => Polymer Element - オブジェクトや関数型のプロパティの初期化に関数の戻り値を指定するのは、インスタインスをユニークにするため。
- 配列を深く監視にはパスにsplicesを指定しPolymerの配列変更メソッドを利用することで監視可能。
参照 => ドキュメント - addItem、setItem、clearCartはshop-appのイベントリスナーから呼び出されるのでpublicに実装
shop-category-data
カテゴリデータだけでなく表示する商品情報の管理
- カテゴリデータオブジェクトがグローバル空間を汚染しないように即時関数で囲む
- カテゴリーイメージのプレースホルダーにブラーエフェクトの掛かった画像を使用
- categoryNameやitemNameはshop-detailのrouteDataからバインド
- categoriesはカテゴリデータオブジェクトの配列
- categoryは最後に選択したカテゴリのオブジェクトデータ
- itemは、取得したカテゴリの商品データ
- failureは商品情報の取得失敗時に利用(shop-network-warningでhiddenを解除する)
- XMLHttpRequestで非同期に取得(必要に応じてリトライ)
- _fetchItemsメソッド内でプロパティfailureを設定するが、read-onlyなので専用setterメソッド(_setFilure)で変更する必要あり
- XMLHttpRequestのリクエスト処理はエラーのリトライにそなえて再帰関数を使用(最大3回まで)
- Polymer.DebouncerとPolymer.Asyncを使ってsnackbarを閉じる処理を非同期に実装
詳細 => Qiita - refreshメソッドはshop-listやshop-detailから呼び出される可能性があるので、publicで実装
Access
shop-analytics
Googleアナリティクス用へ送信するためのスクリプト
- 他のJSライブラリもこのようにhtmlファイル化することで利用可能
-
is
がない!
Utilities
lazy-resources
*トップページの初回レンダリング後に、即時には必要ないリソースを遅延ロード *
- レイジーリソースの解説は、https://www.polymer-project.org/2.0/toolbox/prpl参照
- shop-homeはデフォルトページなので先行読み込み
- 404やネットワークエラーはいつでも発生しうるので、最初の描画が終わったタイミングでshop-network-warningやshop-404-warningをインポートしておく。
コンポーネント以外のファイル(未了)
app.yaml
bower.json
manifest.json
polymer.json
service-worker.js
sw-precache-config.js
あとがき
- ディレクトリの構成を確認
- コンポーネントの概要を把握
- ページとルーティングの構成確認
- コンポーネントの詳細(依存関係・イベント・データフローなど)
- 各コンポーネントのファイルを読む
- あとがき
アプリケーションが複雑なので読むのは苦労しますが、このように実用レベルのサンプルを公開してくれているのは本当に助けになります。優れたデモを製作してくれた作者に感謝です。一方で「データフロー」のセクションで解説したように、Polymerを使った大規模プロジェクトの課題も見えてきました。
それこそMVVMやFluxやReduxという技術が必要とされる理由です。機会を改めこの辺りを整理したいと考えています。(幸いPolymer Reduxという心強いライブラリがあります!)
稚拙な長文をここまでお読みいただきありがとうございました。
なにっ!足りませんと?
- Google Advocate Eiji Kitamura氏による認証機能付きSHOPを読む(ありがたい!)
- Polymerのカンファレンスでサンプルとして使われたchatアプリ
- 二つ目のデモNEWSアプリの方も読む
- Polymer Summit 2016のデータフローに関しての解説動画