漸進的型付け言語の時代に必要なもの
最近では、Gradual Typing、漸進的型付けと呼ばれる型システムを備えた言語(拡張)が増えてきています。
次のようなもの
- JavaScript: TypeScript / Flowtype
- Python: mypy / pyre-checker
- PHP: hack / php-storm
flow/pyre-checker/hack と facebook 製が多いですね。
この記事は、それらを使う動機と運用について書きます。この記事の出発点として、 おそらく TypeScript/Flow で発生した問題が後発の言語で発生すると思っており、それらを使う方や、設計する人への提言でもあります。
自分は昔 https://github.com/mizchi/TypedCoffeeScript というAltJS作ろうとして、実装のツラミはなんとなく知ってるつもりです。ホビーレベルで作るものではなかった…。
型アノテーションの再評価
一昔前の Web プログラミング言語のトレンドは動的型付け一辺倒でしたが、その時代も終わり、静的な型宣言を再評価するフェーズが来ているように思います。
この背景には、おそらく Web プログラミングの規模が年々肥大化しており、動的検査のコストが増してきたのが理由にあるでしょう。
WAFの考え方だと、型をつけづらい外部IOであるところのHTTPリクエストを受け、HTML文字列を返す、という世界観では、型宣言は単に「おまじない」を多くするだけの邪魔者だったかもしれません。外部IOに型をつけづらいのは、外部IOの本質的な問題ではあります。
しかし今では、どのWAFも内部では分厚いビジネスロジックを持ち、実質的に静的なフィールドを持つORMを読み書きし、何らかの型を暗に想定する JSON を返す、という風にトレンドが変わってきました。型やIDEの支援なしにコードを育て続けるには、逆に高度なモジュール分割のノウハウや、状況に応じたストラテジーが必要になっています。
それらの肥大化の対応として、ドキュメントを書く文化や、テスト駆動も普及してきましたが、一周回って「検査可能なドキュメント」としての型アノテーションの価値が再評価されたように思います。
誰のための型か
注意してほしいのは、ここでいう型宣言の需要は、人間のために書く、ドキュメントとしての型アノテーションで、コンパイラに効率よくランタイムを生成してもうらための型ではありません。これを混同している人が多いですし、「高水準な、良く設計されたプログラミング言語」はそれらを区別せずにプログラマに書かせようとしてきます。(Rust などは低水準を目指しているので明示的に区別します)
言語としてその方針は間違ってないですが、使う側や、言語を選定する側は区別しないといけません。抽象メモリマシンである C や、型の表現が未発達だった時代の Java の冗長な宣言などに引っ張られて静的型付けを批判する人がいますが、それらは現代の静的型付け言語ではないです。最近の推論機は優秀で、見た目上は動的型付けと同じものを書けます。
また Language Server Protocol といったエディタのためのプロトコルによって、静的解析できるメタデータの重要性も上がっています。
何の機能をもってどう表現するかは、 私と型システムとポエム - The curse of λ という記事で、よく雰囲気が表現されています。
漸進的型付けの登場
What is Gradual Typing: 漸進的型付けとは何か - Qiita
基本的に、動的型付の言語に後付で型宣言を追加するものです。このとき、これらが新しい言語やコンパイラと言うのはちょっと微妙なところで、例えば TypeScript + Flow は拡張文法のパーサ + 拡張文法を取り除くだけのコンパイラ + 静的検査という組み合わせで、どちらというと Linter などに立ち位置が近いです。
自分が知る限り(主に TypeScript, Flow, Dart)、こういう特徴があります。
- 未知の変数は any 型であると仮定する
- あらゆる変数の型は any 型にアップキャストできる(Top)
- any 型はあらゆる型にダウンキャストできる(Bottom)
- あらゆる型は any 型と合わせて操作すると any にアップキャストされる
- 自分で宣言した型を扱う限り、型エラーが発生する
- 型アノテーションはランタイムに関与しない
(typescript の scrictAny や noImplicitAny オプションはこの挙動をより厳密にコントロールできます)
つまり、こういうコードが通ってしまいます。
const x: number = 1 const y: any = x const z: string = x + y // pass
これが駄目だという話ではなく、型システムを後付している以上、柔軟性の方が重視されます。というかそうでないと、コンパイラを納得させられずに開発が「詰む」ことがあります。 any を書いた際に守るかどうかは自己責任です。
「型アノテーションはランタイムに関与しない」が特徴的で、型アノテーションを取り除くコンパイラとともに実装されるのも合わせて、なんというかそれ自身では控えめなコンパイラです。その結果として現れる挙動として、Flow/TypeScript は int や float の宣言はできず、必ず number として扱う必要があります。
また、既存のものに無理矢理型を当てはめるという特性上、Generics、Union Type, Subtyping といった高度な型表現を備えているのが事実上必須な特性となっています。
type X<T> = T | null | [T, T] | Array<T | 1> | 'yeah!' | 1 | 2
こんな風になったりしますね。
元は動的型付けからの発展だと思うと、この柔軟さや、any への妥協は必要なラインだと思います。
漸進型付けの利点
- 既存の資産をそのまま利用できる
- 「自分で書く範囲のコードは」厳密に運用することができる
一応言っておきますが、僕は動的型付の言語をすべて否定しているわけではなく、大規模なコードに適用するのはそぐわないという意見です。
一つのことをうまくやるUNIX哲学に照らし合わせても、文字が来て文字を返すぐらいに抽象化されたエコシステムでは、型宣言はそこまで重要視されないでしょう。たとえば npm のほとんどそういう世界だと思っています。
その上で、動的型付けのノウハウと資産を活かしつつ、その利用者になりつつ、巨大なアプリケーションを構築する手段として漸進型付けは有用だと思っています。
漸進型付けで発生した問題
ここからは自分が TypeScript と Flow の運用で発生した問題について。
TypeScript/Flow では、npm の資産をそのまま動かせる、というかランタイムに全く関与しないのですが、基本的に、扱うライブラリの型宣言はないです。
https://github.com/DefinitelyTyped/DefinitelyTyped や https://github.com/flowtype/flow-typed/ という型定義の集積リポジトリがあります。ただしこれらが使い物になるかというと、おおよそ使えつつも様々な問題が起きます。
- そもそも型が付けられるインターフェースではない(redux.combineReducers など)
- 作者が型定義の運用に興味がない(PRを受け付けてくれない)
- ↑の問題の結果、作者以外によって書かれた型アノテーションが間違っている
- Generics がある以上、型定義が一意に定まらず、定義者の主張が強く出てしまう
そもそも型が付けられるインターフェースではない(redux.cobineReducers など)
たとえば、Redux の combineReducers は (State, Action) => State
の reducer と呼ばれる関数を複数合成して、新しい (State', Action') => State'
の新しい関数を生成する関数です。
combineReducers({ counter, user, auth })
これで、合成される State' は次のような型になります。
{ counter: {...}, user: {...}, auth: {...} }
これらをTypeScript/Flowの推論器で一般化するのはちょっと難しく、flow では専用の $compose という型で実装されようとしていますが、いまいち使いづらいので、結局推論器に任せずに自分でその型を宣言しなおすことになります。
これにかぎらず、JS の昔ながらのイディオムだと options: boolean | { isXXX: boolean, ... }
みたいな引数が多く、型を付けられなくはないが、ダイナミックすぎて、ユニオンタイプを酷使するみたいな状況が起こりがちです。
作者が興味がない | 作者以外が書いた型が間違っている | 型の解釈の意見が合わない
そもそも言語拡張なので誰もが使うというものではなく、立場が弱いです。作者が興味が無いから PR が無視されたり、メンテ出来ないからごめんね、といってリジェクトされます。
また、Generics によって型の一意性はないです。
type Foo = { a: number, b: string }
という型があったとして、その定義は
type A<T> = { a: number, b: T } type Foo = A<string>
なのか
type B<T> = { a: T, b: string } type Foo = B<number>
なのか、使う人によって解釈が異なります。作者以外が書いてる場合は尚更です。ユーザーが使いたい時の値が Foo だとして、ライブラリとして提供される型の表現が A なのか B なのか、解釈に依存します。またどれぐらいの厳密なのかも方針次第で異なって、単に any で型名がわかるだけだったり、逆にオプショナルな値が解釈違いで必須になっていたりといった手続きが頻出します。
また、厳密な型定義ができるように Generics を大量に使ってしまった結果、高難易度な型パズルが頻出するライブラリが疎まれたりします。DefinitelyTyped/index.d.ts at master · DefinitelyTyped/DefinitelyTyped · GitHub
気に食わないなら自分で書き治すか、諦めて any にキャストする必要があります。
実際の運用について
興味がないのものを握りつぶす
実際に型がないと困るのは自分の扱うコードと接する範囲です。これらを厳密に扱う必要がないときは、単に無視します。 これに型を書いてもいいですが、現実に扱うすべてのコードに型をつけるのは現実的ではありません。よほど暇な時でしょう。
// declaration.d.ts declare module "a"; declare module "b";
// flow-typed/lib.js.flow declare module "a" { }; declare module "b" { };
握りつぶした上で、段階的に自分が興味がある範囲の型を徐々に追記していく、ということが多いです。
外部IOの出口を押さえる
とにかく大事なことは、自分が扱う範囲の型を守ることです。
function parseXXX(input: {args: string[]}): {data: string} { // input から文字列を取り出して加工する return { data } } const {data} = parseXXX(input as any)
input にはもっといろんなプロパティがありますが、ここでは args にしか興味がないとして、 最終的に data: string が帰ればOK、というやつです。
これは 最終的に string にダウンキャストして扱っています。ここは型がない危険な領域ですが、まあ頑張るとする。
express のようなサーバーを扱ってると、うまく型がつかず、こういう風にラップしていることが多いです。
後発の言語へ言いたいこと
Python 3.5 や、もしかすると Ruby 3 にも型が入るかもしれません。
TypeScript/Flow はそれ自身の立場が弱いので、ライブラリへの型定義ファイルのPRが受け入れられず、外部の型集積は結構な地獄になってしまいましたが、静的型付けを受け入れられない作者がおそらく一定数いる以上、どの環境でもないよりはマシです。
ただし、それらは握りつぶせる必要があります。原則的に型宣言や型推論ありきで運用するのは、別の言語として再スタートしない限りは不可能です。
また、既存のコードが型を想定していない以上、推論器の限界で表現できないものがたくさんあります。また、逆説的ですが、表現力がありすぎる型とその型推論は、使う側も結構辛いです。
たとえば、 flow の Redux connect の型定義はこうなっています
declare export function connect<Com: ComponentType<*>, A, S: Object, DP: Object, SP: Object, RSP: Object, RDP: Object, MDP: Object, MP: Object, RMP: Object, ST: $Subtype<{[_: $Keys<Com>]: any}> >( mapStateToProps: ?MapStateToProps<S, SP, RSP>, mapDispatchToProps: ?MapDispatchToProps<A, DP, RDP>, mergeProps: MDP, options: ConnectOptions<S, SP & DP & MP, RSP, RMP> ): (component: Com) => ComponentType<$Diff<ElementConfig<Com>, RMP> & SP & DP & MP> & $Shape<ST>;
パッと見、よくわかりません。推論機の癖を読み切ってジェネリクスを一つずつ解いて型パズルを完成させる必要があります。単に無視されてアップキャストされることのほうが多いです。そして割れ窓になります。
matz は Ruby に型宣言を入れずに推論機でどうにかしたいと言っていましたが、おそらく後方互換性を保ったままだと不可能だと思います。それはそれとしてドキュメントとしての型宣言は選択肢として提供するべきではないでしょうか。
Flow や mypy みてると思ったのは、むしろ言語として実装するのは型アノテーションのフィールドの構文予約だけでよくて、その実装はコミュニティに丸投げでいいのかもしれません。
最後に
ネガティブな点を多々挙げましたが、これらは 0 を 1 にする際の、型宣言がなかったものをあるようにするための一歩のための苦しみで、これ自体は間違いなく進歩だと思います。
最初から型がある言語でやればいいのでは?というツッコミはもっともなんですが、サーバーサイド以外はプラットフォームによって言語を選べないことが多く、JavaScriptはその最たるもので、ある種苦肉の策であります。
個人的には単一の推論ルールを持った処理系を、環境によってその厳しさを調整できると嬉しいような気がしていますが、そういう柔軟性がある言語はいまのところ無いですね。
新技術の紹介する際の「魂が震える」テキストのパターン
これは自己観察の結果で、自分が新しい技術の採用を行う際にアジる記事のパターン、個人的に「魂が震えるシリーズ」と呼んでるんですが、それがどういう文章構造を持つことが多いかメタ的に解釈したものです。
単に誰もがこうすればいいという話ではないではないです。功罪あると思ってます。
導入
- 新技術の既存の文脈での解釈
- +αの示唆
- 仮想敵の宣言
概要
- 説明
- ポテンシャルの例示
- 極端な例の例示
- 現実的な制約の存在で現実に引き戻す
- ユーザーが知るべきことを要約
実例
- 既存の技術とのアナロジー
- 古い手法から進化している点を指摘
- 今の手法の問題点をいかに解決してどんな未来が来るか
応用
- 既存の考え方を、あたらしい技術で再解釈
- 本来は無関係だった他の技術との親和性を指摘
課題
- 新しい技術ゆえのエコシステムのなさを指摘
- 構造上の欠陥を指摘
- まず取り掛かれる現実的なエントリポイントを例示
未来
- ここまで読んできたなら良い面悪い面わかってるはずだ、といって再読を促す
- 俺はこの技術にベットすると宣言し、お前はどうだ、と煽る
いつ ReactNative を使っても大丈夫か
AirBnb がReactNativeをやめることが話題になってますね。
RNの未熟さ、社のRNのForkのメンテナンスコスト、JavaScriptのスケールのしなさ、JavaScriptCoreの実装の違い、クラッシュレポートが信頼できない、開発者は主に片方のプラットフォームしか知らないのでOSSのライブラリはバグってる、結局ブリッジを描く人間が必要、人が雇えない、山ほど出てくる…
— Hello (@rejasupotaro) 2018年6月19日
以下私見です。
RN採用可否のフローチャート
自分がRN使いたいといって相談された際にはこういう感じで返してます。基本的にはExpo 採用可能か否かで判断してます。 Expo ではじめる ReactNative 開発環境 - Qiita
- プラットフォームごとにUXを突き詰める必要がある => RN やめとけ
- Q: 社内にモバイルのエンジニアがいない
- YES:
- Q: ネイティブAPIが Expo 組み込みのAPIで足りて(https://docs.expo.io/versions/latest/ 参照)、開発側で広告SDKなど外部要件がコントロールできる
- YES: => RN+Expo
- NO: 諦めた方が無難
- Q: ネイティブAPIが Expo 組み込みのAPIで足りて(https://docs.expo.io/versions/latest/ 参照)、開発側で広告SDKなど外部要件がコントロールできる
- NO:
- Q: Web開発者多数 + モバイルエンジニア少数の環境である
- YES: => RN採用可能
- NO: 別の案を検討
- Q: Web開発者多数 + モバイルエンジニア少数の環境である
- YES:
- とにかくエッジなのを採用してモチベーション上げたい => Flutter
- C# 派 => Xamarin
- Web標準派 => PWA
Expo を推してる理由は、RNの根本的に不安定な部分を、比較的安定している Expoに押し付けることができるからです。Expoを使わない場合、現時点ではRNのバージョンアップの追従で3プラットフォーム全てに習熟する必要があり、技術的な要求レベルも割くべきリソースも必要です。
正直なところ、 RN も PWA も、現時点では貧者のツールなのは否定できません。また、RN 開発で必要なノウハウはWebのスキルと言うより、もはや GUI 開発のスキルなので、JSが使える以上のスキル転用は望めません。僕のようなSPA開発者が漲ってコード書ける環境、という方が近いでしょう。
UXを突き詰めたい、というのも温度感次第で、突き詰めたくないと思ってる人はいないので、程度問題です。簡単な答えはなく、結局チーム構成に依存します。AirBnbのような会社なら重いパラメーターでしょうが、とりあえずAppStoreのディスカバリーに載せたい程度の気持ちで作ってるならRNでも十分だと思います。色々いいましたが、いわゆるウェブサービスのモバイルアプリは、ほとんどはこれに該当すると思います。
以下ツイートから引用
RNはNative部分を使わないWebアプリとは違う別のガワ+αぐらいで使わないときついだろうという気はする
— human eslint --fix (@mizchi) 2018年6月20日
僕がRN薦める際は、要件をエンジニアがコントロールできるときに expo 使って expo 由来のもの以外使わないか、Web開発者多数 + iOS 専属 + Android 専属というチーム構成を取れるときです
— human eslint --fix (@mizchi) 2018年6月20日
Native要件が少なくて expo を採用できれば RN は最高なんだけど、 expo を採用できるかどうかはかなり社内政治の色が強いので、正直技術の話ではない
— human eslint --fix (@mizchi) 2018年6月20日
RN のバージョンアップ、 Web/iOS/Android どれか一つに付いていくのは簡単だけど、全部についていくのは地獄
— human eslint --fix (@mizchi) 2018年6月20日
現状RNが貧者の開発ツールなのは否定しません
— human eslint --fix (@mizchi) 2018年6月20日
一応AndroidとFlashとUnityの経験した上で、仮想DOM+Fluxのアーキテクチャに未来を見出してて、今その原稿を書いてるんですが、正直なところ仮想DOMというコンセプトを実現するだけなら Flutter 使えという話で終わってしまう
— human eslint --fix (@mizchi) 2018年6月20日
Flutter コミット見る限りは、Android側へ開発リソースが偏ってるので、たぶん本当に色々使うと iOS 側のAPI足りないんだろうなという予感がある
— human eslint --fix (@mizchi) 2018年6月20日
AirBnbみたいなリソースがある会社だと、特定の UIViewやActivity の中で RNを小さく使うという選択は技術的にあるはずで、個人的にその検証は済ませてるんだが、コミュニティはそっちを向いてないので自前でやることになる https://t.co/x70aux3ynl
— human eslint --fix (@mizchi) 2018年6月20日
RCTRootView(
— human eslint --fix (@mizchi) 2018年6月20日
bundleURL: jsCodeLocation,
moduleName: "app",
initialProperties: nil,
launchOptions: launchOptions
)
でパスを指定する
なぜ RN + expo を採用することが「社内政治」なのかというと、僕の経験上モバイル開発の末期は開発側でアンコントローラブルな広告SDKを要求されることが多く、expo だとそもそも要求仕様が詰んでることが多いからです
— human eslint --fix (@mizchi) 2018年6月20日
RN+Expo を採用できればフロントエンド開発者のリソースをそのままネイティブ転用できるが、細かい要件をコントロールするにはネイティブ側のブリッジが必要で、そうも言ってられなくなる。広告SDKなどアンコントローラブルな事情が発生しうる
— human eslint --fix (@mizchi) 2018年6月20日
「普通の」ウェブ開発者のスキルをRNに転用できるかというと怪しくて、仮想DOM概念やFluxとReactは完全にGUIの開発フローになるので、マインドセットがRailsみたいなWAFのMVCべったりの人間がこれにスイッチするには時間がかかる。JSである以上の共通点はない。
— human eslint --fix (@mizchi) 2018年6月20日
あとWebの人間はモバイル基準だとインタラクションに激弱なので、スワイプ周りを制御するための PanResponder とかで手こずる。Flashの経験がある人は強いと思う
— human eslint --fix (@mizchi) 2018年6月20日
ブラウザ上で完結するGit組み込みエディタ作っている
PWA-Editor(仮)
デザインとかは適当なんだけど、コンセプト的にどこまで実装可能かの検証を一通り終えた。頑張れば本格的なものが作れそう、という手応えがある。 IndexedDB バックエンドに fs 動かして ismorphic-git を動かしている。 UIは全然足りないが、 ポテンシャル的には GitHub に push できることも検証済み。ServiceWorker でオフラインで動くようになっている。
デプロイ先は https://nervous-kilby-73c9b0.netlify.com/
開発中のものなので、予告なく互換が壊れることがある。
動機
Chromebook 買ったんだけど、やはり開発機として使うには厳しい気持ちがあった。主にまっとうなエディタがないのが辛い。cloud9 とか試したけど、辛かった。
フロントエンドのツール周りはJSで完結して PWA でオフライン化できるのは検証済みで、 isomorphic-git が使い物になる品質だったので、一旦は GitHub を実質的なバックエンドとして編集したファイルの push/pull/clone できるもの、というゴールを設定した。
うまいことやれば金になりそうな気がするので、まだソースの公開はしない。これを利用したビジネスプランがあって金を出せる!みたいな人がいたら twitter @mizchi まで教えてほしい。
前作ってたエディタは、コンセプト実装終わった段階のセルフレビューで、使い心地が微妙、という感じで、お蔵入りしてしまった。前回の反省を生かしていい感じに進めたい。
ゴール
非エンジニアを開発ワークフローに巻き込むのに足りないのは Git 周りのツールだと思う。Git がブラウザで動いて push できれば、いい感じにブラウザ上のUIから特定ドメインの作業を手軽に使えるようにして、markdown みたいなドキュメントとか、フロントエンド系のプレビューを組み込めばいい感じにできそうな気がしている。
React Component 視点でのアトミックデザインの解釈といくつかの疑問
フロントエンドの中でも、JS書くプログラマと、CSSを書くマークアップと、デザインカンプを作るデザイナで、コンポーネントという概念がズレる。だいたいこれらが一人だったり兼任だったりで1~2レイヤーの開発ステップになるが、完全分業だったり人が多くなると混乱の元になる。
誰かが決定的に間違ってるというつもりはない。正直、どっちかというと本来のデザイナ側の用語定義に倒した方がいい気がしているが、プログラム上の都合もいろいろ混ざってきて、話が簡単ではない。
自分の理解が間違ってる可能性もある。この記事はレビューをもらうために書いている側面もあり、指摘されたら追記していく。
読んだもの。
Atomic Design の大雑把な理解
基本的にはあるコンポーネントは所属するドメインではなく、独立した稼働単位(Atoms)でコンポーネントを分割して、それを積んで(Molecules)、それらを稼働単位(Organisims)でまとめ、ページ(Page)は複数の稼働単位を持つ、という考え方、と理解している。
自分は Reactの src/components はこういう構成を取っていることが多い。
atoms/ Text.js Button.js Comment.js molecules/ CommentList.js organisms/ Header.js UserProfile.js pages/ User.js
コンポーネント名は仮のもの。
ゴールの一致
プログラマ視点だと、コンテキストを作るのが難しいコンポーネントを storybook で作れたり、 storyshots でスナップショットテストできたり、 puppeteer でスクショとったりできて嬉しい。あと地味に嬉しい点で、粒度別にアルファベット順になってエディタで読みやすい。
コンポーネント境界と分割指針がズレる
ここから問題。
デザインとプログラムで、「コンポーネントが独立稼働できる」の単位が違う。
プログラマ的にはコンポーネント定義の再利用性で決めることができる。例えば React のプログラム的に独立した atom を発見するのは簡単で、他の Component を import していない、ということになる。
しかし、デザイン上の出現単位とってプログラム的な依存ステップは必ずしも一致しない。そういうときは、自分はさらにディレクトリを掘って分解している。
atoms/ Button/ ButtonIcon.js ButtonLabel.js index.js
1ファイルに押し込めないのは、コードが長くなって可読性が悪くなったりテストしづらい、というプログラム上の問題で、後は import path を深くしたくないので、 ./atoms/Button
で import できるというのは変えない。index.js から import するだけ。index.js 以外は、「外から見てプライベートなモジュール」としている。
ただ正直 Atoms と Molecules はプログラム上の境界が曖昧でいつも困っている。上の例だと atoms/Icon.js になるのかもしれないが、そう出来ないときもある。
こうして、プログラム上では Atoms が Atoms に依存したり、Molecules が Molucules に依存しそうになって、(さすがに Atom が Molecules に依存することはないが)都度解釈をこねくり回している。
プログラム上にしか存在しないデータパス(props)
プログラマ的には、React Component は基本的に props のデータパスを設計し、末端でデザインが表出する、というマインドセットになる。
逆に、デザイナ的にはデザインモックから起こしていくので、その過程にデータパスは存在しない。プログラマはそのデザインモックを見て、props 渡す経路を考えたり、 connect する単位を考えることになる。
コンポーネント A - B はデザイン上独立しているように見えているのに、A - B のデータパス上の関係においては依存があって、実質的に紐付いている場合、これは Atom か Molecule かの解釈が立場によってズレる。
自分は、基本的には Organisms から redux へ connect にするように心がけるが(これは React のパフォーマンス的な理由もある)、デザイン的な稼働単位に忠実に分割すると、 Atoms に直接 connect せざるを得ないケースも出現する。
Pages はデザインモックなのか
Pages は 「ページ単位のデザインモック」単位と定義されていることが多いが、 実データを入力に持てる JS ではページ単位の入力という単位に pages を当ててることのほうが多いような気がする。自分はとりあえず Router でマウントする単位としている。
この辺や Templates、いろんな実装を見たが人によって解釈が違う。 templetes/
に DefaultLayout.js
みたいな 「ヘッダーとその中身」みたいなコンポーネントがあったのもみた。(流石にこれは語感から拾っただけだと思うが)
React や storybook を使うとき、storybook が実質的にデザインモックの実装になったりするので、 Pages の実装では、モックであるというというのは忘れたほうが良いのではないか。
また、例示した DefaultLayout のような、Layout 情報だけを持っているユーティリティ的なコンポーネントがどこに所属するか難しい。Row や Column などは Atom でいいのか? それに相当する言葉を自分が知らないだけかも。
おわり
とりあえず自分が決めているのは、デザインガイドから作るのではなく、大きな Organisms を作りながら、ユースケース単位で小さなコンポーネントに分割していく、というフローを取っている。最初にデザインガイドをトップダウンでみっちり決めてもあんまり使われない。
正直一人で作るときは完全にプログラマ都合で作るのだが、大規模だとそうもいってはいられなくなる。
間違っていたり、こうしたらいいい、というのを指摘いただけると嬉しいです。
最近のフロントエンドのエディタ事情
これは、個人でどんなエディタを使うべきか、ではなく、「チームとして」新しいものを採用するとき、あるツールがエディタ横断で便利かどうかを考える必要がある。
自分個人としては、基本はAtomを使って、TypeScriptを書くときだけVS Code を使っている。ターミナルでは Vim。
環境でエディタを選ぶ
最近の新規プロジェクトでは、とくにブロッカーがなければ TypeScript を使っていいと思う。TypeScript を使うなら当然 VS Code を使うことになる。Atom や Vim でもいいが、TypeScriptのエディタとしては、流石に完成度が頭一つ抜けてる。JavaならJetBrains 的なノリで、TSならVSCode、そういうものと思ったほうが楽。
TS以外なら、エディタはなんでもいいが、ある程度流行ってるものでないとエコシステムに追いついてくれない。
prettier の勝利
フォーマッタは当然のように prettier が入る。もう選択肢はなくて、猫も杓子もprettier。すでに勝負は決している。
保存の度に自動整形を掛ける。人によってはエディタに書き換えられるのが嫌かもしれないが、最終的にCIで通ってればなんでもいい。.prettierrc
あればよし。
話がややこしくなるのは、styled-components のような CSS in JS 系が入ってきたときで、 JS(TS)内のインラインでCSS書くときにCSSとしてハイライトされてほしい。それができる環境を作れる人は CSS in JS を便利だと思えるが、そもそもそれができるという発想がないとハイライトがない環境でCSSを書くことになる。このとき CSS in JS へのヘイトが当然のように高まる。これは、CSS だけでなく、GraphQL などにも当てはまって、GraphQLのクエリも prettier で整形されるし、インラインでハイライトされる。
なので、最近はこういうJSも、環境を整えればちゃんとハイライトされる。
// GraphQL const query = gql` query { users { name } } ` // StyledComponents const RedText = styled.span` color: red; ` // JSX export default () => <RedText>red</RedText>
Linter
エディタ環境、今ではいろんな意味で prettier ファーストで作ると圧倒的に楽で、prettier 自体はエディタとは独立しているので、単にそういうプラグインとして実装すればいい。流行ってればだいたいある。なければ作るか諦める。
あとは linter。エディタで動的にプレビューされてほしい。とくに大事なのは unused-vars で、未使用変数が常に通知されるのはコードの書く順序に少なからぬ影響がある。これから使う変数なのか、消えるべき参照なのか。
最近、空白系はeslint/tslint に書かずに prettier に丸投げするのが流行っている。むしろ干渉するから書くべきではないという意見もある。
最近試した中では https://www.npmjs.com/package/lynt という lint ツールが便利だったが、これはエディタ組み込みがなくて、小さい npm パッケージ作るときぐらいしか採用できなかった。
追記: Languagre Server Protocol
最近のエディタはTSのとき以外なんでもいい、といった理由だけど、最近は Language Server Protocol という補完エンジンの規約があって、言語側で実装されてればエディタがこの仕様で実装されていれば補完できる。自分がしる限り、 TypeScript/Flow/C#/Rust とかで実装されてる。たしかMSとGitHubあたりが規約作ってたはず
自分が主にAtomを使ってる理由は、エディタのコードをだいたい全部読んでるので、いざとなったら自分で色々やって解決できるという安心感があるから。
大事なこと
自分一人で開発する際に便利だと思って環境を作っても、他人が不便な環境でやってることに気づけないことがあって、開発環境作る人は誰かの不便を可能な限りキャッチアップしてカバーするのが大事だと思う。
ちょっと前に、JSXハイライトがない状態で、JSX出現すると以降のハイライトが全部壊れる環境でReactを書いてる人を見たことあるが、「そういうものだと思っていた」みたいなことを言っていた。こういう状況を放置すると無意味なヘイトを生んでしまう。
Flutterのコミットログを読んで、これは一体何由来なのか調べた
本当に凄い雑にコミットメッセージと気になったdiff だけ読んだ感じなので正確性は保証しません。
Flutterの実態、Chrome と同じく描画エンジンの Skia を直接叩いている点、Dart であるという点、仮想DOM相当のモジュールが後付である点などから、これはおそらく Dartium から既存のウェブブラウザの後方互換を切り捨てたところからスタートしてると想像できる
— human eslint --fix (@mizchi) 2018年5月22日
結果としてこれは間違ってました
とりあえず dart engine の initial commit 見つけたが昔の名前は Sky だった模様 https://t.co/HwFKCpKtYv
— human eslint --fix (@mizchi) 2018年5月22日
The approach we're exploring is to create a layered framework based around a retained hierarchy of semantic elements.
— human eslint --fix (@mizchi) 2018年5月22日
flutter これ最初期は blink のコピペなのは間違いないっぽいけど最初は dart じゃないな… https://t.co/6FLCKBTzWq
— human eslint --fix (@mizchi) 2018年5月22日
やっぱりflutterは新しいブラウザ作ろうとしたことは間違いない https://t.co/Q27KyUPVJN
— human eslint --fix (@mizchi) 2018年5月22日
割と早い段階で既存ブラウザとの後方互換性を切ってるので、dart は途中で採用されったぽくて、WebComponents のようなモジュールシステムに 仮想DOM的な差分更新アルゴリズムを入れようとしたプロジェクトっぽい
— human eslint --fix (@mizchi) 2018年5月22日
Flutter(Sky)にDartが導入されたのここだ https://t.co/eJgiK2z8Ju
— human eslint --fix (@mizchi) 2018年5月22日
しれっと demenic が Promise 警察してる https://t.co/5SfxzoUKNd
— human eslint --fix (@mizchi) 2018年5月22日
Sky は engine 名、 Flutter 相当の部分は mojo なのかなぁ https://t.co/zxhpRZpY8W
— human eslint --fix (@mizchi) 2018年5月22日
s/demenic/domenic 今思うとここは単にmerge コミットのような気がする
以降 dart のコード追加が急激に増えてる
— human eslint --fix (@mizchi) 2018年5月22日
Android で動かすためのコミットは頻繁に入ってるけど、 iOS 対応はかなり後っぽいな
— human eslint --fix (@mizchi) 2018年5月22日
たまに出てくるこの Effenってのはたぶんレイアウト計算エンジン?っぽい https://t.co/5p1HbZTOty
— human eslint --fix (@mizchi) 2018年5月22日
RenderCanvas が PaintingCanvas に。これもしかして最初は skia じゃなくて canvas に描画してたんだろうか。そんな予感がする(自信がない) https://t.co/WR5dtXhkpf
— human eslint --fix (@mizchi) 2018年5月22日
“Sky: An Experiment Writing Dart for Mobile” https://t.co/Q4ygd8PO9K
— human eslint --fix (@mizchi) 2018年5月22日
この辺の時期の話ですかね?https://t.co/GlOyziNq0i
— takutaro.jp (@takutaro09) 2018年5月22日
まさにこれっぽいです。このときは Dart on Mobile がコンセプトなんですね
— human eslint --fix (@mizchi) 2018年5月22日
mojo: Cross Platform Messaging System か
— human eslint --fix (@mizchi) 2018年5月22日
あとはこの動画で言ってる通りクロスプラットフォーム邁進してる感じなのでこの先はいいか
— human eslint --fix (@mizchi) 2018年5月22日