PWA (Progressive Web Application) [1]
PolymerでMarkdown形式のテキストをページ毎に整形して切り替える簡単なPWAを作ってみます。
ビルドツール(Polymer CLI)を使い、 Firebaseのホスティングで確認します。
※ Polymer サンプルコード (3) SPA の続きです。
- 動作確認
- Windows 10 : ○ Chrome60 Firefox54 Edge40 IE11
- Android 7.1 : ○ Chrome60
- macOS 10.12 : ○ Safari10.0
- iOS 10.3 : ○ Safari10.0
画面イメージ
表示上は前回とほぼ同じです。
Lighthouseの結果
PWAの要件チェックツール(Lighthouse)で100点でした。chrome60からはデベロッパーツールに統合されたのでチェックしやすくなっています。
実行ステップ
1. 環境設定(Firebase)の設定
ファイルのアップロード先となるFirebaseの管理コンソールからプロジェクトを作成します。
-
プロジェクト名
に適当な名前を入力、国 / 地域
は日本を選択。プロジェクト名からプロジェクトIDが生成されるので、覚えておきます(サンプルではsample-1
と入力してプロジェクトIDはsample-1-52253
となりました)
2-A. Google Cloud Shellでの実行
Google Cloud Shellにはgitやnpm、bower、firebaseが予めインストールされていて手軽に使えます。こちらから起動して下記のコマンドで実行してください。
polymer serve
での確認も「ウェブでプレビュー」から表示することができます。
# polymer-cliのインストール(5分程度かかります)
# Cloud Shellはグローバルへのインストールが一定時間でクリアされてしまうので、ホームディレクトリ下で動かします
$ npm install polymer-cli
# polymer-cliをコマンドとして使えるようにaliasを設定
$ alias polymer="~/node_modules/polymer-cli/bin/polymer.js"
# githubからソースをダウンロード
$ git clone -b sample-1 https://github.com/howking/polymer-sample-pwa sample-1
# 作業ディレクトリに移動
$ cd sample-1
# 利用するWebライブラリを取得
$ bower install
# サーバを立ち上げて動作確認
$ polymer serve
# アップロードするファイルを構築
$ polymer build
# Firebaseにログイン(表示されたURLをブラウザで開いて認証コードを入力してください)
$ firebase login --no-localhost
# 使用するプロジェクトを指定( $ firebase list で一覧が表示されます)
$ firebase use [プロジェクトID]
# ファイルをアップロード
$ firebase deploy
2-B. Windows等のローカルマシンでの実行
パッケージ管理ソフトの準備
Git(ソース管理ツール)とnpm(Node.jsのパッケージ管理ツール)が必要です。Windowsはダイアログに沿ってインストールしてください(途中の選択肢は特に変更しなくて大丈夫です)。
コマンドの準備
コマンドプロンプトから下記を入力してBower(Webコンポーネントのパッケージ管理ツール)、Polymer CLI(Polymer操作ツール)、Firebase CLI(Firebase操作ツール)をインストールしてください(途中エラーが表示される場合がありますが、最後まで終れば大丈夫です)。
$ npm install -g bower
$ npm install -g polymer-cli
$ npm install -g firebase-tools
ビルドとファイルのアップロード
ファイル一式をこちらからダウンロード(sample-1
ブランチに切り替えて)するか、後述のサンプルコードからファイルを作成してください。
下記を入力するとビルドされ、ファイルがアップロードされます。
# githubからダウンロードする場合
$ git clone -b sample-1 https://github.com/howking/polymer-sample-pwa sample-1
# 作業ディレクトリに移動
$ cd sample-1
# 利用するWebライブラリを取得
$ bower install
# ローカルでサーバを立ち上げて動作確認 → http://localhost:8000/
$ polymer serve
# アップロードするファイルを構築
$ polymer build
# Firebaseにログイン
$ firebase login
# 使用するプロジェクトを指定
$ firebase use [プロジェクトID]
# ファイルをアップロード
$ firebase deploy
- 参考記事: firebaseでhostingするまでの備忘録
3. サイト確認
https://[プロジェクトID].firebaseapp.com/ で確認できます。
A. デバッグ方法
単純にソースを編集して更新、リロードしてもServiceWorkerによってオフラインキャッシュが使われてしまい、ページが更新されない場合があります。
Chromeの場合は、
か、
で再読み込みをしてください。
Firefoxはアドレスバーにabout:debugging#workers
を入力するとServiceWorkerがリストされるので解除してください。
サンプルコード
├── README.md
├── bower.json
├── firebase.json
├── index.html
├── manifest.json
├── my-view.html
├── pages
│ ├── view1.md
│ ├── view2.md
│ └── view3.md
├── polymer.json
└── sw-precache-config.js
-
index.html
トップページ、<my-view>
タグの呼出し元
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Polymer Sample PWA</title>
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#29549a">
<script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="/my-view.html" async>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
</head>
<body>
<my-view key="view1" pages='[["view1","Page 1"], ["view2","Page 2"], ["view3","Page 3"]]'>
<svg viewBox="0 0 24 30" fill="#eee" width="128"
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; margin: auto;">
<path d="M19 4h-4L7.11 16.63 4.5 12 9 4H5L.5 12 5 20h4l7.89-12.63L19.5 12 15 20h4l4.5-8z"/>
</svg>
<noscript>Polymer Sample PWA</noscript>
</my-view>
</body>
</html>
-
pages/view[1-3].md
マークダウンで書かれたテキスト
# こんにちわ
これは1ページ目です。
# 2ページ目
これはサンプルのテキストです。
## 見出し1
[リンク](https://www.google.com/)も書けます。
## 見出し2
**強調表示**
# こんにちわ
これは3ページ目です。
-
my-view.html
<my-view>
タグを表示するHTML
<link rel="import" href="bower_components/polymer/polymer-element.html">
<link rel="import" href="bower_components/polymer/lib/elements/dom-repeat.html">
<link rel="import" href="bower_components/app-layout/app-header-layout/app-header-layout.html">
<link rel="import" href="bower_components/app-layout/app-drawer-layout/app-drawer-layout.html">
<link rel="import" href="bower_components/app-layout/app-header/app-header.html">
<link rel="import" href="bower_components/app-layout/app-toolbar/app-toolbar.html">
<link rel="import" href="bower_components/app-layout/app-drawer/app-drawer.html">
<link rel="import" href="bower_components/iron-icons/iron-icons.html">
<link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="bower_components/paper-listbox/paper-listbox.html">
<link rel="import" href="bower_components/paper-item/paper-item.html">
<link rel="import" href="bower_components/marked-element/marked-element.html">
<dom-module id="my-view">
<template strip-whitespace>
<style>
:host {
display: block;
--app-primary-color: #29549a;
}
app-header {
background-color: var(--app-primary-color);
color: white;
}
app-header paper-icon-button {
--paper-icon-button-ink-color: white;
}
div[main-title] { margin-left: 10px; }
marked-element { margin-left: 15px; }
paper-item:not(.iron-selected) { cursor: pointer; }
</style>
<app-drawer-layout force-narrow fullbleed>
<app-drawer slot="drawer" swipe-open>
<app-header fixed>
<app-toolbar>
<paper-icon-button icon="close" drawer-toggle></paper-icon-button>
</app-toolbar>
</app-header>
<paper-listbox selected="{{key}}" attr-for-selected="key">
<template is="dom-repeat" items="[[pages]]">
<paper-item key="[[item.0]]" drawer-toggle>[[item.1]]</paper-item>
</template>
</paper-listbox>
</app-drawer>
<app-header-layout has-scrolling-region>
<app-header slot="header" reveals>
<app-toolbar>
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div main-title>My app</div>
<paper-icon-button icon="delete"></paper-icon-button>
<paper-icon-button icon="search"></paper-icon-button>
<paper-icon-button icon="close"></paper-icon-button>
</app-toolbar>
</app-header>
<marked-element>
<div slot="markdown-html"></div>
<script type="text/markdown" src="pages/[[key]].md"></script>
</marked-element>
</app-header-layout>
</app-drawer-layout>
</template>
<script>
class MyView extends Polymer.Element {
static get is() { return 'my-view' }
static get properties() { return { key: String, pages: Object } }
}
customElements.define(MyView.is, MyView)
</script>
</dom-module>
-
sw-precache-config.js
Service Worker Precacheというオフラインキャッシュの設定ファイル
module.exports = {
staticFileGlobs: [
'/index.html',
'/manifest.json',
'/bower_components/webcomponentsjs/*.js'
],
navigateFallback: 'index.html'
};
-
manifest.json
Webアプリケーションの定義ファイル
{
"name": "Polymer Sample PWA",
"short_name": "Polymer Smpl",
"start_url": "/",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#29549a",
"icons": [{
"src": "https://www.polymer-project.org/images/logos/p-logo-192.png",
"sizes": "192x192",
"type": "image/png"
},{
"src": "https://www.polymer-project.org/images/logos/p-logo-512.png",
"sizes": "512x512",
"type": "image/png"
}]
}
-
bower.json
使用するWebコンポーネントライブラリの設定ファイル
{
"name": "polymer-sample-pwa-1",
"dependencies": {
"polymer": "Polymer/polymer",
"webcomponentsjs": "webcomponents/webcomponentsjs",
"app-layout": "PolymerElements/app-layout",
"iron-icons": "PolymerElements/iron-icons",
"paper-ui-elements": "PolymerElements/paper-ui-elements",
"marked-element": "PolymerElements/marked-element"
}
}
-
polymer.json
Polymer CLIの設定ファイル
{
"entrypoint": "index.html",
"shell": "my-view.html",
"extraDependencies": [
"manifest.json",
"bower_components/webcomponentsjs/*.js"
],
"builds": [{
"name": "default",
"preset": "es5-bundled"
}]
}
-
firebase.json
Firebaseの設定ファイル(Hostingのみ)
{
"hosting": {
"public": "build/default"
}
}
各行の説明
前回記載した部分は省きます。
index.html
<html lang="ja">
...
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#29549a">
<html>
の言語指定や<meta>
のテーマカラーを設定しないとLighthouseで点数が下がります。
<link rel="import" href="my-view.html" async>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
<link async>
で非同期読み込みとし、レンダリングをブロックさせないようにしますが、ページ読み込み時には一瞬素のHTMLが表示されることになります。
<script>
で対応しているブラウザ向けにserviceWorker
を有効化します。
<my-view key="view1" pages='[["view1","Page 1"], ["view2","Page 2"], ["view3","Page 3"]]'>
<svg viewBox="0 0 24 30" fill="#eee" width="128"
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; margin: auto;">
<path d="M19 4h-4L7.11 16.63 4.5 12 9 4H5L.5 12 5 20h4l7.89-12.63L19.5 12 15 20h4l4.5-8z"/>
</svg>
<noscript>Polymer Sample PWA</noscript>
</my-view>
作成する<my-view>
にkey
とpages
のパラメータを渡します。Arrayを渡すには'
シングルクォーテーションでかこったJSON文字列を指定します。
素のHTMLが表示される部分に<svg>
でPolymerのロゴ(ここからコピー)を表示し、またPolymer(Webコンポーネント)に対応していないブラウザ向けにはLighthouseで点数が下がるので<noscript>
タグを入れておきます。
my-view.html
<paper-listbox selected="{{key}}" attr-for-selected="key">
<template is="dom-repeat" items="[[pages]]">
<paper-item key="[[item.0]]" drawer-toggle>[[item.1]]</paper-item>
</template>
</paper-listbox>
index.html
から渡された[[pages]]
パラメータ([["view1","Page 1"], ["view2","Page 2"], ["view3","Page 3"]]
)をメニューに表示します。配列の各要素は.0
のようにインデックスの数を指定します。
<marked-element>
<div slot="markdown-html"></div>
<script type="text/markdown" src="pages/[[key]].md"></script>
</marked-element>
index.html
から渡された[[key]]
パラメータ(view1
)をmarked-element
にパスとして指定してMarkdownのコンテンツを読み込ませます。
<script>
class MyView extends Polymer.Element {
static get is() { return 'my-view' }
static get properties() { return { key: String, pages: Object } }
}
customElements.define(MyView.is, MyView)
</script>
ページの一覧や内容、その切り替えは 全てHTML側に記述したので 、単純にタグ(エレメント要素)の宣言だけとなっています。
sw-precache-config.js
module.exports = {
staticFileGlobs: [
'/index.html',
'/manifest.json',
'/bower_components/webcomponentsjs/*.js'
],
navigateFallback: 'index.html'
};
ビルド時に自動でオフラインキャッシュに必要なServiceWorker用のファイル(/serviceWorker.js
)が生成されますが、いくつか明示的に指定する必要があるファイルをsw-precacheライブラリを使って指定します。
manifest.json
{
"name": "Polymer Sample PWA",
"short_name": "Polymer Smpl",
"start_url": "/",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#29549a",
"icons": [{
"src": "https://www.polymer-project.org/images/logos/p-logo-192.png",
"sizes": "192x192",
"type": "image/png"
},{
"src": "https://www.polymer-project.org/images/logos/p-logo-512.png",
"sizes": "512x512",
"type": "image/png"
}]
}
「ホーム画面へ追加」を実現する為に使用します。
ホーム画面で表示される名前のshort_name
は12文字以内、background_color
やtheme_color
、icons
も指定しないとLighthouseで点数が下がります。
bower.json
"dependencies": {
"polymer": "Polymer/polymer",
"webcomponentsjs": "webcomponents/webcomponentsjs",
"app-layout": "PolymerElements/app-layout",
"iron-icons": "PolymerElements/iron-icons",
"paper-ui-elements": "PolymerElements/paper-ui-elements",
"marked-element": "PolymerElements/marked-element"
}
Bowerで使用するバッケージ設定ファイルです。app-layout
パッケージで<app-drawer-layout>
や<app-header>
等が使えるようになり、paper-ui-elements
のエイリアスパッケージで<paper-icon-button>
や<paper-listbox>
等が使えるようになります。
ちゃんとパッケージとバージョンを指定するには下記のようにします(2017/05にリリースされたv2.0向け)
"dependencies": { "polymer": "Polymer/polymer#^2.0.0", "webcomponentsjs": "webcomponents/webcomponentsjs#^1.0.0", "app-layout": "PolymerElements/app-layout#^2.0.0", "iron-icons": "PolymerElements/iron-icons#^2.0.0", "paper-icon-button": "PolymerElements/paper-icon-button#^2.0.0", "paper-listbox": "PolymerElements/paper-listbox#^2.0.0", "paper-item": "PolymerElements/paper-item#^2.0.0", "marked-element": "PolymerElements/marked-element#^2.0.0" }
polymer.json
"entrypoint": "index.html",
"shell": "my-view.html",
PWAのPRPLパターンとして、index.html
をトップページ、my-view.html
をApp Shellとして設定します。
"extraDependencies": [
"manifest.json",
"bower_components/webcomponentsjs/*.js"
],
前述のオフラインキャッシュに必要なファイルを追加の依存関係として設定します。
"builds": [{
"name": "default",
"preset": "es5-bundled"
}]
Polymer CLIでビルドする種類を指定します。現状では
es5-bundled
es6-bundled
es6-unbundled
の3種類が定義されていて、es5-bundled
はES5への変換、各ファイルをまとめつつminify(圧縮)します。
FirebaseのホスティングはHTTP/2に対応しているので、IE11等に対応する必要がなければes6-unbundled
が適しているのかもしれません。
firebase.json
"public": "build/default",
アップロードするトップディレクトリを指定します(Polymer CLIのデフォルトはbuild/default
にビルドされます)。
最後に
コメント、編集リクエスト歓迎
Polymer サンプルコード (5) PWA [2] へ続きます。
以上