このところ Polymer ならびにその関連技術と格闘していたので知見を記録します。

Polymer とは

Google 社のエンジニアが生みだした JavaScript ライブラリで、Web Comonents の要素技術を採用しつつ UI コンポーネントのセットを簡単に利用できるよう構築されたもの、というのが、雑な説明ですが Polymer のあらましです。Material Design な UI を簡単に導入できることもよく語られる特徴です。

Polyfills とは

Polyfills とは、非モダンブラウザでもモダンブラウザに実装された技術 – HTML5 や JavaScript、CSS3 – が動作するよう、どうにかこうにか頑張ろうという人類の営みの総称です(たぶん)。したがって Polyfills という概念の内側に Polymer という実装が存在するという位置づけと言えるでしょう。Polymer は、ブラウザ、ブラウザのバージョン、デバイスという垣根を超えるものとして期待されています。あるいは、いました。

Polyfills について語るうえで切っても切れないのが、Web Components です。

Web Components は新しいものではないため、以下を例とする詳細な解説が既に存在します。

以下が Web Components の要素技術であるとされます。

  • HTML Templates
  • HTML Imports
  • Custom Elements
  • Shadow DOM

Polymer のいいところ

一般に Polymer 導入のモチベーションとなる点をあげます。

  • Polymer Element Catalog にある UI コンポーネントを利用したい、UI をコンポーネント化したうえで開発したい
  • Shadow DOM を簡単に扱いたい
  • Material Design を導入したい

今回 Polymer の技術調査をした一番の理由は Shadow DOM を使いたかったためでした。Shadow DOM については後述します。

Polymer の導入

Polymer プロジェクトを始めるには、polymer-cli を利用するのが簡単です。公式手順のママですが、以下が polymer-cli の導入方法です。また、Polymer は各コンポーネントのインストールなどに公式手順として bower を使うことが記されています。脱 bower が進むなか大丈夫かという気がいたしますが、bower も導入します。

npm install -g polymer-cli bower
mkdir my-app
cd my-app
polymer init

polymer init でプロジェクトの雛形を作成できます。今回は表示される選択肢の中から application を選んだ場合とします。以下は作成される index.html です。

<!doctype html>

<html>
  <head>
    <title>my-app</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="manifest" href="/manifest.json">
    <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
    <link rel="import" href="/src/my-app-app/my-app-app.html">
  </head>
  <body>
    <my-app-app></my-app-app>
  </body>
</html>

Web Components の仕様である import 機能を利用するために、webcomponents-lite.js を読み込んでいます。次に、<link rel="import" ...> として、Polymer で実装したコンポーネントファイルを読み込んでいます。

<my-app-app> は Custom Tag です。

次に、同時に作成された ./src/my-app-app/my-app-app.html の中身を確認します。

<link rel="import" href="../../bower_components/polymer/polymer.html">

<dom-module id="my-app-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <h2>Hello [[prop1]]</h2>
  </template>

  <script>
    Polymer({
      is: 'my-app-app',

      properties: {
        prop1: {
          type: String,
          value: 'my-app-app',
        },
      },

    });
  </script>
</dom-module>

Web Components の仕様である Template 機能を利用しているのが <template> で囲われた部分です。CSS Style と HTML をセットで記述します。<script> タグの内部には、Polymer として必須の記述のほかに、イベントハンドラなど、このコンポーネントを動作させるのに必要な機能を実装していきます。

Polymer catalog の追加

Polymer の最小構成がわかったところで、Polymer Element Catalog を利用して UI を追加してみます。Material Design でよくみかける 丸いボタンを追加します。

Component を個別に導入するには、bower を利用します。導入のコマンドは catalog の各ページに記載されています。

bower install --save PolymerElements/paper-fab

my-app-app.html で、以下の2つの Component を読み込みます。

<link rel="import" href="../../bower_components/paper-fab/paper-fab.html">
<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">

Custom Tag を記述します。

<h2>Hello [[prop1]]</h2>
<paper-fab icon="favorite" title="heart"></paper-fab>

index.html を確認すると以下のようにボタンが表示されています。

このように、Polymer Element Catalog から UI Component を導入できます。

プロダクション利用にあたり - minify など

これまでの例では、小さなパーツを組み立てるだけでも、複数の html ファイルを読み込まなければなりませんでした。JavaScript ファイルは concat + minify したうえで deploy するのが常套手段ですが、Polymer においてもアプローチは同じです。

各 Component は、Vulcanize というツールを利用して結合可能です。

上記の記事で紹介されている処理をひとまとめに行う polybuild という Gulp プラグインもあります。しかし、Vulcanize や polybuild では minify はされないため、別途 minify の必要はあります。

Polymer を採用するうえでの注意

Polymer の概要がわかったところで、採用するうえでの注意を書いていきます。

ドキュメントが親切とは言えず学習コストが高い

paper-fab を導入するにあたり、参考にしたページは以下です。

先ほど、paper-fab の説明をするにあたり、

以下の2つの Component を読み込みます。

と記載しました。しかし、依存する Component の読み込みについて上記のドキュメントには書かれていません。paper-fab については bower の インストールコマンドに書かれているため、依存関係を想像することは容易かもしれません。iron-icons についてはどうでしょうか。

実際説明を書くにあたり、paper-fab だけではアイコンが表示されなかったので、たぶんそうだろうなと考えながら iron-icons を追加しました。

Component についての説明であるため、極力限定した解説にとどめているのかも知れません。しかしながら、このように (非常に単純な機能でさえ) 想像を働かせながら利用しなければいけないので、人にはあまりおすすめができません。

Shadow DOM は 人類の見た夢

Shadow DOM は Web Components の仕様の1つです。Shadow DOM のもっとも興味深い点は、CSS の隔離空間 (分離されたスコープ) を構築できることです。CSS の利用において、しばしば名前の衝突や意図しない継承によるレイアウト崩れが問題となります。人々は BEM をはじめとする CSSの設計手法によりこの問題と向き合ってきました。

いっぽうで Shadow DOM は、Shadow DOM 中に記載した CSS が Shadow DOM の外部へ影響を及ぼさず、また、Shadow DOM により構築された HTML は、外部 (上位) の CSS の影響を受けないというアプローチを取りました。これは素晴らしいアイデアのようです。

しかし、人の夢と書いて儚い、とかつて誰かが言っていたように、Shadow DOM は人類の見た夢でした。Polymer (1.0) の採用したアプローチは、以下のものでした。

  • Component 内部で指定した Style は Component の外部に干渉しない
  • Component の外部で指定した Style よりも、内部で指定した Style が優先される。しかし外部からの干渉は可能

「外部からの干渉」に特別な手段は必要ありません。Component 内部で指定していないスタイルを外部で指定するだけで、Component の Style に影響します (p {color: "red";} など) 。また、優先順位の問題であるため !important を利用して簡単に上書きできます。

Polymer Project はこの仕様に shady DOM という名前を付けました。経緯や理由については以下に説明があります。

パフォーマンスや実装面を考慮すると厳格な Shadow DOM は採用できなかったという感じでしょうか。いずれにしても、CSSの隔離空間に期待していた場合、Polymer のメリットの1つは無くなってしまいます。

ところで、Shadow DOM については明るいニュースもあります。Safari 10 が Shadow DOM の V1 を採用しました。

ブラウザ対応が進めば、完全なスコープの分離を利用した Web開発が進むことが期待されます。

残念ながら、悪いニュースもあります。現時点で Edge や Firefox がサポートしていません。

歴史的経緯により、Web Components の仕様実装の遅れに関してはあまりブラウザベンダーを責められない状況です。いずれにしても現実的には「Shadow DOM っぽい何か」を利用するよりないようです。

なお、Polymer に依存せず webcomponents.js のみで Shadow DOM を利用しようとする 場合、SAN値と引き替えになることを申し添えておきます。

代用手段がすでに発達していること

フロントエンド開発において、Component指向のアプローチが強まっています。フロントエンドフレームワークについては (役割は完全に一致しないものの) React.jsAnglar 2.0 の2強時代がもう少し続きそうです。MithrilRiot.js も、再利用可能な Component の実装に向いたフレームワークです。

いわゆる React の Component と、Web Components のコンテクストでみてきた Component はまったく同じものではありません。また、Polymer と React や Anglar の役割も完全に競合するものではありません。しかしながら、ごく個人的には、template 機能を活用した Component 実装よりも React の JSX のほうが筋がよいように見えるし、どのみち CSSスコープの完全な分離ができないのであれば、Inline StyleCSS in JS のアプローチで事足りるように思います。

また、Polymer のウリの1つ Material Design の導入も、Angular MaterialMaterial-UI を使えば、単一のフレームワークの学習コストの範囲内で実現できます。

以上のことを念頭におきながら、本当に Polymer 採用が適切かどうかを検討するとよいでしょう。

Polymer の導入のしどころ

ごく単純な Component を Widget的 に配布したい場合には選択肢の1つになり得るでしょう。

たとえば Google Analytics のタグは以下のようにできるかも知れません。

<script src="https://example.com/webcomponents-lite.js"></script>
<link rel="import" href="https://example.com/google-analytics.html">

<google-analytics uid="UA-xxxxxxxx-1" send="pageview"></google-analytics>

まぁこれが嬉しいかどうかですが… 上記の例はともかくとして、HTMLタグとして導入でき、Pure な DOM よりは 多少 Robust な Elements を展開できると考えれば、利用の動機になる場合もあるのではないでしょうか。

Polymer の今後

Polymer 2.0 Preview がリリースされています。記事によると、Shadow DOM V1 に準拠した仕様を導入したようです。しかし、おそらくネイティブに対応しないブラウザについては引き続き「Shadow DOM 的な何か」が動作がすると思われるため、やはりブラウザの追従に期待するよりないように思われます。