Polymer サンプルコード (5) PWA [2]

# PWA (Progressive Web Application) [2]

<img src="https://www.polymer-project.org/images/logos/p-logo.png" height="26"> [Polymer](https://www.polymer-project.org/)でMarkdown形式のテキストをページ毎に整形して切り替えつつ、編集もできる[PWA](https://developers.google.com/web/fundamentals/getting-started/codelabs/your-first-pwapp/?hl=ja)を作ってみます。
ビルドツール([Polymer CLI](https://www.polymer-project.org/2.0/docs/tools/polymer-cli))を使って、<img src="https://firebase.google.com/_static/images/firebase/touchicon-180.png" height="26"> [Firebase](https://firebase.google.com/)の[リアルタイムDB](https://firebase.google.com/docs/database/?hl=ja)、[認証](https://firebase.google.com/products/auth/)、[ホスティング](https://firebase.google.com/docs/hosting/?hl=ja)を使用します。

※ [Polymer サンプルコード (4) PWA [1]](http://qiita.com/howking/items/27fec1598558045b74d1) の続きです。

<dl>
  <dt>動作確認</dt>
  <dd>Windows 10 : ○ Chrome60 Firefox56 × Edge40 IE11</dd>
  <dd>Mac 10.3 : ○ Chrome60 Safari10.1</dd>
  <dd>Android 7.1 : ○ Chrome60</dd>
</dl>

## 画面イメージ

![qiita-fb-pwa2.gif](https://qiita-image-store.s3.amazonaws.com/0/90678/11976ca3-1c39-1f8f-5a46-e3e93f5b553f.gif)

[:globe_with_meridians: デモサイト (Firebase)](https://sample-2-8952f.firebaseapp.com/)
**・Googleでログインすれば編集できますが、いたずら無しでお願いします**
**・定期的にデータクリアしてます**

### 機能

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90678/f00ace61-359e-3e2c-85c8-1bbd589d91ab.png)

- Lighthouse PWA 100点
- Markdownの表示([gfmgithub-markdown-css](https://github.com/sindresorhus/github-markdown-css)、コードハイライト)
- フレンドリーURL(OGPは未対応、GoogleBotもちゃんと描画してくれてないみたい)
- リアルタイム編集(ユーザ認証、サニタイズ)

## 実行ステップ

ビルドツール(Polymer CLI)やFirebaseのプロジェクトの準備は[前回](http://qiita.com/howking/items/27fec1598558045b74d1)と同じです。

### 1. 認証とリアルタイムDBへデータ設定

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90678/146e9c39-a271-dd5c-92ae-f2c400363054.png)

Firebaseの[管理コンソール](https://console.firebase.google.com/)の左メニューから「Authentication」を選択し、「ログイン方法」→ログインプロパイダで「Google」を「有効」にします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90678/67aa286a-5c26-4eb0-105b-44dfd6599785.png)

続いて左メニューから「Database」を選択し、表示するデータとセキュリティのルールを設定します。

- データの登録

```json
{
  "data" : {
    "view1" : {
      "contents" : "# こんにちわ\n\nこれは1ページ目です。",
      "title" : "Page 1"
    },
    "view2" : {
      "contents" : "# 2ページ目\n\nこれはサンプルのテキストです。\n\n## 見出し1\n\n[リンク](https://www.google.com/)も書けます。\n\n## 見出し2\n\n**強調表示**\n",
      "title" : "Page 2"
    },
    "view3" : {
      "contents" : "# 3ページ目\n\nこれはサンプルのテキストです。\n\n```html\n<hello>hello world!</hello>\n```\n",
      "title" : "Page 3"
    }
  }
}
```

上記のJSON形式のデータをインポートします。下記のようにコピペで作成もできます(ちょっとコツがいりますが、、、)。

![qiita-fb-pwa2-cp.gif](https://qiita-image-store.s3.amazonaws.com/0/90678/8b6ec98d-6337-81a4-ae3c-e204e11519d5.gif)


- ルールの設定

初期状態では認証されたユーザしか読み書きできないので、「ルール」タブをクリックし、下記のルールをペーストして上書き、「公開」ボタンをクリックしてください(`/data`以下のデータの読み込みを許可し、認証されたユーザのみ書きこみを許可します)。

```json
{
  "rules": {
    ".read": false,
    ".write": false,
    "data": {
    	".read": true,
        ".write": "auth != null"
    }
  }
}
```

> ドメインや個人でアクセス制限をするには下記のように書き換えます。
>
> ・個人に制限する場合
>
> ```
> ".write": "auth != null && auth.token.email == 'hoge@gmail.com'"
> ```
> 
> ・ドメインで制限する場合
>
> ```
> ".write": "auth != null && auth.token.email.endsWith('@hoge.co.jp') != -1"
> ```
>


### 2. ファイルの取得とAPIキーの設定

ファイル一式を[こちら](https://github.com/howking/polymer-sample-pwa)からダウンロード(`sample-2`ブランチ)するか、後述の[サンプルコード](#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89)からファイルを作成してください。
**`src/my-fire.html`の`<firebase-app>`タグの`api-key`、`auth-domain`、`database-url`はご自分のプロジェクトのものを使用してください。**

```bash
# githubからダウンロードする場合
git clone -b sample-2 https://github.com/howking/polymer-sample-pwa sample-2

# 作業ディレクトリに移動
cd sample-2

# my-fire.htmlを修正
emacs src/my-fire.html
```

```html:src/my-fire.html
    <firebase-app
      api-key="AIzaSyAXoRzCFBT2eGSZCKnl5Rspz7Cg-cUvtU8"
      auth-domain="sample-2-8952f.firebaseapp.com"
      database-url="https://sample-2-8952f.firebaseio.com"></firebase-app>
```

書換える文字列はプロジェクトページのトップ画面から「ウェブアプリにFirebaseを追加」をクリックし、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90678/f1cfb1cc-a210-a214-e324-4a3dd0469c62.png)

表示された`apiKey`、`authDomain`、`databaseURL`の値を使用してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90678/acdd08e4-9d88-fd6c-857d-5f9fb4128e1e.png)

### 3. ビルドとファイルのアップロード

下記を入力するとビルドされ、ファイルがアップロードされます。

```bash
# 利用するWebライブラリを取得
bower install

# ローカルでサーバを立ち上げて動作確認 → http://localhost:8000/
polymer serve

# アップロードするファイルを構築
polymer build

# !!注意!! polymerfireのビルドにはバグがあるので、ビルド後のファイルを修正します。。。
# https://github.com/Polymer/polymer-cli/issues/701
perl -pi -e 's/ks\(this\);var lI=this.Hd;if\(lI\)for\(var uI=\[\],hI=1;/ks(this);var uI=[];var lI=this.Hd;if(lI)for(var hI=1;/' build/default/src/my-view.html

# Firebaseにログイン
firebase login

# 使用するプロジェクトを指定
firebase use [プロジェクトID]

# ファイルをアップロード
firebase deploy
```

https://[プロジェクトID].firebaseapp.com/ で確認できます。 :tada:

## サンプルコード

```bash:files
├── README.md
├── bower.json
├── firebase.json
├── index.html
├── manifest.json
├── polymer.json
├── service-worker.js
├── src
│   ├── my-fire.html
│   ├── my-home.html
│   ├── my-icons.html
│   ├── my-marked.html
│   ├── my-theme.html
│   └── my-view.html
└── sw-precache-config.js
```

- `index.html` トップページ、 `<my-view>`タグの呼出し元

```html:index.html
<!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">
  </head>
  <body>
    <my-view>
      <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>
    <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="/src/my-view.html">
    <script>
      if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
          navigator.serviceWorker.register('/service-worker.js');
        });
      }
    </script>
  </body>
</html>
```

- `src/my-view.html` `<my-view>`タグを表示するHTML

```html:src/my-view.html
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../bower_components/polymer/lib/elements/dom-repeat.html">
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.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/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="my-home.html">
<link rel="import" href="my-icons.html">
<link rel="import" href="my-fire.html">
<link rel="import" href="my-marked.html" async>

<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;
      }
      my-home { display: block; margin-left: 10px; }
      div[main-title] { margin-left: 10px; }
      a paper-icon-button { text-decoration: none; color: white; }
      paper-listbox a { text-decoration: none; color: black; }
      paper-listbox a.iron-selected { color: lightgray; }
      paper-item:not(.iron-selected) { cursor: pointer; }
    </style>
    
    <app-location route="{{route}}"></app-location>
    <app-route route="{{route}}" pattern="/:key" data="{{routeData}}"></app-route>

    <app-drawer-layout force-narrow fullbleed>
      
      <app-drawer slot="drawer" swipe-open>
        <app-header fixed>
          <app-toolbar>
            <a href="/"><paper-icon-button icon="my:home" drawer-toggle></paper-icon-button></a>
          </app-toolbar>
        </app-header>
        <paper-listbox>
          <template is="dom-repeat" items="[[pages]]">
            <a href="/[[item.$key]]"><paper-item drawer-toggle>[[item.title]]</paper-item></a>
          </template>
        </paper-listbox>
      </app-drawer>
      
      <app-header-layout has-scrolling-region>
        <app-header slot="header" reveals>
          <app-toolbar>
            <paper-icon-button icon="my:menu" drawer-toggle></paper-icon-button>
            <div main-title>My app</div>
            <paper-icon-button icon="my:delete"></paper-icon-button>
            <paper-icon-button icon="my:search"></paper-icon-button>
            <my-fire key="[[routeData.key]]" pages="{{pages}}" view="{{view}}" login="{{login}}"></my-fire>
          </app-toolbar>
        </app-header>

        <template is="dom-if" if="[[!routeData.key]]">
          <my-home></my-home>
        </template>
        
        <template is="dom-if" if="[[routeData.key]]">
          <my-marked contents="{{view.contents}}" login="[[login]]"></my-marked>
        </template>

      </app-header-layout>
      
    </app-drawer-layout>
  </template>
  
  <script>
    class MyView extends Polymer.Element {
      static get is() { return 'my-view' }
    }
    customElements.define(MyView.is, MyView)
  </script>
</dom-module>
```

- `src/my-marked.html` `<my-marked>`タグを表示するHTML

```html:src/my-marked.html
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../bower_components/marked-element/marked-element.html">
<link rel="import" href="../bower_components/prism-element/prism-highlighter.html">
<link rel="import" href="../bower_components/prism-element/prism-theme-default.html">
<link rel="import" href="../bower_components/sanitize-element/sanitize-element.html">
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../bower_components/paper-input/paper-textarea.html">

<!-- https://poly-style.appspot.com?id=github-markdown-styles&url=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/2.8.0/github-markdown.min.css -->
<link rel="import" href="my-theme.html">

<dom-module id="my-marked">
  <template>
    <style include="prism-theme-default my-theme">
      :host {
        display: block;
        margin: 10px 15px;
        --paper-input-container: {
          width: 100%;
        }
      }
      paper-toggle-button {
        margin-top: 15px;
      }
    </style>
    
    <prism-highlighter></prism-highlighter>

    <sanitize-element
      sanitizer="{{_sanitizer}}"
      config='{"elements":["my-home","img"],
               "attributes":{"img":["class","src"]},
               "protocols":{"img":{"src":["https"]}}}'></sanitize-element>
    
    <marked-element sanitize sanitizer="[[_sanitizer]]" markdown="[[contents]]">
      <main class="markdown-body" slot="markdown-html"></main>
    </marked-element>
    
    <template is="dom-if" if="[[contents]]">
      <template is="dom-if" if="[[login]]">
        <paper-toggle-button checked="{{_edit}}"></paper-toggle-button>
        <paper-textarea value="{{contents}}" hidden="[[!_edit]]"></paper-textarea>
      </template>
    </template>
      
  </template>
  <script>
    class MyMarked extends Polymer.Element {
      static get is() { return 'my-marked' }
      static get properties() {
        return {
          login: Boolean,
          contents: {
            type: String,
            notify: true
          }
        }
      }
    }
    customElements.define(MyMarked.is, MyMarked)
  </script>
</dom-module>
```

- `src/my-theme.html` github-markdownのCSSファイル

```html:src/my-theme.html
<dom-module id="my-theme">
  <template>
    <style>
      @font-face{font-family:octicons-link;src:url(data:font/woff;charset=utf-8;base64,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) format('woff')}.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.5;color:#24292e;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body .pl-c{color:#6a737d}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:#005cc5}.markdown-body .pl-e,.markdown-body .pl-en{color:#6f42c1}.markdown-body .pl-s .pl-s1,.markdown-body .pl-smi{color:#24292e}.markdown-body .pl-ent{color:#22863a}.markdown-body .pl-k{color:#d73a49}.markdown-body .pl-pds,.markdown-body .pl-s,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sra,.markdown-body .pl-sr .pl-sre{color:#032f62}.markdown-body .pl-smw,.markdown-body .pl-v{color:#e36209}.markdown-body .pl-bu{color:#b31d28}.markdown-body .pl-ii{color:#fafbfc;background-color:#b31d28}.markdown-body .pl-c2{color:#fafbfc;background-color:#d73a49}.markdown-body .pl-c2::before{content:"^M"}.markdown-body .pl-sr .pl-cce{font-weight:700;color:#22863a}.markdown-body .pl-ml{color:#735c0f}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{font-weight:700;color:#005cc5}.markdown-body .pl-mi{font-style:italic;color:#24292e}.markdown-body .pl-mb{font-weight:700;color:#24292e}.markdown-body .pl-md{color:#b31d28;background-color:#ffeef0}.markdown-body .pl-mi1{color:#22863a;background-color:#f0fff4}.markdown-body .pl-mc{color:#e36209;background-color:#ffebda}.markdown-body .pl-mi2{color:#f6f8fa;background-color:#005cc5}.markdown-body .pl-mdr{font-weight:700;color:#6f42c1}.markdown-body .pl-ba{color:#586069}.markdown-body .pl-sg{color:#959da5}.markdown-body .pl-corl{text-decoration:underline;color:#032f62}.markdown-body .octicon{display:inline-block;vertical-align:text-top;fill:currentColor}.markdown-body a{background-color:transparent;-webkit-text-decoration-skip:objects}.markdown-body a:active,.markdown-body a:hover{outline-width:0}.markdown-body strong{font-weight:inherit}.markdown-body strong{font-weight:bolder}.markdown-body h1{font-size:2em;margin:.67em 0}.markdown-body img{border-style:none}.markdown-body svg:not(:root){overflow:hidden}.markdown-body code,.markdown-body kbd,.markdown-body pre{font-family:monospace,monospace;font-size:1em}.markdown-body hr{box-sizing:content-box;height:0;overflow:visible}.markdown-body input{font:inherit;margin:0}.markdown-body input{overflow:visible}.markdown-body [type=checkbox]{box-sizing:border-box;padding:0}.markdown-body *{box-sizing:border-box}.markdown-body input{font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body a{color:#0366d6;text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body strong{font-weight:600}.markdown-body hr{height:0;margin:15px 0;overflow:hidden;background:0 0;border:0;border-bottom:1px solid #dfe2e5}.markdown-body hr::before{display:table;content:""}.markdown-body hr::after{display:table;clear:both;content:""}.markdown-body table{border-spacing:0;border-collapse:collapse}.markdown-body td,.markdown-body th{padding:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:0;margin-bottom:0}.markdown-body h1{font-size:32px;font-weight:600}.markdown-body h2{font-size:24px;font-weight:600}.markdown-body h3{font-size:20px;font-weight:600}.markdown-body h4{font-size:16px;font-weight:600}.markdown-body h5{font-size:14px;font-weight:600}.markdown-body h6{font-size:12px;font-weight:600}.markdown-body p{margin-top:0;margin-bottom:10px}.markdown-body blockquote{margin:0}.markdown-body ol,.markdown-body ul{padding-left:0;margin-top:0;margin-bottom:0}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body code{font-family:SFMono-Regular,Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px}.markdown-body pre{margin-top:0;margin-bottom:0;font:12px SFMono-Regular,Consolas,"Liberation Mono",Menlo,Courier,monospace}.markdown-body .octicon{vertical-align:text-bottom}.markdown-body .pl-0{padding-left:0!important}.markdown-body .pl-1{padding-left:4px!important}.markdown-body .pl-2{padding-left:8px!important}.markdown-body .pl-3{padding-left:16px!important}.markdown-body .pl-4{padding-left:24px!important}.markdown-body .pl-5{padding-left:32px!important}.markdown-body .pl-6{padding-left:40px!important}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .anchor{float:left;padding-right:4px;margin-left:-20px;line-height:1}.markdown-body .anchor:focus{outline:0}.markdown-body blockquote,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;padding:0;margin:24px 0;background-color:#e1e4e8;border:0}.markdown-body blockquote{padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#444d56;vertical-align:middle;background-color:#fafbfc;border:solid 1px #c6cbd1;border-bottom-color:#959da5;border-radius:3px;box-shadow:inset 0 -1px 0 #959da5}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:#1b1f23;vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1{padding-bottom:.3em;font-size:2em;border-bottom:1px solid #eaecef}.markdown-body h2{padding-bottom:.3em;font-size:1.5em;border-bottom:1px solid #eaecef}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{font-size:.85em;color:#6a737d}.markdown-body ol,.markdown-body ul{padding-left:2em}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}.markdown-body table{display:block;width:100%;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table td,.markdown-body table th{padding:6px 13px;border:1px solid #dfe2e5}.markdown-body table tr{background-color:#fff;border-top:1px solid #c6cbd1}.markdown-body table tr:nth-child(2n){background-color:#f6f8fa}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:#fff}.markdown-body code{padding:0;padding-top:.2em;padding-bottom:.2em;margin:0;font-size:85%;background-color:rgba(27,31,35,.05);border-radius:3px}.markdown-body code::after,.markdown-body code::before{letter-spacing:-.2em;content:"\00a0"}.markdown-body pre{word-wrap:normal}.markdown-body pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:0 0;border:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f6f8fa;border-radius:3px}.markdown-body pre code{display:inline;max-width:auto;padding:0;margin:0;overflow:visible;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown-body pre code::after,.markdown-body pre code::before{content:normal}.markdown-body .full-commit .btn-outline:not(:disabled):hover{color:#005cc5;border-color:#005cc5}.markdown-body kbd{display:inline-block;padding:3px 5px;font:11px SFMono-Regular,Consolas,"Liberation Mono",Menlo,Courier,monospace;line-height:10px;color:#444d56;vertical-align:middle;background-color:#fafbfc;border:solid 1px #d1d5da;border-bottom-color:#c6cbd1;border-radius:3px;box-shadow:inset 0 -1px 0 #c6cbd1}.markdown-body :checked+.radio-label{position:relative;z-index:1;border-color:#0366d6}.markdown-body .task-list-item{list-style-type:none}.markdown-body .task-list-item+.task-list-item{margin-top:3px}.markdown-body .task-list-item input{margin:0 .2em .25em -1.6em;vertical-align:middle}.markdown-body hr{border-bottom-color:#eee}
    </style>
  </template>
</dom-module>
```

- `src/my-home.html` `<my-home>`タグを表示するHTML

```html:src/my-home.html
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<script>
  class MyHome extends Polymer.Element {
    static get is() { return 'my-home' }
    static get template() { return `<h1>ようこそ!</h1>` }
  }
  customElements.define(MyHome.is, MyHome)
</script>
```

- `src/my-fire.html` `<my-fire>`タグを表示するHTML

```html:src/my-fire.html
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../bower_components/polymerfire/firebase-app.html">
<link rel="import" href="../bower_components/polymerfire/firebase-query.html">
<link rel="import" href="../bower_components/polymerfire/firebase-document.html">
<link rel="import" href="../bower_components/polymerfire/firebase-auth.html">
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">

<dom-module id="my-fire">
  <template>
    <style>
      paper-icon-button {
        --paper-icon-button-ink-color: white;
      }
    </style>
    
    <firebase-app
      api-key="AIzaSyAXoRzCFBT2eGSZCKnl5Rspz7Cg-cUvtU8"
      auth-domain="sample-2-8952f.firebaseapp.com"
      database-url="https://sample-2-8952f.firebaseio.com"></firebase-app>
    <firebase-query path="/data" data="{{pages}}"></firebase-query>
    <firebase-document path="/data/[[key]]" data="{{view}}"></firebase-document>
    <firebase-auth signed-in="{{login}}" provider="google"></firebase-auth>

    <paper-icon-button icon="my:[[_icon(login)]]" on-tap="_auth"></paper-icon-button>
    
  </template>
  <script>
    class MyFire extends Polymer.Element {
      static get is() { return 'my-fire' }
      static get properties() {
        return {
          key: {
            type: String
          },
          pages: {
            type: Array,
            notify: true
          },
          view: {
            type: Object,
            notify: true
          },
          login: {
            type: Boolean,
            notify: true
          }
        }
      }
      _icon(login){ return login ? 'logout' : 'google' }
      _auth(){
        const auth = this.shadowRoot.querySelector('firebase-auth')
        auth.signedIn ? auth.signOut() : auth.signInWithRedirect()
      }
    }
    customElements.define(MyFire.is, MyFire)
  </script>
</dom-module>
```

- `src/my-icons.html` アイコン集ファイル

```html:src/my-icons.html
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html">
<iron-iconset-svg size="24" name="my">
  <svg><defs>
    <g id="menu"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></g>
    <g id="close"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></g>
    <g id="delete"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></g>    
    <g id="search"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></g>
    <g id="google"><path d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12C5,7.9 8.2,4.73 12.2,4.73C15.29,4.73 17.1,6.7 17.1,6.7L19,4.72C19,4.72 16.56,2 12.1,2C6.42,2 2.03,6.8 2.03,12C2.03,17.05 6.16,22 12.25,22C17.6,22 21.5,18.33 21.5,12.91C21.5,11.76 21.35,11.1 21.35,11.1V11.1Z" /></g>
    <g id="home"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></g>
    <g id="logout"><path d="M17,17.25V14H10V10H17V6.75L22.25,12L17,17.25M13,2A2,2 0 0,1 15,4V8H13V4H4V20H13V16H15V20A2,2 0 0,1 13,22H4A2,2 0 0,1 2,20V4A2,2 0 0,1 4,2H13Z" /></g>
  </defs></svg>
</iron-iconset-svg>
```

- `sw-precache-config.js` [Service Worker Precache](https://github.com/GoogleChrome/sw-precache)というオフラインキャッシュの設定ファイル

```js
module.exports = {
  staticFileGlobs: [
    '/index.html',
    '/manifest.json',
    '/bower_components/webcomponentsjs/*.js'
  ],
  navigateFallback: 'index.html',
  navigateFallbackWhitelist: [/^(?!\/__)/]
};
```

- `manifest.json` Webアプリケーションとしての設定ファイル

```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"
  }]
}
```

- `bower.json` Webコンポーネントライブラリの設定ファイル

```json
{
  "name": "polymer-sample-pwa-2",
  "dependencies": {
    "polymer": "Polymer/polymer",
    "webcomponentsjs": "webcomponents/webcomponentsjs",
    "app-route": "PolymerElements/app-route",
    "app-layout": "PolymerElements/app-layout",
    "paper-ui-elements": "PolymerElements/paper-ui-elements",
    "paper-toggle-button": "PolymerElements/paper-toggle-button",
    "marked-element": "PolymerElements/marked-element",
    "prism-element": "PolymerElements/prism-element",
    "sanitize-element": "howking/sanitize-element",
    "polymerfire": "firebase/polymerfire"
  }
}
```

- `polymer.json` Polymer CLIの設定ファイル

```json
{
  "entrypoint": "index.html",
  "shell": "src/my-view.html",
  "fragments": [
    "src/my-marked.html"
  ],
  "extraDependencies": [
    "manifest.json",
    "bower_components/webcomponentsjs/*.js"
  ],
  "sources": [
    "src/**/*.html"
  ],
  "builds": [{
    "name": "default",
    "preset": "es5-bundled"
  }]
}
```

- `firebase.json` Firebaseの設定ファイル

```json
{
  "hosting": {
    "public": "build/default",
    "rewrites": [{
      "source": "!/__/**",
      "destination": "/index.html"
    }]
  }
}
```

## 各行の説明

**[前回説明した部分](http://qiita.com/howking/items/27fec1598558045b74d1)は省きます。**

### index.html

```html:index.html
    <my-view>
      ...
    </my-view>
    <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="/src/my-view.html">
    <script>
      if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
          navigator.serviceWorker.register('/service-worker.js');
        });
      }
    </script>
  </body>
```

[前回](http://qiita.com/howking/items/27fec1598558045b74d1)まではWeb Componentsのポリフィルや`my-view`タグ、ServiceWorkerの読み込みを`<head>`の中に入れていましたが、表示速度をチューニングしている[Polymer HN](https://hn-polymer-2.firebaseapp.com/)等と同じように`<body>`の最後に入れています。

### src/my-view.html

```html:src/my-view.html
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">
...
    <style>
      ...
      a paper-icon-button { text-decoration: none; color: white; }
      paper-listbox a { text-decoration: none; color: black; }
      paper-listbox a.iron-selected { color: lightgray; }
      ...
    </style>

    <app-location route="{{route}}"></app-location>
    <app-route route="{{route}}" pattern="/:key" data="{{routeData}}"></app-route>
        ...
            <a href="/"><paper-icon-button icon="my:home" drawer-toggle></paper-icon-button></a>
        ...
            <a href="/[[item.$key]]"><paper-item drawer-toggle>[[item.title]]</paper-item></a>
```

フレンドリーURLを使う為に[`app-location`](https://www.webcomponents.org/element/PolymerElements/app-route/elements/app-location)、ルータ機能を使う為に[`app-route`](https://www.webcomponents.org/element/PolymerElements/app-route/elements/app-route)を読み込みます。
ページ遷移は`<a>`タグでリンクを指定するだけです。`<app-route pattern"/:key">`で定義された第一階層のパラメータが`{{routeData.key}}`として格納されます(`view1`や`view2`など)。

ただ、[Fetch as Google](https://www.google.com/webmasters/tools/googlebot-fetch)ではちゃんと表示してくれませんでした、、、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/90678/12f2eaa5-f3ea-90fe-1484-0cef9be74898.png)

```html:src/my-view.html
<link rel="import" href="my-home.html">
<link rel="import" href="my-icons.html">
<link rel="import" href="my-fire.html">
<link rel="import" href="my-marked.html" async>
```

`<my-view>`の見通しをよくする為にコンポーネント(タグ)を分けます。`my-marked`は`async`で多少読み込みを遅らせています。

```html:src/my-view.html
            <my-fire key="[[routeData.key]]" pages="{{pages}}" view="{{view}}" login="{{login}}"></my-fire>
```

`<my-fire>`タグはログインボタンを表示しつつ、データ取得やログイン状態を返すようにします。
`key`で指定したページのコンテンツを`{{view}}`として戻す処理、`{{pages}}`にページのリンク先やタイトルを格納します。
また、Markdownの編集はログインした人のみに限定する為に、ログイン状態は`{{login}}`という真偽値(Boolean)に格納するようにします。

```html:src/my-view.html
        <template is="dom-if" if="[[!routeData.key]]">
          <my-home></my-home>
        </template>
        
        <template is="dom-if" if="[[routeData.key]]">
          <my-marked contents="{{view.contents}}" login="[[login]]"></my-marked>
        </template>
```

URLが`{{routeData}}`に格納されているので、`[[!routeData.key]]`は`routeData.key`に値がない場合(=トップページ)を表し、`<my-home>`を表示させます。`/view1`などのページ指定があった場合には`<my-marked>`でMarkdownのデータを表示させます。

```html:src/my-view.html
  <script>
    class MyView extends Polymer.Element {
      static get is() { return 'my-view' }
    }
    customElements.define(MyView.is, MyView)
  </script>
```

[前回](http://qiita.com/howking/items/27fec1598558045b74d1)と比べてプロパティも必要なくなったので、**タグを宣言するだけ**となりました。

### src/my-marked.html

```html:src/my-marked.html
<link rel="import" href="../bower_components/prism-element/prism-highlighter.html">
<link rel="import" href="../bower_components/prism-element/prism-theme-default.html">
...
<!-- https://poly-style.appspot.com?id=github-markdown-styles&url=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/2.8.0/github-markdown.min.css -->
<link rel="import" href="my-theme.html">
...
    <style include="prism-theme-default my-theme">
    ...
    <prism-highlighter></prism-highlighter>
    ...
      <main class="markdown-body" slot="markdown-html"></main>
```

[`prism-element`](https://github.com/PolymerElements/prism-element)でソースの色付けをできるようにします。`<style include="prism-theme-default">`でスタイルシート情報を読み込んでおいてから、`<prism-highlinghter>`タグを書いておけば、`marked-element`と連携して色付けしてくれます。

`my-theme.html`は[github-markdown.css](https://github.com/sindresorhus/github-markdown-css)を[PolyStyle](https://poly-style.appspot.com/demo/)を使って[出力](https://poly-style.appspot.com?id=github-markdown-styles&url=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/2.8.0/github-markdown.min.css)した結果をファイルに保存したものです。`<main class="markdown-body">`でクラスを指定すると反映されます。

```html:src/my-marked.html
<link rel="import" href="../bower_components/sanitize-element/sanitize-element.html">
...
    <sanitize-element
      sanitizer="{{_sanitizer}}"
      config='{"elements":["my-home","img"],
               "attributes":{"img":["class","src"]},
               "protocols":{"img":{"src":["https"]}}}'></sanitize-element>
...
    <marked-element sanitize sanitizer="[[_sanitizer]]" markdown="[[contents]]">
```

**手前味噌のコンポーネント** ですが、[`sanitize-element`](https://www.webcomponents.org/element/howking/sanitize-element)でMarkdown上表示させたくないHTMLタグをサニタイズします([Sanitize.js](https://github.com/gbirke/Sanitize.js)を利用するだけのもの)。
`<marked-element>`に`sanitize sanitizer="[[_sanitizer]]"`を指定し、`<sanitize-element>`で使用したいタグや属性を`config`属性にホワイトリスト形式で指定します。
サンプルでは`<img>`タグと**`<my-home>`タグが使える**ようにしています。

```html:src/my-marked.html
    <template is="dom-if" if="[[contents]]">
      <template is="dom-if" if="[[login]]">
        <paper-toggle-button checked="{{_edit}}"></paper-toggle-button>
        <paper-textarea value="{{contents}}" hidden="[[!_edit]]"></paper-textarea>
      </template>
    </template>
```

編集用のトグルボタンは内容(`[[contents]]`)が存在していて、ログイン(`[[login]]`)されていればテキストエリアとともに表示させます。
`<paper-toggle-button>`はONになると`checked`属性が自動で付与され、また`<paper-textarea>`は`hidden`属性をONにすると表示が消えるので、**`{{_edit}}`でつなげるだけで表示の切替ができて便利**です。

### src/my-home.html

```html:src/my-home.html
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<script>
  class MyHome extends Polymer.Element {
    static get is() { return 'my-home' }
    static get template() { return `<h1>ようこそ!</h1>` }
  }
  customElements.define(MyHome.is, MyHome)
</script>
```

「ようこそ!」と表示するだけのコンポーネントですが、**なんということでしょう、、、`<dom-module>`がありません。** 元々使えた`static get template()`ですが、先日(2017/08/22)の[PolymerSummit 2017](https://summit.polymer-project.org/)では今後の[Polymer3.0](https://www.polymer-project.org/blog/2017-08-22-npm-modules)に向けてBower、HTML Importsを廃止してNPM、ES6 modulesに移行するそうで、template関数側にHTMLを書くようになりそうです。

### src/my-icons.html

```html:src/my-icons.html
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html">
<iron-iconset-svg size="24" name="my">
  <svg><defs>
    ...
    <g id="google"><path d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12C5,7.9 8.2,4.73 12.2,4.73C15.29,4.73 17.1,6.7 17.1,6.7L19,4.72C19,4.72 16.56,2 12.1,2C6.42,2 2.03,6.8 2.03,12C2.03,17.05 6.16,22 12.25,22C17.6,22 21.5,18.33 21.5,12.91C21.5,11.76 21.35,11.1 21.35,11.1V11.1Z" /></g>
    ...
  </defs></svg>
</iron-iconset-svg>
```

[前回](http://qiita.com/howking/items/27fec1598558045b74d1)までは[`<iron-icons>`](https://www.webcomponents.org/element/PolymerElements/iron-icons/demo/demo/index.html)から適当なアイコンを選んで使っていましたが、使っていないデータも読み込むのは無駄なので、使うものだけ選んで定義しておきます。
[Polymer Iconset Generator](https://poly-icon.appspot.com/)からポチポチ選ぶと楽ですが、[Material Design Icons](https://materialdesignicons.com/)等からSVGを貼り付けても使えます(`View SVG`で表示しつつ`fill="#000000"`などは削る必要があります)。

### src/my-fire.html

```html:src/my-fire.html
    <firebase-query path="/data" data="{{pages}}"></firebase-query>
    <firebase-document path="/data/[[key]]" data="{{view}}"></firebase-document>
```

[`<firebase-query>`](https://www.webcomponents.org/element/firebase/polymerfire/elements/firebase-query)でキーやタイトル情報を一式取得し、[`<firebase-document>`](https://www.webcomponents.org/element/firebase/polymerfire/elements/firebase-document)で`[[key]]`から指定されたMarkdownのテキスト情報を取得します。

> そもそも`<firebase-query>`で全てのデータを取得してしまっているので、`<firebase-document>`で個別に指定する必要はないのですが、データが増えていく場合は、毎回全部データを取得するのは重い処理になってしまうので、キーやタイトルだけ別のツリーにするなどフラットなデータ構成に変更する必要があります。

```html:src/my-fire.html
    <firebase-auth signed-in="{{login}}" provider="google"></firebase-auth>

    <paper-icon-button icon="my:[[_icon(login)]]" on-tap="_auth"></paper-icon-button>
    ...
    <script>
      ...
      _icon(login){ return login ? 'logout' : 'google' }
      _auth(){
        const auth = this.shadowRoot.querySelector('firebase-auth')
        auth.signedIn ? auth.signOut() : auth.signInWithRedirect()
      }
```

ユーザ認証用のボタンなど、自分で一からつくるのは気が遠くなりますが、FirebaseとPolymerで簡潔に書けます(エラー処理とかはしてないですが)。
**ただし、現状PolymerfireとPolymer CLIの組み合わせは悪く、ビルド時に修正を加えないと動きません** 。IEやEdgeの動きがおかしいのもこのせいな気がしますが、[Polymer/polymer-cli#701](https://github.com/Polymer/polymer-cli/issues/701)が早く直ることを切に願うばかりです。

```bash:無理矢理動かすPerlスクリプト
$ perl -pi -e 's/ks\(this\);var lI=this.Hd;if\(lI\)for\(var uI=\[\],hI=1;/ks(this);var uI=[];var lI=this.Hd;if(lI)for(var hI=1;/' build/default/src/my-view.html
```

### firebase.json

```html:firebase.json
    "rewrites": [{
      "source": "!/__/**",
      "destination": "/index.html"
    }]
```

firebaseではホスティングのパスで`/__/`が認証等のライブラリ用に[予約されている](https://firebase.google.com/docs/hosting/url-redirects-rewrites?hl=ja#section-priorities)ので、そこをはずしつつフレンドリーURLを使う為にリライトの設定をします。

### sw-precache-config.js

```html:sw-precache-config.js
  navigateFallbackWhitelist: [/^(?!\/__)/]
```

こちらも認証時のURL `/__/`以下でServiceWorkerが応答してしまわないように設定をしておきます。

## 最後に

**:bow: コメント、編集リクエスト歓迎**

[サンプルコード(1) Hello World!](http://qiita.com/howking/items/d23c850265c0965ef55c) から今回のサンプルコード (5) PWA [2] までで、ざっくりと<img src="https://www.polymer-project.org/images/logos/p-logo.png" height="26"> [Polymer](https://www.polymer-project.org/) の良さを知ってもらえればです!

以上