📦

WindowsでNode.jsのimportにC:から始まるパスは使えない話

に公開

開発環境

  • OS: Windows11
  • tsx@4.19.4
  • TypeScript@5.8.3
  • discord.js@14.19.3
  • Node.js@22.16.0

経緯

discord.jsのガイド「Command handling」を見ながらコードを書いていて、個人的にCommonJSよりもES Modulesのほうが好きなので、ガイドの内容をES Modules形式に置き換えながら進めていたところ

import fs from "node:fs/promises";
import path from "node:path";
import url from "node:url";
import { Client, Collection, GatewayIntentBits } from "discord.js";

const fileName = url.fileURLToPath(import.meta.url);
const dirName = path.dirname(fileName);

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.commands = new Collection();

const foldersPath = path.join(dirName, "commands");
const commandFolders = await fs.readdir(foldersPath);

for await (const folder of commandFolders) {
  const commandsPath = path.join(foldersPath, folder);
  const commandFiles = (await fs.readdir(commandsPath)).filter((file) => file.endsWith(".ts"));
  for await (const file of commandFiles) {
    const filePath = path.join(commandsPath, file);
    const command = await import(filePath);
    // Set a new item in the Collection with the key as the command name and the value as the exported module
    if ("data" in command && "execute" in command) {
      client.commands.set(command.data.name, command);
    } else {
      console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
    }
  }
}

以下のようなエラーが出ました。

> pnpm tsx --env-file=.env .\src\discord.ts 

node:internal/modules/run_main:123
    triggerUncaughtException(
    ^
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'e:'
    at throwIfUnsupportedURLScheme (node:internal/modules/esm/load:209:11)
    at defaultLoad (node:internal/modules/esm/load:107:3)
    at nextLoad (node:internal/modules/esm/hooks:748:28)
    at load (file:///E:/prog/discord-user-group-visualyzer/node_modules/.pnpm/tsx@4.19.4/node_modules/tsx/dist/esm/index.mjs?1748769704314:2:1768)
    at nextLoad (node:internal/modules/esm/hooks:748:28)
    at Hooks.load (node:internal/modules/esm/hooks:385:26)
    at handleMessage (node:internal/modules/esm/worker:199:24)
    at Immediate.checkForMessages (node:internal/modules/esm/worker:141:28)
    at process.processImmediate (node:internal/timers:485:21) {
  code: 'ERR_UNSUPPORTED_ESM_URL_SCHEME'
}

Node.js v22.16.0

原因

原因はimportの引数にC:\hoge\形式のパスが入ってたこと。

  for await (const file of commandFiles) {
    const filePath = path.join(commandsPath, file);
    // filePathの中身: E:\prog\discord-user-group-visualyzer\src\commands\utility\createGraphImage.ts
    const command = await import(filePath);

Node.jsのドキュメントには
https://nodejs.org/docs/latest-v22.x/api/esm.html#file-urls:~:text=file%3A%2C node%3A%2C and data%3A

file:, node:, and data: URL schemes are supported.

と書いてある。
Windowsのパス表現C:←ドライブレターが悪さしてるっぽいのでfile:///C:/hoge/など形式に置き換える必要がある

対処法

そんなときはurl.pathToFileURLで変換してからimportする方法が良い

 import url from "node:url";

-     const command = await import(filePath);
+     const command = await import(url.pathToFileURL(filePath).href);

何も考えずにreqire("hoge")await import("hoge")とするのは良くない。しっかりと仕様を調べよう...

最後に

Google検索してもなかなか情報が出てこなかったので使命感という都合の良い感情に動かされて書いてみました。(私の勉強不足なだけでimport系にWindowsの絶対パスをそのまま渡してはいけないのはみんな知ってるのかも)
これは初めての記事になるとは...(誰かの役に立てば...嬉しいな.....)
記事書くの慣れていなさすぎて書いてるときにも、当時理解できなかった時に考えていたことが理解した今には分からない状態で「これ何がつまずいていたんだ?」と...書くのが難しかったです...コード書いてる時の思考をどこかに記録してみたりしたほうが良いのかなと考えてみたりね?
まあ、とにかく!この記事を読んでくれてありがとうございます!!
(もし間違いなどがありましたらぜひコメントに...優しく教えていただけると幸いです)

Discussion