2021/08/16

javascriptのひすとりーが知りたい

@ 酒井悠宇

JavaScript

https://www.youtube.com/watch?v=PFeR332LurM&list=PLwM1-TnN_NN4SV6DEs4OtfA51Up6XzTfB&index=5
しまぶーのモダンJavaScriptの歴史動画をまとめます。

なぜJavaScriptを使う必要があるのかを勉強する
前回の記事

前回の復習

前回はそもそもなぜJavaScriptが必要なのか、そしてJavaScriptの誕生からサーバーサイドJavaScriptの動きをまとめました。
流れを簡単に復習します。

  1. JavaScriptがNetscapeとSun Microsystemsによって1995年12月に発表。
  2. MicrosoftのInternet Explorer3.0に搭載されJavaScriptが急速に発展し始める。しかしNetscape Navigatorと互いに独自の機能追加を行なっていた為、開発者はそれぞれに合うサイトを作る必要があった。
  3. 開発者にとってあまりよくない状況を打開するためにNetscapeはJavaScriptをEcma Internationalに提出。ECMAScriptの第1版が公開された。
  4. JavaScript起因のクラッシュや悪用ウイルスが多発し、JavaScriptの人気が落ちる。
  5. Microsoftが開発したAjaxによって、高機能なWebアプリケーション開発言語として再び注目を集めた。特にGoogle Maps は非常に話題になった。
  6. JQuery誕生。少ない記述で多くの実装ができる為、たくさんの開発者に使われた。
  7. JavaScriptをサーバーサイドでも使えるようにするために、kevin dangoorがServerJS というプロジェクトを立ち上げた。しかしサーバーサイドでJavaScriptを使うにはさまざまなAPIが不足していた。
  8. せっかくいろんなAPIを作るなら、サーバーサイドだけでなくていろんなところで使えた方がいいよね。という考えから、より広い範囲のAPIを対象とすることを示すために、プロジェクト名が現在のCommonJSへと改名された。


JavaScriptの歴史

CommonJSでいろんなAPI仕様が作られていく。その中でも特にフロントエンド開発にも大きく影響する「モジュール」からまとめていく。

モジュールとは

モジュールはただのファイルです。1つのスクリプトは1つのモジュールです。モジュールは相互に読み込んだり、exportとimportを使用して機能をやり取りしたり、あるモジュールの関数を別のモジュールから呼び出したりすることができます。


モジュールのありがたみをわかりやすくするために、JavaScriptの現状の問題点をストーリーで見ていく。

  1. 一つのindex.jsというコードが数千行書かれたファイルがあったとする。
  2. あるエンジニアは「流石にこれは見にくいからファイルを分けよう」と考える。
  3. エンジニアは1つのindex.jsを、a.js、b.js、c.jsに分割。
  4. HTMLファイルでも3つのJSファイルを読み込むようになる。
  5. しかしすぐにバグが起きた。
  6. 名前空間が一つしかないことにより、意図しない変数の上書きが起きていたことが原因だった。
  7. 今度はa.jsのコードを変更するとバグが起きた。
  8. a.jsのコードをもとにb.js、c.jsが作られていたことが原因だった。(依存関係)


<JavaScriptの現状の問題点>

  • 名前空間が一つしかない
  • 分割したファイルに依存関係がある


一つ目の名前空間の問題を解決するのがモジュール。依存関係は後述するパッケージ管理(npm)によって解決する。

モジュールがあるとこうなる。
<a.js>

var foo = 123;
module.exports = foo;

<b.js>

var foo = "foo";

{
<c.js>

var foo = require("./a");
console.log(foo); //123


このように値を読み込むファイルを明示的に指定することで名前空間の問題を解決することができる。

<スコープについて>
モジュールにはスコープの概念がある。JavaScriptのモジュールのスコープは1ファイル単位で、ファイル内の変数や関数は外部に影響を及ぼさない。

名前空間の問題の解決

モジュールによってJavaScriptの名前空間の問題が解決された。しかしこれはあくまでCommonJS(サーバーサイドでもJavaScriptをかけるようにしようねというプロジェクト)の話で、ブラウザのJavaScriptで使うことができない。ブラウザのJavaScriptはずっと名前空間は1つで、モジュールが使えないので問題は依然として残り続ける。 

Node.jsの誕生

  • 2009年にRyan Dahlによって作られた。最初はCommonJSのモジュールAPIの仕様に準拠していた。


Node.jsとCommonJS

  • CommonJSのコミュニティがうまく機能しなかったこともあり、Node.jsは独自の進化を遂げる。
  • 「CommonJS形式のモジュール」という言葉自体は残っているが、プロジェクトはもう動いていない状態。


CommonJS形式のモジュール

  • JavaScriptにはモジュールの仕様が複数存在している。その中のCommonJS形式はNode.jsで使われている。
  • CommonJSと検索してもいまいちわかりにくいのは、モジュールの仕様が複数存在していることによるもの。


機能の細分化

モジュールのおかげで名前空間の問題が解決されて、機能が細かく分けられるようになった。さらにいろんな機能を組み合わせて、便利なことができるようになった。

共有の需要

機能が細分化されていくと今度はそれらを共有して使用するニーズが生まれる。これは他のサーバーサイド言語ではすでに実現されていること。Node.jsでもこれが求められた。

パッケージとは

共有したい機能の単位がパッケージである。1ファイルの場合もあれば、ディレクトリの場合もある。

どうやって共有するのか

Node.jsで色んなパッケージが開発されるようになるとそのパッケージのバージョンを管理したり、共有するためのシステムの必要性が生まれた。そこで登場するのがパッケージ管理システム。

パッケージ管理システムとは

  1. リポジトリの購読
  2. パッケージのインストール・削除
  3. 依存関係の解決←前回の話
  4. 設定管理


リポジトリの購読

ローカル環境にインストールしたパッケージを更新できる。またパッケージを検索できる。

パッケージのインストール・削除

パッケージを指定してローカルにインストールすることもできる。反対にローカルから削除することもできる。

依存関係の解決

パッケージに必要な別のパッケージを自動的にインストールや更新することができる。

設定管理

設定を書くことによって、上記3つを自動で行うことができる。毎回手動でパッケージを入れたりする必要がなくなり、チームでの環境を簡単に揃えたりすることができる。

npmの誕生

2010年にlsaac Z. SchlueterによってNode.jsのパッケージ管理システムであるnpmが誕生した。Node package managerの略語である。

サーバーサイドJSの準備は整った

名前空間の問題はモジュールで解決され、依存関係の問題はnpmで解決された。Node.js製のツールがどんどん開発されていった。

Node.jsが普及する

Yahooが2009 - 2010年頃からNode.jsを採用
LinkedInが2011年にNode.jsを採用
PayPalが2013年にJava → Node.jsに移行

あくまでサーバーサイドの話

  • ブラウザではモジュールも使えない
  • npmのパッケージも使えない(npmのパッケージはCommonJS形式で書かれていたので、これはブラウザでは使うことができない)


ブラウザ側の努力

ブラウザ側でもモジュールを実現しようと努力していた。

ブラウザ側モジュールのパターン

  1. Anonymous closure
  2. Global import
  3. Object interface
  4. Revealing module pattern


IIFE(即時実行関数式)を利用している

上記4つのモジュールでは、IIFEというものを利用していた。

即時関数は、関数を定義すると同時に実行するための構文
(function(){
  var foo = "foo";
})();
foo; //foo in not defined

JavaScriptには、グローバルスコープと関数スコープがある。
即時実行関数式によって任意にスコープを狭めることで、擬似的にモジュールのような仕組みを作っていた。

それだけだと不十分

言語仕様としてのモジュールではないので、名前空間の問題も完全に解決されるわけではなかった。

AMD & RequireJS誕生

ブラウザでのモジュールの扱いを改善するためにAMDという仕様が2009年頃に誕生した。この仕様を実装しているものがRequireJS。 

AMD(Asynchronous Module Definition[非同期モジュール定義])とは

ブラウザ環境での実行を考慮し、依存関係の解決(IIFEではここの解決ができていなかった)及び遅延ロードに対応した仕様。

define(['jquery', 'underscore'], function ($, _) {
    function a(){}; // public
    function b(){}; // private
    return a;
});


ブラウザでもモジュールが使える

AMD形式でブラウザでもモジュールが実現。しかも依存関係も解決することができる。次に欲しいのは共有やバージョン管理のためのブラウザ向けパッケージ管理システム。

Bower誕生

クライアントサイド開発向けのパッケージ管理システムBowerが2012年に誕生した。Bowerがあれば、npmと似たことができる。

Bowerのモジュール形式

ほとんどのBowerのパッケージは、IIFEモジュールかAMDモジュール(ブラウザ向けに作られたモジュール)を利用している。

AMD形式でモジュールが使えたが...

サーバー側の機能とは互換性がなく、AMD形式の構文はCommonJS形式と比較して冗長。
依存関係が多いと、メンテナンスが大変で、パフォーマンス面でも問題があった。

更にBowerも...

どのパッケージがどう依存しているかをユーザーが手作業で定義する必要があった。
同一ベージ内にある同じパッケージの異なるバージョンをサポートしていなかった。(一つのページ内で、同じパッケージの異なるバージョンを使用できなかった。)

モジュール形式まとめ

  • IIFE形式(即時実行関数式)←名前空間の問題は解決したが依存関係は解決できなかった。
  • AMD形式(difineのやつ)←名前空間も依存関係も解決したが表現が冗長。
  • CommonJS形式←サーバーサイドで動くJavaScript、Node.jsで成功。


考え方を変える

「CommonJS形式で書かれたものを事前にブラウザ向けに変換しよう」

事前にブラウザ向けに変換

  • Bundle(バンドル)
  • Compile(コンパイル)


Bundleを使うと...

  1. 開発時はCommonJSモジュールで開発。
  2. モジュールの依存関係を解決して1ファイルに変換。(これがBundle)
  3. 変換したコードをいつも通りscriptタグで読み込む。


Browserifyの誕生

CommonJS形式で書かれたものをブラウザ向けにバンドルするツールBrowserifyが2011年に誕生。

Browserifyとは

  • Browserifyは全ての依存関係を束ねて、ブラウザでrequire('modules')を使用可能にする。
  • ブラウザにはrequireメソッドが定義されていないが、Node.jsには定義されている。
  • Browserifyを使うとNode.jsと同様にrequireを使うコードが使用できる。


Browserifyによって、ブラウザでもrequire構文を用いてモジュールを書くことができるようになった。Node.jsのパッケージがブラウザ向けに移植された。

ブラウザでもnpmが主流に

元々npmはCommonJS形式で書かれたパッケージが多く、ブラウザでは使えなかったが、Browserifyでブラウザ向けに変換できることで、ブラウザでもnpmが使われるようになった。   

webpackの誕生

新たなバンドルツールwebpackが2012年に誕生。Browserifyより高機能で現在でもwebpackが主流。(最初からBrowserifyよりwebpackの方が高機能だったわけではないみたい)

webpackとは

主にJavaScript向けだが、対応するローダーがあれば、HTML、CSS、画像などのフロントエンドのアセットも変換(バンドル)することができる。

バンドルする前はファイル同士に依存関係があるが、バンドルした後のファイルには依存関係がない。

Browserifyとの違い

CommonJS形式モジュールをバンドルするのがBrowserify。
JSに限らず、なんでもバンドルするのがwebpack。

webpackのCode SpLitting 

webpackを使うと、コードを複数のchunkに分割できる。chunkは実行時に非同期的にロードされる為、最初のロード時間を短くすることができる。

EcmaScriptに言語仕様としてのモジュールを

今までの話は全てJavaScriptの言語自体が持っている仕様ではなく独自の仕様である。なので、言語仕様としてのモジュールが求められていた。  

ESModules誕生

ES2015でJavaScriptの言語仕様として、モジュールの仕組みがついに導入された。 
CommonJS形式ではrequire構文を使用するが、ESModulesでは、importという構文を使用する。

しかし...ESModulesができても、まだほとんどのブラウザでサポートされていなかったので、モジュールバンドラーが必要な流れは変わらない。

ESModulesで書きたい

言語仕様としてモジュールが使えるようになり、独自のCommonJS形式でコードを書くよりも、ESModulesでコードを書きたいという需要が高まる。

webpackがESModules対応

バージョン2でESModulesをネイティブサポート。ESModules形式でモジュール間のやり取りが可能に。
webpackがESModulesに対応したことによって、開発時はimport構文でコードを書くことができる。そのimport構文をバンドルすることによって、ブラウザで動くようなコードになる。

現在のESModulesのサポート対応

ESModulesはEcmaScriptの標準仕様なので、現在ではwebpackを通さなくても、IE以外のモダンブラウザとNode.jsのversion12系以上では標準で使用可能。

ここで一旦まとめ

  • モジュールバンドラーが流行る(特にwebpack)
  • モジュールバンドラーがあれば、CJS(require)やESM(import)形式でモジュールを使ってコードが書ける。
  • CJSが主流だったnpmもクライアント開発で使える=パッケージ管理システムも使える。


ここまではバンドルの話

ここまでの話はBundleの話、次はCompileについて説明していく。

Compile(コンパイル)を使うと...

  1. 「開発時はブラウザでは動かないけど開発に便利な機能」を使ってコードが書ける。 
  2. 書いたコードをブラウザで動くように変換する。(これがコンパイル)
  3. 変換したコードをいつも通りscriptタグで読み込む。


ES2015はモジュール以外にも

let, const, class, Promise, アロー関数, 分割代入, スプレッド構文, テンプレート文字列...などのたくさんの仕様が追加された。しかしすぐに使えたわけではなかった。

Babel(6 to 5)誕生

ES2015等で書かれたコードを従来の環境でも動くように古いJavaScriptに変換するコンパイラ。Babel(元は6 to 5 という名称)が2014年に誕生した。

Babelはwebpackと一緒に使えた

webpackの設定でbabelを一緒に使うことができた。モジュールが使えるようになるだけでなく、ES2015の新機能まで使えるということで、急速にJavaScriptが便利になっていった。

コンパイルの可能性

コンパイルが当たり前になることで、ES2015以外にも便利なパッケージが流行り出す。

  • React (.jsx)
  • Vue (.vue)
  • TypeScript (.ts)


まとめ

ブラウザでモジュールを使う為に模索した結果、コードを事前に変換することが主流になった。コードを事前に変換すると、モジュールを使える以外にもいろんな恩恵があるということ。

パラダイムシフト

事前に変換というパラダイムシフトによって、JQueryが使われなくなったと考えられる。事前に変換することでモジュールも使えるし、ESの新機能やTypeScriptなどが使える。

はい、ということでモダンJavaScriptの歴史をまとめていきました。
現在使われているwebpackやBabel、ReactやTypeScriptがなぜ生まれたのか、その裏にはどんなストーリーがあったのかをいい感じに知ることができました。
最後までごらんいただきありがとうございました〜!

© 2021 powerd by UnReact