はじめに
今回はwebpackやBabelについて学習したことを理解を深めるために文章としてアウトプットしていこうと思う。
そもそもなぜwebpackやBabelに関して学習しようと思ったかは、これまでTypeScript、React等を用いて様々なアプリケーションを開発してきたが、webpackやbabelに関する知識が曖昧なまま開発ばかりを行なっていた。なんとなく"とりあえず変換してくれているもの"程度の知識しかなかったので、しっかり理解していこうと思う。
そんな中でwebpackやbabelの必要性を学習していくうちにJavaScriptの歴史を理解した方がわかりやすいと思ったため、JavaScriptの歴史についても触れていこうと思う。
JavaScriptの歴史
JavaScriptの誕生
JavaScriptの1995年にNetscapeの技術者であるブレンダン・アイクが開発し、Netscape Navigator2.0に実装されていた。元々はJavaScriptではなくLiveScriptという名前で呼ばれていたが、サン・マイクロシステムズ(現在:オラクル)のプログラミング言語Javaが注目を浴びており、Netscapeが業務提携をしていたこともあり、その注目度にあやかってJavaScriptに変更されたという経緯がある。よくJavaScriptとJavaを混合する方もいますが、それはこのような経緯があり、名前が似ているためです。
また、1996年にMicrosoftのInernet Explorer3.0に搭載され、JavaScriptがどんどん普及していった。
しかし、Netscape NavigatorとInernet Explorerは互いに独自の機能追加を行なっており、開発者はそれぞれに合わせてサイトを作る必要があり、大変であった。そこで仕様を一つに統合しようという動きがあった。
ECMAScriptの公開
NetscapeはJavaScriptを国際的な標準化団体EcmaInternationalに提出し、その結果、1997年6月にECMAScriptという標準の第1版が公開された。その後、1998年に第2版、1999年に第3版が公開されていった。当時の呼称はES5のようにESの後ろに版の番号をつけて呼んでいたが、2015年の第6版から年号を後ろにつけるようになり、ES2015のように呼ぶようになった。そこから毎年リリースする形となった。
ECMAScriptとは
JavaScriptの中核となる言語仕様であり、どの実行環境でも共通な動作のみが定義されているものである。JavaScriptにはいろんな実行環境があり、例えばサーバーサイドで動くJavaScriptであるNode.jsが挙げられる。ここにはブラウザ上のJavaScriptには存在したUIを操作するための機能がなかったりする。このようなNode.jsやブラウザの違いなどを意識せずにECMAScriptで開発をすることができる。
第一次ブラウザ戦争
1990年代後半にNetscape NavigatorとInernet Explorerとの激しいシェア争いが起こり、Inernet Explorerが勝利をした。これにより、1998年にWindows98にデフォルトでInternet Explorerが搭載されるようになり、多くの人に使用されるようになった。
しかし、ここでJavaScript起因のクラッシュや悪用ウイルスが多発したり、不快なアニメーションも多く、JavaScriptの機能をオフにする人も...
同時期にはFlashという、動作が軽いアニメーションが登場し、おもしろフラッシュ倉庫も話題となった。
JavaScriptの復権
一時はJavaScriptに対する失望が多くあった中、JavaScriptが再び注目される時が来る。それがAjax(非同期通信)の登場である。 これは高機能なWebアプリケーション開発言語として注目を集め、特にGoogle Mapsが非常に話題となった。そこで使用されていたのがJavaScriptであった。
また、JavaScriptのためのライブラリであるJQueryも誕生し、キャッチャーコピーでもある「write less,do more」にあるように、少ない記述で多くの実装ができるようになり、ブラウザ間の差異も吸収することができた。
Ajaxとは
Asynchronous JavaScript + XMLの略称であり、あるWebページを表示した状態のまま、別のページや再読込などを伴わずにWebサーバ側と通信を行い、動的に表示内容を変更する手法。
第二次ブラウザ戦争
2000年代後半からInternet Explorer,Google Chrome,Firefox,Safari,Opereの5択で争いが起こり始める。2012年にChromeがFirefoxに追いつき、2014年にChromeのシェアが50%に達し、この争いが終結した。
Server Side JavaScript
ここからJavaScriptはどんどん進化をしようとする。当時はブラウザ上はJavaScriptで書かれ、サーバーサイドはPHPやJavaといった他の言語で書かれており、どうせならどっちもJavaScriptで書きたいという声も多くあった。
そのため、2009年1月にMozillaのエンジニアKevin DangoorがServerJSというブロジェクトを立ち上げた。しかし、実際にサーバーサイドでJavaScriptを使用するにはAPI(ここではJavaScriptの機能)が不足しており、APIを作る必要があり、作成することになる。
ここで、どうせ作るなら共通のAPIを作成をしようという動きとなり、2009年8月にSeverJSからCommonJSへとプロジェクト名を変更した。
ここで様々なAPIが開発され、代表的なものとしてモジュールAPIが開発された。
JavaScriptの問題点
上記のモジュールAPIが開発された背景にはJavaScriptの問題点がある。それは以下の2つである。
- 名前空間が1つしかない(複数のファイルで同じ変数名を使うと影響を及ぼしてしまう)
- 依存関係でバグる(他のファイルに依存しているため、あるファイルで変更があると、他のファイルにも影響してしまう)
この2つの問題点のうち、1つ目に対しては、先ほどのモジュールが解決し、2つ目に関してはパッケージ管理(npm)が解決することとなる。(後述します)
モジュールの概念であるスコープ
JavaScriptのモジュールは1ファイル単位でファイル内の変数や関数は他のファイルに影響しない
これらの問題に対して、モジュールやパッケージ管理によって、問題が解決したかに見えるが、これはCommonJSでの話であるため、サーバーサイドでしか使えないものである。依然として、フロントエンドのJavaScriptでは名前空間の問題が残ってしまっている。そこでブラウザでもどうにかしてモジュールを使っていきたいという流れになる。ここでもう少し、このサーバーサイドの流れを深掘りしていく。
Node.jsの誕生
2009年にRyan Dahlによって作られ、最初はCommonJSのモジュールAPIに準拠して作られていた。しかし、CommonJSのコミュニティがうまく機能していなかったこともあり、Node.jsは独自の進化を遂げていき、CommonJSのプロジェクトは動かなくなっていった。
モジュールAPIに準拠していたため、このNode.jsによってサーバーサイドでのモジュール問題は解決することができた。モジュールのおかげで、機能が細かく分けられるようになり、いろんな機能を組み合わせて便利なことができるようになった。また、機能の細分化によって、それらを共有したいという需要(他のサーバーサイド言語では実装済み)が生まれていった。そこで登場するのがパッケージのバージョンを管理したり、共有することができるパッケージ管理システムである。
パッケージとは
package.jsonで記述されたファイルやディレクトリのことであり、共有したい機能の単位
パッケージ管理システム
以下の4つの機能がある
- リポジトリの購読
- パッケージのインストール・削除
- 依存関係の解決
- 設定管理
リポジトリの購読:ローカル環境にインストールしたパッケージを更新できる、またはパッケージを検索できるもの
パッケージのインストール・削除:パッケージを指定してインストールや削除ができるもの
依存関係の解決:パッケージに必要な別のパッケージを自動的にインストールや更新することができるもの
設定管理:設定を書くことで1,2,3の機能を自動で行え、毎回手動でパッケージを入れたりする必要がなくなるものであり、チームでの環境を簡単に揃えられる
パッケージを検索でき、依存関係を視覚化するサイト
https://npm.anvaka.com/#/
例:"Express"で検索するととても多くの依存関係があることがわかる
このようなパッケージ管理システムがNode.jsでも求められており、そこで誕生するのがnpmである。
npmの誕生
Node package managerの略語であり、2010年にIsaac Z. Schlueterによって作られたNode.jsのパッケージ管理システムである。npmを使用することで依存関係のあるパッケージを自動的にインストールしたり、管理することができるようになった。
これにより、サーバーサイドJSの準備は整い、Node.js製のツールがたくさん開発されていくことになる。
どんどん普及していったことで、Yahoo!やNetflix、UberなどでNode.jsが採用されていくことになった。
ただ、npmはほとんどCommonJSの形式で書かれていたため、これもフロントエンドでは使えないという状態...
ここからブラウザ側でもモジュールやパッケージ管理をしようと模索していくことになる。
ブラウザのモジュールパターン
実はサーバーサイドでモジュールを使えるようになる前からブラウザ側でもモジュールのような仕組みが存在していた。それが以下の4つである。
- Anonymous closure
- Global import
- Object interface
- Revealing module pattern
これらのパターンはIIFE(即時実行関数式)を利用しており、疑似的にモジュールを作り出していた。
以下はIIFEの例であり、関数をハックして疑似的にプライベートな空間を作っている。
(function(){
var foo="foo";
})();
foo;//foo is not defined
しかし、上記だけだと不十分であり、完全に名前空間の名前を解決したわけでない。そこでできたのが、AMDとRequireJSというものである。
AMDとRequireJSの誕生
AMDとはブラウザでモジュールの扱いを改善するための仕様であり、RequireJSはその仕様を実装しているものであり、ブラウザ環境での実行を考慮し、依存関係の解決および遅延ロードに対応した仕様である。
define(["moduleA","moduleB"],function(fnA,fnB){
//returnしたものがpublic
return function(){};
});
これによりブラウザでもモジュールが実現でき、依存関係も解決できた。
Bowerの誕生
さらにクライアントサイド開発向けのパッケージ管理システムとして2012年にBowerが誕生した。これは簡単に言うとnpmと似たようなことができ、IIFEモジュールやAMDモジュールを利用することができるものである。
しかし、AMD形式はサーバー側の機能とは互換性がなく、構文もCommonJSと比べると冗長的であり、依存関係が多いとメンテナンスが大変であり、パフォーマンス面でも問題となった。
さらに、Bowerも、どのパッケージがどう依存しているかをユーザーが手作業で定義する必要があり、同一ページ内で同じパッケージで異なるバージョンをサポートしていなかった...
パラダイムシフトが起こる
そこで考え方の転機が訪れる。以前までは実際に書いているコードがそのままブラウザで動いていたが、これからはCommonJS形式で書かれたものを事前にブラウザ向けに変換して表示させるという考え方に変わっていった。これは実際に書いたコードと画面で動くコードが異なることである。
これにより、事前にブラウザ向けにJavaScriptを変換するためにバンドルとコンパイルという概念が生まれた。
バンドルとは
モジュールの依存関係を解決して、書いたコードを1ファイルにJSで変換する考え方であり、開発時はCommonJSモジュールで開発することができる。変換したコードをいつも通りscriptタグで読み込むことができ、CSSや画像等もバンドルすることができる。
Browserifyの誕生
そこで2011年に、CommonJS形式で書かれたものをブラウザ向けにバンドルするツールであるBrowserifyが誕生した。Browserifyは全ての依存関係を束ねてブラウザでrequire(’modules’)を使用可能にした。
これにより、ブラウザではrequireメソッドが定義されていないが、Node.jsには定義されており、Node.jsと同様にrequireを使うコードが使用できるようになり、ブラウザでもrequire構文を用いてモジュールを書くことができるようになった。また、Node.jsのパッケージがブラウザ向けに移植され、Node.jsの便利なパッケージがブラウザでも使用が可能になり、ブラウザでもnpmが主流になった。
元々はCommonJS形式で書かれたパッケージが多くブラウザでは使えなかったがBrowserifyのおかげでブラウザ向けに変換でき、ブラウザ側でもnpmが使われるようになった。ここで、さらに登場したのがwebpackである。
webpackの誕生
Browserifyより高機能である新たなバンドルツールとして2012年にwebpackが誕生し、現在でも主流のツールである。webpackは主にJavaScript向けではあるが、対応するローダーがあれば、HTML、CSS、画像などもバンドル(変換)することができるものである。ざっくりとしたBrowserifyとの比較はBrowserifyはCJSをバンドルし、webpackはJSに限らずなんでもバンドルすることができるもの。さらにwebpackが優れていた点として、Code Splittingがある。これはwebpackを使用することでコードを複数のchunkに分割することができた点である。chunkとは実行時に非同期的にロードが起こるため、最初のロード時間を短縮することができるものである。開発したコードをバンドルして1つのファイルにするとかなり膨大な量のファイルになるため、このCode Splittingは画期的な機能であった。
ローダーとは
JavaScriptは基本的にJSしか読み込めないが、HTML、CSS、画像なども読み込めるようにするもの
ES Modulesの誕生
webpackの誕生により、モジュールやパッケージ管理の問題は解決した。しかし、これらはJavaScriptの仕様ではなく、JavaScriptをうまく使用するためのツールであった。そこでJavaScript自体にモジュールという仕様を追加しようとしてECMAScriptが動き、ES Modulesというモジュールの仕組みがES2015で導入された。これによりimport構文の使用が可能になった。
しかし、この仕組みはほとんどのブラウザでサポートされていなかったため、バンドルツールが必要な流れは変わらず...
そこでwebpackもES Modulesに対応し、さらにV2ではローダーがなくても使用できるネイティブサポートされた。これにより、import構文で開発が可能になり、それをバンドルすることでブラウザで動くようになった。
現在ではwebpackを通さなくても、Inernet Explorer以外のモダンブラウザとNode.jsのVersion12系以上では標準で使用が可能になっているが、今ではフロントエンドフレームワークであるReactやVueとかを使用する際にwebpackを通すのが当たり前のようになっているため、まだまだ必要性は高いものである。
これらにより、ブラウザ側でもモジュールやパッケージ管理の問題を解決することができた。
コンパイルという概念
ここでより開発を便利にしていくための概念としてコンパイルがある。これは開発時はブラウザでは動かないが、開発に便利な機能を使ってコードを書くことができ、書いたコードをブラウザで動くように生のJavaScriptに変換することである。これはバンドルする前のJavaScriptの前に、さらに開発に便利な書き方をするものである。コンパイルを使用している例としてTypeScriptを挙げて流れを説明すると、開発時はTypeScriptで書き、コンパイルすることでJavaScriptに変換し、それをバンドルしてJSに変換することでブラウザに表示するといった流れである。
Babelの誕生
ES2015ではモジュール以外にもlet,const,class,Promise,アロー関数,分割代入、スプレッド構文等様々な機能が追加されたが、これらもブラウザでは使用することできなかった。そこでES2015以降等で書かれたコードを従来の環境でも動くように古いJavaScriptに変換するコンパイラが2014年に誕生した。それがBabelである。元の呼称は6to5であり、人によってはES2015をES6と呼ぶ人も少なくなく、そのES6の構文をES5に戻すコンパイラということで6to5と呼ばれていた。
このBabelはwebpackと一緒に使うことができたため、モジュールやパッケージ管理の問題に加えてES2015の新機能も合わせて使えるようになり、JavaScriptが便利になっていった。
コンパイルの可能性
コンパイルが当たり前になることで便利なパッケージがどんどん流行りだし、ReactやVue、TypeScriptが流行していった。これらは事前に変換することでブラウザでも使用が可能であり、今では当たり前のように使用している人も多くなっている。
最後に
このようなJavaScriptの歴史の流れがあり、webpackやBabelがあることで今では当たり前となっているTypeScriptであったり、フロントエンドフレームワークであるReactやVueが使えるようになっていったという経緯があることを改めて理解することができた。
私自身は2021年の10月からエンジニアに転職して、その時からプログラミングをやった身であるため、TypeScriptやReactを使うのが当たり前という考えでプログラミングを学んでいた。
そのため、webpackやBabelの必要性に関して深く考えたことがなかったので、この学習をしてとても良い学びを得ることができた。これからも疑問に思ったことはしっかり深掘りしながら学習していきたいと思う。