Search code examples
node.jswindowsabsolute-path

Get ERR_UNSUPPORTED_ESM_URL_SCHEME or ENOENT or ERR_INVALID_URL_SCHEME when use absolute path


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?


Solution

  • 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.

    fileUrl npm