hello.mjsを直接実行するnpm publish: パッケージをnpmレジストリにアップロードする$PATHこの章では、Node.js ESMモジュールを介してシェルスクリプトを実装する方法を学びます。これを行うには、2つの一般的な方法があります。
次の2つのトピックについて、ある程度精通している必要があります。
Windowsは、JavaScriptで記述されたスタンドアロンのシェルスクリプトを実際にはサポートしていません。したがって、最初に、Unix用のファイル名拡張子付きのスタンドアロンスクリプトを作成する方法を見ていきます。その知識は、シェルスクリプトを含むパッケージの作成に役立ちます。後で、以下について学びます。
パッケージを介したシェルスクリプトのインストールは、§13「npmパッケージのインストールとbinスクリプトの実行」のトピックです。
パッケージ内になくても実行できるUnixシェルスクリプトにESMモジュールを変換してみましょう。原則として、ESMモジュールのファイル名拡張子は2つから選択できます。
.mjsファイルは常にESMモジュールとして解釈されます。
.jsファイルは、最も近いpackage.jsonに次のエントリがある場合にのみESMモジュールとして解釈されます。
"type": "module"ただし、スタンドアロンスクリプトを作成する場合は、package.jsonが存在することに頼ることはできません。したがって、ファイル名拡張子.mjsを使用する必要があります(後で回避策について説明します)。
次のファイルの名前はhello.mjsです。
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);このファイルはすでに実行できます。
node hello.mjs
hello.mjsをこのように実行できるようにするには、2つのことを行う必要があります。
./hello.mjsこれらのことは次のとおりです。
hello.mjsの先頭にハッシュバン行を追加する。hello.mjsを実行可能にする。Unixシェルスクリプトでは、最初の行はハッシュバンです。これは、シェルにファイルの実行方法を指示するメタデータです。たとえば、これはNode.jsスクリプトで最も一般的なハッシュバンです。
#!/usr/bin/env node
この行は、ハッシュ記号と感嘆符で始まるため、「ハッシュバン」という名前です。「シーバン」とも呼ばれることがよくあります。
行がハッシュで始まる場合、ほとんどのUnixシェル(sh、bash、zshなど)ではコメントです。したがって、ハッシュバンはこれらのシェルによって無視されます。Node.jsも無視しますが、最初の行である場合に限ります。
このハッシュバンを使用しないのはなぜですか?
#!/usr/bin/node
すべてのUnixで、そのパスにNode.jsバイナリがインストールされているわけではありません。では、このパスはどうですか?
#!node
悲しいかな、すべてのUnixが相対パスを許可しているわけではありません。そのため、絶対パスでenvを参照し、それを使用してnodeを実行します。
Unixのハッシュバンの詳細については、Alex Ewerlöfによる「Node.js shebang」を参照してください。
コマンドラインオプションなどの引数をNode.jsバイナリに渡したい場合はどうすればよいですか?
多くのUnixで機能する1つの解決策は、envにオプション-Sを使用することです。これにより、引数すべてをバイナリの単一の名前として解釈しなくなります。
#!/usr/bin/env -S node --disable-proto=throw
macOSでは、前のコマンドは-Sなしでも機能します。Linuxでは通常機能しません。
Windowsでテキストエディターを使用して、UnixまたはWindowsのいずれかでスクリプトとして実行する必要があるESMモジュールを作成する場合は、ハッシュバンを追加する必要があります。これを行うと、最初の行はWindowsの行末記号\r\nで終わります。
#!/usr/bin/env node\r\n
このようなハッシュバンを含むファイルをUnixで実行すると、次のエラーが発生します。
env: node\r: No such file or directory
つまり、envは実行可能ファイルの名前がnode\rであると考えています。これを修正する方法は2つあります。
まず、一部のエディターは、ファイルですでに使用されている行末記号を自動的にチェックし、それらを使用し続けます。たとえば、Visual Studio Codeでは、右下のステータスバーに現在の行末記号(「行末シーケンス」と呼びます)が表示されます。
\nの場合はLF(ラインフィード)\r\nの場合はCRLF(キャリッジリターン、ラインフィード)そのステータス情報をクリックして、行末記号を選択できます。
次に、Windowsで編集しないUnixの行末記号のみを含む最小限のファイルmy-script.mjsを作成できます。
#!/usr/bin/env node
import './main.mjs';シェルスクリプトになるには、hello.mjsはハッシュバンを持っていることに加えて、実行可能(ファイルのアクセス許可)である必要もあります。
chmod u+x hello.mjs
ファイルを実行可能にした(x)のは、作成したユーザー(u)に対してであり、全員に対してではないことに注意してください。
hello.mjsを直接実行するhello.mjsは実行可能になり、次のようになります。
#!/usr/bin/env node
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);したがって、次のように実行できます。
./hello.mjs
悲しいかな、nodeに任意の拡張子を持つファイルをESMモジュールとして解釈するように指示する方法はありません。そのため、拡張子.mjsを使用する必要があります。後で説明するように、回避策は可能ですが複雑です。
このセクションでは、シェルスクリプトを含むnpmパッケージを作成します。次に、そのようなパッケージをインストールして、そのスクリプトがシステム(UnixまたはWindows)のコマンドラインで使用できるようになる方法を調べます。
完成したパッケージはこちらで入手できます。
rauschma/demo-shell-scriptsとして。@rauschma/demo-shell-scriptsとして。これらのコマンドは、UnixとWindowsの両方で機能します。
mkdir demo-shell-scripts
cd demo-shell-scripts
npm init --yes
これで、次のファイルができました。
demo-shell-scripts/
package.json
package.json1つのオプションは、パッケージを作成してnpmレジストリに公開しないことです。それでも、(後で説明するように)そのようなパッケージをシステムにインストールできます。その場合、package.jsonは次のようになります。
{
"private": true,
"license": "UNLICENSED"
}説明
"UNLICENSED"は、他のユーザーがどのような条件でもパッケージを使用する権利を否定します。package.jsonパッケージをnpmレジストリに公開する場合は、package.jsonは次のようになります。
{
"name": "@rauschma/demo-shell-scripts",
"version": "1.0.0",
"license": "MIT"
}独自のパッケージの場合は、"name"の値を、自分に適したパッケージ名に置き換える必要があります。
グローバルに一意の名前。このような名前は、他のユーザーが名前を使用するのを妨げたくないため、重要なパッケージにのみ使用する必要があります。
または、スコープ付きの名前:パッケージを公開するには、npmアカウントが必要です(取得方法は後で説明します)。アカウントの名前は、パッケージ名のスコープとして使用できます。たとえば、アカウント名がjaneの場合は、次のパッケージ名を使用できます。
"name": "@jane/demo-shell-scripts"次に、スクリプトの1つで使用する依存関係をインストールします。パッケージlodash-es(LodashのESMバージョン)です。
npm install lodash-es
このコマンドは、
ディレクトリnode_modulesを作成します。
パッケージlodash-esをその中にインストールします。
次のプロパティをpackage.jsonに追加します。
"dependencies": {
"lodash-es": "^4.17.21"
}ファイルpackage-lock.jsonを作成します。
開発中にのみパッケージを使用する場合は、"dependencies"ではなく"devDependencies"に追加できます。npmは、パッケージのディレクトリ内でnpm installを実行した場合にのみインストールしますが、依存関係としてインストールした場合はインストールしません。ユニットテストライブラリは、典型的な開発依存関係です。
開発依存関係をインストールする方法は2つあります。
npm install some-packageを使用。npm install some-package --save-devを使用し、some-packageのエントリを"dependencies"から"devDependencies"に手動で移動できます。2番目の方法は、パッケージが依存関係であるか開発依存関係であるかの決定を簡単に延期できることを意味します。
readmeファイルと、シェルスクリプトである2つのモジュールhomedir.mjsとversions.mjsを追加しましょう。
demo-shell-scripts/
package.json
package-lock.json
README.md
src/
homedir.mjs
versions.mjs
npmに2つのシェルスクリプトについて知らせ、インストールしてもらう必要があります。そのために、package.jsonのプロパティ"bin"を使用します。
"bin": {
"homedir": "./src/homedir.mjs",
"versions": "./src/versions.mjs"
}このパッケージをインストールすると、homedirとversionsという名前の2つのシェルスクリプトが利用可能になります。
シェルスクリプトにファイル拡張子.jsを使いたい場合もあるでしょう。その場合は、前のプロパティの代わりに、次の2つのプロパティをpackage.jsonに追加する必要があります。
"type": "module",
"bin": {
"homedir": "./src/homedir.js",
"versions": "./src/versions.js"
}最初のプロパティは、Node.jsに.jsファイルを(デフォルトのCommonJSモジュールではなく)ESMモジュールとして解釈するように指示します。
これがhomedir.mjsの内容です。
#!/usr/bin/env node
import {homedir} from 'node:os';
console.log('Homedir: ' + homedir());このモジュールは、Unixで使用する場合に必須の前述のハッシュバンで始まります。組み込みモジュールnode:osから関数homedir()をインポートし、それを呼び出して、結果をコンソール(つまり、標準出力)に出力します。
homedir.mjsは実行可能である必要はありません。npmは、インストール時に"bin"スクリプトの実行可能性を保証します(すぐに確認します)。
versions.mjsの内容は次のとおりです。
#!/usr/bin/env node
import {pick} from 'lodash-es';
console.log(
pick(process.versions, ['node', 'v8', 'unicode'])
);Lodashから関数pick()をインポートし、それを使ってオブジェクトprocess.versionsの3つのプロパティを表示します。
たとえば、次のようにしてhomedir.mjsを実行できます。
cd demo-shell-scripts/
node src/homedir.mjs
homedir.mjsのようなスクリプトは、npmが実行可能なシンボリックリンクを介してインストールするため、Unixで実行可能である必要はありません。
$PATHにリストされているディレクトリに追加されます。node_modules/.bin/に追加されます。Windowsにhomedir.mjsをインストールするために、npmは3つのファイルを作成します。
homedir.batは、nodeを使用してhomedir.mjsを実行するコマンドシェルスクリプトです。homedir.ps1はPowerShellで同じことを行います。homedirは、Cygwin、MinGW、MSYSで同じことを行います。npmはこれらのファイルをディレクトリに追加します。
%Path%にリストされているディレクトリに追加されます。node_modules/.bin/に追加されます。(以前に作成した)パッケージ@rauschma/demo-shell-scriptsをnpmに公開しましょう。npm publishを使用してパッケージをアップロードする前に、すべてが正しく構成されていることを確認する必要があります。
公開時にファイルを除外および含めるために、次のメカニズムが使用されます。
トップレベルのファイル.gitignoreにリストされているファイルは除外されます。
.gitignoreは、同じ形式のファイル.npmignoreでオーバーライドできます。package.jsonプロパティ"files"には、含めるファイルの名前を持つ配列が含まれています。つまり、除外するファイル(.npmignore内)と含めるファイルのどちらかをリストすることを選択できます。
一部のファイルとディレクトリはデフォルトで除外されます。たとえば、
node_modules.*.swp._*.DS_Store.git.gitignore.npmignore.npmrcnpm-debug.logこれらのデフォルトを除き、ドットファイル(名前がドットで始まるファイル)は含まれます。
次のファイルは決して除外されません。
package.jsonREADME.mdとそのバリアントCHANGELOGとそのバリアントLICENSE、LICENCEnpmドキュメントには、公開時に何が含まれ、何が除外されるかについて詳細が記載されています。
パッケージをアップロードする前に、いくつかのことを確認できます。
npm installのドライランは、何もアップロードせずにコマンドを実行します。
npm publish --dry-run
これにより、アップロードされるファイルとパッケージに関するいくつかの統計が表示されます。
また、npmレジストリに存在する場合と同様にパッケージのアーカイブを作成できます。
npm pack
このコマンドは、現在のディレクトリにファイルrauschma-demo-shell-scripts-1.0.0.tgzを作成します。
次の2つのコマンドのいずれかを使用して、パッケージをnpmレジストリに公開せずにグローバルにインストールできます。
npm link
npm install . -g
それが機能したかどうかを確認するために、新しいシェルを開き、2つのコマンドが利用可能かどうかを確認できます。また、グローバルにインストールされたすべてのパッケージをリストすることもできます。
npm ls -g
パッケージを依存関係としてインストールするには、(ディレクトリdemo-shell-scriptsにいる間に)次のコマンドを実行する必要があります。
cd ..
mkdir sibling-directory
cd sibling-directory
npm init --yes
npm install ../demo-shell-scripts
これで、たとえば、次の2つのコマンドのいずれかでhomedirを実行できます。
npx homedir
./node_modules/.bin/homedir
npm publish: パッケージをnpmレジストリにアップロードするパッケージをアップロードする前に、npmユーザーアカウントを作成する必要があります。npmドキュメントで、その方法が説明されています。
そして、最後にパッケージを公開できます。
npm publish --access public
デフォルトは
スコープなしパッケージの場合はpublic
スコープ付きパッケージの場合はrestrictedであるため、パブリックアクセスを指定する必要があります。この設定により、パッケージはプライベートになります。これは、主に企業で使用される有料のnpm機能であり、package.jsonの"private":trueとは異なります。npmの引用によると、「npmプライベートパッケージを使用すると、npmレジストリを使用して、あなたと選択されたコラボレーターのみが参照できるコードをホストできるため、プロジェクトでパブリックコードとプライベートコードを並行して管理および使用できます。」
オプション--accessは、最初に公開するときのみ効果があります。その後は省略でき、アクセスレベルを変更するにはnpm accessを使用する必要があります。
package.jsonのpublishConfig.accessを介して、最初のnpm publishのデフォルトを変更できます。
"publishConfig": {
"access": "public"
}特定のバージョンでパッケージをアップロードしたら、そのバージョンを再度使用することはできません。バージョンの3つのコンポーネントのいずれかを増やす必要があります。
major.minor.patch
majorを増やします。minorを増やします。patchを増やします。パッケージをアップロードするたびに実行したい手順がある場合があります。たとえば、
これは、package.jsonプロパティ"scripts"を介して自動的に実行できます。このプロパティは次のようになります。
"scripts": {
"build": "tsc",
"test": "mocha --ui qunit",
"dry": "npm publish --dry-run",
"prepublishOnly": "npm run test && npm run build"
}mochaはユニットテストライブラリです。tscはTypeScriptコンパイラです。
次のパッケージスクリプトは、npm publishの前に実行されます。
"prepare"が実行されます。npm packの前npm publishの前npm installの後"prepublishOnly"は、npm publishの前のみに実行されます。このトピックの詳細については、§15「npmパッケージスクリプトによるクロスプラットフォームタスクの実行」を参照してください。
Node.jsバイナリnodeは、ファイルがどの種類のモジュールであるかを検出するためにファイル拡張子を使用します。現在、それをオーバーライドするコマンドラインオプションはありません。そして、デフォルトはCommonJSであり、これは私たちが望むものではありません。
ただし、Node.jsを実行するための独自の実行可能ファイルを作成し、たとえばnode-esmと呼ぶことができます。次に、以前のスタンドアロンスクリプトhello.mjsを(拡張子なしで)helloに名前変更できます。ただし、最初の行を次のように変更する必要があります。
#!/usr/bin/env node-esm
以前は、envの引数はnodeでした。
これは、Andrea Giammarchiが提案したnode-esmの実装です。
#!/usr/bin/env sh
input_file=$1
shift
exec node --input-type=module - $@ < $input_fileこの実行可能ファイルは、標準入力を介してスクリプトの内容をnodeに送信します。コマンドラインオプション--input-type=moduleは、受信したテキストがESMモジュールであることをNode.jsに伝えます。
また、次のUnixシェル機能を使用します。
$1には、node-esmに渡された最初の引数(スクリプトのパス)が含まれています。shiftを使用して引数$0(node-esmのパス)を削除し、残りの引数を$@を介してnodeに渡します。execは、現在のプロセスをnodeが実行されるプロセスに置き換えます。これにより、スクリプトがnodeと同じコードで終了することが保証されます。-)は、Nodeの引数とスクリプトの引数を分離します。node-esmを使用する前に、実行可能であり、$PATHを介して見つけることができることを確認する必要があります。その方法は後で説明します。
ファイルに対してではなく、標準入力に対してのみモジュールタイプを指定できることがわかりました。したがって、Node.jsを使用して自分自身をESMモジュールとして実行するUnixシェルスクリプトhelloを作成できます(sambal.orgの作業に基づく)。
#!/bin/sh
':' // ; cat "$0" | node --input-type=module - $@ ; exit $?
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);ここで使用しているシェル機能のほとんどは、この章の冒頭で説明されています。$?には、実行された最後のシェルコマンドの終了コードが含まれています。これにより、helloがnodeと同じコードで終了できるようになります。
このスクリプトで使用される重要なトリックは、2行目がUnixシェルスクリプトコードとJavaScriptコードの両方であることです。
シェルスクリプトコードとして、引数を展開してリダイレクトを実行する以外は何もしない、引用符で囲まれたコマンド':'を実行します。その唯一の引数はパス//です。次に、現在のファイルの内容をnodeバイナリにパイプします。
JavaScriptコードとして、これは(式文として解釈され、何もしない)文字列':'の後にコメントが続きます。
JavaScriptからシェルコードを隠すことの追加の利点は、JavaScriptエディターが構文を処理および表示するときに混乱しないことです。
.mjsの構成WindowsでスタンドアロンNode.jsシェルスクリプトを作成する1つのオプションは、ファイル拡張子.mjsを使用し、それを持つファイルがnodeを介して実行されるように構成することです。残念ながら、これはコマンドシェルでのみ機能し、PowerShellでは機能しません。
もう1つの欠点は、その方法ではスクリプトに引数を渡すことができないことです。
>more args.mjs
console.log(process.argv);
>.\args.mjs one two
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\jane\\args.mjs'
]
>node args.mjs one two
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\jane\\args.mjs',
'one',
'two'
]
コマンドシェルでargs.mjsなどのファイルを直接実行するようにWindowsを構成するにはどうすればよいでしょうか?
ファイル関連付けは、シェルでファイル名を入力したときにファイルを開くアプリケーションを指定します。ファイル拡張子.mjsをNode.jsバイナリに関連付けると、シェルでESMモジュールを実行できます。その1つの方法は、Tim Fisherによる「Windowsでファイル関連付けを変更する方法」で説明されているように、設定アプリを使用することです。
さらに、変数%PATHEXT%に.MJSを追加すると、ESMモジュールを参照する際にファイル拡張子を省略することもできます。この環境変数は、設定アプリで「変数」を検索することで永続的に変更できます。
Windowsでは、ハッシュバンのようなメカニズムがないという課題に直面しています。したがって、Unixで拡張子のないファイルに使用したのと同様の回避策を使用する必要があります。つまり、Node.jsを介してJavaScriptコードを内部で実行するスクリプトを作成します。
コマンドシェルスクリプトのファイル拡張子は.batです。script.batという名前のスクリプトは、script.batまたはscriptのどちらかで実行できます。
hello.mjsをコマンドシェルスクリプトhello.batに変換すると、次のようになります。
:: /*
@echo off
more +5 %~f0 | node --input-type=module - %*
exit /b %errorlevel%
*/
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);このコードをファイルとしてnodeを介して実行するには、存在しない2つの機能が必要です。
したがって、ファイルのコンテンツをnodeにパイプするしかありません。また、次のコマンドシェル機能を使用します。
%~f0には、現在のスクリプトのフルパス(ファイル拡張子を含む)が含まれます。一方、%0には、スクリプトを呼び出すために使用されたコマンドが含まれます。したがって、前者のシェル変数を使用すると、helloまたはhello.batのどちらかでスクリプトを呼び出すことができます。%*には、コマンドの引数が含まれており、これらをnodeに渡します。%errorlevel%には、最後に実行されたコマンドの終了コードが含まれます。この値を使用して、nodeによって指定されたのと同じコードで終了します。前のセクションで使用したのと同様のトリックを使用して、hello.mjsをPowerShellスクリプトhello.ps1に次のように変換できます。
Get-Content $PSCommandPath | Select-Object -Skip 3 | node --input-type=module - $args
exit $LastExitCode
<#
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);
// #>このスクリプトは、次のどちらかで実行できます。
.\hello.ps1
.\hello
ただし、それを行う前に、PowerShellスクリプトを実行できるように実行ポリシーを設定する必要があります(実行ポリシーの詳細)。
Restrictedであり、スクリプトを実行できません。RemoteSignedでは、署名されていないローカルスクリプトを実行できます。ダウンロードしたスクリプトは署名されている必要があります。これはWindowsサーバーでのデフォルトです。次のコマンドを使用すると、ローカルスクリプトを実行できます。
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
npmパッケージpkgは、Node.jsパッケージを、Node.jsがインストールされていないシステムでも実行できるネイティブバイナリに変換します。次のプラットフォームをサポートしています:Linux、macOS、Windows。
ほとんどのシェルでは、ファイル名を入力する際にファイルを直接参照しなくても、その名前を持つファイルをいくつかのディレクトリで検索して実行できます。これらのディレクトリは通常、特別なシェル変数にリストされています。
$PATHを介してアクセスします。%Path%を介してアクセスします。$Env:PATHを介してアクセスします。PATH変数は2つの目的で必要です。
node-esmをインストールする場合。$PATHほとんどのUnixシェルには、コマンドを入力したときにシェルが実行可能ファイルを検索するすべてのパスをリストする変数$PATHがあります。その値は次のようになります。
$ echo $PATH
/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
次のコマンドはほとんどのシェルで動作し(ソース)、現在のシェルを終了するまで$PATHを変更します。
export PATH="$PATH:$HOME/bin"
2つのシェル変数のいずれかにスペースが含まれる場合に備えて、引用符が必要です。
$PATHを永続的に変更するUnixでは、$PATHの構成方法はシェルによって異なります。実行しているシェルは、次のようにして確認できます。
echo $0
MacOSはZshを使用しており、$PATHを永続的に構成するのに最適な場所は、スタートアップスクリプト$HOME/.zprofileです。 — このようになります。
path+=('/Library/TeX/texbin')
export PATHWindowsでは、コマンドシェルとPowerShellのデフォルトの環境変数を、(永続的に)設定アプリで設定できます。「変数」を検索してください。