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

Add import.meta.directory and import.meta.file properties #47756

Closed
SetTrend opened this issue Apr 28, 2023 · 29 comments
Closed

Add import.meta.directory and import.meta.file properties #47756

SetTrend opened this issue Apr 28, 2023 · 29 comments
Labels
esm Issues and PRs related to the ECMAScript Modules implementation. feature request Issues that request new features to be added to Node.js.

Comments

@SetTrend
Copy link

SetTrend commented Apr 28, 2023

What is the problem this feature will solve?

Currently, import.meta.url gets the current file's absolute file path in URL format. This format cannot be used with node:path for creating absolute file paths.

The proposed additional properties will provide the current file's file path details in a format that can be used with node:path.

What is the feature you are proposing to solve the problem?

import.meta should provide the following additional properties:

  1. directory
  2. file

Example

Given the following absolute file path (on Windows):

C:\user\repos\mine\main.js

import.meta would provide the following properties:

{
  "directory"  : "C:\\user\\repos\\mine\\"
  "file" : "main.js"
  "url"  : "file:///C:/user/repos/mine/main.js"
}
@SetTrend SetTrend added the feature request Issues that request new features to be added to Node.js. label Apr 28, 2023
@sindresorhus
Copy link

dir

Should be directory. Lets not abbreviate for the sake of it.

@Mesteery Mesteery added the esm Issues and PRs related to the ECMAScript Modules implementation. label Apr 30, 2023
@SetTrend SetTrend changed the title Add import.meta.dir and import.meta.file properties Add import.meta.directory and import.meta.file properties May 1, 2023
@SetTrend
Copy link
Author

SetTrend commented May 1, 2023

Absolutely. I amended OP and title accordingly.

@aduh95
Copy link
Contributor

aduh95 commented May 3, 2023

What would we use as values when the module is loaded from outside of the FS? E.g. from the network or from a custom loader.

@SetTrend
Copy link
Author

SetTrend commented May 4, 2023

One option may be to define both as : string | nothing.

@aduh95
Copy link
Contributor

aduh95 commented May 4, 2023

I’m not sure it’s worth adding something that you can rely upon because it may not be defined. Maybe we should ask why import.meta.url and the URL API doesn’t work for you today. You say in the OP that you’d like an absolute file path, why? What’s your use case, if I may ask?

@SetTrend
Copy link
Author

SetTrend commented May 4, 2023

Sure.

Webpack, for instance, requires an absolute path. This requirement brought me to the idea that something intrinsic, important is missing.

Multi-threaded applications or libraries might also prefer to work with absolute paths.

@aduh95
Copy link
Contributor

aduh95 commented May 4, 2023

That looks like an issue on Webpack side rather than Node.js. Did you try reaching out to them? In the mean time, you can use fileURLToPath(import.meta.url) when you need a path, but IMO not supporting file: URL should be considered a bug.
I suggest we close this issue for now, we can reopen it a compelling use case arises, and we find a compromise for non-FS modules, and other JS runtimes show interest. Wdyt?

@SetTrend
Copy link
Author

SetTrend commented May 4, 2023

I have a different perspective. Writing desktop applications or libraries will not be able to use import.meta.url. They need to be presented with a file system path.

@aduh95
Copy link
Contributor

aduh95 commented May 4, 2023

I have a different perspective. Writing desktop applications or libraries will not be able to use import.meta.url. They need to be presented with a file system path.

Why is that? What is it that you can do with an absolute path that you can’t do with a file: URL?

@SetTrend
Copy link
Author

SetTrend commented May 9, 2023

After further digging into the Webpack issue I'm facing, I'm not quite sure if it isn't a Webpack issue after all.

However, from my observation I noticed that the path library yields inconsistent output:

import * as path from 'node:path';

const url = import.meta.url;
const dir = path.dirname(import.meta.url);
const newdir = path.join(path.dirname(import.meta.url), 'dust');

console.log(`url: ${url}`);
console.log(`dir: ${dir}`);
console.log(`newdir: ${newdir}`);

… yields:

url: file:///D:/Documents/Visual%20Studio-Projekte/HTML/MyProject.js/webpack/webpack.config.mjs
dir: file:///D:/Documents/Visual%20Studio-Projekte/HTML/MyProject.js/webpack
newdir: file:\D:\Documents\Visual%20Studio-Projekte\HTML\MyProject.js\webpack\dust

path.join() converts the path format while path.dirname() doesn't. The two different formats lead to ambiguous results, thereby rendering manual path operations error prone.

@aduh95
Copy link
Contributor

aduh95 commented May 9, 2023

Using node:path on URL is never going to yield anything useful, it’s not a supported use case. You should only use paths with that, and use the URL APIs when dealing with URLs.

const newdir = new URL('./dust/', import.meta.url);

@SetTrend
Copy link
Author

SetTrend commented May 9, 2023

That's exactly what this issue is about: Providing path compatible data.

@aduh95
Copy link
Contributor

aduh95 commented May 9, 2023

But why? Shouldn’t you use URLs instead?

@SetTrend
Copy link
Author

SetTrend commented May 10, 2023

Here's a simple test program using the file system and the (failing) result from running it:

import * as path from 'node:path';
import * as fs from 'node:fs';

const filePath = path.join(path.dirname(import.meta.url), 'test.txt');
console.log(filePath);
fs.writeFileSync(filePath, 'My test.');
C:\Program Files\nodejs\node.exe .\main.mjs
file:\D:\Documents\Visual%20Studio-Projekte\NodeJs\Node-Test\test.txt
Process exited with code 1
Uncaught Error Error: ENOENT: no such file or directory, open 'file:\D:\Documents\Visual%20Studio-Projekte\NodeJs\Node-Test\test.txt'
    at openSync (fs:601:3)
    at writeFileSync (fs:2249:35)
    at <anonymous> (d:\Documents\Visual Studio-Projekte\NodeJs\Node-Test\main.mjs:6:4)
    at run (internal/modules/esm/module_job:194:25)
    --- await ---
    at processTicksAndRejections (internal/process/task_queues:95:5)
    --- await ---
    at runMainESM (internal/modules/run_main:55:21)
    at executeUserEntryPoint (internal/modules/run_main:78:5)
    at <anonymous> (internal/main/run_main_module:23:47)

Same for:

import * as fs from 'node:fs';

const filePath = new URL('Test.txt', import.meta.url).toString();
console.log(filePath);
fs.writeFileSync(filePath, 'My test.');
C:\Program Files\nodejs\node.exe .\main.mjs
file:///D:/Documents/Visual%20Studio-Projekte/NodeJs/Node-Test/Test.txt
Process exited with code 1
Uncaught Error Error: ENOENT: no such file or directory, open 'file:///D:/Documents/Visual%20Studio-Projekte/NodeJs/Node-Test/Test.txt'
    at openSync (fs:601:3)
    at writeFileSync (fs:2249:35)
    at <anonymous> (d:\Documents\Visual Studio-Projekte\NodeJs\Node-Test\main.mjs:5:4)
    at run (internal/modules/esm/module_job:194:25)
    --- await ---
    at processTicksAndRejections (internal/process/task_queues:95:5)
    --- await ---
    at runMainESM (internal/modules/run_main:55:21)
    at executeUserEntryPoint (internal/modules/run_main:78:5)
    at <anonymous> (internal/main/run_main_module:23:47)


Are you convinced now?

@targos
Copy link
Member

targos commented May 10, 2023

Here's the same program written using URL:

import * as fs from 'node:fs';

const fileUrl = new URL('test.txt', import.meta.url);
console.log(fileUrl);
fs.writeFileSync(fileUrl, 'My test.');

@SetTrend
Copy link
Author

SetTrend commented May 10, 2023

I added the URL example to my previous post. Fails just the same.

@targos
Copy link
Member

targos commented May 10, 2023

It fails because you convert it to a string. You need a URL instance.

@SetTrend
Copy link
Author

Yes, you are right. My machine is currently building, so I'm very slow in responding and amending my comments at the moment.

Yet, it doesn't look like Url provides path manipulation methods, like path does. Or am I missing something?

@aduh95
Copy link
Contributor

aduh95 commented May 10, 2023

URL APIs won’t provide path manipulation, only URL manipulations.
I’ve been using the URL APIs for quite some time myself, and it’s quite rare I need to translate them into a path (and when I do it’s to content a third party library that haven’t upgraded to support URL).

EDIT: I should point out that there are specific use case that are not supported (e.g. basename, extname). One might be tempted to considere those niche-enough that no one bothered to create an equivalent API, but it’s not too late.

@SetTrend
Copy link
Author

@aduh95: I comprehend and agree.

So, if path has become, well, obsolete, then why not deprecate the path library and add corresponding methods to url?

@aduh95
Copy link
Contributor

aduh95 commented May 12, 2023

node:path is still useful when dealing with paths, however it is not the correct API to deal with URLs. Sorry if I gave you the impression that it was obsolete, it's not (it's still quite useful on CJS modules for example).

why not […] add corresponding methods to url?

yeah why not, PRs welcome!

@SetTrend
Copy link
Author

SetTrend commented May 12, 2023

I could give it a try. Would you mind pointing me to the url lib source?

Never mind … found it.

@SetTrend
Copy link
Author

SetTrend commented May 12, 2023

I tried a preliminary solution on this issue, just to see if it works (PR: #47982).

Unfortunately, I'm not able to set-up a running development system in due time.

Would you mind checking if the changes I made are appropriate? I could add further path related methods then.

@tniessen
Copy link
Member

Why don't you use path.basename(url.fileURLToPath('file:///C:/user/repos/mine/main.js')) etc.? This will throw, as expected, when the URL is not actually a file URL -- which is good, because most applications should treat the pathname of non-file URLs as opaque. If you really want to parse paths of arbitrary URLs, you can use path.posix.basename(new URL('file:///C:/user/repos/mine/main.js').pathname) etc. -- but don't be surprised if the results are meaningless.

@SetTrend
Copy link
Author

SetTrend commented May 13, 2023

Slowly I feel being kidded with. I added these two properties because @aduh95 suggested to do so. I didn't feel like and I didn't express that I'd like to have these two properties at hand.

I regularly need path.join(), path.normalize(), path.resolve(), path.dirname(). These are the ones I (and probably others) regularly need when dealing with desktop applications.

That's not a niche. It's rather a quite daily business.

@tniessen
Copy link
Member

I regularly need path.join(), path.normalize(), path.resolve(), path.dirname(). These are the ones I (and probably others) regularly need when dealing with desktop applications.

That's not a niche. It's rather a quite daily business.

As multiple people have tried to explain throughout this discussion, that's a valid use case and it is already possible using, for example, fileURLToPath or URL.prototype.pathname.

This is widely used. If you take a look at the MDN page about import.meta, you will even find examples that do just that.


That's exactly what this issue is about: Providing path compatible data.

Your original feature request was the addition of new properties to import.meta. This is unlikely because it would break compatibility with the HTML Standard. As explained multiple times, it is easy to obtain a path from import.meta.url, assuming it is indeed a file URL.

In #47982, you added Fixes: #47756, so I assumed that PR was supposed to resolve this issue. Based on your last comment in this issue, that seems unlikely now.

Could you please clarify what exactly your feature request is at this point?

@aduh95
Copy link
Contributor

aduh95 commented May 14, 2023

Apologies if I led you into a dead end here @SetTrend, this was not my intent. I was thinking on adding them to node:url or to add support for URL instances in node:path APIs, as any change to the URL class would need to be added to the WHATWG spec. Maybe we should start by documenting how to use each API, and from there see which use case could be improved. Something like:

  • Access relative files:
    • in CommonJS:
      const path = require('node:path');
      const subdir = path.join(__dirname, 'subdir');
      const filePath = path.join(subdir, 'somefile.txt');
    • In ES module:
      const subdir = new URL('./subdir/', import.meta.url);
      const fileURL = new URL('./somefile.txt', subdir);
  • Get the file extension:
    • in CommonJS:
      const path = require('node:path');
      const filePath = path.join(__dirname, 'somefile.txt');
      console.log(path.extname(filePath)); // '.txt'
    • In ES module:
      import { posix as path } from 'node:path';
      const fileURL = new URL('./somefile.txt', import.meta.url);
      console.log(path.extname(fileURL.pathname)); // '.txt'
  • etc.

And maybe we'll find that the current APIs are good enough as is, and it would help folks who are familiar with node:path migrate to the non-Node.js-specific URL API.

@SetTrend
Copy link
Author

SetTrend commented May 15, 2023

Thanks for clarifying.

Given that some programs store a number of variables for storing their data within a hierarchy, and given the user is able to set these variables, I still have the impression that URL will be cumbersome for dealing with those:

import URL from 'node:url';

let langId= ...;
let images = ...;
let packages = ...;
let libLocation = ...;

const newUrl = new URL(langId, new URL(images, new URL(packages, new URL(libLocation, import.meta.url))));

@aduh95
Copy link
Contributor

aduh95 commented May 16, 2023

If langId, images, packages, and libLocation are strings, here's how you would do it:

// No import is needed, URL is available as a global.
// If you want to import it, you can do this as such:
import { URL } from 'node:url';

let langId= ...;
let images = ...;
let packages = ...;
let libLocation = ...;

const newUrl = new URL(`./${libLocation}/${packages}/${images}/${langId}/`, import.meta.url);

More likely you would store the intermediate folder references as URL instances:

let langId= ...;

const libLocation = new URL('./lib/', import.meta.url);
const packages = new URL('./packages/', libLocation);
const images = new URL('./images/', packages);

const newUrl = new URL(`./${langId}/`, images);

All in all, it's not a perfect API but it's the most closest thing to perfect: it's standard. I encourage you to use it over node:path on ESM, and to request the libraries you are using to add support for it. It does take a bit of work to get familiar with it, I personally think it's worth it as that's knowledge that can be applied for any JS runtime, but also feel free to convert file URLs to paths and keep using the API you are used to if you prefer.

Anyways, I'm going to close this issue, as it's derived quite some bit from the original request. Feel free to continue the discussion and ask more questions though.

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. feature request Issues that request new features to be added to Node.js.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants