これは、なにをしたくて書いたもの?
ひとつ前に、こんなエントリーを書きました。
TypeScriptコードを直接実行したい(ts-node、esbuild-register、tsx) - CLOVER🍀
じゃあ、こういう用途で作成したTypeScriptファイルを、人が読めるようなJavaScriptファイルに変換したくなったらどうするのか…?
ということでちょっと遊んでみました。
TypeScriptはJavaScriptにトランスパイルされるもの、という事実はさておき。
きっかけ
そもそもお題が「そんなこと気にするの?」ですが、ふと調べてみるとTypeScriptをオンラインでJavaScriptに変換するサイトがありまして。
TypeScript to plain JavaScript
これ、どうやっているんだろう?と思って見てみるとBabel(@babel/plugin-transform-typescript)を使っていました。
https://github.com/ritz078/transform/blob/master/pages/api/typescript-to-javascript.ts
割とキレイなJavaScriptに変換されていたので、他の方法と比べてどうなんだろう?と思って試してみようと思ったのがきっかけです。
お題
使う対象は、以下とします。
esbuildは完全にオマケです。この中で型チェックを行うのはtscのみです。
いずれも、npmモジュールのインストールだけして、設定ファイルは作らないようにします。
多少、オプションで出力結果を調整するのは許容、にします…。
なお、結論を言うとtscでいいかな、と思いました…。
変換する対象は、以下の2パターンにします。
単一のファイル。
index.ts
/** * get message * @param word word * @returns message */ function getMessage(word: string): string { return `Hello ${word}!!`; } // call function const message: string = getMessage('TypeScript'); // print console.log(message);
ディレクトリ内の複数のファイル。
src/index.ts
// import import { getMessage } from './sub/func'; // call function const message: string = getMessage(`TypeScript`); // print console.log(message);
src/sub/func.ts
/** * get message * @param word word * @returns message */ export function getMessage(word: string): string { return `Hello ${word}!!`; }
なんとなく、コメントも入れています。このあたりを含めて、どうなるか見ていこうかなと。
環境
今回の環境は、こちら。
$ node --version v18.18.2 $ npm --version 9.8.1
tsc
最初はtscから。
インストール。
$ npm i -D typescript
バージョン。
"devDependencies": { "typescript": "^5.3.2" }
単一のファイルの変換。
$ npx tsc --target esnext --module commonjs index.ts
結果。
index.js
/** * get message * @param word word * @returns message */ function getMessage(word) { return `Hello ${word}!!`; } // call function const message = getMessage('TypeScript'); // print console.log(message);
実行。
$ node index.js Hello TypeScript!!
ディレクトリごと変換。tsconfig.json
を書かない状態だとできない感じだったので、find
+xargs
にしました。
$ find src -name '*.ts' | xargs npx tsc --target esnext --module commonjs --outDir dist
結果。
dist/index.js
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); // import const func_1 = require("./sub/func"); // call function const message = (0, func_1.getMessage)(`TypeScript`); // print console.log(message);
dist/sub/func.js
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMessage = void 0; /** * get message * @param word word * @returns message */ function getMessage(word) { return `Hello ${word}!!`; } exports.getMessage = getMessage;
実行。
$ node dist/index.js Hello TypeScript!!
コメントも残るんですね。ちょっと驚きました。
以降は、実行結果は省略します。
Babel CLI+@babel/plugin-transform-typescript
続いて、Babel CLI+@babel/plugin-transform-typescript。
インストール。
$ npm i -D @babel/core @babel/cli @babel/plugin-transform-typescript
バージョン。
"devDependencies": { "@babel/cli": "^7.23.4", "@babel/core": "^7.23.3", "@babel/plugin-transform-typescript": "^7.23.4" }
単一ファイルの変換。
$ npx babel --plugins @babel/plugin-transform-typescript index.ts --out-file index.js
結果。
index.js
/** * get message * @param word word * @returns message */ function getMessage(word) { return `Hello ${word}!!`; } // call function const message = getMessage('TypeScript'); // print console.log(message);
ディレクトリごと変換。
$ npx babel --plugins @babel/plugin-transform-typescript src --out-dir dist Successfully compiled 2 files with Babel (158ms).
dist/index.js
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var func_1 = require("./sub/func"); var message = (0, func_1.getMessage)("TypeScript");
dist/sub/func.js
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMessage = void 0; function getMessage(word) { return "Hello ".concat(word, "!!"); }
複数ファイルにすると、一気に挙動が変わりましたね。コメントが失われ、構文がES5相当になりました。
というか、単一ファイルの変換の方が特殊そうですね…。
esbuild
最後はオマケでesbuildです。esbuildはバンドラーなのでここに並べるのはちょっと違うと思いますが、個人的な興味で動かしてみました。
インストール。
$ npm i -D esbuild
バージョン。
"devDependencies": { "esbuild": "^0.19.7" }
単一ファイルの変換。
$ npx esbuild index.ts --bundle --platform=node --target=node18 --outfile=index.js index.js 132b ⚡ Done in 2ms
結果。
index.js
// index.ts function getMessage(word) { return `Hello ${word}!!`; } var message = getMessage("TypeScript"); console.log(message);
ディレクトリごと変換。といっても、1ファイルにまとめられますが。
$ npx esbuild ./src --bundle --platform=node --target=node18 --outfile=dist/index.js dist/index.js 156b ⚡ Done in 3ms
結果。
dist/index.js
// src/sub/func.ts function getMessage(word) { return `Hello ${word}!!`; } // src/index.ts var message = getMessage(`TypeScript`); console.log(message);
構文がES5になりますね…。
ちなみに、出力先をディレクトリにもできますが、その場合は出力結果が入力ディレクトリ(もしくはファイル)の名前になりました。
$ npx esbuild ./src --bundle --platform=node --target=node18 --outdir=dist dist/src.js 156b ⚡ Done in 3ms
あと、ちょっとハマったのはディレクトリを指定する場合は./
を最初につけないと失敗しますね。
$ npx esbuild src --bundle --platform=node --target=node18 --outfile=dist/index.js ✘ [ERROR] Could not resolve "src" Use the relative path "./src" to reference the file "src/index.ts". Without the leading "./", the path "src" is being interpreted as a package path instead. 1 error
おわりに
思ったはずみにちょっと試してみましたが、生成されたJavaScriptファイルに対してソースコードして向き合うなら、やっぱりふつうに
書いた方がいいな、と思いました…。
使ったとしても、tscでの変換でしょうね。
ちょっとした興味からのお題でした。