This repository has been archived by the owner on Sep 2, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 44
ESM in .js files proposals #150
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
This PR is meant for discussion of responses to [this issue](https://github.com/nodejs/modules/issues/149). I’ve opened it as a PR so that we can start separate discussion threads under the various proposals, with the PR commenting-on-code interface keeping things organized. If people suggest new proposals, or revisions to current ones, I’ll update the PR accordingly. | ||
|
||
## Proposals | ||
|
||
### Do nothing | ||
|
||
Fixing the issue isn’t necessary; or the least-bad solution is worse than the issue itself. | ||
|
||
### `import.meta.require` | ||
|
||
Node ES `import` statements would not be able to import CommonJS JavaScript, just as browsers’ `import` statements can’t import CommonJS (or Script, a.k.a. non-ESM) JavaScript. If a user wants to import CommonJS, they need to use a different API. | ||
|
||
`import.meta.require` would be that new API, where `require` is our old friend from CommonJS (or as close as can be): `import.meta.require('./commonjs-file.js')`. | ||
|
||
### `import { importCommonJS } from 'module'` | ||
|
||
The same as `import.meta.require`, but not attaching a nonstandard method to `import.meta`. A new function on a Node core module would take two arguments: `import.meta`, to know the context of the importing module, and a string that’s the import specifier. For example: | ||
|
||
```js | ||
import { importCommonJS } from 'module'; | ||
importCommonJS(import.meta, './commonjs-file.js'); | ||
``` | ||
|
||
### `createRequireFunction` | ||
|
||
See [nodejs/node#19360](https://github.com/nodejs/node/pull/19360): | ||
|
||
```js | ||
import { createRequireFunction } from 'module'; | ||
const require = createRequireFunction(new URL(import.meta.url)); | ||
const x = require('./commonjs-file.js'); | ||
``` | ||
|
||
### `.cjs` file extension | ||
|
||
If a user wants to use the `import` statement to import a CommonJS file, that file needs to have a `.cjs` extension. This would allow intermixing of ESM `.js` files and CommonJS `.cjs` files, and potentially the use of CommonJS in browser environments. The `.cjs` extension would map to `application/node`, even on the Web. | ||
|
||
Since no `.cjs` files currently exist, this solution either requires renaming files or creating symlinks, e.g. `file.cjs` pointing at `file.js`. This solution definitively ends the ambiguity inherent in the `.js` extension: a user could use `.mjs` and `.cjs` exclusively and purge ambiguity from their filenames. | ||
|
||
### `package.json` flag to tell Node how to handle `.js` files | ||
|
||
This would be [nodejs/node/pull/18392](https://github.com/nodejs/node/pull/18392) or something like it. That PR proposes a `"mode": "esm"` to tell Node that all `.js` files within that `package.json`’s package boundary should be treated as ESM files. | ||
|
||
### `package.json` section to tell Node how to handle any file | ||
|
||
Similar to [nodejs/node/pull/18392](https://github.com/nodejs/node/pull/18392), but this would be a new section in `package.json` where the user can treat Node like a configurable webserver, and define the MIME types it should use to “serve” files that get imported: | ||
|
||
```json | ||
"mimes": { | ||
"js": "application/node", | ||
"mjs": "text/javascript", | ||
"json": "application/json" | ||
} | ||
``` | ||
|
||
This makes explicit the equivalence between Node’s deciding how to interpret files and webservers choosing MIME types for those files. A user could edit the above to set `js` to be `text/javascript`, for example, and/or add `cjs` for `application/node`. | ||
|
||
### [Unambiguous grammar](https://github.com/bmeck/UnambiguousJavaScriptGrammar), defaulting to ESM | ||
|
||
In the case of `import './mystery.js'`, the presence of references inside `mystery.js` to any of CommonJS’ magic globals—`require`, `module`, `exports`, `__filename`, `__dirname`—would trigger parsing as CommonJS, while otherwise the file is treated as ESM. | ||
|
||
### Unambiguous grammar, defaulting to CommonJS | ||
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
Sorry, something went wrong. |
||
|
||
The opposite of the above. The presence of an `import` or `export` statement inside `mystery.js` would cause Node to treat the file as ESM; otherwise, the file is assumed to be CommonJS. A `.js` file that doesn’t import or export anything would need an empty export, like `export {}`, to trigger detection as ESM. | ||
|
||
This doesn’t solve the issue presented in the [demo](https://github.com/GeoffreyBooth/browser-equivalence-edge-case), because files that are truly ambiguous (fail both the “looking for CommonJS” and the “looking for ESM” tests) are treated as ESM by browsers. | ||
|
||
### Import interoperability only via a loader, defaulting to ESM | ||
This comment was marked as off-topic.
Sorry, something went wrong. |
||
|
||
`import` statements in Node would by default only load JavaScript as ESM, matching browser behavior. If one wanted to have an `import` statement load CommonJS, a loader would need to be used. Various loaders could be written to enable importing CommonJS in various ways, either via the `import` statement or by adding a new API like `import.meta.require` or via some other method. | ||
|
||
### `import` statements work _only_ via loaders | ||
|
||
By default, `import` statements don’t function at all. The user must pass in a CLI flag to tell Node what loader to use for `import` statements: | ||
|
||
```bash | ||
node --loader=esm index.mjs | ||
``` | ||
|
||
The `esm` loader itself would be part of core, and would itself need to be written in CommonJS. | ||
|
||
### `"use module"` | ||
|
||
JavaScript files with a `"use module";` statement in the code, before any other statements, are treated as ESM. This was suggested to TC39 and rejected, but there’s nothing invalid about it. It has the advantage that it’s an author-defined signal that a file should be treated as ESM, like the `.mjs` extension; but because it’s in the source code itself, it’s compatible with build pipelines that don’t support `.mjs` or multiple extensions for JavaScript. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This comment was marked as off-topic.
Sorry, something went wrong.