フロントエンドの歴史が知りたいっ
どういう技術がなんの課題を解決するために生まれてきたのかを把握することで、フロントエンドの基礎知識を固めたい。
そもそもなぜ “モダン” – 現代の – フロントエンド開発と呼ばれるのでしょうか。Web のフロントエンド技術の中心は HTML / CSS / JavaScript であり基本的な構成は従来と変わりません。しかしスマートフォンを始めとするフロントエンド端末の性能が向上したことにより、サーバーサイドで行わなくてはいけなかった重たい処理をブラウザ側で完結させることが出来るようになりました。
スマホ性能の向上が「モダン」の背景にはある
1995年に登場した JavaScript は Webとともに急速に普及していきました。 しかし、当初はブラウザベンダーが独自に言語仕様を拡張していたため、ブラウザ間の互換性が低い状態でした。少しづつ標準仕様が定まり2009年5月に公開された ES5 では、言語仕様の解釈を明文化し各ブラウザの実装として標準化されました。そして、2015年6月にメジャーアップデートとなる ECMAScript 2015(ES2015 / ES6)がリリースされました。ES2015では新しい機能や文法が追加されただけではなく既存機能もアップデートされており、より安全で便利に、そして効率的にプログラムを書くことができるようになっています。これがモダンフロントエンドの大転機となります。
ES6ってそんなすごいことやったんや。
モダンフロントエンド開発が解決している課題は様々ありますが、私が考えている代表的なものはマルチチャネルでの UI/UX の向上、ビジネススピードを加速させるマイクロサービスとの親和性、フロントエンド開発品質の向上の3つです。
おお、はやくも盛り上がってきた
フロントエンドとバックエンドでシステムを切り離すことが出来るためフロントエンドチームは UI/UX に集中し試行錯誤をしやすくなり、お客様にとってより良い体験を作ることが出来るようになります。
ビジネスロジックとデータを分割しサービス化することでビジネスの意思決定を迅速化することがマイクロサービスの特徴です。マイクロサービス化したシステムは Web API を介したチャネルフラットなバックエンドシステムになります。モダンフロントエンド開発は前述の通りフロントで完結するためマイクロサービスとも親和性が高いアーキテクチャでもあるのです。
規格化されたinterfaceを利用してひたすら疎結合にしていくというお決まりの流れ
Node.js はフロントエンド開発にも大きな恩恵をもたらしました。Node.js は開発者のコンソール・コマンドラインで動かせるため、Babel, wepack, ESLint, Prettier などの開発ツールが生まれ、フロントエンド開発のモダン化が押し進められました。
なるほど、確かに言われてみれば
- npmでいろんなツールが使えるようになる
- webpackでうまいことバンドルできるようになる
- 新しい書き方が出てもBabelが後方互換性を保ってくれる
React はデータ管理の考え方が従来とは異なります。 MVC モデルのように双方向ではなく、Flux というデータの流れを一方向にする考え方で設計されています。開発規模が大きくなったとしてもデータの流れが明確であることがメリットです。
ここも進化だったのか
開発を進める上では様々な設計考慮が必要になってきます。本記事ですべてを解説することは時間の関係で難しいのですが、今後 React に取り組まれる方は例えば以下の観点で整理されていくとよろしいかと思います。
関数コンポーネント、UIコンポーネント、ルーティング、API通信、フォーム・バリデーション、認証認可、非同期処理、エラーハンドリング・エラーログ、、、など
Testing Torophyよく引用されている。詳しく見たほうがいいのかもしれない。
おおまとめ
すなわち、JavaScript の言語的な性格として、主流となる実装を参照して仕様が決まるのではなく、仕様が先にあって、それぞれがその仕様に従いながら、速度改善など独自の拡張を施す、というものです。
競争的なんだな、ある種
この中で、 Chrome に搭載された V8 Engine がとても優秀だったので、これを java, ruby, python と同じくサーバーサイドの言語として使おうとしたのが Node.js です。
なるほど
...Node.js はイベントドリブンの実現のものというより(その用途も広がりましたが)、汎用的なスクリプト言語として発展します。とくに元々は Ruby,Python,Perl が担っていた書き捨てのスクリプトや、フロントエンドのためのツールチェインを記述するものになっていきました。とくに言語成立経緯から自分自身の言語仕様が厳密に決まっている言語なので、言語処理系に関するライブラリが多く、中には TypeScript という、言語拡張すらあります。
なるほど
言語機能的にも、2015年からは毎年言語の仕様が更新されることになり、(過去のやっつけ感がなんだったのかというくらい)とても洗練された議論、文法定義、セマンティクス定義が行われており、最近でも async/await や dynamic import のような先進的な仕様が勧告されては、モダンブラウザはそれを参考にこぞって実装する(半年~2年)、という状態です。
ふむふむ
とにかくユーザー層が厚いので、新しい概念やプラットフォームに対して、ライブラリが生まれる速度、そして枯れる速度が速いです。今できないことも、半年待ってれば可能になってることが多いです。
TC39, ECMAScript の仕様決めるグループの方針として、後方互換性を維持する、つまり過去に作られたウェブサイトを壊さないという方針があります。JavaScript のアップデートによってウェブサイトが壊れてしまうと、インターネットという基盤自体が文書を保存するのに信用されないものと捉えられてしまう可能性があります。それは避けたいので、過去の負債を捨てることができていません。
なるほど
自分は、一時期GitHubのTrendingに上がってくるリポジトリののExampleを片っ端から動かすみたいなことをしていました。これは無茶苦茶勉強になりましたが、体力を使います。
やっぱすごい人は1000本ノック的なことを通ってきている
その代表的な例がフロントエンドの React と、バックエンドの k8s で、どちらも時系列に基づいた状態の宣言と、フレームワーク側による状態遷移処理、 Reconcillation(調停) が基礎にある。 フロントエンドとバックエンドという両極端な世界で、この変化が起きたのがこの時代を反映したものであると思う。
k8sもそうなのか...触ったことがないのでまるでイメージがつかないが
と思ったら書いてあった。こっちも冪等なんだ。
例えば、サーバーにログインしてちょっとずつ設定を変更して運用すると、その過程で何をどう変更したかの情報が失われる。コンテナを常に作り直すので、 Docker によってコンテナレベルの冪等性を担保して、 k8s の宣言的なリソースの差分からローリングデプロイさせるのが、k8s の意義。AutoScaler のようなリソース自体の変化も宣言的なものとしてリソース定義に含んでいる。
Reconcillation の対象が DOM か インフラかで違うが、やってることは「宣言的なリソース宣言」「フレームワークによる差分検出と実環境への適用」と共通している。そもそもインフラがコードで管理される時代になっている。コードの抽象がそのままインフラの抽象になる。
なるほど
静的なデータを生成するためのプログラミング処理においての、宣言されるデータの予測可能性というのが重要になってくる。何に依存してリソースが変化するかがアプリケーションの振る舞いの本質になり、デバッグとは副作用の発生する経路を分析することになる。そしてその複雑さが低いほど良いコードと言える。
「複雑さが低い」が「冪等(または純粋性、参照透過性など)」につながる
予測可能性はデバッガビリティだけではなく、パフォーマンスの文脈でも重要になる。とくにキャッシュ構築は参照透過性がある処理に対して行うことが大事で、参照透過性を頼って入力に対して何らかの一意なキーを作って、そのキーについてのキャッシュがあれば計算過程をすっ飛ばす、というのが可能になる。
ここ歴史が詰まってる感すごい
一応、現代では class のデータと振る舞いを同時に記述するというやり方から離れて、明確にデータと振る舞いを区別する手法が整理されつつある。プログラミング言語の世界でも、 2010 年代に新規に流行った Rust や Go はもはや class を持たない。Scala や PHP は trait がある。 Swift には Protocol があるし、C# にも拡張メソッドがある。
class書いたこと無い
いいたいのは、 状態コンテナとしての class とメンバーが不必要な副作用を生み出す割れ窓になっていて、副作用を持つメンバや関数というのは例外的な事項として扱うべきである、ということ。class をまったく書くべきではないという話ではなく、class は例外的な状態コンテナであるということ。
Haskell を使うと型のセマンティクスのレベルで副作用を起こす関数と起こせない関数が自然と分離できる(ことを学ぶのにセマンティクスを学ぶのがしんどい)。Haskell を使わない環境でも、この指針を採用して、計算するだけの関数、副作用を起こす関数、という風に分離するプログラミング指針は常に有用。
JS/TS(とDart)くらいしか書いたこと無いのでこういうチャレンジは今の自分に効きそう
なぜこのように推移したかというと、これはきしださんの先の記事で述べられたように、時代的な背景が大きい。昔はメモリが希少で、確保したリソース(メモリ)に対して何度も読み書きしていたが、現代では仮想化技術による投機的なコンテナ生成、汎用プログラミング言語に置いては GC による投機的なインスタンス破棄に依存して、宣言的なデータを繰り返し生成しては破棄する、というプログラミングモデルになっている。ただし、そのような「富豪的な」プログラミングスタイルだからこそ、パフォーマンス計測もセットになっていると思う。経験的に、実際に発生するボトルネックは全体の小さな積み重ねではなく、局所的に発生する。
ここも歴史詰まってる
FP、式指向が支持を集めるのは必然だったか
CoffeeScript は当時の JS(ES3~5) に足りない機能を補ってくれて、Python と同じく空白制御のオフサイドルールなのが気に入った。見た目が少しだけ Ruby っぽいので当時全盛だった Rails の人間に訴求するにも有利だった。
Node.js のモジュールシステムである Commonjs は Python と同じくファイルスコープが明示的で、 Ruby + Rails で感じた名前空間の暗黙の発散が起こりづらく、また npm がツールとして rubygems より洗練されていたように感じた。後発の強みを十分に生かした。
Ruby, Railsとの比較、兼ね合いの話があるとよりわかりやすい
ベストプラクティスが定まらない時代は、それが柔軟性として有利に働いていた。昔は npm とフロントエンドが分離されていたので、 webpack のようなビルドステップも、 Commonjs/ESM 両対応なども必要なかった。
今は、そのような時代ではない。ベストプラクティスの集約・収斂の時代であって、それは TypeScript ファースト, Node/ブラウザ相互利用を前提としたユニバーサルな設計, Next.js のようなスタックの隠蔽、 マイクロサービスを背景とするモノレポによる分割統治に進んでいる。
俯瞰具合すごい
Wasm について話す前に、ここで思い出すのが8年ぐらい前の「AltJS でJSを駆逐する」みたいな話がどうなったか、という話をしよう。
すごかった...
フロントエンドツールチェインの今の課題感は、巨大な動的ページやSPAを構築するバンドルサイズで、ここに依存の数だけ累積でのしかかってくる。
TypeScript が理想の言語かと言うと全くそうではない。 typeof null => object が修正されるわけでもないし、 var や with が消えるわけでもない。 Error はそのままだし、 null と undefined の使い分けは面倒だし、 class はいろんな言語の最大公約数的な仕様で使い辛いし、 危険なオブジェクトへのアクセスに制約を掛けられるわけでもない。 enum は制約が多い上に非標準、複雑な推論に違反した際のエラーメッセージは結構不親切。 ESM 対応が雑。本体は namespace で書かれてて Treeshake できない。言語上の不備は不健全なアップキャストで解決しろ。 tsconfig.json 次第で安全性が変わるが、断片的に渡されたコードからはわからない。
すごい
あくまで型があると思いこんでるだけで JavaScript であってランタイムの振る舞いに責任を持つのは書き手である。自分は訓練の結果まあ確度が高い保証ができてるとして、他人のコードを型が通ってるから信頼できるかというと、できない。そういう言語であると思う。熟練度がそのまま安定性に直結する。
なるほど
Rustが愛される理由の裏側っていう感じなんじゃないだろうか
だから、よく練られた TypeScript の型を提供してあるライブラリをみんなが使うのが一番安定したTSの使い方で、それが機能しているのが React 界隈で、機能してないのが xxx みたいな気持ちがある。
なるほど
jQuery 以前はコーディングスタイルもクソもなく、各自が自分が好きな言語(母言語)の書き方を勝手に適応していた(のが古い StackoverFlow を見ることでわかるだろう)。
2008以降の jQuery 流行以降にフォーマッタの登場等、多少の知性の目覚めみたいなものが見えるが、雑に書く言語なのは変わらず。
おもしろい。2019年にプログラミングをはじめてさわった自分には想像できない世界だ。
この ES5 がなかなか退場しない IE8/11 で動く水準だったので、長らく各種ツールの吐き出すビルド水準として存在し続けることになる。
なるほど
TypeScript は型検査の厳密さよりも段階的に既存コードに型をつけていける漸進的型付けという仕組みを採用しており、2012年の初期にこれが仕込まれたことが、エコシステムを段階的に型化していくインフラを整えて、この型定義の蓄積が今現在の覇権の伏線になっている。
へ〜
SPA 技術の普及と同時に、開発コミュニティの乖離が顕著になる。SPA開発のツールチェインは既存の Web 開発の文脈になく、 Node.js 系ツールチェインの上に構築されていた。Node.js 系開発者は当然JSに強く、既存のWeb開発者はどちらかというと HTML/CSS のマークアップ技術に強い。
人の動きも見ててすごい
フロントエンドにおいて、とくにSEOを意識してサーバーにテンプレートエンジンを置いてクライアントでリタッチするのはもはや限界、というのは 2012 からわかっていた。コンテンツの生成をクライアントと同じ JavaScript, つまり Node.js が担わないといけないことから、サーバーサイドに部分的に進出するのは必然だった。 それが Next/Nuxt。
Nextはルーティングとルーティングごとの入力props、それらがクライアントで決定されるか、サーバーで決定されるか、をAPIとして意味論的に落とし込んだことが、大きな発明だったと思う。
うーむなるほど
そもそも、モバイル対応の名目で、サーバーサイドフレームワークでもAPIとビューが切り出されるのが当然になりはじめていたので、WebだがAPIの利用者、という立ち位置でSPAを切り出して作れるようになる土壌もあった
一番上の記事にもあった
それと同時に、コモディティ化の結果としての開発者の平均レベルの低下が大きな問題になった。我々がNode.js以来追い求めていたサーバーサイドと同じようにライブラリを組み込める基盤による、富豪的で無節操なライブラリ追加問題だ。
もう何度貼ってるかわからないけど、開発者の生産性を掲げ続けた結果、フロントエンドが不要なJSまみれになり、ユーザーに対する裏切りになったという Alex Russell の指摘は何度も読んでほしい。
「パフォーマンスを意識できてはじめて技術者」という俗説?がよく理解できる
SPA肥大化の問題はどこでも起きていて、結果モノリシックなSPAを解体して、部分的にロードするコンパイル時最適化や、ハイドレーション技術が注目されるようになっている。React のコア部分を小さく実装する preact はだいぶ前からあったが、コンパイル時に最適化を済ませてしまう svelte, その合わせ技の solid や quik 等が注目されている。
ここらへんの詳しい比較、面白いかもしれない
コンパイル時処理が複雑化した結果、CIのビルドを最適化し続ける FrontendOps の需要が高まり続けている。自分も 2022年はブラウザみてる時間より、シェルスクリプト書いたりGitHub Actions みてる時間のほうが長かった。
新しい潮流
今フロントエンドに残っている人は、大雑把に次のように分類できると思う。
- 古典派: ES5時代に残った人たち
- テンプレートエンジン派: 宣言的UIを使いつつ、しかし単に新しい記法として需要した人たち
- 富豪的SPA派: Next/Nuxt を既存のサーバーサイドのように使う人たち
- FrontendOps: CI上でパフォーマンス上の問題を発見して尻拭いする人達
自分はたぶん中2つのどっちかだな...
自分に欠けている要素がわかった!mizchiさんありがとうございます!