When I run my code on Linux it works fine. But on Windows with the same node js version(18.18.0) I get errors.
I use modern ES imports and my package.json has string "type": "module"
.
I write discord bot and that is what I do:
const rootDir = dirname(fileURLToPath(import.meta.url));
const commandsPath = join(rootDir, 'src', 'commands');
console.log('BEFORE FILTER');
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith('.js'));
console.log('BEFORE LOOP');
for (const file of commandFiles) {
console.log('BEFORE JOIN');
const filePath = join(commandsPath, file);
console.log('BEFORE AWAIT');
const command = await import(filePath);
console.log('SUCCESS');
// etc...
So, if I run this code I get the logs BEFORE FILTER
, BEFORE LOOP
, BEFORE JOIN
, BEFORE AWAIT
and error
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 'c:'
I tried to change commandsPath
initialization:
const commandsPath = join('file:', rootDir, 'src', 'commands');
Runs with the log BEFORE FILTER
and ENOENT error with the correct path to commands directory
Error: ENOENT: no such file or directory, scandir 'file:\C:\Users\Correct\Path\To\src\commands'
If I try
const commandsPath = fileURLToPath(join('file:', rootDir, 'src', 'commands'));
BEFORE FILTER
, BEFORE LOOP
, BEFORE JOIN
, BEFORE AWAIT
and the first example ERR_UNSUPPORTED_ESM_URL_SCHEME
error
And finally, if I try
const commandsPath = fileURLToPath(join('file:', rootDir, 'src', 'commands'));
and
const command = await import(fileURLToPath(filePath));
BEFORE FILTER
, BEFORE LOOP
, BEFORE JOIN
, BEFORE AWAIT
and TypeError [ERR_INVALID_URL_SCHEME]: The URL must be of scheme file
What should I do?
Apologize ahead, I can't test it since I don't run on Windows, I give an answer from knowledge, hope I have it right.
To solve the issue, you need to fix the URL from 'file:\C:\Users\Correct\Path\To\src\commands'
.
To: file:///C:/Users/Correct/Path/To/src/commands
.
There are two differences, one is 3 /
instead of 1, the second is /
instead of \
.
This is a very stupid behavior of the API, but it is what it is.
To solve it, add \\
, and as you did, use pathToFileURL
to replace /
by \
.
If you want to use URL(path)
to have a full object, use URL(path).href
to get the serialized url.
At your original try:
const rootDir = dirname(fileURLToPath(import.meta.url));
const commandsPath = join(rootDir, 'src', 'commands');
console.log('BEFORE FILTER');
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith('.js'));
console.log('BEFORE LOOP');
for (const file of commandFiles) {
console.log('BEFORE JOIN');
const filePath = join(commandsPath, file);
console.log('BEFORE URL CONVERSION');
const fileURL = new pathToFileURL(`file://${filePath}`); // <- ** here **
console.log('BEFORE AWAIT');
const command = await import(fileURL);
console.log('SUCCESS');
// ... rest of your code
}
This try const commandsPath = join('file:', rootDir, 'src', 'commands');
should be const commandsPath = pathToFileURL(join('file:\\', rootDir, 'src', 'commands'));
.
Lastly, if you don't care having extra package, you can use the simplest option:
npm install file-url
import fileUrl from 'file-url';
const commandsPath = fileUrl('src/commands');
console.log('BEFORE FILTER');
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith('.js'));
console.log('BEFORE LOOP');
for (const file of commandFiles) {
console.log('BEFORE...');
const command = await import(fileUrl(file));
console.log('SUCCESS');
// etc...
fileUrl
has functionality of resolving path by itself. I'm not aware of the level of capabilities, but using the fileUrl cli tool I was able to get path from different locations in my machine using name only.