🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

Polymer 1.0をさわってみて

さて、ここにこういうサイトがあります。

nodefest.jp


実はこのサイト、噂のPolymerの1.0をふんだんに使ってます。
というわけで、そんなそこまでがっつり使い込んだわけではないですが、知見らしいものが一応たまったので・・。
まだ見ぬ未来のポリマリストのために書いておきます。

Polymer Starter Kit

github.com

こういうのがありまして。
とりあえずcloneしてきてビルドすればそれっぽい雰囲気はすぐにつかめると思うので、さわってみたい人はここからはじめると良さそうです。

Gulp

まずは外堀から埋めていきます。

# localhost:3000 で開発(ビルドしない)
gulp serve

# localhost:3000 で開発(ビルドしたものは`dist`配下に)
gulp serve:dist

# ビルドするだけ(`dist`配下に)
gulp

お世話になるのはこのコマンドたちです。
gulpfileは最初にひと通り見ておくとよいです。

Polymer Starter Kit の gulpfile.js を読み解く - Qiita

というイカしたまとめがあることにさっき気付きました。

このタスクの中で特記するならこの2つかなー。

html

このタスクの中で使われてる`gulp-useref`ってやつ、最初にこれを見つけたとき衝撃でした。

`app/index.html`の中身をなめて、

 <!-- build:js scripts/main.min.js -->
 <script src="scripts/lib/jquery.js"></script>
 <script src="scripts/main.js"></script>
 <!-- endbuild -->

的なコメントブロックを見つけると、そこの指定通りにビルドしてくれるってやつです。

つまり、必要なものです!
なんだこの無駄に丁寧なコメント・・って思って無意識に消さないように注意です!!

ビルドのときに一緒にまとめてくれるので、自分で書いたコードを追加するならココに足すとよかです。

vulcanize

vulcanizeはPolymerやるなら避けて通れない概念です。

コンポーネントに分けまくって開発したり、外部のコンポーネントをインポートしたりすると、あちこちのファイルが細かくリクエストを飛ばしまくる地獄絵図が容易に想像できますよね。
それをまとめることでリクエスト減らそうっていうタスクです。

ヴァルカナイズとは、〈ゴムを〉硬化[加硫,硫化]するって意味らしいですよ。

さて、いよいよ踏み込みます。

おれおれ要素を追加する

独自な要素を追加する準備は整ってるので、いざ作るのみです。

webcomponents.org - Discuss & share web components

こういうのもあるので、ここから引っ張ってくるもよし、自分で作るもよし。

手順としては、

  • めぼしいのを見つけて`bower i`
  • もしくは自分で用意して配置
  • `app/elements/elements.html`にパス追記
  • HTML内で使う

ってだけです。
importするの忘れると、もちろんですがおれおれ要素として認識してくれないので注意。

How to カスタム要素

サンプルあるので見ればわかると思いますが、いちおう。
これが最小構成。

<!-- 最小カスタム要素 -->
<dom-module id="my-elm">
  <style>
    /* :hostがこの<my-elm>自体 */
    :host { color: tomato; } 
  </style>

  <template> 
    <div>This is local DOM</div>
  </template>

  <script>
    // dom-moduleのidとあわせる
    Polymer({is: 'my-elm'});
  </script>
</dom-module>

dom-moduleの#idと、Polymerの引数の`is`を一致させるだけで最低限の要素が完成。
1ファイルにHTML/CSS/JS全部書く流れがきてますねー。

構造としても、

- dom-module
  - style
  - template
  - script

ってだけ守ればよしです。

styleはtemplateの中に入れても良いとか、scriptはdom-moduleの外でも良いとか、サンプルが色々出てくるけど、今のところはこれが一番ベターだと思います。

何はともあれこれで、

<my-elm></my-elm>

ってできます。

スタイル

GulpのタスクにはAutoPrefixerとかいましたが、ここには来てくれません。
インラインで書く = 自力でなんとかするなのは、Reactでもあんま変わらん印象。

Inline CSS Prefixer · Issue #325 · Polymer/polymer-starter-kit · GitHub

ふーむ。
やはりビルドタスクでなんとかするしかない流れなのか・・。

ちなみにインラインでCSS書くと、あとで展開されるときに`.hoge.my-elm`的な感じで影響範囲を切ってくれてるのがheadのぞくとわかります。
このコンポーネントに関するものはインラインで、レイアウトとか全体のスタイルにはstyles/**.cssに書くべし。

テンプレート界隈

Data binding helper elements - Polymer Project
Data binding - Polymer Project

最初にコレらを読むと、事故率がだいぶ下がります。
{{hoge}} っていうのだけ見て、ふーん、Handlebars的な感じなんでしょってノリでいきなり書くと絶望します。

以下、そんな知ってればよかったTipsたちをお送りします。

id="hoge" -> `this.$.hoge`

お察しの通り、idを振っておくと参照する時に楽です。
Vue.jsとかReactにもこういうのあったやんね。

外から渡す

SassのMixinのアレみたい。

<dom-module id="my-elm">
  <template>
  <ul>
    <li>??</li>
    <content></content>
  </ul>
  </template>
  <script>
    Polymer({is: 'my-elm'});
  </script>
</dom-module>

からの

<my-elm>
  <li>!!</li>
</my-elm>

ってすると

<my-elm>
  <ul>
    <li>??</li>
    <li>!!</li>
  </ul>
</my-elm>

ってできる。

外から渡す 2
<my-elm word="hello"></my-elm>
<my-elm word="goodbye"></my-elm>

ってのは、

<dom-module id="my-elm">
  <template>
    <p>Say <span>{{word}}</span></p>
  </template>
  <script>
    Polymer({
      is: 'my-elm',
      properties: {
        word: { type: String }
        // OR word: String
      },
      ready: function() { console.log(this.word); }
    });
  </script>
</dom-module>

React.propTypesみたいやね。

dom-repeat

配列まわしたいとき。

<template is="dom-repeat" items="{{users}}" as="user">
  <span>{{index}}</span>. <span>{{user.name}}</span>
</template>

template万能すぎぃ。

  • `as`で指定しなかったら`item.**`でアクセス。
  • `index`はいわゆるインデックス。
dom-if

条件分岐したいとき。

<template is="dom-if" if="{{cond}}">
  if
</template>
<template is="dom-if" if="{{!cond}}">
  else
</template>

ちなみに、template要素はトップレベルに一つだけしか置いちゃダメです。
なので、

<template>
  <template is="dom-if" if="{{cond}}">
    if
  </template>
  <template is="dom-if" if="{{!cond}}">
    else
  </template>
</template>

ってな具合にせないかんです。

{{hoge}} vs \[\[hoge\]\]
<span>{{hoge}}</span> vs <span>[[hoge]]</span>

この違いは、データのバインディングが双方向か、一方向かの違い。

必ず要素で囲う
<!-- ok -->
<div>{{hoge}}</div>

<!-- ng -->
<div>
  {{hoge}}
</div>

<!-- ng -->
{{hoge}}

そして

<!-- ok -->
<div>
  HOGE: <span>{{hoge}}</span>
</div>

<!-- ng -->
<div>HOGE: {{hoge}}</div>

Polymerまじ・・・。

属性にも変数を
<!-- href -->
<a href$="{{url}}">

<!-- dataset -->
<div data-bar$="{{baz}}"></div>

こういう書き方で書くと、内部的に`setAttribute`してくれるからこっちのが良いらしいですよ。

文字列連結

コレを見て察してください。

<dom-module id="my-tw">
  <template>
    <a href$="{{_tw(id)}}" title$="{{id}}">@<span>{{id}}</span></a>
  </template>
  <script>
    Polymer({
      is: 'my-tw',
      _tw: function(id) { return 'https://twitter.com/' + id; }
    });
  </script>
</dom-module>

Polymerほんとまじ・・・。

inner-h-t-m-l

外部からHTMLをいれこみたいとかありますよね??????

Polymerでは基本的に全部エスケープされちゃうので、どうしようもないです!
お手上げです!ってなってたんですけど・・・。

<dom-module id="my-html">
  <template>
    <div inner-h-t-m-l="{{htmlStr}}"></div>
  </template>
  <script>
    Polymer({
      is: 'my-html',
      ready: function() { this.htmlStr = '<p>HTML!</p>'; }
    });
  </script>
</dom-module>

なんだこれ。
ドキュメント検索しても出てこないしドコ情報やねんって感じですけど、動きます。

ちなみに、

  • o: inner-h-t-m-l=""
  • o: inner-H-T-M-L=""
  • o: inner-h-T-m-l=""
  • o: inner-h-t-M-l=""
  • o: inner-h-t-m-L=""
  • x: innerHTML=""
  • x: innerHtml=""
  • x: inner-HTML=""
  • x: inner-Html=""
  • x: inner-html=""

ハイフンが重要です。
Polymer・・・ほんとまじ・・・。

ハマったとこ

上記の内容すべて

面倒になって手探りではじめてしまったのがもったいなかった。(けど仕方ないと思う

ドキュメント

  • 巨大で読む気失せる
  • かといってコードはもっと巨大なので読む気(ry
  • 検索すると0.5の情報ばっか出てくる
  • 1.0のページで検索すると0.5の情報へのリンクが出てくる
  • サンプルコードあったりなかったり

すべてはWebComponentsReadyから

各要素をquerySelectorしたり、メソッド叩いて何かしたりする場合、このイベントを待ってからじゃないとダメです。

ブラウザによって`WebComponentsReady`の発火順が違ったり色々なので、
bodyの最後に読み込まれるjsで`WebComponentsReady`を待って、全部の処理を開始すべし。

IE x dom-repeat x table is 死

Dom-repeat doesn't work in tables in IE · Issue #1567 · Polymer/polymer · GitHub

そうやね、仕方ないね。
Polymer is currently in “developer preview." やし、仕方ないね。

コンポーネントのスコープ?

言葉が不適切かもしれないけども。
SPAなんかでよくあるタブ切り替えのページを実装する場合。

  • paper-tabs
  • iron-pages

この2つを使うとしましょう。

これらの要素は、それぞれ`selected`ってプロパティにインデックスの数値を入れると、そこがアクティブになる・・って仕組みです。
で、どうやってこの`selected`を共有するかが問題。

<dom-module id="my-elm">
  <template>
    <paper-tabs selected="{{selected}}">
      <paper-tab>Tab 1</paper-tab>
      <paper-tab>Tab 2</paper-tab>
      <paper-tab>Tab 3</paper-tab>
    </paper-tabs>

    <iron-pages selected="{{selected}}">
      <div>Page 1</div>
      <div>Page 2</div>
      <div>Page 3</div>
    </iron-pages>
  </template>
  <script>
  Polymer({
    is: 'my-elm',
    ready: function() {
      this.selected = 0;
    }
  });
  </script>
</dom-module>

この例みたく、同じ要素で閉じ込めちゃえば問題なし。
selectedはちゃんと同期します。

ただもしこれが、

<my-tabs><!-- こっちタブ --></my-tabs>
<my-pages><!-- こっちページ --></my-pages>

こうしちゃった場合にどうすればいいか。
そのままではそれぞれの要素内で`selected`が存在するだけで、お互いには関係ないのでもちろん動きません。

まぁもうわかりそうですが、手動で同期させる必要があります。

Polymer Starter Kitのデモだと、page.jsを使ってやってます。
`app`って変数をbody直下に`dom-bind`して、それで同期させてます。

https://github.com/PolymerElements/polymer-starter-kit/blob/master/app/elements/routing.html

このへん見ればなるほどなーってなるかと。
ただこのHTMLにあれこれ書くのがイヤで、今回はこのやり方採用しなかったです。

paper-header-panel x scrollTop

GitHub - PolymerElements/paper-header-panel: A Material Design panel with top and bottom panes

これを使ってて、ページ内スクロールをjsでやりたい場合。

window.scrollTo(0, 0); // なにもおきない

// 別にquerySelectorとかでもいい
// $('paper-header-panel').querySelector('#mainContainer').scrollTop = 0;
$('paper-header-panel').lastElementChild.firstElementChild.scrollTop = 0;
  • paper-header-panelは、ヘッダとパネルの2つを子要素に持つマークアップをするもの
  • パネルの中にはコンテナと装飾用の要素があるだけ
  • このコンテナが実体なのでコレのscrollTopを変更する

ふーむ。

SEOとは

あれもこれも独自要素にしてしまうことの欠点ですかね。

「ソースを表示」ってやるとすぐバレますが、独自で作ったカスタム要素は空っぽに見えちゃいます。
GoogleBotがどこまで解釈してくれるか知りませんが、たぶん期待できないので状況によっては注意かなーと。

謎のWebFont

答えがわかれば別に謎でもなんともないんですけど、いちおう。

最初の方に紹介したカタログの中にある、「Paper Elements」ってやつ。
いわゆるマテリアルデザインをさくっと取り入れられるよ!って要素たちです。

このシリーズのだいたいがpaper-stylesってのをdependenciesに持ってて、ここにRobotoのウェブフォントがいます。

まったく身に覚えないのに(最悪使ってないのに)ウェブフォントやら同じようなライブラリやらがロードされまくってページのロードがやけに遅い・・とか、WebComponentsの未来のあるあるが見えた気がした。

おわりに

さて、2週間ほどの短い期間ですが、Polymer 1.0に触れていろいろ感じた結果、熱い思いがあふれそうになったのでココに書き出してみた次第です。

  • テンプレまわりはだいぶがっかりやった
  • けど、全体的には思ってたより良かった
  • コンポーネント指向ってのは納得できる
  • ただServiceWorker用の要素とか、もはや要素とはなんやねんってなるのでやっぱ好きくない
  • 使いドコロが一番の問題で、楽できそうなら使えば良いと思う
  • でも手軽ではない
  • モバイルでは絶対使いたくない(個人の感想です)
  • 総じて設計の難易度は上がった感じする

以上、個人の感想でした。(どこかで誰かの参考になれば幸いです。