旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section2 ~CommonJSモジュールと仲良くなろう~

  • 921
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

この記事は「旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門」の2つ目の記事です。

シリーズの最初から読みたい方は
旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section1 ~すぐにでも現代っぽく出来るワンポイントまとめ~
へどうぞ。

また、このシリーズではECMAScript5を概ね対応するブラウザを対象としています。

もっと平たくいうと、IE8以下は切り捨てます。ご了承ください。

そしてプロによるマサカリ :knife: 対策として一つ重要な注意書きをします。

この記事中で出てくる「CommonJSモジュール」という表現は全て「CommonJSのModules 1.0仕様をベースとして、Node.jsが独自に拡張したCommonJS派生のモジュール仕様及びインターフェース(require/exports.○○/module.exports)」を指すものとします。
標準仕様策定(プロジェクト)としてのCommonJS」や「CommonJS Modules 1.0仕様そのもの」を指すものではありません。

このシリーズを通して、原則として厳密さよりも分かりやすさを優先するためこのようにします。予めご了承ください。

目次

Section2 ~CommonJSモジュールと仲良くなろう~

前回は注意すべきポイントを抑えていく形でしたが、今回は主に歴史の話をしていきます。

ところで皆さんは色んなサイトを見て回った時に、こんな感じのコードを見て絶望したことはないだろうか?

requireこわいひぇぇ…
var hoge = require("hoge");

var piyo = hoge.fuga();
// ...(以下適当な処理)
module.exportsこわいひぇぇ…
function Hoge(){
    // ...(以下適当な処理)
}

module.exports = Hoge;

:fearful: 「ちょっとまって私の知ってるJavaScriptにはrequireもmoduleも無かっただけどなにこれは????」
:rage: 「試しにコピペしてみたけどやっぱりブラウザで動かないじゃないか!!!」

…なんてことが1度は経験したことがあると思います。

安心してください :laughing: :thumbsup:
これらコードは(殆どの場合) :sparkles: ブラウザでも動かせます :sparkles:

しかし、ブラウザで動かすには、ある程度知っておかないといけないお約束があります… :disappointed_relieved:

以降の話は、これら記述が一体何なのか、どこから来たのか、どのように付き合っていけば良いのかについてを話していきたいと思います。 :thumbsup:

モジュールという単位に分けるということについて :scissors:

ある程度スクリプトの規模が大きくなってきた場合、よく使う機能を集約して、モジュールとして切り離すことがあると思う。

今まで、ブラウザ上でそれを実現するには、.jsファイルを作りHTMLを介して複数読込することで実現していたと思う。

例えば、みんな大好きjQueryを使う時は、(CDNを使わない場合は)ローカルにあるjQueryのファイルをscriptタグで呼び出して使っていたはずだ。

jQueryモジュールを利用する例
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 省略 -->
    <script src="./jquery.min.js"></script>
    <script>
        // jQueryを外部モジュールとして呼び出したので、使用することが出来る!
        console.log(typeof(window.jQuery)); // "function"

        jQuery(function($){
            // ここに処理を書いていく
        });
    </script>
</head>
<body>
    <!-- 省略 -->
</body>
</html>

しかし、このモジュールの割り方には致命的な欠陥がいくつかある。 :fearful:
その中でも特に問題なのが「名前空間の圧迫」だ。

例えば私が、jQueryとは全く関係ないモジュールを作り、そのモジュールの名前にたまたま「jQuery」という名前を付けてしまったとしましょう。

myjquery.js
!function(){
    window.jQuery = jQuery;
    window.$ = jQuery;

    // 日本語の文字列をクエリストリングに変換するモジュール
    // Japanese-Query-String略してjQuery
    function jQuery(obj){
        var keys = Object.keys(obj);
        if(!keys.length) return "";

        return "?" + keys.map(function(key){
            return encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]);
        }).join("&");
    }
}();

/*
var text = jQuery({
    name: "がお",
    age: 18,
});

console.log(text); // ?name=%E3%81%8C%E3%81%8A&age=18
*/

そしてこのモジュールと、(みんなが大好きな方の)jQueryを同時に読み込むと、悲劇が起きる :confounded:

名前が被っちゃった結婚の例
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 省略 -->
    <script src="./jquery.min.js"></script>
    <script src="./myjquery.js"></script>
    <script>
        // jQueryを外部モジュールとして呼び出したので、使用することが出来る!
        console.log(typeof(window.jQuery)); // "function"

        // しかしそこにあったjQueryは我々の知っているjQueryではなかった…
        jQuery(function($){
            // ここに処理を書いても一切処理されない!!!
            // (オレオレjQueryモジュールの方を優先してしまうのです)
        });
    </script>
</head>
<body>
    <!-- 省略 -->
</body>
</html>

それでは困る!
ということで、人類は :star: CommonJSモジュール :star: というものを生み出しました(過去形)
(今ではCommonJSモジュールというのはあまり積極的に使われておらず、むしろ無くす動きがあります。しかしその話は :sparkles: ES2015モジュール :sparkles: の話をする時にでも。)

2016年現在、残念ながらまだCommonJSモジュールは現役なので、見かける機会は多々あります。
その為、依然としてこのモジュールの対策について知っておく必要があるのです。 :sweat_smile:

(補足) :poop: AMDモジュール :poop: について

AMDモジュールは、絶滅した。私からは、話したくない。(火種投下) :bomb:

CommonJSモジュールって一体何者…? :confounded:

実はこの辺りについて延々と語っていくと宗教戦争にもなりかねません。この記事ではゆるふわにいきます。

CommonJSモジュールは、以下の約束事を守って作られたモジュールのことです :heart:

まず、モジュールを作成する時は「module.exports」(もしくは「exports.○○」)を使います :sunglasses:

myjquery.jsのCommonJS実装
/* windowオブジェクトに直接与えるのをやめる
window.jQuery = jQuery;
window.$ = jQuery;
*/

// 代わりにmodule.exportsに与える
module.exports = jQuery;

// 日本語の文字列をクエリストリングに変換するモジュール
// Japanese-Query-String略してjQuery
function jQuery(obj){
    var keys = Object.keys(obj);
    if(!keys.length) return "";

    return "?" + keys.map(function(key){
        return encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]);
    }).join("&");
}

そして、CommonJSのモジュールを呼び出す場合は、「require関数」を用います :sunglasses:

requireを用いたモジュール呼び出し
var jQuery = require("jQuery"); // みんな大好きの方のjQuery
var myJQuery = require("./myjquery.js"); // みんなが知らない方のjQuery

// それぞれの変数に、それぞれのモジュールが格納される
console.log(typeof(jQuery)); // "function"
console.log(typeof(myJQuery)); // "function"

// CommonJSに従うと、windowオブジェクト下に付かないので汚染されない
console.log(typeof(window.jQuery)); // "undefined"

// jQueryは我々の知っているjQueryになっている!
jQuery(function($){
    // ここに処理を書いていく
});

// 同時に、オレオレjQueryの方も使用することが出来る!
var text = myJQuery({
    name: "がお",
    age: 18,
});

console.log(text); // ?name=%E3%81%8C%E3%81%8A&age=18

サラッとみんなが大好きな方のjQueryのCommonJSモジュール実装を出していますが気にしないでください。
(jQueryぐらい有名なモジュールだと、えらい人が既に作っているのです。)

さて、これで既存モジュールをCommonJSモジュールに書き換えが完了しました。
しかし、書き換えただけでは当然「ブラウザ上で動きません」よね。 :fearful:

神はNode.jsを与えた :innocent:

:frowning: 「ちょ、ちょっとまって?Webの話してたのにいきなりNode.jsが出てくるんだよ?」

安心してください :laughing:
滅茶苦茶関係しています。

因みにNode.jsも登場から5年近く経過して、流石に知名度が上がってきたので説明不要になりつつありますので、ここではざっくりと説明します。

Node.jsは、V8エンジンで駆動するサーバーサイド向けに作られたJavaScript環境です。
Node.js

Node.jsはECMAScriptこそ準拠しているものの、当然ながらDOM APIを持っていなかったり、そもそもブラウザ上ではなくコンソール上で動くなど、ブラウザ向けにコードを書いてきた人間には慣れない概念がいくつかあります。

とはいえ、私が本当に話したいのはNode.jsじゃないのでここでは詳しくは触れません。

歴史的な観点で重要な点だけを挙げていきます。

  • Node.jsが流行った(流行っている)
  • Node.jsはCommonJSモジュールを利用できる

この2点です。

そうです。今まで散々CommonJSモジュールと連呼していたのは、
Node.jsがCommonJSモジュールを流行らせて、それが巷に出回っているから
という背景があるのです。 :flushed:

CommonJSモジュールをWebに適用する

勘の良い人は察しがつくかと思いますが、CommonJSモジュールをWebブラウザ向けに適用するには、

Node.jsを使います。 (※基本的にはです)

Node.jsを使わない方法も理論上は可能なのですが、この手のを作る開発層がJSエンジニアに偏っている性質上仕方ないですね。 :sweat_smile:

ちなみにWebブラウザ向けに変換するためのモジュールはいくつか存在しますが、一番有名なのは :sparkles: Browserify :sparkles: です。(これについては後で少し説明を入れます)

以上をまとめると、CommonJSモジュールを取り巻く運用方法は以下の図のとおりです。
CommonJSモジュールの扱い方まとめ.png

特に注目していただきたいのは、
requireするところまでの手順は、Node.js向けにおいても、Webブラウザ向けにおいても共通である
ということです。

Webブラウザ向けには、その先で変換処理を挟む一手間が必要。
たったこれだけのことなのです :smile:

Browserifyとは何か?

先程から何度か説明していますので、なんとなくわかると思いますが、CommonJSモジュール記述されたコードを、ブラウザでも動作するように変換するツールです。

(因みに今回のセクションでは、Node.js及びBrowserifyの導入方法については話しません。)

例えば、先のmyjquery.jsをrequireしたinput.jsを作成したとしましょう。

input.js
var jQuery = require("./myjquery.js");

var text = jQuery({
    name: "がお",
    age: 18,
});

console.log(text); // ?name=%E3%81%8C%E3%81%8A&age=18

これを、Browserifyを用いて、変換すると以下の様なコードになります。

output.js
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var jQuery = require("./myjquery.js");

var text = jQuery({
    name: "がお",
    age: 18,
});

console.log(text); // ?name=%E3%81%8C%E3%81%8A&age=18
},{"./myjquery.js":2}],2:[function(require,module,exports){
/* windowオブジェクトに直接与えるのをやめる
window.jQuery = jQuery;
window.$ = jQuery;
*/

// 代わりにmodule.exportsに与える
module.exports = jQuery;

// 日本語の文字列をクエリストリングに変換するモジュール
// Japanese-Query-String略してjQuery
function jQuery(obj){
    var keys = Object.keys(obj);
    if(!keys.length) return "";

    return "?" + keys.map(function(key){
        return encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]);
    }).join("&");
}

},{}]},{},[1]);

人間がかろうじて処理を理解できるギリギリのレベルのコードが吐かれますね。 :dizzy:

ここで、前回Section1で説明したように、F12を押して開発者ツールを開いてみてください。
そして、試しにこのoutput.jsのコードをコピペして、コンソールウィンドウ上で実行してみてください。

ブラウザ上でもエラーを吐かずに実行できるかと思います。 :sparkles:
そして「"?name=%E3%81%8C%E3%81%8A&age=18"」という文字列を、コンソールウィンドウに出力するかと思います。

このように、CommonJSモジュールは、 :sparkles: Browserify :sparkles: を用いて機械的に変換処理を行えば、ブラウザ上でも使用することが出来るのです :heart: (めでたしめでたし)


というわけで、Section2についての内容はここまでとなります。

まだ実際に変換する方法こそ習得してないものの、この記事を読むだけで比較的最近のモジュールの使い方を概念的に理解できたかと思います。。。!

Section3では、Node.jsとBrowserifyの具体的な導入方法について見ていきたいと思います。

次の話はこちらっ
旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section3 ~Browserifyをマスターしよう~