私は普段、書き捨てのバッチ処理を、よくNode.jsで書きます。そうしたコードに、ちょっとした処理を加えたい時に、わざわざnpmからモジュールをインストールするのは、ファイル数が多く、ファイルサイズも膨らむので、余り好みません。
また、UIが必要な際は、HTML5を使って、処理をJavaScriptで書きます。
そうした際に、Webブラウザ用の「script
」タグで読み込むコードと、Node.jsの「require
」で読み込むコードが、共通していた方がよいです。それも、必要な最小限の部品になっていた方が、何かと都合がよいです。
ここではそうした書き捨ての、Webブラウザ/Node.js両対応のコードを書くための、1つの手法を示します。
例として「hello world
」を出力する関数を作っています。1番重要なのは、コード中の「init
」関数です。詳細は「解説」をご覧下さい。
ファイル構成は以下の通りです。>>ダウンロード
'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)
」のようにしておけば、後ろの方で宣言しても、前の方でその関数を利用できます。そのため、こうした書き方にしています。
作成したライブラリの読み込み方の例は、「ドキュメントのサンプル」をご覧下さい。
// 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'
<!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>
'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());
});
});
html,
body {
margin: 0;
padding: 0;
}
textarea,
button {
box-sizing: border-box;
width: 100%;
vertical-align:bottom;
}
textarea {
height: 5em;
}
// モジュールの読み込み
const helloWorld = require('./helloWorld.js').helloWorld;
// 実行
console.log(helloWorld());