Node.jsでESModulesを利用する
CommonJSとESModules
JavaScriptのサーバーサイドでの実行環境であるNode.jsはCommmonJS
と呼ばれるモジュールシステムを利用しています。
Node.js上でTypeScriptを用いて開発を行おうとするとき、TypeScriptはCommonjsではなくブラウザで動くJavaScriptで使われている
ESModules
を使用する必要があります。
Node.jsではv8からESModulesを使用できるようになっていますが、TypeScriptを使わないのであれば現状はCommonJSを使用するのが無難だと思います。
package.jsonの設定
まずESModulesを扱う場合は、package.jsonの"type"属性 を"module"に設定します。 CommonJSを使う場合は"commonjs"です。
{ "name": "server", "version": "1.0.0", "description": "", "main": "index.js", "type": "module", <- (以下略)
CommonJSの特有の機能をESModulesで使う
CommonJSでよく使われるメソッドのうち、ESModulesでは仕様上使えないものがあります。
その対応として、Node.jsではこれらを使うためのAPIを提供しています。
require
import module from "module"; const require = module.createRequire(import.meta.url);
__filename と __dirname
import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename);
ESModuleのファイルをインポートする
Node.js上のTypeScriptのファイルでTypeScriptのファイルを呼び出すとき、 import文では拡張子の .ts を省略して表記します。
// app.ts で foo.tsを呼び出す import {foo} from './foo'
しかし、Node.js上ではこれが上手くいかない。
Error: Cannot find module '/***/foo' imported from /***/app.ts (略)
TypeScriptコンパイラはimport文については拡張子の補完を行わず出力(ビルド)するが、
(Node.js上の)ESModulesは拡張子の補完機能がないため、該当ファイルを発見することができないのが原因だそうです。
(突っ込んだ理屈についての解説が下記リンクにあるが完全には理解できなかったorz)
対策としては、拡張子に .js を付けて呼び出すとうまく動作します。 ( ここで .ts を付けるとTypeScriptコンパイラに怒られる。)
// app.ts で foo.tsを呼び出す import {foo} from './foo.js'
TypeScriptのファイルを.jsを付けて呼び出すという不思議な記述になってしまったが、これが現状ではベターな解決方法のようです。
結論
Node.js上でTypeScriptはまだちょっとしんどい箇所が多いので、現時点ではCommonJSモジュールでJavaScriptで書くか、静的型付けがしたいなら他の言語で書いたほうが楽そうな気がします。
参考
Node.js Dual Packages (CommonJS/ES Modules) に対応した npm パッケージの開発
Node.js 13.2.0 で--experimental-modulesが外れたのでESMを試す