Repository navigation
ES Module loading with abolute path fails on windows #31710
Description
Activity
You'd need to write
file:///c:/x/y/zinstead ofc:/x/y/zReacted by jonerer, ExE Boss, Aidan Temple, Void, Wahaj Dar, Jan Ochwat, Walter van der Giessen, joinmouse, Khoa Bean, Owen Buckley and 17 moreReacted by Nijat Mursali, Taha Fakhran, Akshay, Nikolay Kuchumov, Ryosuke, Jackson Jones, nus-rick, Vitaly, Viktor Vincze, lillallol and 11 moreReacted by Pius Lucky, Wynn Slater, fabi.s, Ooker, reecebeck and Noam GaashAs per node.js documentation:
The specifier of an import statement is the string after the from keyword, e.g. 'path' in import { sep } from 'path'. Specifiers are also used in export from statements, and as the argument to an import() expression.
There are four types of specifiers:
-
Bare specifiers like 'some-package'. They refer to an entry point of a package by the package name.
-
Deep import specifiers like 'some-package/lib/shuffle.mjs'. They refer to a path within a package prefixed by the package name.
-
Relative specifiers like './startup.js' or '../config.mjs'. They refer to a path relative to the location of the importing file.
-
Absolute specifiers like 'file:///opt/nodejs/config.js'. They refer directly and explicitly to a full path.
Bare specifiers, and the bare specifier portion of deep import specifiers, are strings; but everything else in a specifier is a URL.
Only file: and data: URLs are supported. A specifier like 'https://example.com/app.js' may be supported by browsers but it is not supported in Node.js.
Specifiers may not begin with / or //. These are reserved for potential future use. The root of the current volume may be referenced via file:///.
Reacted by Feranmi Akinlade and Matthias-
@kiranpwr1260 thanks!
It works on mac os though. Maybe not the same way you posted, but it works according to expectation.
On macos, if you do this:
- type: module in package.json
- In index.js, put
import "/Users/<username>/prog/node/genast/other.mjs"- In other.mjs, put
console.log('wep') node index.js
Outputs, as expected,wep
I expected the same to be true on windows, hence the issue report :)
Thanks @devsnek as well!
In the issue I posted over on "jest" about this, they said you could userequire('url').pathToFileURL(specifier).hrefto turn a path into a "file url". But it seems a bit weird if this is required for windows but not macos (and presumably linux). A lot of the packages I depend on will probably not know about thisReacted by Kiran Pawar, Nikolay Kuchumov, Ronan McCarter and Noam GaashYeah, I think this is either a bug on macos & linux or windows.
Mac and Linux (alpine docker image, to be precise):
$ echo 'export default "hello!";' > file.mjs $ node -e 'import(require.resolve("./file.mjs")).then(console.log, console.error)' (node:28) ExperimentalWarning: The ESM module loader is experimental. [Module] { default: 'hello!' }
Windows:
$ echo 'export default "hello!";' > file.mjs $ node -e 'import(require.resolve("./file.mjs")).then(console.log, console.error)' (node:3036) ExperimentalWarning: The ESM module loader is experimental. Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only file and data URLs are supported by the default ESM loader at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:33:11) at Loader.resolve (internal/modules/esm/loader.js:85:40) at Loader.getModuleJob (internal/modules/esm/loader.js:188:40) at Loader.import (internal/modules/esm/loader.js:163:28) at importModuleDynamically (internal/modules/cjs/loader.js:1094:27) at exports.importModuleDynamicallyCallback (internal/process/esm_loader.js:37:14) at Object.<anonymous> (C:\Users\IEUser\script.js:1:16) at Module._compile (internal/modules/cjs/loader.js:1151:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10) at Module.load (internal/modules/cjs/loader.js:1000:32) { code: 'ERR_UNSUPPORTED_ESM_URL_SCHEME' }
With
url.pathToFileURLit works, so we can use that in Jest. I'd still consider this a bug in node, thoughReacted by jonerer, Jaid, Ronan McCarter, Dimitri POSTOLOV, Pierre Cavin and Mikhail Hozhythe issue is that
C:/is ambiguous with a URL. (likeHTTP:/) and we use URLs in the loader.Reacted by ExE Boss and David ChenFrom a user perspective it's really weird that absolute paths work for some OSes, but not all. Would it make sense to throw on absolute paths without
file://on mac and linux as well?Reacted by Bo Lingen, Ronan McCarter, Wynn Slater and Philipp Spiessyou should use https://nodejs.org/api/url.html#url_url_pathtofileurl_path
Reacted by Yury Kozlov, joão melo, Steven, Jaid, r5nt and Dmitry ArapovYeah, I've changed to use that in Jest now, but it's still odd to me that it behaves differently across OSes, even though
pathToFileURLfixes it. You have to actually test on windows to notice absolute paths doesn't work (in our case, we only tested windows on Node 12, while our test only runs on Node 13), then find about that API (followed by either fixing your own code, or reporting it to some library).Maybe the error message could include something about passing the file path provided through
pathToFileURL? Should hopefully mean less googling for people hitting itReacted by joão melo, Owen Buckley, Ronan McCarter and Wynn Slater- addedesmIssues and PRs related to the ECMAScript Modules implementation.Issues and PRs related to the ECMAScript Modules implementation.
on Feb 14, 2020 This is because
C:/path/to/module.jsgets parsed as:URL { scheme: "c:", host: null, path: "/path/to/module.js", }
And Node doesn’t know how to deal with a URL with a scheme of
c:.What you want is
file:///C:/path/to/module.js:URL { scheme: "file:", host: "", path: "/C:/path/to/module.js", }
Reacted by Weslley Araújo, Aaron Lin, Luis Alberto Fernández Abeleira, Alexandr Zhidovlenko, Ooker and NorLzYeah, I understand why it happens, I just disagree with the behavior that absolute paths are treated differently between platforms, and would prefer it to just accept either relative paths or absolute paths that has the
fileprotocol to avoid the foot gun of code working fine on one platform and not the other.That ship might have sailed though 🙂
Reacted by Marie Hoeger, Dariusz Kobuszewski, joão melo, Steven, pushkine, Jaid, Ren Hiyama, David Chen, Owen Buckley, Ronan McCarter and 7 more@SimenB it doesn't accept paths, it accept urls. like i said, the correct thing to do if you have a path is to use pathToFileURL.
Reacted by ExE Boss, Aidan Temple and n1xx1Reacted by jonerer, George Walker, joão melo, Jackson Jones, Jaid, Hu_Zijin, Ronan McCarter, Wynn Slater, talkohavy, Sander Altman and 1 moreAbsolute paths (either unix or windows style) aren't valid URLs, though.
$ node -p 'new URL("/Users/user/file.js").href' internal/url.js:243 throw new ERR_INVALID_URL(input); ^ TypeError [ERR_INVALID_URL]: Invalid URL: /Users/user/file.js
(same behavior in Chrome)
I 100% agree with passing the path through
url.pathToFileURLfirst (that's the fixed I've applied in Jest), but I think it should be required on all platforms, not just Windows.Reacted by Marie Hoeger, George Walker, joão melo, Steven, Ronan McCarter and Alex YangSo to summarize and clarify, ESM importing right now works like this:
- relative paths:
- works on mac/linux
- works on windows
- absolute paths:
- works on mac/linux
- doesn't work on windows
- "local file urls"
- works on mac/linux
- works on windows
This issue report is about the inconsistency regarding "absolute paths".
For my money, I would love to have absolute paths work on windows as well. It only makes sense that a function to include other files can actually handle paths. At least when running in a node.js environment.
I understand the difficulties differentiating between a windows absolute path and an url, since the drive letter could be confused with a URL scheme. And single-letter schemes seem to be valid according to this RFC: https://tools.ietf.org/html/rfc3986#section-3.1 . But I think it's safe to assume that introducing single-letter URL schemes in a world where windows exists is not likely to happen
Reacted by Wynn Slater and Noam Gaash- relative paths:
@SimenB they aren't valid absolute urls, but they are valid as relative urls (https://url.spec.whatwg.org/#path-absolute-url-string), which is why they're accepted in
import.I'm not sure how we could detect the intent of someone when they typed it, since that's all that separates an absolute path from a relative path absolute url string.
Reacted by Simen Bekkhus, ExE Boss and Steven19 remaining items
- added a commit that references this issue
on Jul 24, 2023 - added 2 commits that reference this issue
on Sep 15, 2023 - added 2 commits that reference this issue
on Nov 5, 2023 It would be nice to be able to load an absolute path... in this case I don't have a way to set the current working directory, and
file:///c:/...orfile://c:/...do not work. It ends up prepending the current path (which again I don't have control over) and attempting to load likec:\tmp\file:\c:\fornode file:///c:/tmp/shell/node_modules/...I just happen to be in c:\tmp when testing this, but that is not likely to be the path.- added a commit that references this issue
on Apr 25, 2025 - added 2 commits that reference this issue
on Jul 4, 2025 - added a commit that references this issue
on Sep 10, 2025
What steps will reproduce the bug?
C:\prog\node\genast(as expected!)
It errors out with:
internal/modules/run_main.js:54
internalBinding('errors').triggerUncaughtException(
^
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only file and data URLs are supported by the default ESM loader
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:33:11)
at Loader.resolve (internal/modules/esm/loader.js:85:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:188:40)
at ModuleWrap. (internal/modules/esm/module_job.js:42:40)
at link (internal/modules/esm/module_job.js:41:36) {
code: 'ERR_UNSUPPORTED_ESM_URL_SCHEME'
}
Using forward- och backward slashes doesn't make a difference.
How often does it reproduce? Is there a required condition?
100%
What is the expected behavior?
Absolute includes should work the same as relative
What do you see instead?
Additional information
The same issue is seen with both static ES imports and dynamic ES imports.