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 path helpers proposal #54

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions import-meta-path-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Helpers on `import.meta` for working with paths

## Background

A common user request from developers writing ESM code is easy access to file path information of the current module. Or put another way, they miss the `__dirname` and `__filename` local variables from CommonJS, that define the directory and full path of the current module. These values are distinct from `import.meta.url`, which is the URL of the current module. To illustrate:

- `import.meta.url` is a string like `file:///Users/Geoffrey/my-project/src/index.js`
- `__filename` is a string like `/Users/Geoffrey/my-project/src/index.js`
- `__dirname` is a string like `/Users/Geoffrey/my-project/src`

Since `__filename` and `__dirname` have been around [since Node.js 0.1.27](https://nodejs.org/api/modules.html#__dirname), released in 2010, they are very well known to Node.js developers and very broadly referenced by `npm` packages. It is common to find documentation suggesting passing `__dirname` into a function for serving the static assets of a folder, for example, or passing `__filename` into a function used for logging.

In Node.js, developers working with such path-based APIs need to jump through a few hoops. Node.js provides a helper `fileURLToPath` that developers can import from `node:url`, to enable code like `serveStaticAssets(dirname(fileURLToPath(import.meta.url)))`. A common complaint of Node.js developers is that this is too verbose, and a way to achieve the same with simpler code and without needing to import utility helpers would be preferable.

## Proposal

For runtimes that support filesystem-based modules, we propose adding two new optional properties to `import.meta`:

### `import.meta.filename`

A string containing the full absolute filesystem path to the current module, like CommonJS `__filename`. This is the same as `fileURLToPath(import.meta.url)` in Node.js. An example value would be `/Users/Geoffrey/my-project/src/index.js`.

The property will be absent for modules that are not loaded from the filesystem, such as modules whose `import.meta.url` begins with `http` or `data`.

### `import.meta.dirname`

A string containing the full absolute filesystem path of the folder containing the current module, like CommonJS `__dirname`. This is the same as `dirname(fileURLToPath(import.meta.url))` in Node.js. An example value would be `/Users/Geoffrey/my-project/src`.

The value should not have a trailing slash. This is consistent with CommonJS `__dirname`, so that it can be used as a drop-in replacement.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s also consistent with filesystems, where the trailing slash means inside the directory, and without the slash means the directory.


The value will be undefined for modules that are not loaded from the filesystem, such as modules whose `import.meta.url` begins with `http` or `data`.

## Notes

These properties would be optional, even if a runtime supports filesystem-based modules. This allows for runtimes where modules are _primarily_ run from HTTP / HTTPS URLs to avoid creating a portability hazard where these properties exist while developers are working locally but are undefined when the code is deployed.

This portability hazard is a primary objection to this proposal. We feel that on balance, the convenience of these properties outweighs the potential hazard; and that a different portability hazard exists without these helpers, in that developers will write code such as `fileURLToPath(import.meta.url)` that works locally but not over CDN similarly to how `import.meta.filename` would work locally but not over CDN.
Copy link

@pi0 pi0 Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really wish we have clarification and standards about Windows path normalization and consider forward slash possibility as standard once we have a chance with new spec or at least mentioned briefly to be under considration.

An example URL would be file:///C:/path/ and normalized today as C:\path with Node.js utils.

Modern Windows systems well support forward slashes (some history). Forward slashes make life much better in the Javascript ecosystem where as backslash is a escape character. upath and pathe are already being used by >18M users weekly.

(sorry for rambling in the comments, just wanted to trigger a discussion from somewhere)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same thought too at first, but now I’m not so sure. I think the existing behavior is because Node’s path module assembles the parts of the path, then does essentially parts.join(path.sep), and the path separator for Windows is a backslash. It could very well be that this is 13-year-old behavior for old versions of Windows that aren’t relevant anymore, I don’t know. I also wonder if it would be bad to be inconsistent within the same runtime: currently in Node, __filename and fileURLToPath(import.meta.url) return backslash-delimited paths in Windows, so having a forward-slash path in import.meta.filename might be surprising. As far as the spec is concerned, maybe this is best left as an implementation detail for hosts to determine; other runtimes without path-based APIs that use backslashes might be freer to standardize on forward slashes everywhere. Though I’m open to defining the slashes as needing to be forward slashes if those who know this topic better than me can confirm that it’s safe and wise.

Copy link

@pi0 pi0 Aug 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the references. I think it might be sensible for Node.js util which is inheriting legacy reasons for preserving backslashes (while I hope that would be something they might consider for the new import.meta.filename API for the sake of portability...).

Since this is a proposal, would it makes sense to at least mention the possibility of a "recommendation" to prefer forward slashes and increasing web/url compatibility? Especially with new runtimes adopting import.meta.filename props, they can do it in a non-breaking sense.

AFAIK bun is not windows supported yet, that would be a really good example of adoption and increasing compatibility with toolings. Today Nuxt, Vitest and dozens of packages in the ecosystem are doing this normalization in each place to just make sure windows is compatible... unofficially due to Node.js legacy behavior.


## Alternatives Considered

### `import.meta.__filename` or `import.meta.__dirname`

As the primary goal of this proposal is convenience, we feel that shorter names are preferable; and the `__` prefix is inconsistent with other `import.meta` properties.

### `import.meta.path` or `import.meta.dir`

Bun has shipped `import.meta.path`, an equivalent to the `import.meta.filename` proposed here; and `import.meta.dir`, an equivalent to the `import.meta.dirname` proposed here.

On the one hand, Bun’s names are shorter and `import.meta.path` is arguably more correct of a name for a value that refers to a full absolute path like `/Users/Geoffrey/my-project/src/index.js` rather than just the filename `index.js`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, all of path, pathname, filename, and filepath are open to interpretation as to what they refer to. All of them can be a synonym of dirname as defined in this document, or refer to the full absolute path to the file.


On the other hand, `__filename` and `__dirname` are _so_ well known by developers, being used by just about every Node.js developer who has written any CommonJS code over the past decade-plus, that preserving those names might aid in usability due to familiarity and consistency with documentation of existing ecosystem libraries.

We feel that on balance, the familiarity and legacy of `__filename` and `__dirname` outweighs the benefits of new names that are shorter and more technically correct.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.


### `import.meta.dirname` by itself, without `import.meta.filename`

There are many more use cases for `dirname` than for `filename`. It’s very common to want to reference files that are siblings to the current module; it’s less common to need to reference the current module itself. We also have `import.meta.url` as an URL-based reference to the current module, that can be converted to `import.meta.filename` easily enough.

However it feels incomplete to offer a helper only for the current module’s parent folder, and not also to the current module itself. This feels inconsistent with `import.meta.url`, and to the legacy of `__dirname` and `__filename`.

### Also `import.meta.file` or `import.meta.basename`

If we’re going to have `import.meta.dirname`, a helper for the file path of the containing folder, why not also a helper for the current module’s filename such as Bun’s `import.meta.file` (returning a string like `index.js`)? This would be equivalent to `basename(fileURLToPath(import.meta.url))`.

Along the lines of the previous section, it’s not common enough to refer to the current file _as a file path_ to warrant a helper on `import.meta`. The current module’s filename can easily be derived via helper utilities.

## Prior Art

- [Node.js `__dirname` and `__filename` documentation](https://nodejs.org/api/modules.html#__dirname)
- [Bun `import.meta.path` and `import.meta.dir`](https://bun.sh/docs/api/import-meta)
- [Node.js PR to add `import.meta.dirname` and `import.meta.filename`](https://github.com/nodejs/node/pull/48740)
- [Earlier Node.js PR to add `import.meta.__dirname` and `import.meta.__filename`](https://github.com/nodejs/node/pull/39147)

## References

- [WinterCG issue](https://github.com/wintercg/proposal-common-minimum-api/issues/50)