ntaoo blog

主にDart, Flutter, AngularDartについて書いていきます。ときどき長文。日本語のみ。Twitter: @ntaoo

AngularDartの魅力

Dartアドベントカレンダー14日めの記事

Flutter経由でDartの魅力に気づいた人が増えてきているので、ここでAngularDartにもすこしでも興味を持ってもらって、使い始める人が増えると嬉しいと思い、この記事を書いた。

網羅的な解説をする余裕がないので、覚え書きの質で書きちらす。また、具体的な機能の特徴をコードを交えて解説するものではない。それに関しては、https://webdev.dartlang.org/を参照されたい。

なお、よく知られたTypeScript版のAngularとの比較が多くなるため、ここでは便宜的にそれをAngularTSと呼称して、AngularDartと区別する。

Hummingbird (Flutter Web)が発表されたけど

Hummingbird (Flutter Web)が発表されたが、まだオープンソースにもなっておらず評価するには早すぎるので、これからはAngularDartでなくHummingbirdだと決めつけてしまうのは年単位で気が早い。

短期的には、Webで要求されるユースケースのすべてを代替できるかについては懐疑的。将来もしHummingbirdが成功するならば、時期を見定めて数ヶ月から数年かけてゆっくりと置き換えていけばいいのではないか。

それまでは、BLoCパターンなどでModelをDartで共有して、WebをAngularDartで、ネイティブをFlutterで書くというパターンが有効。プラットフォームに依存したコード以外の、Model、ライブラリと開発基盤は再利用できるのが強み。

AngularDartの略歴

一昔前にAngularTSからフォークした。それまではAngularTSをDartコンパイルして提供されていたが、よりDartらしくコードを改善し、パフォーマンスと生産性を高めていく目的。Google AdsのWebクライアントで採用されているのが定番の宣伝文句。その他、Youtubeの一部など、Google内部では活発に使われているらしい。

ちなみにさらに歴史を遡ると、AngularJS (Angular 1)のメジャーバージョンアップの構想ためのアイデアの実装としての側面がAngularDart(初代)にはあり、それがAngularJSにバックポートされつつAngular 2の開発が進められ、そのAngular 2ではTS版もDart版もひとつのコードベースで書かれていたが、なんやかんやあって結局AngularはTypeScript版が推されていき、Dart版はフォークして独自のフレームワークになった。

そして、数ヶ月前のDartのversion2.0のリリースによって、Dartの基盤を利用したAngularDartもversion5.0で成熟したとみなせるようになったので、こうして安心して紹介できる。

AngularTSが、Webのドラフト段階のAPIや将来の構想を先取りした巨大なポリフィルとして冒険するエキサイティングな側面があるのに対し、AngularDartは、巨大なミッションクリティカルなアプリで採用されていることもあり、フレームワークの安定性と生産性とパフォーマンスを非常に重視していると感じられる。

AngularTSがWebコミュニティを積極的に巻き込んでGitHub主体でオープンソースで運営している一方、AngularDartはGoogleの内部リポジトリで揉んで決定されたコードがGitHubに同期されてパブリックになる。ただしGitHub Issueなどで意見を募ったりバグ報告やプルリクエストは受け付けてそれがGoogle内部リポジトリに逆に取り込まれたりといったことがまれにある。

エコシステムやコミュニティの広さはAngularTSのほうがはるかに上である。

なぜAngularDartか

Dartで書ける。つまり、Dartのエコシステムに乗ることで、TypeScript (JavaScript) の欠陥や機能不足、乱雑で混沌としたエコシステムやビルド環境に悩まされずにすむ。JavaScriptをハックする必要がない。

  • ビルドエンジニアを用意したり、Webpackと格闘したりする必要がない。基本的に、package:webdevに用意されているコマンドを叩けばよしなにやってくれる。
  • クロスブラウザ対応のためにJSのAPIのポリフィルを導入したりする必要がない。Dartの豊富なライブラリを利用して開発する。デプロイ時はES5にコンパイルしてくれる。
  • JavaScriptのライブラリを利用したくなる場合は、TypeScriptと同様にJSと通信するコードを用意する。package:jsdart:jsで、d.tsのようなものを書く感覚。ただし、Dartのエコシステムがあるので、そのようなコードを書いてJSのライブラリを利用したくなることはあまりない。

また、Flutterでモバイルアプリを書くならば、Web版はAngularDartで開発してModelを共有しておけば、Flutter WebやDesktopが成功して安定した際はスムーズに移行できるだろう。

主にAngularTSと比較してのAngularDartの特徴

網羅的な比較をする余裕はないので、覚え書き。

AngularDartはAngularTSの知識の大部分を流用できるので、TS版を知っているならば学習コストが少ないし、逆にDart版を知ればTS版も苦労せずに応用できる。ZoneChangeDetection、大部分のテンプレート構文、Dependency Injectionといった、中核となる概念を共有している。

AngularTSは、フルスタックフレームワークとして、JavaScriptの混沌としたエコシステムからの技術選定をせずにすみ、統一した開発環境を提供していることが大きな強みだ。AngularDartでは、Dart言語の基盤がそのような役目を担っており、AngularDart自体はその基盤の上でのWebのUI層を司る位置づけになっている。普段の開発でJavaScriptを意識することはほとんどない。

TypeScriptが、良くも悪くもJavaScriptに型システムを追加しただけの方言にすぎず、JavaScriptの作法やエコシステムへの依存度が大きいことに比べ、Dartは完全に別の言語であり、シンプルで堅牢な言語仕様、ビルド基盤、充実した標準ライブラリ、独自のエコシステムを構築している。Flutterやサーバーサイドなど、Web以外の適用範囲が非常に広い。ただし、そのエコシステム自体はまだ発展途上なのが現実。

テンプレート構文

開発チームはAngularTSからの知識の再利用ができる点については気を払っており、たとえば*ngForもTS版と同じノリで*ngFor="let e of list"と書いたりする。Dartなのに。フォーク前から存在する構文に破壊的変更が入るのをなるべく控える方針のようだ。

ただ、TS版と異なりDart版はテンプレート構文の種類が絞り込まれている。TS版での構文の増加に追随しなかったとも言える。これは良し悪しがあって、テンプレート構文が便利になればなるほど、テンプレートに本来はModelに書くべきロジックが入り込む誘惑が強くなるし、テンプレートのコンパイラの複雑さやIDEサポートの困難が増したり、学習コストが増えたりコードレビューが面倒になったりする。一方、テンプレート構文が便利になるとたしかに簡単さを感じるので、Webコンポーネント的なコンポーネントでhtml(テンプレート)とスクリプト部分が分離している設計では、ある意味仕方のない進化なのかとも思う。

複雑なテンプレート構文と付き合うプログラマーのためにIDEサポートが充実しているのもTS版の特徴で、普通に補完がきいたりリファクタリングサポートがちゃんと動くのもよい。WebStormかVSCodeを使っているとその恩恵を強く感じる。Dart版もテンプレートを解析してサポートしてくれるが、TS版ほど充実しているわけではなく、ここははっきりとTS版に劣るところだ。しかし、そもそもテンプレートの解析が必要なほどのロジックを持ち込むなという良い制約になっているという解釈もできなくはない。 ここは対象ユーザー層の量と質の違いがはっきりと現れているのではないか。

私的には、テンプレートには最低限現状の種類のものがあればよく、増えてもメンテナンスが面倒になるだけだと思う。TS版で同僚がさまざまなテンプレート構文を駆使してテンプレートの複雑度が増していくのをあまり抑止できなかったのが残念ポイントだった。Pipeも一見簡単便利なのだが、本来はModelに書くべきものがViewに露出してしまっていることがほとんどなので、使用を禁止したいくらいだ。

AngularDartは、機能を増やすよりも、パフォーマンスと安定性、生成するJSのコードサイズの削減を重視して、あまり使われていない機能を整理、削除することが多い。GitHubのAngularDartリポジトリCHANGELOGをみたらよく分かる。

AngularCLIは?

AngularTSのCLIは、JavaScriptの面倒な部分をだいぶ見ないですむようにしてくれて、生産性の向上に大きく貢献している。AngularDartではその役割はpackage:webdevが担っており、AngularDartに限らないWeb開発のサポート機能が提供されている。package:webdevの各コマンドを叩けばそれでよく、それ以上のことをする必要がもしあるならば、Dartのビルドシステムを学習してプラグインを書く必要がある。

dart:html

AngularTSのようなマルチプラットフォーム対応のための抽象化層は、フォークのあとに廃止された。現在はdart:htmlをそのまま利用する。パフォーマンスの向上とコンパイル後コードサイズの削減のため。

dart:htmlはDOM APIの一部のおかしな挙動(Node, Elementとか)を修正しているので快適。また、XSSを防ぐためのセキュリティ機能を提供しているので安全である。AngularTSにも似た機能はあったはずだが、AngularDartではフレームワークではなくその基盤のdart:htmlAPIを使う。

SSR

抽象化層を廃した代償として、SSR機能がない。そのため、SSRが必須だと判断されたプロジェクトでは選択肢から外れる。 ただし、2019年の環境で、SSRがそのプロジェクトで本当に必要なのかはよく考えたほうがよい。SSRを維持するためのさまざまなコストを支払えるか、PWA+CDNで問題ないのではなど。

https://speakerdeck.com/kazuyaseki/state-of-seo-for-spa-2018

個人的には、SSRは過渡期の技術とみなしていて、もはや積極的に採用することはない。ほぼアンチパターンだとみなしている。

PWA

AngularDart製のアプリが、Lighthouseのスコアでほぼすべて満点を取れている。 PWAは、Angularフレームワークに同梱はされず、Dartらしくdart:htmlや独自パッケージでサポートしている。

StreamとRx

RxJSの知識を再利用したい、またはBehaviorSubjectを利用したいならば、RxDartを採用する。RxDartは標準ライブラリのStreamを継承して書かれている。

私的な好みでは、Rxの大量のオペレーター群を見るのにうんざりしているので、BehaviorSubject以外は、Dartの標準ライブラリのStreampackage:async, package:stream_transformを組み合わせて使っている、チーム開発ではRxDartを使うのが無難かもしれない。

AngularDartはAngularTSとは異なり、RxStreamも必須ではない。

HttpClient

AngularTSではHttpClientフレームワークの一部扱いだが、AngularDartではpackage:httpを使う。Flutterでも利用できるパッケージ。

Module (NgModule)

AngularTSでは、NgModuleはアーキテクチャ上重大な役目を果たすフルスタックフレームワークの要ともいえるものだが、AngularDartでは、単にProviderを合成してまとめておく薄い機能にすぎず、使わなくとも問題ない。特に困っておらず、逆にTS版ではNgModuleに悩まされることが多かった(個人の経験談)。

Test

package:testpackage:mockitoを使う。成熟している。Flutterでもサーバーサイドでも、どのような環境でも利用できる。

AngularTSでは、型システムを生かしていない設計のJavaScriptテストフレームワークjasmineを使うか、マイナーだがmockitoのTS版を使うかという選択肢がある上に、Angularの外に目を向けるとさまざまなテストフレームワークがそれぞれそれなりに使われている混沌とした状況だが、Dartではパッケージの選定に迷うことはなく、さらにWebでもネイティブアプリでも普通に同じパッケージを利用できる。

スタイルガイド

AngularTSでは、独自のスタイルガイドが細かく規定されてLintが提供され、AngularCLIもそれを前提に作られている。 AngularDartでは、Dartのpackage conventionとスタイルガイドに従う。AngularTSのスタイルガイドはAngularDartでは通用しない。

AngularTSのスタイルガイドは、フルスタックフレームワークとして、コミュニティ内でスタイルに迷いがでないようになる利点は確かにあるが、そのスタイル自体はあまり良いものではないと個人的には思う。

package:angular_components

package:angular_componentsでかなり楽にMaterial Design対応できる。

ほぼ一通りのUIコンポーネントは揃っているが、Flutterほど充実して最新の仕様に追随しているわけではない。

付属のSCSSにMaterial Designのスタイルが一通り定義されているので、その変数を利用してスタイリングしていく。独自にmarginやcolorなどを定義していく必要はない。UIコンポーネントのcolor themeの変更についても、UIコンポーネントごとにSCSSのmixinが提供されているので、それを利用する。

たとえば、'package:angular_components/css/material/material'や、 'package:angular_components/app_layout/layout'をimportする。

個別のUIコンポーネントのスタイルのカスタマイズ用SCSSは、 たとえば'package:angular_components/material_button/mixins';にある。

ちょっと困ったところ

まれに落とし穴に落ちてしまう。

  • UnmodifiableListViewを*ngForに渡すとフリーズする。
  • async pipeにstream transformerをかましたstreamを渡すと、無限ループする。streamを適当にキャッシュして、ChangeDetectionのたびにいちいちstream transformerをかまさないように注意する必要がある。もしくはasync pipeを使わない。

需要があれば詳細と回避方法を書く。

入門のための学習リソース

フィードバック

後半になるほど息切れした。抜け、漏れがたくさんあるかもしれない。

フィードバックをいただけると喜んで回答します。