カスタマイズとパーソナライズ

このドキュメントは作業中です。リリース前の情報であり、変更される可能性があります。

このページでは、アプリを変更して独自のコンテンツを追加するために必要な手順について説明します。

目次

ディレクトリ構造

アプリは多くのフォルダとファイルで初期化されます:

my-app
├── images
│   ├── favicon.ico
│   └── manifest
│       ├── icon-48x48.png
│       └── ...
├── src
│   ├── store.js
│   ├── actions
│   │   └── ...
│   ├── reducers
│   │   └── ...
│   ├── components
│   │   └── ...
├── test
│   ├── unit
│   │   └── ...
│   └── integration
│       └── ...
├── index.html
├── README.md
├── package.json
├── polymer.json
├── manifest.json
├── service-worker.js
├── sw-precache-config.js
├── wct.conf.json
├── .travis.yml

必要に応じて、アプリケーション固有のフォルダを追加し、コードを整理しておくことができます。たとえば、 src/views/フォルダを作成し、そこにあるすべてのトップレベルのビューを移動することができます。

命名規則とコード規則

このセクションでは、このテンプレートにある命名規則とコーディング規則の背景について説明します:

import { LitElement, html } from '@polymer/lit-element';
class SampleElement extends LitElement {
  // 要素が使うプロパティ
  static get properties() { return {
    publicProperty: { type: Number },
    _privateProperty: { type: String }    /* 先頭にアンダースコアを使っています */
  }};

  constructor() {
    super();
    // ここでプロパティのデフォルト値を設定
    this.publicProperty = 0;
    this._privateProperty = '';
  }

  render() {
    // オブジェクトの分割代入の使っています。
    // レンダリングに使用するプロパティを呼び出します。
    const {publicProperty, _privateProperty} = this;

    // レンダリングに関連するコードはここで行う必要があります。

    return html`
      <!-- 要素のテンプレートはここにあります -->
    `;
  });

  firstUpdated() {
    // ここに一度だけ呼び出されるレンダリングに依存するコード
    // (例えばイベントリスナーの設定など)
  }
  ...
}
window.customElements.define('sample-element', SampleElement);

アプリのカスタマイズ

アプリをパーソナライズするためにいくつかの変更を加えたい場合があります。

アプリの名前を変更する

デフォルトでは、あなたのアプリは my-appと呼ばれます。これを変更したい場合は、あなたは複数の場所で変更を加える必要があります:

新しいページを追加する

アクティブなページに使用される場所は4箇所あります:

新しいページを追加するには、それぞれの場所に新しいエントリを追加する必要があります。ツールバーに外部リンクやボタンだけを追加したい場合は <main>要素に追加するのをスキップすることができます。

新しいページの作成

まず、ページの新しいビューを表す新しい要素を作成しましょう。これを行う最も簡単な方法は、<my-view404>をコピーすることです。これは基本的な出発点です:

import { html } from '@polymer/lit-element/lit-element.js';
import { PageViewElement } from './page-view-element.js';
import { SharedStyles } from './shared-styles.js';

class MyView4 extends PageViewElement {
  render() {
    return html`
      ${SharedStyles}
      <section>
        <h2>Oops! You hit a 404</h2>
        <p>The page you're looking for doesn't seem to exist. Head back
           <a href="/">home</a> and try again?
        </p>
      </section>
    `
  }
}
window.customElements.define('my-view4', MyView4);

(🔎このページは最適化として LitElementではなく PageViewElementを継承しています。さらに詳しい情報は条件付き描画を参照してください)

ページをアプリケーションに追加する

すばらしいです!新しい要素ができたので、アプリケーションに追加します。

まず、ナビゲーションリンクの各リストに追加します。ツールバー(ワイドスクリーンビュー)への追加:

<nav class="toolbar-list">
  ...
  <a ?selected="${_page === 'view4'}" href="/view4">New View!</a>
</nav>

同様に、引き出し内のナビゲーションリンクのリストに追加することもできます:

<nav class="drawer-list">
  ...
  <a ?selected="${_page === 'view4'}" href="$/view4">New View!</a>
</nav>

そして、メインコンテンツ自体へ:

<main class="main-content">
  ...
  <my-view4 class="page" ?active="${_page === 'view4'}"></my-view4>
</main>

これらのすべてのコードスニペットでは、 selected属性がハイライトの為に使用されています。アクティブなページだけが実際にレンダリングされることを保証するために active属性も使用されます。

最後に、このページを遅延読み込みに対応させる必要があります。これがなければ、リンクは表示されますが、my-view4が定義されていないので(ソースコードのどこにもインポートしてるところがないため)、新しいページに移動することはできません。 loadPageアクションクリエータに新しいcaseステートメントを追加します:

switch(page) {
  ...
  case 'view4':
    import('../components/my-view4.js');
    break;
}

_action creator_が何であるかを知らないと心配しないでください。 Reduxのページでは、状態管理について完全に説明されています。

これでおしまい!ここでページを更新すると、新しいリンクとページが表示されるはずです。この新しいページを出力に追加する必要があるため、プロダクションにデプロイする前にアプリケーションを再ビルドすることを忘れないでください(またはそのビルドをテストしてください)。

push manifestにページを追加する

HTTP/2サーバープッシュを利用するには、新しいページに必要なスクリプトを指定する必要があります。 push-manifest.jsonに以下の新しいエントリを追加してください:

{
  "/view4": {
    "src/components/my-app.js": {
      "crossorigin": "anonymous",
      "type": "script",
      "weight": 1
    },
    "src/components/my-view4.js": {
      "crossorigin": "anonymous",
      "type": "script",
      "weight": 1
    },
    "node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js": {
      "type": "script",
      "weight": 1
    }
  },

  /* 他のエントリ */
}

アイコンの使用

ページ内で <svg>を直接インライン化することはできますが、再利用可能なアイコンがあれば、一度定義していくつかの場所で使うことができます。 my-icons.jsはそのための良い場所です。新しいアイコンを追加するには、そのファイルに新しい行を追加するだけです:

export const closeIcon = html`<svg>...</svg>`

次にそれをインポートし、それを要素の render()メソッドでテンプレートリテラルとして使用することができます:

import { closeIcon } from './my-icons.js';
render() {
  return html`
    <button title="close">${closeIcon}</button>
  `;
}

スタイルの共有

同様に、共有スタイルはエクスポートされたテンプレートリテラルにすぎません。 shared-styles.jsを見ると、要素の render()メソッドでインライン化された<style>ノードのテンプレートがエクスポートされています:

import { SharedStyles } from './shared-styles.js';
render() {
  return html`
    ${SharedStyles}
    <div>...</div>
  `;
}

フォント

アプリはコンテンツにウェブフォントを使用しませんが、アプリのタイトルにGoogleフォントを使用します。あまりにも多くのフォントを読み込まないように注意してください: 最初のページのダウンロードサイズを増やすこととは別に、Webフォントもパフォーマンスを低下させ、スタイルのないコンテンツを点滅させてしまいます。

Reduxを使わない方法は?

pwa-starter-kitは、かなり複雑なPWAを構築するうえよい方法だと思われますが、それによって制限があるわけではありません。あなたが何をしているのか分かっていて、Reduxを使ってアプリケーションの状態を管理したくないのなら、それはよい考えだと思われます!同じUIとPWAを持つ別のテンプレートtemplate-no-reduxが用意されていて、要素をメインテンプレートするのは同じですが、Reduxを使いません。

Instead, it uses a unidirectional data flow approach: some elements are in charge of maintaining the state for their section of the application, and they pass that data down to children elements. In response, when the children elements need to update the state, they fire an event.

代わりに、一方向データフローアプローチを使用しています。いくつかの要素が状態の維持を担当しており、アプリケーションにセクションを追加し、子要素にデータを渡します。その応答で子要素が状態を更新する必要があるとき、彼らはイベントを発生させる必要があります。

高度な使い方

レスポンシブレイアウト

デフォルトでpwa-starter-kitはレスポンシブレイアウトとなりますがあります。 460pxでは、アプリケーションは広いデスクトップビューから小さなモバイルビューに切り替わります。モバイルレイアウトを異なるサイズで適用する場合は、この値を変更できます。

For a different kind of responsive layout, the template-responsive-drawer-layout template displays a persistent app-drawer, inline with the content on wide screens (and uses the same small-screen drawer as the main template).

異なる種類のレスポンスレイアウトの場合、template-responsive-drawer-layoutテンプレートには、永続的なアプリケーションドロワーがワイド画面のコンテンツとインラインで表示されます(メインテンプレートと同じ小さなdrawerを使用します)。

ワイドスクリーンスタイルを変更する

ワイドスクリーンスタイルはCSSのmedia-queryで制御されます。このブロックでは、ウィンドウビューポートの幅が少なくとも460pxのときにのみ適用されるセレクタを追加できます。これらのスタイルが適用されるサイズを変更する場合はこのピクセル値を変更できます(複数のブレークポイントが必要な場合は別のスタイルを追加できます)。

ナロースクリーンスタイルの変更

my-appの他のスタイルは、media-queryの外部であり、一般的なスタイル(メディアクエリスタイルで上書きされない場合)としてこのようにナロースクリーン向けに定義されています(この例では<nav class =" toolbar-list ">はナロースクリーンでは隠されていて、ワイドスクリーンで表示されます)。

JavaScriptでレスポンシブスタイルを制御する

サイズが幅の広い画面から狭い画面に変更されたときに特定のJavaScriptコードを実行する場合(例えばdrawerをそのままにするなど)、 pwa-helpersinstallMediaQueryWatcherヘルパーが使えます。もし設定されていれば、メディアクエリが一致するたびに実行されるコールバックを指定できます。

条件付きレンダリングビュー

ある時点でどのビューが表示されているかは、active属性によって制御されます。これは、ページの名前が場所と一致する場合に設定され、スタイリングされます:

<style>
  .main-content .page {
    display: none;
  }
  .main-content .page[active] {
    display: block;
  }
</style>
<main class="main-content">
  <my-view1 class="page" ?active="${page === 'view1'}"></my-view1>
  <my-view2 class="page" ?active="${page === 'view2'}"></my-view2>
  ...
</main>

ただし、特定のビューが表示されていないという理由だけで、そのビューが「非アクティブ」であることを意味するわけではありません。そのJavaScriptは引き続き実行できます。 特に、アプリケーションがReduxを使用しており、ビューが接続されている場合、(例えばmy-view2のように)、Reduxストアが変更されたどんなタイミングでもrender()を呼び出すことができます。 もっともほとんどの場合、これはおそらくあなたが望むものではありません。実際に画面に表示されるまで、隠しビューは更新されるべきではありません。たとえば、タイトルは、最後に設定したビューであるため、これらの非アクティブなビューの1つによって誤って設定される可能性があります。

この問題を回避するために、ビューはLitElementではなくPageViewElementのベースクラスを継承し、この基本クラスは active属性がホスト上で設定されているかどうか(スタイリングに使用するのと同じ属性)をチェックし、render()設定されている場合のみを呼び出されます。

もしこの動きが気にくわなくて、隠れたページをバックグラウンドで更新したいのであれば、ビューの基本クラスを LitElementに戻すだけです(つまり、ここを変更するだけです)。実際にそこで起きる副作用を見てみてください!

ルーティング

このアプリは非常に基本的なルータを使っていて、それは window.locationへの変更を待ち受けます。ルータをインストールするには、コールバックを渡します。コールバックは、ロケーションが変わるたびに呼び出される関数です:

installRouter((location) => this._locationChanged(location));

これでリンクがクリックされるたびに(またはユーザーがページに戻るとき)、新しいロケーションで this._locationChangedが呼び出されます。 Reduxページをチェックして、この場所がReduxストアにどのように格納されているかを確認することができます。

リンク移動を上書きするカスタマイズがしたい場合やナビゲーションをカスタマイズして管理している場合など、この部分(およびReduxストア)を変更したい場合があるでしょう。その場合は、ブラウザの履歴状態を手動で更新してから、 this._locationChangedメソッドを手動で呼び出すことで実現できます(ルータからのアクションをシミュレートします):

// この関数は、手動で場所を管理するときに
// 呼び出されます。

onArticleLinkClick(page) {
  const newLocation = `/article/${page}`
  window.history.pushState({}, '', newLocation);
  this._locationChanged(newLocation);
};

SEO

Open Graphプロトコル(Facebook、Slackなどで使用)、Twitter Cardなどのリッチなソーシャルグラフコンテンツを各ページに追加されています。

これは2か所で行われます:

別のアプローチは、あなたがどのページであるかに応じて、このメタデータを異なる方法で更新することです。たとえば、Booksアプリではメインのトップレベル要素ではメタデータを更新せず、サブ要素で指定しています。詳細ページで書籍の画像サムネイルを使用し、検索ページに検索クエリを追加します。

If you want to test how your site is viewed by Googlebot, Sam Li has a great article on gotchas to look out for – in particular, the testing section covers a couple tools you can use, such as Fetch as Google and Mobile-Friendly Test.

データの取得

APIまたは別のサーバーからデータをフェッチする場合は、Redux actionでaction creatorをdispatchしてから非同期で処理することをお勧めします。

For example, the Flash Cards sample app dispatches a loadAll action creator when the main element boots up; it is that action creator that then does the actual fetch of the file and sends it back to the main component by adding the data to the state in a reducer.

たとえば、Flash Cardsサンプルアプリケーションは、メイン要素が起動するときにloadAll actionをdispatchします。そのaction creatorが実際のfetchを実行し、その状態をreducerで追加して、メインコンポーネントに戻します。

A similar approach is taken in the Hacker News app where an element dispatches an action creator, and it’s that action creator that actually fetches the data from the HN API. Hacker Newsアプリでも[action creatorをdispatch]((https://github.com/PolymerLabs/polymer-redux-hn/blob/master/src/components/hn-item.ts#L55)する同様のアプローチが取られています。実際にHN APIからデータを取得するのはaction creatorです。

ネットワーク状態を取得する

ネットワーク状態の変化(オフラインからオンライン)への応答としてUIを変更することができます。

pwa-helpersinstallOfflineWatcherヘルパーを使用して、オンラインまたはオフラインになるたびに呼び出されるコールバックを追加できます。それはスナックバーで使用されています。snack-bar.jsでコンテンツとスタイルを設定することができます。 シナックバーはRedux action creatorにdispatchされた結果として表示されることを覚えておいてください。

FYI 参考までに、オフラインステータスを使用して、アプリケーションに条件付きUIを表示できます。 例えば、Booksアプリオフラインになると、詳細画面ではなくオフラインビューとなります。

状態管理

アプリケーションの状態を管理するにはさまざまな方法があり、適切なものを選択することはチームとアプリケーションのサイズに大きく依存します。

単純なアプリケーションでは、一方向のデータフローパターンで十分かもしれません(最上位レベルの <my-app>要素は状態真理のソースである可能性があり、各要素に渡すことができます)。それで問題なければtemplate-no-reduxブランチを見てください。

別の一般的なアプローチは、Reduxです。これは、アプリケーションの外部のストアに状態を保持し、不変のコピーを各要素に渡します。設定方法を確認するには、Reduxと状態管理セクションを参照してください。

テーマ

このセクションは、アプリのデフォルトの色を変更したい場合や、ユーザーが異なるテーマを切り替えることができるようにする場合に便利です。

デフォルトの色を変更する

テーマ設定を簡単にするため、アプリケーションで使用するすべての色をCSSカスタムプロパティ<my-app>要素で宣言されています。カスタムプロパティは、CSS全体で再利用できる変数です。たとえば、アプリケーションのヘッダーをラベンダーの背景に白いテキストに変更するには、次のプロパティを更新するだけです:

--app-header-background-color: lavender;
--app-header-text-color: black;

また、アプリケーションの他のUI要素も同様です。

テーマの切り替え

アプリ全体を再テーマ化するには、アプリ全体で使用されるカスタムプロパティを更新する必要があります。

独自のテーマでデフォルトテンプレートをパーソナライズしたいだけの場合は、アプリのカスタムプロパティの値を変更するだけです。

アプリケーションの2つの異なるテーマ(明るいと暗いなど)を切り替えるには、my-app要素にdark-themeなどのクラスを設定し、別々にそのスタイルを指定します。これは次のようなコードになるでしょう:

:host {
  /* これはデフォルトで明るい色調のテーマ */
  --app-primary-color: red;
  --app-secondary-color: black;
  --app-text-color: var(--app-secondary-color);
  --app-header-background-color: white;
  --app-header-text-color: var(--app-text-color);
  ...
}

:host.dark-theme {
  /* これは暗い色調のテーマ */
  --app-primary-color: yellow;
  --app-secondary-color: white;
  --app-text-color: var(--app-secondary-color);
  --app-header-background-color: black;
  --app-header-text-color: var(--app-text-color);
  ...
}

これは「ダークテーマを使用する」ボタンがクリックされたときや、その場所のハッシュパラメータ、または時刻などに基づいてこのクラスがいつ追加されるかをコントロールすることになるでしょう。

次のステップ

これで、アプリケーションの設定が完了しました。次のステップに進みましょう: