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.json
1つのオプションは、パッケージを作成して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
.npmrc
npm-debug.log
これらのデフォルトを除き、ドットファイル(名前がドットで始まるファイル)は含まれます。
次のファイルは決して除外されません。
package.json
README.md
とそのバリアントCHANGELOG
とそのバリアントLICENSE
、LICENCE
npmドキュメントには、公開時に何が含まれ、何が除外されるかについて詳細が記載されています。
パッケージをアップロードする前に、いくつかのことを確認できます。
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
に次のように変換できます。
-Content $PSCommandPath | Select-Object -Skip 3 | node --input-type=module - $args
Get
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 PATH
Windowsでは、コマンドシェルとPowerShellのデフォルトの環境変数を、(永続的に)設定アプリで設定できます。「変数」を検索してください。