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

import.meta.url is undefined when using stdin such as ... | node --input-type=module - #38714

Open
nettybun opened this issue May 18, 2021 · 16 comments · May be fixed by #42392
Open

import.meta.url is undefined when using stdin such as ... | node --input-type=module - #38714

nettybun opened this issue May 18, 2021 · 16 comments · May be fixed by #42392
Labels
esm Issues and PRs related to the ECMAScript Modules implementation.

Comments

@nettybun
Copy link

nettybun commented May 18, 2021

  • Version: 16.1.0
  • Platform: Linux
  • Subsystem:

What steps will reproduce the bug?

I write TypeScript which Node doesn't read so I write esbuild thefile.ts | node --input-type=module - to strip the TS typings and pass the ESM directly to Node. This works 💯👍

However, if the code uses import.meta.url such as const __dirname = path.dirname(fileURLToPath(import.meta.url)); this throws:

node:internal/process/esm_loader:74
    internalBinding('errors').triggerUncaughtException(
                              ^

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of URL. Received undefined
    at new NodeError (node:internal/errors:363:5)
    at fileURLToPath (node:internal/url:1372:11)
    at file:///home/today/_/work/acorn-macros/test/style.macro/[eval1]:12:32
    at ModuleJob.run (node:internal/modules/esm/module_job:175:25)
    at async Loader.eval (node:internal/modules/esm/loader:170:24)
    at async node:internal/process/execution:49:24
    at async loadESM (node:internal/process/esm_loader:68:5) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Because import.meta.url is undefined. Shouldn't it be "file:///home/today/_/work/acorn-macros/test/style.macro/[eval1]" since that's what's shown in the stacktrace?

How often does it reproduce? Is there a required condition?

Using import.meta.url when accepting a script via stdin.

> echo "console.log(import.meta.url)" | node --input-type=module
undefined

What is the expected behavior?

The url property is "file:///home/today/_/work/acorn-macros/test/style.macro/[eval1]"

> echo "console.log(import.meta.url)" | node --input-type=module
file:///the/real/path/[eval1]

As a workaround you can hack this in via:

if (import.meta.url === undefined) {
  try {
    throw new Error();
  } catch (e) {
    const url = /at (.+):\d+:\d+$/.exec(e.stack.split('\n')[1])[1];
    console.log('Reset to', url); // Logs "Reset to file:///home/today/_/work/acorn-macros/test/style.macro/[eval1]"
    import.meta.url = url;
  }
}
// ... now carry on as normal, no errors: 
const __dirname = path.dirname(fileURLToPath(import.meta.url));

What do you see instead?

Throws :(

Additional information

Thank you for maintaining a wonderful project and doing all the work to make ESM in Node a success! ✨

@Ayase-252 Ayase-252 added the esm Issues and PRs related to the ECMAScript Modules implementation. label May 18, 2021
@targos
Copy link
Member

targos commented May 18, 2021

@nodejs/modules

@nettybun
Copy link
Author

Is this marked as a bug or can it be closed as expected behaviour? Thank you!

@DerekNonGeneric
Copy link
Contributor

We should probably be pinging @nodejs/loaders from now on as most of the people from the old modules team have low bandwidth these days.

I too have quite a bit on my plate atm, but let's see if anyone can triage first.

Thanks for your patience!

@ljharb
Copy link
Member

ljharb commented May 20, 2021

This isn't related to loaders, though - it's likely an oversight in the --input-type implementation.

@bmeck
Copy link
Member

bmeck commented May 20, 2021

This actually was brought up in #36472 that we don't have a good URL to give these things.

@nettybun
Copy link
Author

Thanks for the updates! Also no worries about low bandwidth - same here 😅 It'll happen when it happens.

@bmeck do you mean it's not a good URL because it could conflict with the filesystem? I imagine people who were worried about the edgecase could stat the fs to check for the file if it starts with [eval

@bmeck
Copy link
Member

bmeck commented May 20, 2021

@heyheyhello I just mean that we haven't made a decision and consensus on what to call them; yes it can conflict with files and that is a bit troublesome but the bigger issue is if we give it a file: url it simply doesn't exist for fs operations to me at least, so passing import.meta.url around becomes a bit more error prone vs leaving it as undefined. For example, using app: or something would avoid the fs problem at least and the collision.

@zackschuster
Copy link

@bmeck what about a data: url? it seems to me like it maps the cleanest/most appropriately

@bmeck
Copy link
Member

bmeck commented May 22, 2021

@zackschuster data: has the potential to be forged so timing gets weird potentially as well as not being something that is easy to detect. More concretely these are the off the top of my head concerns:

  1. you have to base64 or URL encode the whole body, which is a lot more than just a small sentry value like app:input
  2. you could preemptively import so it evaluated prior to entry point evaluation due to being already cached, but this is already possible with file: entry points so this argument seems weak but real.
  3. checking if the module is from input (either CLI or STDIN) seems possible if we add some kind of field, but means that importing the data: URL form would collide and suddenly look like it was from input
  4. a sentinel would be fairly simple to detect (right now I guess this is possible via checking undefined) E.G. `if (import.meta.url === 'app:input') ...). IDK the use cases for doing so though.

So, while I think it would be somewhat simple, it is not very clean. We could also use a blob: which are coming to Node soon if we don't want to use the non-standards track app: scheme.

@nettybun
Copy link
Author

I worry that using an app:input would be difficult to determine the current process directory, which is what I'm trying to use import.meta.url for as a replacement for __dirname. Is this still possible in the proposed app: or data: layouts?

@devsnek
Copy link
Member

devsnek commented May 25, 2021

stdin isn't a location on the filesystem. i think using a data: url seems reasonable, and then we expect code to use process.cwd() if they're doing weird relative operations while also being run from stdin.

@bmeck
Copy link
Member

bmeck commented May 25, 2021

neither app: nor data: would resolve a directory. This kind of stuff is part of why __dirname doesn't directly map to ESM. Like @devsnek says, in general tooling can use process.cwd() since there isn't a directory related to modules created from bytes of memory.

@ilkkao
Copy link

ilkkao commented May 30, 2021

Could the interim fix be to return undefined instead of throwing.

@nettybun
Copy link
Author

nettybun commented May 31, 2021

@ilkkao import.meta.url doesn't throw but it's fileURLToPath() that does - is that what you mean?

@devsnek I know it's a "weird relative operation" but process.cwd() doesn't feel as safe as __dirname. I've seen __dirname used a lot when working with build tools like webpack/esbuild that need to specify a root project location like a dist/ folder in their config.

It feels more clear to place items to relative to eachother rather than relative to me (the directory my shell is parked in). Like saying "the bowls are in the cupboard above the cutlery drawer" instead of "the bowls are 45deg CW and 2 steps from you right now". It's possible that maybe both options are equally as fragile considering binaries and config files can (and do!) move around; moving webpack.config.js to webpack/base.config.js will break __dirname. Still, I think __dirname is a popular option right now.

Edit: I also just remembered that using npm run xyz will set the current working directory to the package.json directory because it spawns a new shell, which is nice and makes things a bit more consistent. Although I'd still want my scripts to have a if (process.cwd() !== packageRoot) throw "Must run with npm run" or similar...

@ilkkao
Copy link

ilkkao commented May 31, 2021

@heyheyhello right, I didn't read the description well enough. Just as an option, maybe root path file:/// could also hint "no path" in a primitive way?

nettybun added a commit to nettybun/acorn-macros that referenced this issue Jun 1, 2021
@bmeck bmeck linked a pull request Mar 18, 2022 that will close this issue
@bmeck
Copy link
Member

bmeck commented Mar 18, 2022

I've made a PR for this

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

Successfully merging a pull request may close this issue.

9 participants