LoginSignup
9
10

More than 5 years have passed since last update.

JavaScript requireの無い環境でrequireを提供する

Last updated at Posted at 2017-06-04

元記事

以前こちらの記事で、requireで書ける node.js や browserify に対応し
かつ、素のHTMLでのscriptタグリンクでも動作するような
モジュールの書き方をまとめました。

JavaScriptの名前空間とモジュール分割方法 require(node.js/browserify) HTMLScriptタグ WSH フル対応 - Qiita

モジュールを作成する場合は、このようにしておいた方が、様々な環境用に提供できる汎用的なライブラリにできます。

ただ、これは呼び出し側では、HTMLのscriptタグを使う場合には、グローバル唯一の名前空間を使うという、やり方でした。

require のない環境でも require を使えるようにする

今回は require のない環境でも require 使えるようにして、モジュール側の汎用性だけではなく、モジュールを使用する側のコードの汎用性も高めます。

こうすることで、同一モジュールのバージョン違いのものを同時に使用して動作確認する、などの用途にも使えるようになります。
※モジュール側が少し変えなければいけない箇所はあると思いますが、比較的やりやすくなります。

概要

requireがある環境では、既存のrequire と moduleの仕組みを利用し、requireがない環境では、自作のrequireが動作するようにします。

自作の require関数では、require(文字列)という形式で、モジュール(ファイル)内の名前空間オブジェクトを呼び出せるようにします。

実装してしまえば、簡単なことですが、実装しないとなかなか理解できませんでした。

モジュール分割部分や名前空間の説明は、元記事でしているのでしません。
実際に動かしてみるといろいろ理解できると思います。

この記事のrequireはシンプルな実装なので、
node.js や browserify の require の仕組みの全てを網羅はしていませんが、いろいろ使える場面もあると思いますので、必要な方はご利用ください。

requireの自作

通常、require するために、module.export をすると思います。

main オブジェクトという名前空間のように使用するものがあったとして、

  module.exports = main;

とするのは、node.js や bowserify を使用している人にはおなじみですよね。

これを(少し行数がありますが)次のように置き換えます。

if (typeof module === 'undefined') {
  var requireList = requireList || {};
  var require = function (funcName) {
    for ( var item in requireList) {
      if (funcName === item) {
        if (requireList.hasOwnProperty(item)) {
          return requireList[item];
        }
      }
    }
  };

  requireList['main'] = main;
  requireList['mainModule.js'] = main;
  requireList['./mainModule.js'] = main;
} else {
  module.exports = main;
}

説明すると、module が定義されていない場合は、
requireListを作成して、そこに呼び出し名とともにモジュールを定義します。
require 関数も作成し、名前で登録されたモジュールを呼び出します。

グローバル名前空間では、requireと、requireListを汚してしまいます。requireListをグローバルに持たない方法がわかる人はコメント欄で教えてください。

モジュール側のコード

libA.js
libB.js
mainModule.js
を作成しています。
それぞれの依存関係は元記事を参考にしてください。

全てをrequire対応して動くようにしています。

libA.js
//nsはNameSpaceの省略

//名前空間ns1を定義
var ns1 = ns1 || {};
(function (global) {
  var _ = ns1;
  //上記までが名前空間の先頭定義

  //名前空間ns1_1を定義
  _.ns1_1 = ns1.ns1_1 || {};
  (function () {
    var _ = ns1.ns1_1;
    //名前空間の先頭定義終わり

    //private
    var privateValue01 = 1;
    var privateFunc01 = function () {
      return -2;
    };

    //public
    _.publicValueNum01 = 2;
    _.publicValueStr01 = 'A';
    _.publicFunc01 = function () {
      return -4;
    };

    //メソッド(public)を定義する
    _.plus = function (valueA, valueB) {
      return valueA + valueB + 
        (privateValue01*2) + privateFunc01() + 
        (_.publicValueNum01*2) + _.publicFunc01();
    };

  }()); //名前空間ns1_1の終わり

}(this)); //名前空間ns1の終わり

if (typeof module === 'undefined') {
  var requireList = requireList || {};
  var require = function (funcName) {
    for ( var item in requireList) {
      if (funcName === item) {
        if (requireList.hasOwnProperty(item)) {
          return requireList[item];
        }
      }
    }
  };

  requireList['./libA.js'] = ns1;
} else {
  module.exports = ns1;
}
libB.js
if (typeof module !== 'undefined') {
  var ns1 = require('./libA.js')
}

//名前空間ns1を定義
var ns1 = ns1 || {};
(function (global) {
  var _ = ns1;

  //名前空間ns1_1を定義
  _.ns1_1 = ns1.ns1_1 || {};
  (function () {
    var _ = ns1.ns1_1;

    //private
    var privateValue02 = 1;
    var privateFunc02 = function () {
      return -2;
    };

    //public
    _.publicValueNum02 = 2;
    _.publicValueStr02 = 'A';
    _.publicFunc02 = function () {
      return -4;
    };

    //メソッド(public)を定義する
    _.plusPlus = function (valueA, valueB) {
      return _.plus(valueA, valueB) + 
        _.plus(valueA, valueB) + 
        (privateValue02*2) + privateFunc02() +
        (_.publicValueNum02*2) + _.publicFunc02();
    };

  }());

}(this));

if (typeof module === 'undefined') {
  var requireList = requireList || {};
  var require = function (funcName) {
    for ( var item in requireList) {
      if (funcName === item) {
        if (requireList.hasOwnProperty(item)) {
          return requireList[item];
        }
      }
    }
  };

  requireList['./libB.js'] = ns1;
} else {
  module.exports = ns1;
}

mainModule.js
if (typeof module !== 'undefined') {
  var ns1 = require('./libB.js')
}

//alertのない環境(node.js)に対応するために実装
if (typeof alert === 'undefined') {
  alert = function (message) {
    console.log(message);
  };
}

//渡された2つの値が不一致ならメッセージを出す関数を用意する
//requireを使用しない場合、グローバルを汚染するが動作確認のためだけに使う。
function check(a, b) {
  if (a !== b) {
    alert('a:' + a + '\n' +
      'b:' + b );
  }
}

var main = function () {
  //libAから
  check(3, (ns1.ns1_1.plus(1, 2)));
  check(2, ns1.ns1_1.publicValueNum01);
  check('A', ns1.ns1_1.publicValueStr01);
  check(-4, ns1.ns1_1.publicFunc01());

  check('object', typeof ns1);                          //名前空間
  check('object', typeof ns1.ns1_1);                    //名前空間
  check('undefined', typeof ns1.ns1_1.privateValue01);  //private
  check('undefined', typeof ns1.ns1_1.privateFunc01);   //private
  check('number', typeof ns1.ns1_1.publicValueNum01);   //public
  check('string', typeof ns1.ns1_1.publicValueStr01);   //public
  check('function', typeof ns1.ns1_1.publicFunc01);     //public
  check('function', typeof ns1.ns1_1.plus);             //public

  //libBから
  check(6, (ns1.ns1_1.plusPlus(1, 2)));

//  alert('test finish テスト終了');
  return 'main finish';
};

if (typeof module === 'undefined') {
  var requireList = requireList || {};
  var require = function (funcName) {
    for ( var item in requireList) {
      if (funcName === item) {
        if (requireList.hasOwnProperty(item)) {
          return requireList[item];
        }
      }
    }
  };

  requireList['main'] = main;
  requireList['mainModule.js'] = main;
  requireList['./mainModule.js'] = main;
} else {
  module.exports = main;
}

呼び出し側のコード

HTMLのscriptタグリンクでの、require
node.js の require
browserify の require
WSH JScript のrequire
これらで動作確認をしています。

check関数が謎な方は、こちらを参照してください。
JavaScriptで(そしてどんな言語でも同じで)世界一簡単なテストフレームワークを作って使おう - Qiita

runHtml.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>

    <script src="./libA.js"></script>
    <script src="./libB.js"></script>
    <script src="./mainModule.js"></script>
    <script>

  var main = require('./mainModule.js');
  main();

  var ns1 = require('./libB.js');
  //libAから
  check(3, (ns1.ns1_1.plus(1, 2)));

  //libBから
  check(6, (ns1.ns1_1.plusPlus(1, 2)));

  var ns2 = require('./libA.js');

  //libAから
  check(3, (ns2.ns1_1.plus(1, 2)));

  //libBから
  check(6, (ns2.ns1_1.plusPlus(1, 2)));

  alert('test finish テスト終了');

    </script>
  </head>
  <body>
  </body>
</html>

runNodeJs.js

function check(a, b) {
  if (a !== b) {
    alert('a:' + a + '\n' +
      'b:' + b );
  }
}

  var main = require('./mainModule.js');
  main();

  var ns1 = require('./libB.js');
  //libAから
  check(3, (ns1.ns1_1.plus(1, 2)));
  //libBから
  check(6, (ns1.ns1_1.plusPlus(1, 2)));

  var ns2 = require('./libA.js');
  //libAから
  check(3, (ns2.ns1_1.plus(1, 2)));
  //libBから
  check(6, (ns2.ns1_1.plusPlus(1, 2)));

  alert('test finish テスト終了');

//コマンドは
//  node runNodeJs.js

runHTML_browserify.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>

    <script src="./build/build.js"></script>
    <script>

//browserifyコマンドは次の通り
//browserify -r ./mainModule.js:main -o ./build/build.js

  var main = require('main');
  main();

//browserify の場合は、1ファイルにまとまるため
//require の指定が複数はできないので
//下記は動作しないのでコメントアウトする

//  var ns1 = require('./libB.js');
//  //libAから
//  check(3, (ns1.ns1_1.plus(1, 2)));
//
//  //libBから
//  check(6, (ns1.ns1_1.plusPlus(1, 2)));
//
//  var ns2 = require('./libA.js');
//
//  //libAから
//  check(3, (ns2.ns1_1.plus(1, 2)));
//
//  //libBから
//  check(6, (ns2.ns1_1.plusPlus(1, 2)));

  alert('test finish テスト終了');

    </script>
  </head>
  <body>
  </body>
</html>

runWsh.wsf
<?xml version="1.0" encoding="UTF-8" ?>
<job>
    <script language="JavaScript" src="./libA.js"></script>
    <script language="JavaScript" src="./libB.js"></script>
    <script language="JavaScript" src="./mainModule.js"></script>

    <script language="JavaScript">
    <![CDATA[
//----------------------------------------

//wshではalertがないので定義する
function alert(messageText) {
    WScript.Echo(messageText);
}

  var main = require('main');
  main();

  var ns1 = require('./libB.js');
  //libAから
  check(3, (ns1.ns1_1.plus(1, 2)));

  //libBから
  check(6, (ns1.ns1_1.plusPlus(1, 2)));

  var ns2 = require('./libA.js');

  //libAから
  check(3, (ns2.ns1_1.plus(1, 2)));

  //libBから
  check(6, (ns2.ns1_1.plusPlus(1, 2)));

  alert('test finish テスト終了');

//----------------------------------------
    ]]>
    </script>
</job>

グローバル汚染を防ぐ

上記コードでは、まだ、グローバルに1つのライブラリ名をつかったり、mainもグローバル定義でしたが、

もう少し改良して、コードを無名関数で囲み、グローバル汚染なくし、require を使った読み出し方しか許可しない方法で、自作のライブラリに組み込みました。

stsLib.js/stslib_core.js at master · standard-software/stsLib.js

これで、node.js や browserify を使う環境の開発でも、使わない(使えない)環境の開発でも、ほぼ同じコードで動作させられるようになり、ライブラリバージョンが異なった場合の同時使用なども行えるものが作れます。

汎用モジュールの作成と使用の参考に、ご利用ください。

9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10