やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

ブラウザでJSをモジュール化する方法一覧

コードの大規模化にそなえて。

成果物

モジュール化とは

 モジュール化とは、別ファイルに分割することである。

問題

 モジュール化しないとコードの管理が大変。とくにコードの規模が大きくなるとバグの原因にもなりうる。

規格

 Node.jsでのモジュール化規格を含めると4つある。今回はブラウザで使える方法に限る。

歴史

 現在はモジュール化できる言語仕様がある。import, export文だ。昔はなかった。以下のような段階を経て、コード分離してきた。

方法

0. HTML内

index.html

<html>
<head>
<script>alert("JavaScript!!");</script>
</head>
</html>

 モジュール化されていない状態。HTML内にJSが混在している。

1. 外部ファイル化

index.html

<html>
<head>
<script src="main.js"></script>
</head>
</html>

main.js

alert("JavaScript!!");

 HTMLとJSを分離してスッキリ。でも……

JSからJSを呼び出せない

 JSコードの規模が大きくなると、JSファイルを複数に分割したくなる。なのにJSには外部呼出する言語仕様がない。(現在はimport文がある)

index.html

<html>
<head>
<script src="main.js"></script>
</head>
</html>

main.js

class Human {
    constractor(name=null) { this._name = name; }
    Speak() { alert(this._name); }
}
const h = new Human("NAME");
h.Speak();

 クラス定義と呼出を別ファイルに分離したいのに……。

2. 順序呼出

 JSのファイル分割はできる。ただ、苦肉の策である。HTMLの<script>タグでJSコードを順に呼び出すことになる。

index.html

<html>
<head>
<script src="Human.js"></script>
<script src="main.js"></script>
</head>
</html>

main.js

const h = new Human("NAME");
h.Speak();

Human.js

class Human {
    constractor(name=null) { this._name = name; }
    Speak() { alert(this._name); }
}

依存順序を把握せねばならない

 呼出の順序が重要である。main.jsではHumanを使用しているので、main.jsをロードする前にHumanを定義してあるHuman.jsをロードせねばならない。さもなくば参照エラーになる。

<script src="Human.js"></script>
<script src="main.js"></script>

 もしJSコードの依存順序を変更することになれば、それに伴いHTMLの<script>タグ順序も変更せねばならない。

 開発者はJSコード全体の依存順序をすべて把握している必要がある。そして依存関係に変更があれば随時HTMLを更新せねばならない。これは規模が大きくなると非常に苦痛である。

HTMLを変更せねばならない

 JSのファイル名やパスが変更されるとsrc属性値を変更せねばならない。

 JSの変更なのにHTMLファイルを編集するのだ。リビジョン管理するときの対象ファイルにHTMLが入ってしまう。

3. DOMでscriptタグ追加

 JSコード内で<script>タグを追加する。

 ロードもJSで書けるので、HTMLファイルを修正する必要は最小限で済む。ただ、依存する順序に沿ってロードせねばならない問題は残る。

index.html

<html>
<head>
<script src="jsloader.js"></script>
</head>
</html>

jsloader.js

function Load(path) {
    var script=document.createElement('script');
    script.setAttribute("src", path);
    document.body.appendChild(script);
}
Load("Human.js");
Load("main.js");

4. require.js

 require.jsというライブラリを使う。依存順序を気にせず書ける!

 ただ、ライブラリ固有の表現や設定をせねばならない。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>require.js</title>
    <script data-main="app.js" src="js/lib/require/require.min.js"></script>
</head>
<body></body>
</html>

app.js

requirejs(['js/app/main.js']);

 他のライブラリを使うときは以下のようにrequire.config({...});を設定する。

app.js

require.config({
    paths: {
    'jquery': 'js/lib/jquery/jquery-3.3.1.min',
    }
});
requirejs(['js/app/main.js']);

js/app/main.js

define(function(require, exports, module) {
    const $ = require('jquery');
    const Human = require('js/app/Human');
    const h = new Human("NAME");
    h.Speak();
});

js/app/Human.js

define(function() {
    return class Human {
        constractor(name=null) { this._name = name; }
        Speak() { alert(this._name); }
    };
});

5. ES Module

 ES6からimportexport文が言語仕様に加わる。

 ついにJavaScriptは標準でモジュール化できるようになった!

 しかし、使える環境は限られる。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>ES Module</title>
    <script src="main.js"></script>
</head>
</html>

js/app/main.js

import Human from "js/app/Human";
const h = new Human("NAME");
h.Speak();

js/app/Human.js

export default class Human {
    constractor(name=null) { this._name = name; }
    Speak() { alert(this._name); }
};