DEV Community

Ken Okabe
Ken Okabe

Posted on

Node.js やReact、ESM、Viteの説明

まず、大元の話をします。

インターネットというものが登場し、ホームページあるいはウェブサイト という代物が登場しました。

ウェブサイトは、HyperText Markup Language HTMLというハイパーリンク機能があるハイパーテキスト記述言語、マークアップ言語で書かれています。

各ウェブサイトは、ハイパーリンク機能でリンクされていますから、世界規模で、まるで蜘蛛の巣(ウェブ)のようなネットワーク構造を形成しはじめます。

How the World Wide Web Was Born

これがWorld Wide Webです。

WorldWideWebを自在に移動(Explore/Navigate)しながらウェブサイトを閲覧(Browse)

するためにWebブラウザというソフトウェアが登場しました。

Netscape Navigator

Microsoft Internet Explorer

の2トップです。Netscape社とMicrosoft社はインターネットというとつてもない新市場を前に苛烈なシェア争いをします。第一次ブラウザ戦争 1990-2000です。

この頃の最初の最初のウェブサイトは単なるHTMLの文書です。動いたりしません。静的なサイトのみです。

しかし動いたほうがいいということなり、Netscapeの開発者がスクリプト実行機能を搭載した次世代ブラウザNetscapeNavigator2.0をリリースします。

そのとき、たまたまJavaが人気を集めていたので、まるで別物ですが単なるマーケティング上の理由からそのスクリプト言語はJavaScriptと名付けられます。

JavaScriptで動作する動的なWebサイトの登場です。

遅れをとった競合するMicrosoft社は、自社の次世代ブラウザであるIE3.0にもJavaScriptを搭載しようとしましたが、戦争してるわけでNetscapeはライセンス供与を拒否して、泥沼状態になります。

結局NetscapeはMicrosoftのWindowsという圧倒的なシェアを誇るOSとIEブラウザの囲い込み戦略により敗退しますが、Firefoxというブラウザにその技術はオープンソースプロジェクトとして継承されます。

IEのJavaScriptエンジンはクローズドですが、FirefoxのJavaScriptエンジンであるSpiderMonkey はオープンなのでGoogleが資金援助とかやっていました。

しかしFirefoxはオープンソース界隈でよくあることですが、開発が停滞するんですね。Googleのエンジニアはイライラします。ちゃんと仕事をしろ。俺達の技術力ならもっとマシなものを全力で作れるのに、と。こいつらへの資金援助は無駄だと。

Firefoxを見切って開発されたのがGoogle V8 JavaScript Engineというとんでもない実行スピードを叩き出すJavaScirptエンジンであり、それを搭載したChromeブラウザでした。

JavaScript Performance Rundown (https://johnresig.com/blog/javascript-performance-rundown/)

V8エンジンの圧倒的スピードとChromeブラウザのシンプルさはギーク界隈を震撼させます。

第二次ブラウザ戦争 2004-2014年の勃発です。

IE、Firefox、Opera、Safari、Chromeの五択となり当初情勢は混迷を極めますが、ギークだけではなく一般市民までGoogle謹製という信頼性とChromeの筋の良さが評価された結果、現在はご存知のとおりです。

IE 11が増加 - 4月ブラウザシェア

Googleは、V8エンジンだけをChromeブラウザから単体分離してオープンソースで公開しました。自分はリアルタイムでこの辺見てましたが、ああこれはものすごいポテンシャルがあることだなと喜んでいました。これだけ高速なJavaScriptエンジンならばWebブラウザ外でも色々使えるに決まっているからです。

Webブラウザ、レンダリングエンジン、JavaScriptエンジンを整理して図視化してみた - Qiita

2008- node.jsとnpmの台頭

同じように感じたギークは世界中にいて、ライアン・ダールがV8エンジンに入出力を扱えるようにC++で書いたイベントループ機構を足して、ブラウザ外の独立した実行環境としてリリースしました。それがNode.jsです。

Node.js は、イベント化された入出力を扱うUnix系プラットフォーム上のサーバーサイドJavaScript環境である。Webサーバなどのスケーラブルネットワークプログラムの記述を意図している。ライアン・ダールによって2009年に作成され、ダールを雇用しているJoyentの支援により成長している。

V8 JavaScriptエンジンで動作するが、ChakraCoreバージョンやMozillaによるSpiderMonkey移植のプロジェクトも存在する。

Node.jsは、だからブラウザから分離したJavaScript実行環境であって、ブラウザのほうの表示がどうとかいうのはまったく関知していません。

Node.jsは、非常に効率の良いWebサーバとしてサーバーサイドで活用されはじめると同時に、独自のモジュール/パッケージ管理システムであるnpm

を発展させ(JavaScriptの標準仕様としてモジュールが存在しないので独自に発展させるしかしようがない)、2019年現在まで一大エコシステム・コミュニティを形成してきました。

2011- Browserifyの登場 “bundler”

JavaScriptのモジュール化の標準化が定まらない中、独自のエコを発展させたnodeベースの膨大なJavaScriptモジュール資産(npmエコ)をWebブラウザでも活用すべくBrowserifyが登場します。これは実際結構な力技で、当初自分はよくこんなハックが出来たなと衝撃を受けたものですが、npmの依存関係に従ってモジュールを単一のJSファイルにつなぎあわせる、というものです。現在は、webpackのほうが人気だとは思いますが、いわゆる"bundler"(バンドラ)と呼ばれるものの先駆的存在です。

2013- Reactの登場

HTMLはJavaScriptでイジる仕組みになっているわけです。これは現在も一環して揺るぎません。

HTMLの構造は、ドキュメントオブジェクトモデル (DOM)

としてツリー構造のオブジェクトモデルとして抽象化されています。

動的なWebサイトを実現するためには、JavaScirptからは結局HTMLというよりかはこのDOMを操作していくんですが、JavaScriptのDOM操作APIは出来が悪かったのです。

そして最悪なことに、例のブラウザ戦争の沙汰のせいで、ブラウザによってこのAPIやら挙動が微妙に異なるので、素のDOM操作をして動的なWebサイトの開発は困難を極めました。

そこで登場したのが、jQueryというライブラリです。

jQueryには次のような機能・特徴がある。

  • ブラウザに依存しないオープンソースのセレクタエンジン Sizzle を使ったDOMエレメントの選択(Sizzle は jQuery プロジェクトからスピンアウト)[3]
  • DOM操作と変更(CSS 1-3 と基本的なXPathのサポートを含む)

jQueryは開発者コミュニティから絶大な支持を受けていましたが、Facebookのある天才が、とある事実に気づきます。

そもそもDOMがJavaScriptの外部モデルとして存在していて、JavaScriptの操作対象になっているからやりにくい、と。

ならばもう、DOMはJavaScriptの値(ファーストクラスオブジェクト)にしてしまおう。

そうFacebookの技術者は考えました。

ぶっちゃけこれはとんでもないコペルニクス的発想であり、

JavaScriptとは?HTML(DOM)を操作するために生み出されたスクリプト言語

という存在意義、アイデンティティの根幹をひっくり返すような発明です。

しかもそれは、JavaScriptの言語仕様を勝手に変更するということに他ならないので、極めて受け入れられにくいことでしょう。Facebookという巨大なバックがある技術者だから言い出せたことで、結果受け入れられたことだと思います。

要するにこういうことです。

JavaScript + HTML(DOM)= JSX

Swatee Chand's answer to How can I learn React JS from scratch?

JavaScriptでは、HTML(DOM)=操作する対象

であったものが、

JSXでは、HTML(DOM)= 拡張されたJavaScript(JSX)の値(ファーストクラスオブジェクト)

として統合されます。

具体的には、

const helloNode = <div>Hello!</div>;

こう書けます。DOMがJavaScriptの値として組み込まれてしまいます。

こういうのを、仮想 DOM といいます。

この原理をきちんと理解さえできれば、DOMなんてものは、ただのJS(X)の値なので、いくらでも自由に操作できます。

単純なWebアプリならば、静的(動的)なHTMLにたいして、適当に外側のJSから操作することが単純だ、と思いがちですが、ちょっと動的に複雑になれば、途端に破綻します。

プログラムの複雑性に耐久性がなく一貫性もなくなるわけですから、ソフトウェアデザインとして堅牢ではない、ということです。

Vue.jsっていうのもそれですね。Vueっていうのは、こういうJSXがなんたるか?っていうのをイマイチ理解できていない技術者が好む仮想DOMライブラリです。

他方でJSXは素晴らしいのですが、Reactというライブラリが良いか?というと別に良くもありません。これも複雑に肥大しすぎなので見切られてVueのほうへ流れる開発者が多いという事情は理解します。

この辺り踏み込むと泥沼で長くなるので割愛し、ここではJSXの仮想DOMの話だけをします。

たとえば、よくあるToDoリストを考えましょう。

リストが増えたり減ったりするやつです。この場合、DOM自体が増えたり消えたりするわけで、増えたり、消えたりするDOMに対して、外側のJSからどうやってデータを紐づけたりするんでしょうか?

はい、DOMを足せ、DOMを消せ、っていう命令を出すしかないんですね。非常にやっかいです。

一方で、仮想DOMの場合はどうでしょうか?

DOMってのは単なるJS(X)の値です。まあ普通に考えて、DOMのリストっていうのは、JSの配列(Array)のデータ構造になっているでしょうから、その配列の要素を1個増やしたり減らしたりするだけです。

const addArray = arr => item => 
 arr.concat(item); 

const removeArray = arr => index => 
 [...arr.slice(0, index), 
 ...arr.slice(index + 1)]; 
Enter fullscreen mode Exit fullscreen mode

たとえば配列listにToDoのデータが入っていて、これをリストタグに展開したいのであれば、Array.map を使って、以下のように展開してやればよいでしょう。

const newNode = <ul> 
  {list.map(item => <li>{item}</li>)} 
 </ul>
Enter fullscreen mode Exit fullscreen mode

新しいDOMをこさえるわけですが、こういう新しいDOMを全部ひっくるめる、全部のDOMを構築します。

そして最後に古い全部のDOMと新しい全部のDOMをバーっと比較計算して、差分だけ書き換えろ、というJSネイティブのDOMの命令を「裏側」でやらせます。プログラマは関知しません。ただ新しいDOMを構築して投げるだけです。

ちなみにJSXというのはそのままJavaScriptエンジンでは動作しませんから、JavaScriptへトランスパイルしてやります。

2014- ECMAScript標準化の加速

ブラウザ戦争が収束するのと並行して、ブラウザ世界でもJavaScriptの標準化= ECMAScript標準化が活発化していきます。

ES6(ES2015)ではようやくモジュールが標準化され、importexportの仕様が固まりました。今後も、 ES2015-2018と立て続けに続くようで標準化は順調に加速していくようです。

2018 すべてのモダンブラウザがES6-Module(ESM)を実装完了

import

より関数型プログラミング的な、dynamic import(動的読み込み)がFirefoxにはまだ実装されていませんが、とりあえず基本的なモジュール化はモバイルも含めすべてのモダンブラウザ(EdgeじゃないIE除く)で実装完了されており、プロダクションレベルでも積極的に利用可能な情況になっています。

Webブラウザがモジュールをそのまま実行してくれるようになってくると(本来そうであるべき)、わざわざなんか面倒で複雑なバンドラって必要?ということになってきます。ReactのJSXをトランスパイルしなければいけないというのも、実はTypeScriptのTSXで書いていたらTypeScriptがやってくれるのでBabelというトランスパイラは不要とかいろいろすっきりしそうな気がします。というか自分はそうしています。

ESM/NPM(CJS)2つの互換性のないモジュールシステムの問題発生

2018年になって、ようやくモバイル含めてもうすべてのモダンブラウザがES6-Module(ESM)が普通に使えるようになった、素晴らしい。ではこれから書くライブラリはすべてESMで行こう!それならわざわざwebpackつかって面倒なセットアップと開発フローはなくて済むんでしょ?

現状はこういう流れになりつつあるのは間違いありません。それが本来の標準化されたモジュール化なのだから。

しかしここで問題はすでに確立されたnode.モジュールのnpmエコがあるということです。nodeのモジュールフォーマットであるCommonJS(CJS)ば後発のブラウザ標準のESMと互換性はありません。2つのメジャーなモジュールエコが互換性なく並立しているというのが現状です。

Reactのコンポーネントはモジュールにそのまま呼応するので、Reactのドキュメンテーションではかなり早くからES6のimport/export準拠で書かれていました。ブラウザでモジュールが実装される前からこのようにES6準拠となるのは、同時期に台頭したトランスパイラ(Babel)と上記バンドラ(Browserify/webpack)に依存して技術を先取りできているからです。

従って、Reactなどの先進的なフロントエンド開発をする際には、もうトランスパイラとバンドラをセットで使うのがデフォという感じになっています。そして驚くべきことに、React開発陣が完全にこの大前提にたっているためにReactライブラリ自体をES6Module(ESM)の仕組みでimportさせるのにネイティブにESM対応モジュールをリリースしておらず、トランスパイラとバンドラにNPM用のモジュールを読ませて変換させるというリリースの仕方をしています。

現状、別に規格化されているわけでもなんでもないnpmというモジュール/パッケージ管理システムとそれを単一ファイルに統合するバンドラを前提にフロントエンド開発が当たり前のように行われている、というのは、すべてブラウザ戦争により標準化されたモジュール化が立ち遅れたことが原因で、なかなか不安定な過渡期であると言えます。

reactライブラリは現在、npmというNodeのエコのパッケージマネージャーで配布()されていて、WebPackやらのバンドラでブラウザのコードとするのは間違いないですが、これは劣った設計です。

すでにnpmエコにおいてもコミュニティは積極的にimport/export準拠でライブラリ・パッケージをリリースしているのですが、それはもちろんwebpack/browserifyのバンドラが処理してくれるという前提で皆そうしているようです。nodeはそもそもが自前のCJSモジュールベースで実装されているわけで、本来互換性のないESM対応についてはチーム内でもなかなか意見がまとまらないようで現状も混迷を極めており、早急な進展も望めない情況のようです。


2023/01 UPDATE

image

https://esbuild.github.io/

そしてesbuildをベースにした、

image

https://vitejs.dev/

が主流になりつつあります。

Image description

https://2022.stateofjs.com/en-US/libraries/build-tools/build_tools_experience_ranking

Top comments (0)