はじめに
以前、サイトに掲載されているサンプルJavaプログラムをサクっと動かせるとしてjshellの記事を投稿しましたが、今回はTypeScriptをサクっと動かせるツールとしてts-nodeの記事を書きました。
ts-nodeをREPLモードとtsファイル指定モードで設定を変えないといけないのがちょっと今一な記事となっています。だれか改善策を知っている方がいたら是非ご教示下さい。
独り言:ts-nodeをREPLで使う人って少ないのかなー。
バージョン情報
- Nodejs:20.x
- TypeScript: 5.2.2
- ts-node: 10.9.1
環境構築
Node.js
Node.jsインストール
ここからインストーラダウンロードしてインストール。
npm initコマンドでpackage.jsonを作成する
npm init実行サンプル
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (ts-node) ts-node-verify-project
version: (1.0.0)
description: ts-node検証プロジェクト
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\ts-node\package.json:
{
"name": "ts-node-verify-project",
"version": "1.0.0",
"description": "ts-node検証プロジェクト",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes) yes
TypeScript
TypeScriptインストール
npm install -D typescript
TypeScript初期設定
tsc --init
「tsconfig.json」ファイルが作成されるので以下の箇所を修正する。
"target": "es2016"
"module": "commonjs"
// "moduleResolution": "node10"
を
"target": "es2022" // top-level awaitが使いたいから
"module": "Node16"
"moduleResolution": "Node16" // コメントアウトも外す
Node.jsのTypeScript用定義をインストール
npm install -D @types/node
ts-node
ts-nodeをインストール
npm install -D ts-node
ts-nodeをREPLモードで使ってみる
残念ならが設定をREPLモードとtsファイル指定モードで変える必要がある。
詳細は発生したトラブルを参照。
REPLはNode.jsのREPLがベースとなっているので、こちらも参照するとよいかもしれません。
設定
tsconfig.json
{
"compilerOptions": {
たくさんの設定
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
"allowImportingTsExtensions": true,
}
}
pakages.json
typeの設定は不要。
ただし、自作のモジュールをimportしない場合は、削除しなくてもOK。
自作のモジュールとは、別tsファイルでexportするモジュールのこと。
// "type": "module" // ある場合は削除
起動
./node_modules/.bin/ts-node --esm
// or ./node_modules/.bin/ts-node-esm
ts-nodeを使ってみる
では、さっそく。
./node_modules/.bin/ts-node --esm
>
">"と表示され、入力待ち状態になる。
基本
> let i = 1
undefined
> i
1
> console.log(i)
1
undefined
> .type i
let i: number
> function add(a: number, b:number): number { return a + b }
undefined
> console.log(add(1, 2))
3
undefined
入力するたびに「undefined」と表示されるが気にしない。
jshellみないに変数一覧とかのコマンドはなさそうだ。
Node.jsのREPLのコマンドと同じコマンドは使える。
と思ったら.clear
が正常に動作しない。。。これが原因か。。。.exitして再起動かな。
awaitも使えるよ
> await Promise.resolve(123)
123
fsなんかのNode.jsコアモジュールはimportしなくても使えた
> let csvData = fs.readFileSync('sample1.csv', 'utf-8');
undefined
> console.log(csvData)
id1,data1
id2,data2
undefined
>
fsのpromise版の場合は、こんな感じ。
> let csvData2 = await fs.promises.readFile('sample1.csv', 'utf-8');
undefined
> console.log(csvData2)
id1,data1
id2,data2
undefined
>
自作モジュールimportも出来るぞ
ディレクトリ構成
C:/ts-node
│ package-lock.json
│ package.json
│ sample1.csv
│ tsconfig.json
│
├─node_modules
│
└─src
main.ts
Classes.ts
Classes.ts
import { format as dateFormat } from 'date-fns';
export class Util {
static getToday(): string {
return dateFormat(new Date(), 'yyyy/MM/dd');
}
}
export class Person {
private _name : string;
constructor(name : string) {
this._name = name;
}
get name(): string {
return this._name;
}
}
> import { Person, Util } from './src/Classes.ts'
undefined
> let p = new Person('SP')
undefined
> p.name
'SP'
ライブラリも使えるぞ
今回は日付操作のライブラリ「date-fns」を使用。
date-fnsをインストール
npm i -D date-fns
> import { format as dateFormat } from 'date-fns'
undefined
> console.log(dateFormat(new Date(), 'yyyy/MM/dd'))
2023/11/15
undefined
終了
> .exit
または、Ctrl+Cを2度。
ts-node tsファイル起動モード
tsconfig.json
"module": "CommonJS"
などのts-nodeの設定は不要。
{
"compilerOptions": {
たくさんの設定
},
// ts-nodeの設定は不要。 // あったら削除する
// "ts-node": {
// "compilerOptions": {
// "module": "CommonJS"
// }
}
pakages.json
typeを"module"設定する。
"type": "module"
起動
node --no-warnings=ExperimentalWarning --loader ts-node/esm .\src\main.ts
main.ts
import { Person, Util } from './Classes.js';
// Personインスタンス生成
const person = new Person('AOKI');
console.log(person.name);
// 本日日付
console.log(Util.getToday());
発生したトラブル
その1
let i = 1
などのステートメントを入力すると
export {};
^^^^^^
Uncaught SyntaxError: Unexpected token 'export'
とエラーが表示される。
解決策:tsconfig.jsonに"module": "CommonJS"
を追加する。
解決ソース
{
"compilerOptions": {
たくさんの設定
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}
その2
その1の対応した状態でドキュメントに書かれている通り
./node_modules/.bin/ts-node --esm
で起動し自作モジュールをimportしてインスタンス生成するとエラーになる。
./node_modules/.bin/ts-node --esm
// or ./node_modules/.bin/ts-node-esm
> import { Person, Util } from './src/Classes.ts';
<repl>.ts:4:30 - error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
4 import { Person, Util } from './src/Classes.ts';
その8で解決。
その3
その2のimportをClasses**.ts**ではなく、Classes.jsにしたらimportは成功したがインスタンス生成でエラーになる。
// お、importは成功した!!
import { Person, Util } from './src/Classes.js'
undefined
// インスタンス生成
const p = new Person('SP')
node:internal/modules/cjs/loader:1051
throw err;
^
Uncaught Error: Cannot find module './src/Classes.js'
その4
起動方法を変えて、node --loader ts-node/esm
で起動したらワーニングが発生した。
node --loader ts-node/esm
Welcome to Node.js v20.9.0.
Type ".help" for more information.
> (node:34136) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
ワーニングが発生しないように対応。
node --no-warnings=ExperimentalWarning --loader ts-node/esm
Welcome to Node.js v20.9.0.
Type ".help" for more information.
追記
ワーニングを握りつぶさないで、ちゃんと指示通りに起動したほうがいいですね。
node --import "data:text/javascript,import { register } from 'node:module'; import { pathToFileURL } from 'node:url'; register('ts-node/esm', pathToFileURL('./'));" .\src\main.ts
その5
その4対応で自作モジュールをimportしたらエラーになった。
node --no-warnings=ExperimentalWarning --loader ts-node/esm
>import { Person, Util } from './src/Classes.ts'
import { Person, Util } from './src/Classes.ts'
^^^^^^
Uncaught:
SyntaxError: Cannot use import statement inside the Node.js REPL, alternatively use dynamic import: const { Person, Util } = await import("./src/Classes.ts");
エラーの指示通りimportの形式を変更したが別のエラーになった。
> const { Person, Util } = await import("./src/Classes.ts");
Uncaught ReferenceError: exports is not defined in ES module scope
その6
その4対応でtsファイル指定して起動するとエラーになった。
node --no-warnings=ExperimentalWarning --loader ts-node/esm src/main.ts
file:/src/main.ts:2
Object.defineProperty(exports, "__esModule", { value: true });
^
ReferenceError: exports is not defined in ES module scope
その7
その1の対応をやめて、その4の対応で起動し、import形式はconst await import形式したら
import、インスタンスの生成はうまくいったが、REPLがtsモードになっていない。。。
node --no-warnings=ExperimentalWarning --loader ts-node/esm
// import成功!
> const { Person, Util } = await import("./src/Classes.ts");
undefined
// インスタンス生成も成功!
> const p = new Person('SP')
undefined
> p.name
'SP'
// tsモードになっていない!!。だめだ。。
> let i:number = 1
let i:number = 1
^
Uncaught SyntaxError: Unexpected token ':'
その8
ts-nodeのREPLをtsモードで起動するには ./node_modules/.bin/ts-node --esm
で起動するしかなさそうだ。
しかしimportしたら、やはりエラーになった(その2と同じ)。
> import { Person, Util } from './src/Classes.ts'
<repl>.ts:4:30 - error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
4 import { Person, Util } from './src/Classes.ts'
これは、tsconfig.jsonにallowImportingTsExtensionsを追加したら、解決した。
{
"compilerOptions": {
たくさんの設定
},
"ts-node": {
"esm": true,
"compilerOptions": {
"module": "CommonJS", // これもあわせて「その1」解決策のために追加
"allowImportingTsExtensions": true,
}
}
}
その9
その8の対策をしたらimportはうまくいったが、インスタンス作成でエラーになった。
./node_modules/.bin/ts-node --esm
> import { Person, Util } from './src/Classes.ts'
undefined
> const p2 = new Person('SP')
Uncaught:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: C:\myfolder\ts-node\src\Classes.ts
package.jsonの"type": "module"
を削除したらうまくいった。
tsconfingで"module": "CommonJS"
設定しているからかなー。
トラブル解決策
残念ながらts-nodeをREPLモードで起動する場合とmain.tsファイルなどを指定して起動する場合で、設定を変える必要がある。
ts-node REPLモード
tsconfig.json
{
"compilerOptions": {
たくさんの設定
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS", // その1対策
"allowImportingTsExtensions": true, // その8対策
}
}
pakages.json
typeの設定は不要。
// "type": "module" // その9対策
起動
./node_modules/.bin/ts-node --esm
// or ./node_modules/.bin/ts-node-esm
ts-node tsファイル起動モード
tsconfig.json
"module": "CommonJS"
などのts-nodeの設定は不要。
{
"compilerOptions": {
たくさんの設定
},
// ts-nodeの設定は不要。 // その7で行った対策
// "ts-node": {
// "compilerOptions": {
// "module": "CommonJS"
// }
}
pakages.json
typeを"module"設定する。
"type": "module"
起動
node --no-warnings=ExperimentalWarning --loader ts-node/esm .\src\main.ts