Webブラウザ/Node.js 互換関数

Webブラウザを利用した、scriptタグでの読み込みと、Node.jsでのrequireでの読み込みの両方に対応する関数の、簡易的な作り方を紹介します。 2018-06-28

 私は普段、書き捨てのバッチ処理を、よくNode.jsで書きます。そうしたコードに、ちょっとした処理を加えたい時に、わざわざnpmからモジュールをインストールするのは、ファイル数が多く、ファイルサイズも膨らむので、余り好みません。

 また、UIが必要な際は、HTML5を使って、処理をJavaScriptで書きます。

 そうした際に、Webブラウザ用の「script」タグで読み込むコードと、Node.jsの「require」で読み込むコードが、共通していた方がよいです。それも、必要な最小限の部品になっていた方が、何かと都合がよいです。

 ここではそうした書き捨ての、Webブラウザ/Node.js両対応のコードを書くための、1つの手法を示します。

 例として「hello world」を出力する関数を作っています。1番重要なのは、コード中の「init」関数です。詳細は「解説」をご覧下さい。

 ファイル構成は以下の通りです。>>ダウンロード

サンプル動作

sample/helloWorld.js

'use strict';

(function() {
	const _t = init('com.crocro.util');

	// helloWorld | ハロー ワールド
	_t.helloWorld = function() {
		return 'hello world';
	};

	// 初期化用関数
	function init(p) {
		try {return module.exports = {}} catch(e) {}
		return ((o, p) => {
			p.split('.').forEach(k => o = o[k]||(o[k]={}));
			return o})(window, p);
	};
})();

解説

 ここで重要なのは「初期化用関数」と書いてある「init」関数です。

 「init」関数は、「const _t = init('com.crocro.util')」のように使います。


 Node.jsの場合は、「module.exports = {}」としたオブジェクトが、定数「_t」に格納されます。

 この処理の部分は「try {} catch(e) {}」文で囲っています。そもそも「module.exports」がなければ、この部分でエラーが発生して、処理を中断して後段のWebブラウザ用の処理に移行します。

 エラーが発生しなければ、「return」で「module.exports = {}」の結果、つまり「module.exports」に格納した空のオブジェクト「{}」を戻します。

 このオブジェクトのプロパティとして関数を追加していけば、「require」で読み込めるようになります。


 Webブラウザの場合は、「window.com.crocro.util」というオブジェクトが、定数「_t」に格納されます。

 既に、途中のプロパティが宣言されている場合は上書きせずに、プロパティがない場合は「{}」というオブジェクトを追加しながら、上記の階層のオブジェクトを用意して参照を戻します。

 以下、詳細に見ていきます。まずは引数の「'com.crocro.util'」を「split('.')」で、「['com', 'crocro', 'util']」という配列に分割します。次に「forEach(k => ~)」で、各要素を変数「k」として取り出しながら処理を行います。

 変数「k」を利用した処理は「o = o[k]||(o[k]={})」です。オブジェクト「o」に、変数「k」の名前のプロパティが存在していれば、「o = o[k]」の処理になります。

 存在していなければ、「o = (o[k]={})」の処理になり、変数「k」の名前のプロパティに、空のオブジェクト「{}」を格納したあと、変数「o」に代入します。

 この分岐は、ショートカット演算子「||」で行っています。「o[k]」が「undefined」の場合は、「||」の右側の処理が行われます。


 たとえば「const _t = init('com.crocro.util');」として、「_t.hoge = function() {};」とした場合は、以下のようなオブジェクトが「window」に追加されます。


window: {
	…
	…,
	com: {
		crocro: {
			util: {
				hoge: function() {}
			}
		}
	}
}

 このように、変数「_t」のプロパティとして関数を追加していけば、既存のライブラリと、名前のバッティングを避けることができます。


 この部分の処理を、「function init(p)」として、コードの後ろの方に置いているのは、コードの先頭には、実際に行う処理がある方が、見やすいからです。

 「function init(p)」のようにしておけば、後ろの方で宣言しても、前の方でその関数を利用できます。そのため、こうした書き方にしています。


 作成したライブラリの読み込み方の例は、「ドキュメントのサンプル」をご覧下さい。

helloWorld | ハロー ワールド

「hello world」という文字列を戻します。

@return 「hello world」という文字列を戻します。

サンプル

// Webブラウザ
const helloWorld = com.crocro.util.helloWorld;
console.log(helloWorld());
// 'hello world'

// Node.js
const helloWorld = require('./helloWorld.js').helloWorld;
console.log(helloWorld());
// 'hello world'

sample/index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Webブラウザ/Node.js 互換関数 - JavaScript実用サンプルコード解説付き</title>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
    </script>

    <script src="helloWorld.js"></script>
    <script src="main.js"></script>
    <link rel="stylesheet" href="main.css">
  </head>
  <body>
    <textarea id="src"></textarea>
    <button id="exec">実行</button>
    <textarea id="dst"></textarea>
  </body>
</html>

sample/main.js

'use strict';

$(function() {
	// 変数の初期化
	const $src  = $('#src');
	const $dst  = $('#dst');
	const $exec = $('#exec');
	const helloWorld = com.crocro.util.helloWorld;

	// 値のセット
	$src.val(String.raw`
const helloWorld = com.crocro.util.helloWorld;
console.log(helloWorld());
	`.trim());

	// コンソール ログのラップ
	const log = console.log;
	console.log = function() {
		for (let i = 0; i < arguments.length; i ++) {
			$dst.append(arguments[i] + ' ');
		}
		$dst.append('\n');
		log.apply(console, arguments);
	};

	// クリック時の処理
	$exec.click(() => {
		$dst.empty();
		eval($src.val());
	});
});

sample/main.css

html,
body {
	margin: 0;
	padding: 0;
}

textarea,
button {
	box-sizing: border-box;
	width: 100%;
	vertical-align:bottom;
}
textarea {
	height: 5em;
}

sample/node_sample.js

// モジュールの読み込み
const helloWorld = require('./helloWorld.js').helloWorld;

// 実行
console.log(helloWorld());

紹介

Steamでゲームをリリースした時の経験をマニュアル的にまとめた本です。
8bit風RTS「TinyWar」のアルゴリズムを、コード付きで解説した本です。
JavaScriptから手軽に扱える形態素解析器『kuromoji.js』を使い、日本語を分解して遊ぶ本です。
node.jsを使い、「Google Chrome」のユーザーデータを、自動処理でメンテナンスするプログラムを開発する本です。
HTML5でローカルアプリが作れるNW.jsで、同人ゲームを作るための基礎知識の本です。
シミュレーションRPG「TinySRPG」のアルゴリズムを、コード付きで解説した本です。
JavaScriptの実行時エラーを分類して、ワンライナーのソースコードとエラーメッセージを収録した本です。
禁止文字つきコードゴルフを1年以上出題して、その解答ノウハウをまとめた本です。
2011年の春ごろに、Javaで「NyARToolKit」互換のARマーカー認識プログラムを書いた時のレポートです。