Dartsummit中々アツアツでしたね(ちょろっとしか見てないけど)。
DartVMの夢を捨てた代わりに現実世界で頑張る感じに期待が持てます。
Fletchとかどのレベルで使い物になるのか超楽しみっす。
さてさて、それとは特に関係なく、
せっかくのゴールデンウィークなんでDartの勉強のためにWebアプリ作ってました。
SlackReminder
http://sr.takyam.com/
ソースコード
https://github.com/takyam-git/slack_reminder
何か適当に登録するとその日時にSlack経由で通知してくれるだけのWebアプリ。
ログイン周りはSlackのOAuthをそのまま使った。
OAuthでユーザー認証するんじゃねーよ!って怒られそうな気もするけど気にしない。
さて、フロントもバックも全部Dartで書いてみたんですが、結論としては辛い。
以下、感想というか愚痴というか何というか、ただのポエムです。
フロントエンドで使ったPolymer
まず、Polymerが辛かった。
製作時間が約4日半くらいで、その半分以上はPolymerに苦戦してた感じ。
初めてPolymer触ったんだけどクセをつかむまで、、、というか core-elements / paper-elements の使い方や思想を感じられるようになるまで2日近くかかった。
超高機能(になるかもしれない)TwitterBootstrap == Polymer elements な認識。
確かにCustomElementsという閉じた世界で、かなり高機能な事を影響範囲小さく行えるのは素晴らしいし、実際に自分でSPA作ってみてこれは便利だなと思った。
けどそれはそれとして、デフォで提供されてる要素群の使い方が難しい。
答えが分かれば別に「あーね、なるほどね」くらいな感じなんだけど、何故か画面が真っ白に!とか、何でか思った通りに表示されねぇ・・・とかとかいろいろハマりまくった。
ハマったところ
大体問題は以下の3種類。
- import忘れ
- ドキュメント読んでなかった
- そもそもそんな事はできない事をしようとしてた
最初の頃はimport忘れて「何で出ないんじゃぁぁぁ」ってなってたけど10回くらい繰り返せば流石に体が「まずimportされてるか確認しよう」って覚えてくれた。割と忘れる。
ドキュメントは超不親切で、基本的には「demoのソース読め」状態。
何となく「Polymerの雰囲気」つかめてくると「あぁ、必要最低限のこと書いてあるなぁ」とは思えるんだけど、いきなりあのドキュメントだけだと辛い。
あとは何か期待値があがっちゃって「これできそう!やってみよう!」->「んなことできねーよ」は割とあった。
たぶんやりたい事やるには core-elements/paper-elements を継承したオレが考えた最強の core-elements/paper-elements を作ればいいだけなんだけど、そこまでするのはちょっと・・・って感じ。継承元のソースコード読み解くのとか辛い。
Polymerの感想
- データバインディング便利だけどちゃんとドキュメント読んでから使え
- CSSセレクタマジ糞
- かゆいところに手がとどかない
- 意外とクロスブラウザで動いてる(たぶん)
- isomorphic的なやつ
データバインディングの話
データバインディングはかなり便利で、モデルの管理を適当にやってればあとは表示側は本当に適当に変わってくれちゃうので便利だよな、とは思う。
バインディングの指定も細かい事指定せずに <p>{{model.hoge}}</p>
みたいに指定しておけばそのモデルのhogeの値が変わったら勝手にその<p>
タグの部分も変わってくれる感じ。
それはもうそれはもう便利なんだけど、バインディング指定してると<input _value="{{model.hoge}}">
みたいにしてるINPUTで、カチャカチャ入力すると入力した内容にmodel.hoge
の値も変わったりして、それはもう何ていうか超双方向バインディングなわけなんですが、その辺の機能を全く理解せずに作り始めたので、編集フォームとか作ったら保存するまえにモデルのプロパティ書き換わってうわぁぁあみたいな。
CSSセレクタの話
CSSセレクタが超難しくて、未だによく分からんところが多すぎる。
(普通のDOMに対するセレクタは普通に書けるレベルの人間ですよ)
:host(#hoge)
とか ::shadow
とか hoge-elements /deep/ .fuga
とか、shadow-dom用のCSSセレクタがいくつかあるんですが、コレが何か期待どおりに動かない。マジで理由分からんけどスタイルが当たらなかったりする。「とりま /deep/ つけときゃ楽勝っしょwww」とか思ってた時代が俺にもありました。/deep/とか:hostとかそんなチャチなもんじゃスタイルが反映されない事がザラにある。
最終的には style="background-color: white;"
みたいにstyle属性つけるのが最高に間違いない事が分かったので、もうなんかコレでいいかなと思ってる。
かゆいところに手がとどかない
GoogleっぽいUIがお手軽に作れそうな雰囲気の core-elements
と paper-elements
ですが、色指定とかサイズ指定とかその辺はCSSで各自指定してねっって感じのがほとんどなので、Bootstrap気分でデザインの部分を全く気にせずにすむかというとそうでは無いので注意。
あとは前述した通り、「アレができそう!」でも実際は出来ない事が多すぎるので、夢から醒めた状態でコーディングし始めないと辛いと思うですよ。
基本的にはDEMOページでできてる事以上の事はできないつもりでいたほうが精神衛生上よろしい。
クロスブラウザ
何も意識せずに開発中はChromeしか見てなかったけど、<input type="datetime-local">
みたいなPolymerと関係ないところ以外はどのブラウザでも普通に動いてる(ように見える)。
細かいところでもしかしたら挙動に違いとか出てくるのかもしれんけど、開発中に細かく全部のブラウザ気にしながら〜みたいな事はしなくてよさそう。Good。
なお、IEでは見てないので分からん。
isomorphic的なやつ
Polymerで作るとたぶん原理的に「ページ読み込み後のレンダリング待ち時間」が発生する。たぶん。
サーバーサイドでPolymerの必要な部分だけ展開するとかできるのかなぁ・・・
それにしても初期化コストでかそうだけど。
解決方法あるのかなぁ・・・
Polymerの今後について
よう知らんし、Reactによって駆逐されるのかもしらんけど、割と使えるやつだとは思いました。
そもそもバージョン0.5だしChrome以外だとshadow-domの実装がイマイチなんで、今後はどうなっていくんだろうなーわくわく。でいいかと。
サーバーサイドで使ったstart
前回 は http_server 使ってサーバーサイド書いてみたんですが、今回は start 使ってみました。
DartのサーバーサイドフレームワークってExperssだったりRailsだったりの決定版が無い状況なので、とりあえず今回はREST形式のAPIだけ提供できりゃほぼほぼ良かったんで、Sinatra inspired
とかうたってるstartにしてみた感じです。
これが大きな間違いだった。
軽めなのはいいことだが、スーパーフェザー級だと辛い事が分かった。
困った事
- セッションがオンメモリ一択
- テンプレートレンダリングとか無いです
- 共通処理とか出来ると思ったら大間違いです
- CSRFなにそれ美味しいの
- JSONでレスポンスするといったがそれは嘘だ
セッション
Dart標準のdart:io
にあるHttpServer
さんは中にSessionManager
を持ってて、コレがいい感じにセッションを管理してる系なんだけど、これがもうなんていうかオンメモリ一択。
Dart的には「これabstractだしー、適当に実装しちゃってよ」的なテンションなんだろうけど、startは愚直にコレを普通にそのまま使ってるわけ。
まぁmemcacheとかRedisとか用意しなくてもいいならよくね?とか思ったりしたら大間違いで、オンメモリなセッションしか選択できないとう事は、サーバー再起動(コード変えたら再起動するよね)する度に、ログインし直さないといけないという何とも辛い現実にぶち当たった。
セッションの実体はabstractなHttpSession
クラスだからココ差し換えたり自前実装に出来たりするならまだよかったんだけど、そういったセッションハンドラ的な部分の処理の差し換えとかstartはたぶん全く考えてなくて、「いいよね!オンメモリでいいよね!」くらいのノリ。
というかたぶん「何でstartにセッションが必要なの?馬鹿なの死ぬの?」くらいのテンション。
スーパーライトな公開APIオンリーなアプリケーションを作ってね!って感じがひしひしと実装から感じられる。
「いやいやまだ1.0にもなってないし」とか言われると「せやな」としか言えないけど。
正直この1点だけでもstartを選んだ事を公開している。
テンプレート
render(viewName, [Map params])
メソッドがあるとGithubのREADMEには書いてある。
残念だったな!それは嘘だ!
どこにもそんな実装は無い。辛い。
request.response.send(await templateFile.readAsString())
みたいな悲しいコードを書かなきゃいけない。
まぁテンプレートエンジンくらい別のやつ使えよって話なんだろうけどね!
Polymer使ってると最終的に生成される index.html
がそれはもうゴリゴリといろんな処理がかかれた ビルドされたHTML
になっちゃうからテンプレート差し込む余裕なんて無いんですけどね!
共通処理
ExpressとかでいうMiddleware的なやつが無い。
server.addMiddleware(new AuthorizationMiddleware())
みたいな事がしたい!
そんなものは当然ないので
server.get('/hoge').listen((req){
if(await this._isLoggedin(req)){
req.response.redirect('/login');
return;
}
//....
});
server.get('/fuga').listen((req){
if(await this._isLoggedin(req)){
req.response.redirect('/login');
return;
}
//....
});
みたいなねー!そんなのをねー!書かなきゃねー!
と思ったけどwhereなりpipeなりで頑張れば似たような事はいけるのかな・・・?
でもそんな頑張り方はしたくないでござるの巻。
CSRF
当然のようにstartにそんな機能は無い。
さらにpubにもそんなライブラリは無い。
結果的に自前実装せざるを得ない。
辛かった・・・。
今回は適当にCookieベースのワンタイムトークンを実装したけどセキュリティ的に問題が有るのか無いのか良う分からん。たぶん大丈夫だと信じてやまない。ちゃんとするなら何か他の実装を参考にしたいところ。
Content-Typeの罠
request.response.json({"hoge": something})
みたいにするといちいちJSON.encode()
かけなくても勝手にJSONでレスポンスしてくれるメソッドがある。
が、Content-Typeは変わらない。たしか text/plain 。
なんでだよおおおおお!結局、
request.response
..send(200)
..header('Content-Type', 'application/json; charset=utf-8')
..json(responseData);
みたいな事しなきゃいけない。あんだかなぁ・・・
startの感想
たぶんもう二度と使わない。
PR送って改善しろよ!って言われそうな気もするけど、そんなレベルじゃねぇ。
startはフレームワークではなく、http_serverパッケージのラッパー、くらいのテンションで使うと良いと思う。
http_serverパッケージを生で使うよりはだいぶらくなのは確か。
まとめ
- Polymerは悪くない
- startは良くない
- エコシステムが育ってない世界のマイクロフレームワークはフレームワーク足り得ない
- こんなの作ってるせいで昼夜逆転したし、連休の半分が消えた。
- 作りなおしたい。でももう疲れた。