Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES Module loading with abolute path fails on windows #31710

Closed
jonerer opened this issue Feb 9, 2020 · 22 comments
Closed

ES Module loading with abolute path fails on windows #31710

jonerer opened this issue Feb 9, 2020 · 22 comments
Labels
esm Issues and PRs related to the ECMAScript Modules implementation.

Comments

@jonerer
Copy link

jonerer commented Feb 9, 2020

  • Version: v13.8.0
  • Platform: Windows 10 64-bit version 1809
  • Subsystem: "esm" maybe?

What steps will reproduce the bug?

  1. Create a new project in C:\prog\node\genast
  2. Add "type": "module" in package.json
  3. Make a "other.js" with some content, like
const thing = "hej"
export default thing
  1. Make "index.js" with the following:
import other from "./other.js"
console.log(other)
> node index.js
hej

(as expected!)

  1. Change index.js to:
import other from "C:\\prog\\node\\genast\\other.js"
console.log(other)

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.

@devsnek
Copy link
Member

devsnek commented Feb 10, 2020

You'd need to write file:///c:/x/y/z instead of c:/x/y/z

@kiranpwr1260
Copy link

As 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:///.

@jonerer
Copy link
Author

jonerer commented Feb 11, 2020

@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:

  1. type: module in package.json
  2. In index.js, put
import "/Users/<username>/prog/node/genast/other.mjs"
  1. In other.mjs, put console.log('wep')
  2. 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 use require('url').pathToFileURL(specifier).href to 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 this

@SimenB
Copy link
Member

SimenB commented Feb 11, 2020

Yeah, 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.pathToFileURL it works, so we can use that in Jest. I'd still consider this a bug in node, though

@devsnek
Copy link
Member

devsnek commented Feb 11, 2020

the issue is that C:/ is ambiguous with a URL. (like HTTP:/) and we use URLs in the loader.

@SimenB
Copy link
Member

SimenB commented Feb 11, 2020

From 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?

@devsnek
Copy link
Member

devsnek commented Feb 11, 2020

you should use https://nodejs.org/api/url.html#url_url_pathtofileurl_path

@SimenB
Copy link
Member

SimenB commented Feb 11, 2020

Yeah, I've changed to use that in Jest now, but it's still odd to me that it behaves differently across OSes, even though pathToFileURL fixes 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 it

@MylesBorins MylesBorins added the esm Issues and PRs related to the ECMAScript Modules implementation. label Feb 14, 2020
@ExE-Boss
Copy link
Contributor

This is because C:/path/to/module.js gets 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",
}

@SimenB
Copy link
Member

SimenB commented Feb 18, 2020

Yeah, 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 file protocol to avoid the foot gun of code working fine on one platform and not the other.

That ship might have sailed though 🙂

@devsnek
Copy link
Member

devsnek commented Feb 18, 2020

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

@SimenB
Copy link
Member

SimenB commented Feb 18, 2020

Absolute 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.pathToFileURL first (that's the fixed I've applied in Jest), but I think it should be required on all platforms, not just Windows.

@jonerer
Copy link
Author

jonerer commented Feb 18, 2020

So 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

@devsnek
Copy link
Member

devsnek commented Feb 18, 2020

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

@SimenB
Copy link
Member

SimenB commented Feb 18, 2020

Aha, gotcha! Didn't think about the case when it's a path with a separately provided base. Thanks for being patient with me 👍

$ node -p 'new URL("/Users/user/file.js", "http://example.com/some/path").href'
http://example.com/Users/user/file.js

So what essentially happens is import(specifier) => import(new URL(specifier, import.meta.url)) to resolve the absolute path? In that case I don't really have any ideas on how to best detect it 😅 I assume being stricter (i.e. only treating paths starting with ./ as relative and not /) is not an option? Would probably be bad for browser interop.

I think this should be highlighted in the docs somewhere, although I'm not entirely sure where. pathToFileURL is only mentioned in one place in the current ESM docs, and that's in an example transform loader. Maybe in the section talking about relative specifiers to import()? The 2 relative examples use ./, might be an idea to mention / will be treated as relative to the root of the FS, which breaks on Windows? While I now (finally) understand why it behaves the way it does, it feels like a footgun if you're migrating from require to import.

@devsnek
Copy link
Member

devsnek commented Feb 18, 2020

adding something to the docs seems reasonable to me.

@guybedford
Copy link
Contributor

As discussed, this behaviour is by design. A docs PR to help clarify further would be very welcome.

jonerer added a commit to jonerer/node that referenced this issue Mar 12, 2020
Update es module documentation in accordance to nodejs#31710 by providing a gotcha for windows users
jonerer pushed a commit to jonerer/node that referenced this issue Mar 15, 2020
Explains that absolute specifiers and absolute paths
are not the same, and mentions what to do about it.

Refs: nodejs#31710
eliot-akira added a commit to TangibleInc/roller that referenced this issue Jul 24, 2023
lordrip added a commit to lordrip/kaoto that referenced this issue Sep 15, 2023
For windows file path, `C:\` prefix are not supported as they
get confused with a regular URI.

The fix is to write the file path with `file://` protocol.

Fixes: KaotoIO#98

Related issue: nodejs/node#31710
lordrip added a commit to KaotoIO/kaoto that referenced this issue Sep 15, 2023
For windows file path, `C:\` prefix are not supported as they
get confused with a regular URI.

The fix is to write the file path with `file://` protocol.

Fixes: #98

Related issue: nodejs/node#31710
sheerlox added a commit to sheerlox/import-from-esm that referenced this issue Nov 5, 2023
sheerlox added a commit to sheerlox/import-from-esm that referenced this issue Nov 5, 2023
@d3x0r
Copy link
Contributor

d3x0r commented Nov 17, 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:/... or file://c:/... do not work. It ends up prepending the current path (which again I don't have control over) and attempting to load like c:\tmp\file:\c:\ for node 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
esm Issues and PRs related to the ECMAScript Modules implementation.
Projects
None yet
Development

No branches or pull requests