Closed6

JavaScriptのモジュールについてまとめてく: 歴史編

ebi_yuebi_yu

モジュールとは?

関連した機能を持った変数や関数を一つのまとまりにした記法。
モジュールは、特定の機能やコンポーネント、ライブラリをカプセル化し、他のモジュールやプログラムから独立した存在にできる。

import文やrequire文でインポートするライブラリもモジュール形式でビルドされている

// モジュール(ライブラリ)をインポート
import vue form 'vue'
const axios = require('axios')
ebi_yuebi_yu

モジュール化の歴史:モジュール化前

インラインスクリプト

 <html>
   <body>
     <script type="text/javascript">
       function sample () { console.log('a') }
       sample();
     </script>
   </body>
</html>
  • コードの再利用性がない。

<scrip>タグ

 <!DOCTYPE html>
 <html>
   <body>
     <script type="text/javascript" src="./sample.js"></script>
     <script type="text/javascript" src="./hoge.js"></script>
   </body>
</html>

コードの再利用性はできるが

  • 依存関係の解決ができない
    sample内でhoge内の関数を呼び出すには、hogeを先に読み込む必要がある。

  • グローバルが汚染される。
    同じ名前の関数がsample.jsとhoge.jsで存在していた場合、後に読み込まれたスクリプト(hoge.js)によって先に読み込まれたスクリプトの関数が上書きされてしまう。

ebi_yuebi_yu

モジュール化の歴史:モジュール化後

IIFE モジュールパターン

Immediately-invoked function expressioの略。
即時実行関数の形で関数を定義することでグローバルへの汚染を避けるようにした形式。

即時実行関数とは名前そのままの意味で、ロードされるとすぐに実行される関数。
その内部で定義された変数や関数はその内部のみ(ローカルスコープ)で扱える。

 // グローバルスコープ
const myApp = {};
 
 // 別ファイルに切り出された IIFE 形式のモジュール
 (function(){
   const a = 1
   cosnt b = 5
   myApp.add = function() {
     return a + b;
   }  
 })();

 // グローバルスコープ
// num = 6
 var num = myApp.add(1,2);
// aとbは即時実行関数内に定義されているため、アクセスできない
console.log(a)
console.log(b)

IIFEによりグローバル汚染は多少マシになったが、依存関係の解決ができない

CommonJS

サーバサイド JavaScript の仕様。
サーバサイドだと <script> タグによるインポートができない(htmlファイルがない)ので
モジュール機能が作られた。

 // 外部に公開 (export) する側
 module.exports = function Module(a, b){
   return a+b;
 }
 
 // 利用 (import) する側
const module = require(‘Module’);
// num = 4
const num = module(1,3)

外部にmoduleとして公開したいものをmodule.exportsで公開し、
利用する側はrequireでインポートする。

モジュールの依存解決(モジュールの名前をどのモジュールに解決するか)は
お馴染みの**npm(Node Package Manager)**を使って行う。

CommonJSの課題

  • サーバサイド の仕様のため、クライエントサイド(ブラウザ側)では使えない。
  • 同期的にしかモジュールをロードできない。
 // 下のコードが呼ばれるまで
const module = require(‘Module’);
const module2 = require(‘Module2’);
const module3 = require(‘Module3’);

上の例だとsampleが読み込み終わるまでsample2、sample3は読み込まれない。
非同期的に一気に読み込めた方が効率的。

AMD(Asynchronous Module Definition)

クライエントサイド(ブラウザ側)でモジュール形式が使えるようにした仕様。

// definie(モジュール名、[依存関係]、func(依存関係のモジュール))
define('Module1', [], function() {
  function func(a, b) {
    return a * b;
  }
  return func;
});

define('Module2', ['Module1'], function(module1) {
  function func() {
    // 15
    return module1(3,5);
  }
  return func;
});

モジュールは**definie(モジュール名、[依存関係]、func(依存関係のモジュール))**のように定義する。
他の依存関係(モジュール)をインポートし手使いたいときは第二引数に、そのモジュール名を含める。

define('Module2', [], function () {
    function func(a, b) {
        const module1 = require(‘Module1’);
        const num = module1(3, 5)
        return num
    }
    return func;
});

CommonJSと組み合わせて使うことも可能。

AMDを利用するときはhtmlでRequireJS(AMD用のモジュールローダ)をインポートする
必要がある。

 <html>
   <body>
     ...
     <!-- エントリーポイントを指定して RequireJS をロードする -->
     <script data-main="main.js" src="require.js"></script>
   </body>
 </html>

AMDの課題

  • 記述が冗長
  • サーバ側と互換性がない
  • パフォーマンスに影響がある
    たくさんの細かいファイルをロードするのでHTTP1.1においてはパフォーマンスに影響する

UMD

Universal Module Definition の略。
モジュールが、グローバルオブジェクト(IIFE)、CommonJS、AMDどの形式で書かれていても、
利用できるようにした形式。

(function (root, factory) {
    // 環境に応じてモジュールを定義する。
    if (typeof define === 'function' && define.amd) {
        // AMD環境(非同期モジュール定義)の場合、define関数を使用
        define(['Module1'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // CommonJS環境(Node.jsなど)の場合、exportsオブジェクトを使用
        module.exports = factory(require('Module1'));
    } else {
        // 上記以外の環境(通常はブラウザ)の場合、グローバルオブジェクトにモジュールを追加
        root.Module2 = factory(root.Module1);
    }
}(typeof self !== 'undefined' ? self : this, function (Module1) {
        // Module1の呼び出し
        const num = Module1’(3, 5)
        return num
}));
ebi_yuebi_yu

モジュール化の歴史:現在の形式

ES Modules(ESM)

今まで、CommonJS、AMD、UMDなど様々なモジュールの記法が登場してきたが、
それはJavaScript言語に明確なモジュール記法がなかったことに起因する。

ES6(ECMAScript 2015)では、import と export の構文を用いたモジュールシステムがJavascriptの
言語仕様に正式に含まれた。

import Module1 from './Module1';

function func() {
  const tmp =Module1();
  return tmp;
}

export default func;

System.register

ES6で登場したModuleシステム(ESM構文)をES5(ESMができる前のJavascriptの規格)
でも使えるようにするためのモジュール記法。
systemjsをモジュールローダとして使う場合などに用いる。

System.register(['dependency'], function (_export, _context) {
  var dep;
  return {
    setters: [function (_dep) {
      dep = _dep;
    }],
    execute: function () {
      _export({
        name: 'value'
      });
    }
  };
},/*optional metas*/ [
  /*optional meta for dependency*/ {assert: {type: 'javascript'}},
]);

System.register は、ES5 内の ES6 モジュールの正確なセマンティクスをサポートするように設計された新しいモジュール形式と考えることができます。

この形式は以下をサポートします。

  • 動的 import()
  • import.meta (import.meta.url および import.meta.resolve を含む)
  • トップレベルの待機
  • 再エクスポート、スター エクスポート、名前空間インポート、およびこれらの組み合わせによるライブ バインディング更新
  • 関数ホイスティングを含む循環参照 (非実行モジュール内の関数を循環参照の場合に使用できる場合)
  • アサーションをインポートする

公式から引用

ebi_yuebi_yu

参考: Node.jsについて

ブラウザだけではなく、サーバサイドでJavaScriptを実行するために作られたJavaScript実行環境
初期にはCommonJSモジュール規格に準拠していましたが、現在ではESMなどにも対応している。

現在はESMなどにも対応しており、ブラウザ(クライアントサイド)でも使用できる

https://nodejs.org/en/learn/getting-started/introduction-to-nodejs

参考 npmについて

Node.jsのパッケージ(モジュール)管理ツール。
パッケージは、一つ以上のモジュールを含むディレクトリ。
通常、package.jsonファイルが含まれており、パッケージのメタデータや依存関係を定義している。

https://docs.npmjs.com/about-npm

このスクラップは2023/12/29にクローズされました