アペフチ

Polymerを2.0 Previewに上げた

この日記ではPolymerを使っているのだけど、1.xから2.0 Previewに上げた。その時にやったことやはまったことなど。

目次

  1. 前提
    1. 参考ドキュメント
  2. ライブラリーのアップグレード
    1. Paper Elements
  3. コードの修正
    1. webcomponentsjsなど
    2. paper-header-panel
    3. dom-module
    4. 動的に挿入される要素のスタイリング
    5. paper-cardとpaper-buttonのエラー

前提

まず、Polymerを使っていると言うけど、Webコンポーネント用ライブラリー(フレームワーク)としての他、Googleが提唱するUIデザインであるマテリアルデザイン用のコンポーネント(Paper Elements)なども使っていた。そのうちPaper Cardではまったりしたのでまずここで断っておく。

また、自分で作ったコンポーネントは一つだけで、フッターに置いてある検索ボックス用のblog-searchという要素のみ。これの書き換えについても触れる。

参考ドキュメント

https://www.polymer-project.org/2.0/docs/about_20
Polymer 2.0になって変わったことの概要。
https://www.polymer-project.org/2.0/docs/upgrade
Polymer 1.xから2.0へアップグレードする時のガイド。
https://codelabs.developers.google.com/codelabs/polymer-2-carousel/
Polymer 2.0でコンポーネントを作るチュートリアル。

ライブラリーのアップグレード

https://github.com/KitaitiMakoto/apehuci/commit/8bd0c722cdeea984948ee4ebc89f5a3dfd5c74ca

Paper Elements

概要ページのインストールの項目にあるように、Polymerは

bower install --save Polymer/polymer#2.0-preview

でアップグレードできる。 この時に選択肢が出てくるが、Polymerは2.0、webcomponentsjsは1.0を選んでおけばよい。

各コンポーネントをインストールするにも同様に、各コンポーネントの2.0-previewブランチをインストールすればいい。これまではpaper-elementsという全体をまとめたコンポーネントがあって、それをインストールするだけで全Paper Elementがインストールできたのだけど、なぜか2.0-previewブランチはないので、自分が使っている物を個別にインストール必要があった。めんどくさい(イシューは上がってる、と思ったのだけど、今探したら無いな、幻か知ら)。

あと、

bower install --save paper-input#2.0-preview

みたいに、既存の物を2.0-previewにアップグレードすればいいコンポーネントと、

bower install --save PaperElements/paper-card#2.0-preview

みたいに、GitHubのオーガニゼーションも明示しないといけないコンポーネントがある。Polymer/paper-cardを見てみて、2.0-previewブランチがなければPolymerElements/paper-cardを見る、ということをしなくてはならず、これもめんどくさい。

webcomponentsjsなど

webcomponentsjsなどは、パッケージその物はPolymerアップグレードの時に一緒に自動でアップグレードされるのだけど、Polymerが使うファイルが変わっていることがある。例えばwebcomponents.min.jsというファイルを使っていたのがwebcomponents-lite.jsに変わっていたりするので、コンソールのエラーメッセージを見ながら参照先を変えていく。

コードの修正

アップグレードに伴って、いくつか既存のコードを修正する必要があった。大体はアップグレードガイドに従えばいいが、幾つか嵌った所。

paper-header-panel

コンポーネントをアップグレードしたら画面が真っ白になってしまった。

この日記の大部分はpaper-header-panelの中に入っているのだけど、これの使い方が変わっていて、そのせいだった。子要素を配置するためのslot(従来はcontent)にname属性で名前が付くようになって、ユーザー(=僕)が配置する子要素にも、対応する名前を付けておかなければいけなくなっていた。一応、paper-header-panelのソースコードコメントにはそのことが書いてある: paper-header-panel.html#L32-L37

これに合わせてテンプレートを修正した(slot属性を足しているのに注目):

Before:

<paper-header-panel mode=waterfall-tall>
  <paper-toolbar>
    <!-- ... -->
  </paper-toolbar>
  <main>
    <!-- ... -->
  </main>
  <footer>
    <!-- ... -->
  </footer>
</paper-header-panel>

After:

<paper-header-panel mode=waterfall-tall>
  <paper-toolbar slot=header>
    <!-- ... -->
  </paper-toolbar>
  <main slot=content>
    <!-- ... -->
  </main>
  <footer slot=content>
    <!-- ... -->
  </footer>
</paper-header-panel>

dom-module

カスタム要素を定義するのに使うdom-moduleという要素(メタ要素?)をPolymerは提供している。カスタム要素の定義は本来JavaScriptでやるのだけど、dom-moduleを使うとHTMLを使ってある程度宣言的にできて、僕はこのアプローチを気に入っている。

自分で作ったカスタム要素であるblog-searchはこのdom-moduleで定義しているのだけど、その時に、dom-moduleis属性を付けていた。

<dom-module is=blog-search id=blog-search>
  <!-- ... -->
</dom-module>

isというのは、その要素(ここではdom-module)を更に拡張したバージョンの要素を使う、という時に使う物で、例えばオートコンプリート機能を追加した検索インプットを作って

<input type=search is=auto-complete>

などとして使う(ちなみにブラウザーベンダー間で要不要の意見が割れているらしいので、この仕様はなくなるかも)。

dom-moduleを使う時にはis属性は不要なので、ここでは使い方が間違っているのだけど、問題なく動いていた。単に無視されていたのだろうと思う。2.0 Previewに上げても同様に動いていたのだけど、Chromeで全く動かない(blog-searchの定義自体が失敗している)ことに気が付いた。isを外すと動いたので、ChromeがネイティブでWebコンポーネントに対応しているのと関係していそうだが調べていない(他のブラウザーでは、現在のところ、Webコンポーネントの多くの機能がpolyfillやshimで動いている)。

追記

isは、Polymer 1.xの頃には必要(正確にはnameまたはisが必要)だったので、isを使っていたのは正しかった。単に、2.0にする時に外し、idを付与する必要がるということだった。

動的に挿入される要素のスタイリング

この日記の検索機能では、XMLHttpRequestgroonga-httpd (NginxのGroongaモジュール)から検索結果を取得し、DOMツリー内に挿入している(参考:日記に検索機能をつけた)。Groongaが検索キーワードにkeywordというクラスを付けてくれるので、赤い太字になるようスタイリングしていた。

.keyword {
  color: red;
  font-weight: bolder;
}

Chromeでスタイリングされなくなった。これは、ChromeがShadow DOMを実装していて、Polymerもそのネイティブ実装を活かしている、つまり外部でのスタイル宣言がShadow DOM内に影響を及ぼしていないからだと思う。Webコンポーネントの大きな特長がこのカプセル化なのでむしろ歓迎すべき変更。というわけで、喜んで、Shadow DOMに閉じたスタイリングに変更した。

<dom-module id=blog-search>
  <template>
    <style>
      /* ... */

      .keyword {
        color: var(--keyword-color, red);
        font-weight: var(--keyword-font-weight, bolder);
      }
    </style>
  </template>
  <!-- ... -->
</dom-module>

すると、今度はFirefoxでスタイルが外れてしまった。

FirefoxではShadow DOMが実装されておらずshimを使っている。具体的にはHTMLのstyle要素に、Shadow DOM内に閉じているのとある程度同等のスタイル宣言をして、それを持ってスコープ付きのスタイリングとしている。

<html>
  <head>
    <!-- ... -->
    <style>
      /* ... */

      .keyword.blog-search {
        color: var(--keyword-color, red);
        font-weight: var(--keyword-font-weight, bolder);
      }
    </style>
    <!-- ... -->

このスタイル宣言を活かすために行われるのが、「要素名と同じクラスを追加する」という処理だ。

 <blog-search>
  <!-- ... -->
  <paper-input ... class="style-scope blog-search" ...>
    <!-- ... -->
  </paper-input>
  <!-- ... -->
</blog-search>

classblog-searchが追加されている)

元々HTML内に書かれているタグであれば、このようにPolymerが自動でクラスを追加してくれるのだが、検索結果のように、動的に挿入される要素ではそうはいかない。仕方がないので、挿入する時に自分でクラスを追加するようにした。

// ...
snippetHtml: article[3].join("<br>").
  replace('<span class="keyword">', '<span class="keyword style-scope blog-search">'),
// ...

文字列処理なので乱暴だが、この場合はセキュリティホールにはならない(はず)。

「Firefoxはカスタムプロパティには対応しているんだからそっち使ってくれればいいんだけどなあ」とちょっと釈然としないが、まあ、過渡期ということで仕方ないのだろう。

paper-cardとpaper-buttonのエラー

paper-cardpaper-buttonが(例によって)Chromeでだけ動かない。

Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes

というエラーが出てしまっている。

ググるとStack Overflowがヒットして(Failed to execute 'createElement' on 'Document': The result must not have children)、見るとcreated(新しくはconstructor)コールバックの使い方が、新しいCustom Elements仕様としては不正なようだ。しようがないのでフォークしてここをreadyコールバックに書き換えて対応とした。イシューも上げた(2.0-preview throw an error Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes #90)。

readyはCustom Elements標準にはなく、Polymer独自のコールバック。1.xの時代から存在していて、2.0でも残るようなので(Lifecycle changes)、blog-searchでも使っていたがそのままにしてある。

追記

これはpaper-cardpaper-buttonではなく、vulcanizeの問題だということが分かった(PolymerElements/paper-card/issues/90)。どうもJavaScriptのclass構文で定義した物ををvulcanizeがうまく扱えないということみたい。なので2.0でclass構文使う際には注意されたい。


こんなところかな。あとは公式のアップグレードガイドに従えばいいことばかりだった。

コミットログを見るとコードレベルで何をやっているかが分かると思う: https://github.com/KitaitiMakoto/apehuci/commits/master