環境
- OS: Windows 10 Pro
- WSL2
- ディストリビューション: Ubuntu20.04
- node.js と npm はインストール済み
- node.js: v16.15.1
- npm: 8.12.1
- npm にて以下をグローバルインストール済み
- yarn
- typescript
前提
もともと node.js + express で javascript で動いてたものを typescript 化して、
node.js + express + typescript で動かそうとしています。
前提として以下をやってます。
require
をimport
形式に変換- commonjs ではなく EsModule を使用
簡略化すると以下のようなファイル構成です。
// index.ts
import express from 'express';
import cors from 'cors';
(省略)
import loginRouter from './routes/login';
const app = express()
app.use(cors())
app.use(express.static(path.join(__dirname, 'public')));
(省略)
app.use('/login', loginRouter);
(省略)
// routes/login.ts
import express from 'express';
const router = express.Router();
router.post('/', function(省略))
module.exports = router;
Module XXX has no default export. について
起きたこと
実行すると以下のようにエラーになりました。
$ yarn node --loader ts-node/esm index.ts
yarn node v1.22.19
(node:3909) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
/home/xxxx/index.ts:843
return new TSError(diagnosticText, diagnosticCodes, diagnostics);
^
TSError: ⨯ Unable to compile TypeScript:
index.ts:12:8 - error TS1192: Module '"/xxxx/routes/login"' has no default export.
12 import loginRouter from './routes/login';
直訳すると、「routes/login に default export がないからダメ」ってことなのですが、typescript どころか javascript の知識が全くないので、困りました。
解決方法
ググって、以下の解決方法を発見しました。
今回は commonjs ではなく EsModule を採用していたので、それが原因ぽいです。
routes/login.ts
を以下のように書き換えると、該当のエラーは消えました。(他のエラーが出ましたが)
// routes/login.ts
import express from 'express';
const router = express.Router();
router.post('/', function(省略))
- module.exports = router;
+ export default router;
Cannot find module について
起きたこと
上記の対応にて実行すると、今度は以下のようになりました。
$ yarn node --loader ts-node/esm index.ts
yarn node v1.22.19
(node:3964) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
/home/xxxx/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:366
throw new ERR_MODULE_NOT_FOUND(
^
CustomError: Cannot find module '/home/xxxx/routes/login' imported from /home/xxxx/index.ts
どうやら ./routes/login
が読み込み不可ということです。
あーなるほど、 ./routes/login.ts
にすればいいのか!ってことをやっても以下のようになりダメでした。
$ yarn node --loader ts-node/esm index.ts
yarn node v1.22.19
(node:4019) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
/home/xxxx/node_modules/ts-node/src/index.ts:843
return new TSError(diagnosticText, diagnosticCodes, diagnostics);
^
TSError: ⨯ Unable to compile TypeScript:
index.ts:12:25 - error TS2691: An import path cannot end with a '.ts' extension. Consider importing './routes/login.js' instead.
12 import loginRouter from './routes/login.ts';
なんでや・・・
解決方法
↑の記事に助けられました。
というか、コンパイルエラーにも書いてあるままなのですが、
.ts
ファイルであるにも関わらず、読み込み時には.js
を指定する必要があるとのことです。
正直これはキツイ・・・ソースコード上に嘘の内容を書かないといけないので、誤魔化しっぽい感じがして気持ち悪いです。なんやこの仕様・・・
index.ts
を以下のように書き換えると、該当のエラーは消えました。(他のエラーが出ましたが)
import express from 'express';
import cors from 'cors';
(省略)
- import loginRouter from './routes/login';
+ import loginRouter from './routes/login.js';
const app = express()
app.use(cors())
app.use(express.static(path.join(__dirname, 'public')));
(省略)
app.use('/login', loginRouter);
(省略)
__dirname is not defined in ES module scope について
起きたこと
上記の段階で、VSCode上はエラーが消えており、うまくいきそうでした。
しかし実行すると以下のようになりました。
$ yarn node --loader ts-node/esm index.ts
yarn node v1.22.19
(node:4152) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
ReferenceError: __dirname is not defined in ES module scope
解決方法
EsModule の場合、 __dirname
が使用できないみたいです。
↓が参考になりました。
http://var.blog.jp/archives/75679197.html
こちらにある通り、index.ts
を以下のように修正しました。
import express from 'express';
import cors from 'cors';
(省略)
import loginRouter from './routes/login';
import loginRouter from './routes/login.js';
+ const dirname = path.dirname(new URL(import.meta.url).pathname)
const app = express()
app.use(cors())
- app.use(express.static(path.join(__dirname, 'public')));
+ app.use(express.static(path.join(dirname, 'public')));
(省略)
app.use('/login', loginRouter);
(省略)
また、tsconfig.json
についても以下のように変更しました。(これをしないと、import.meta
が使えないっぽいです)
- "module": "es2015",
+ "module": "esnext",
以上で、エラーなく実行できるようになりました。
コメント