PolymerをES6で書く


@laco0416です。Polymerが案外カジュアルにES6で書けるとの話を聞いたので実際に書いてみました。

# 追記(2015/08/27)
↓の記事を書いた後にES6との親和性を改善する変更が入ったので次のように書けるようになりました。 `beforeRegister`イベントハンドラがdesugar処理の前に処理されるようになったのでここで`is`や`properties`など、今までオブジェクトのプロパティとして定義していたものを初期化できます。また、`Polymer(MyElement)`のようにclass(実体はFunction)をプロトタイプとして渡すことが可能になりました。

```js
class MyElement {

  beforeRegister() {
    this.is = "my-element";
    this.properties = {
      foo: String
    };
  }
}

Polymer(MyElement);
```

だいぶ自然に書けるようになりましたね。まだリリースされていないのでbowerからのインストールでは利用できませんが、この変更はPolymer SummitのES6関連のセッションで取り上げられるようです。


# 結論
十分実用的なのですでにBrowserify+Babelする環境あるなら余裕。なくてもきっかけになるかも。

# 事の発端
日頃からPolymerのAPIはイケてないなと思っていて、大体の原因はES5にあると決めつけているので時々「PolymerはES6対応したら化ける」とぼやいているのだけど、今日もPolymerのSlackでES6 classとか使って書けるようにならないの？って投げていたらすでにChromeチームが実験的にやってることが判明した。

https://github.com/GoogleChrome/guitar-tuner

サンプルとはいえES6 + Browserify + Babel + Web Audio API + Service Workerと詰め込みすぎやろ！と突っ込みたくなるアプリケーションである。

このrepoのgulpfileを参考に自分でES6とPolymerの親和性を確かめてみた。

# 準備

## 構成
```
├── bower.json
├── bower_components
├── dtsm.json
├── gulpfile.ts
├── node_modules
├── package.json
└── src
    ├── elements
    │   └── x-hoge
    │       ├── x-hoge.html
    │       └── x-hoge.js
    ├── index.html
    └── scripts
       └── lib.js
```

### bower.json
```
...
  "dependencies": {
    "polymer": "~1.1.1"
  }
...
```

### package.json
```
...
  "devDependencies": {
    "babelify": "^6.2.0",
    "browserify": "^11.0.1",
    "del": "^1.2.1",
    "gulp": "^3.9.0",
    "gulp-vulcanize": "^6.0.1",
    "run-sequence": "^1.1.2",
    "typescript": "^1.5.3",
    "typescript-require": "^0.2.9",
    "vinyl-source-stream": "^1.1.0"
  },
...
```

### dtsm.json
```
...
  "dependencies": {
    "gulp/gulp.d.ts": {
      "ref": "a1226afe5c4ab96610e088d970eb4f8537b1d116"
    }
  }
...
```

# やる

## x-hoge.html
今回は `<x-hoge>` という要素を作る。

```html:x-hoge.html
<link rel="import" href="../../../bower_components/polymer/polymer.html"/>
<dom-module id="x-hoge">
  <template>
    <h4>x-hoge</h4>
  </template>
</dom-module>
<script src="x-hoge.js"></script>
```

参照するx-hoge.jsはES6で書く。


```js:x-hoge.js
import {Greet} from "../../scripts/lib"

class XHoge {

  get is() {
    return "x-hoge"
  }

  constructor() {
  }

  attached() {
    Greet("x-hoge");
  }
}
Polymer(XHoge.prototype);
```

importされるlib.jsは簡単なもの

```js:lib.js
export function Greet(name) {
  console.log(`Hello ${name}!`);
}
```

x-hogeを呼び出すindex.htmlはシンプル

```html:index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <link rel="import" href="elements/x-hoge/x-hoge.html"/>
</head>
<body>
  <x-hoge></x-hoge>
</body>
</html>
```

## gulpfile

もちろんこのままではES6が解釈できずにブラウザでは動作しないので、Babelを通さないといけない。今回はBrowserifyとBabelifyで一挙に解決する。また、gulpfileをTypeScriptで書くのに最近便利さを感じているので今回もgulpfile.tsを書いた。

```ts:gulpfile.ts
///<reference path="typings/bundle.d.ts"/>

var gulp:gulp.Gulp = require("gulp"),
    del = require('del'),
    browserify = require("browserify"),
    babelify = require("babelify"),
    source = require('vinyl-source-stream'),
    runSeq = require("run-sequence");

const bundles = [
  {
    src: "./src/elements/x-hoge/x-hoge.js",
    name: "x-hoge.js",
    dist: "./dist/elements/x-hoge"
  }
];

gulp.task('clean', (done) => {
  del(['dist'], done);
});

gulp.task('root', ["clean"], () => {
  return gulp.src('./src/**/*.*')
      .pipe(gulp.dest('./dist/'));
});

gulp.task("build", ["root"], ()=> {
  bundles.forEach((bundle)=> {
    browserify({
      entries: [bundle.src],
      debug: true
    }).transform(babelify)
        .bundle()
        .on("error", (err) => {
          console.log("Error : " + err.message);
        })
        .pipe(source(bundle.name))
        .pipe(gulp.dest(bundle.dist));
  });
});

gulp.task("default", ["build"], () => {
});

```

流れとしては

1. distを空っぽにする
1. srcの中身をdistにコピーする
1. bundlesに指定したjsをエントリポイントにBrowserify+Babelifyする

これでx-hoge.jsにlib.jsがconcatされ、さらにES5にトランスパイルされるのでブラウザでちゃんと動くようになる。

![kobito.1440517953.868081.png](https://qiita-image-store.s3.amazonaws.com/0/20253/1353d00c-dd74-7936-479f-c05b9296e624.png "kobito.1440517953.868081.png")

## vulcanize
PolymerはHTML ImportsでXHRが増えるのでProductionではVulcanizeしてHTMLを結合してまとめようねというのが主流。オプションでJSやCSSもインラインに解決してくれるのでこの流れに含めてindex.htmlだけで全部解決できるようにする。

gulp-vulcanizeというプラグインがあるので使う。

```ts:gulpfile.ts
///<reference path="typings/bundle.d.ts"/>

var gulp:gulp.Gulp = require("gulp"),
    ...
    vulcanize = require("gulp-vulcanize");

...

gulp.task('vulcanize',() => {
  return gulp.src('./dist/**/*.html')
      .pipe(vulcanize({
        inlineScripts: true,
        inlineCss: false,
        stripExcludes: false
      }))
      .pipe(gulp.dest('./dist'));
});

gulp.task("default", ["build"], () => {
  setTimeout(()=> {
    runSeq(["vulcanize"]);
  }, 1000);
});
```

defaultタスクのなかで`setTimeout`するのはすんごい雑なHackで、本当はなくてもできるはずなんだけど、ウェイト無しでやると何故かトランスパイル前のES6のJSをインラインに展開してしまうということが発生した。Gulp弱者なのでこういうときどうするのが正しいのかわからない。

Vulcanize前はこんな感じ

![kobito.1440518537.296586.png](https://qiita-image-store.s3.amazonaws.com/0/20253/745e6b84-4b51-1756-fd21-5346cc4d2d11.png "kobito.1440518537.296586.png")

Vulcanize後 当然ながらindex.htmlだけになる

![kobito.1440518614.267125.png](https://qiita-image-store.s3.amazonaws.com/0/20253/c3d3d032-fc37-4658-3cb6-84d6003f91d2.png "kobito.1440518614.267125.png")

# まとめ
Gulpで躓いたくらいで他はすんなりいけたので思った以上に親和性高いという印象。
どんどんES6使うのが当たり前みたいな風潮作っていきたい。

# 余談
この「PolymerとES6一緒に使えるぜ」ネタ、Polymerチームが9月のPolymer Summitに向けて温めていたらしく最近コードが表に出てきたっぽい。Summit用のIssueは https://github.com/Polymer/polymer/labels/summit で見られる。まだES6系のしか上がってないけど今後増えるんじゃないか？と予想。