npm パッケージをインストールしながら動く単一 Node.js スクリプトファイルを作る
何を言っているのかというと。
npm install
でパッケージをインストールして使いたいスクリプトを書いている- その環境で予め
npm install
コマンドを叩いておくのではなく、必要に応じて自動的にパッケージをローカルインストールしてほしい package.json
不要で動く仕組みにしたい- スクリプト実行後は
node_modules/
ディレクトリなどのゴミが残らないようにしたい
要するに Deno でスクリプトを書いた時のように、あたかも node_modules/
などが存在しないかのように動作する仕組みを考えてみた。
以下がそのコード全量。// Main
で区切ったところが任意の処理で、const requires
の中身が require()
したいパッケージ情報を書くところ。
#!/usr/bin/env node
// Requires
const requires = [
{ variableName: 'Downloader', packageName: 'nodejs-file-downloader', version: '4.10.6' }
];
(async () => {
let isNpmInstalled = true;
let isExistPackageJson = true;
const loadRequires = () => requires.forEach(requireInfo => globalThis[requireInfo.variableName] = require(requireInfo.packageName));
try {
loadRequires();
} catch(_cannotFindModuleError) {
isNpmInstalled = false;
try {
require('fs').statSync(require('path').resolve(__dirname, './package.json'));
} catch(_noEntryError) {
isExistPackageJson = false;
}
// npm Install
requires.forEach(requireInfo => require('child_process').execSync(`npm install --save ${requireInfo.packageName}@${requireInfo.version}`));
// Re-Require
loadRequires();
}
console.log('Start');
// Main --------------------------------------------------
const urls = [
'https://example.com/index.html'
];
for(const url of urls) {
try {
const downloader = new Downloader({ url, directory: '.' });
const { filePath, downloadStatus } = await downloader.download();
console.log(url, 'OK', { filePath, downloadStatus });
} catch(error) {
console.log(url, 'NG', error);
}
}
// Main ==================================================
// Remove Assets
try {
if(!isNpmInstalled ) require('fs').rmSync(require('path').resolve(__dirname, './node_modules'), { recursive: true });
if(!isExistPackageJson) require('fs').rmSync(require('path').resolve(__dirname, './package.json'));
} catch(error) {
console.log('Unknown Error', error);
}
console.log('Finished');
})();
やっていることは愚直で、require()
しようとしてエラーが出たら、npm install
コマンドを実行してから再度 require()
する、というモノ。
npm install
コマンドをいきなり実行すると、package.json
がなければ自動的に生成される仕組みになっているので、処理が終わったら必要に応じて node_modules/
と package.json
を削除している。
require()
した内容は globalThis
を利用して動的にグローバル変数として定義している。このやり方だと VSCode 内での型推論や入力補完が効かないのが難点か。
ちなみに内部で使っている nodejs-file-downloader
というパッケージは、wget
みたいなモノ。どうも wget
だと User-Agent の関係かうまく DL できないファイルがあって、このパッケージを通せばうまくダウンロードできた次第。
この書き捨てのスクリプトのために npm init
して npm install
して…というのが面倒臭かったので、単一の JS ファイルで実行できる仕組みが作れないかなーと思って、こんなオマジナイを生み出してみたワケ。おかげさまでこのファイルを $ node index.js
などと実行するだけで package.json
と node_modules/
は一瞬しか作られず、ゴミが残らず作業ができる。
いや、素直に Deno で書けよ、という時代ではある。w