Polymerを2.0 Previewに上げた
この日記ではPolymerを使っているのだけど、1.xから2.0 Previewに上げた。その時にやったことやはまったことなど。
目次
前提
まず、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-module
にis
属性を付けていた。
<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
を付与する必要がるということだった。
動的に挿入される要素のスタイリング
この日記の検索機能では、XMLHttpRequest
でgroonga-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>
(class
にblog-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-card
とpaper-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-card
やpaper-button
ではなく、vulcanizeの問題だということが分かった(PolymerElements/paper-card/issues/90)。どうもJavaScriptのclass構文で定義した物ををvulcanizeがうまく扱えないということみたい。なので2.0でclass構文使う際には注意されたい。
こんなところかな。あとは公式のアップグレードガイドに従えばいいことばかりだった。
コミットログを見るとコードレベルで何をやっているかが分かると思う: https://github.com/KitaitiMakoto/apehuci/commits/master