node.js + express + typescript で外部ファイル読み込みをしたら `Module XXX has no default export.` でハマった

Webアプリ開発

環境

  • 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 で動かそうとしています。

前提として以下をやってます。

  • requireimport 形式に変換
  • 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 の知識が全くないので、困りました。

解決方法

ググって、以下の解決方法を発見しました。

import、export、require | TypeScript入門『サバイバルTypeScript』
実務でアプリケーションを作る場合、複数のJavaScriptファイルを組み合わせて、ひとつのアプリケーションを成すことが多いです。いわゆるモジュール指向の開発です。ここではJavaScriptとTypeScriptでのモジュールと、モジュール同士を組み合わせるためのimport、export、requireについて説明...

今回は 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';

なんでや・・・

解決方法

TypeScriptのESMでハマる - くらげになりたい。
markdownからhtmlに変換したいなーと思い、 micromarkを使おうとしたら、 ES Modulesでかなりハマったので、その時の備忘録。 はじまり 今まで使っていたらちょっとしたツールのプロジェクトに、 micromarkをインストールしたら、こんなエラーが。。 $ npx ts-node sample....

↑の記事に助けられました。

というか、コンパイルエラーにも書いてあるままなのですが、
.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",

以上で、エラーなく実行できるようになりました。

コメント

タイトルとURLをコピーしました