私は普段、書き捨てのバッチ処理を、よく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());