Skip to content

Commit

Permalink
module: drop support for extensionless main entry points in esm
Browse files Browse the repository at this point in the history
Backport-PR-URL: #32280
PR-URL: #31415
Backport-PR-URL: #32280
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
GeoffreyBooth authored and MylesBorins committed Apr 1, 2020
1 parent dca3d29 commit 4ee41c5
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 136 deletions.
47 changes: 20 additions & 27 deletions doc/api/esm.md
Expand Up @@ -36,9 +36,8 @@ ES module code:

* Files ending in `.mjs`.

* Files ending in `.js`, or extensionless files, when the nearest parent
`package.json` file contains a top-level field `"type"` with a value of
`"module"`.
* Files ending in `.js` when the nearest parent `package.json` file contains a
top-level field `"type"` with a value of `"module"`.

* Strings passed in as an argument to `--eval` or `--print`, or piped to
`node` via `STDIN`, with the flag `--input-type=module`.
Expand All @@ -53,18 +52,17 @@ or when referenced by `import` statements within ES module code:

* Files ending in `.cjs`.

* Files ending in `.js`, or extensionless files, when the nearest parent
`package.json` file contains a top-level field `"type"` with a value of
`"commonjs"`.
* Files ending in `.js` when the nearest parent `package.json` file contains a
top-level field `"type"` with a value of `"commonjs"`.

* Strings passed in as an argument to `--eval` or `--print`, or piped to
`node` via `STDIN`, with the flag `--input-type=commonjs`.

### `package.json` `"type"` field

Files ending with `.js` or lacking any extension will be loaded as ES modules
when the nearest parent `package.json` file contains a top-level field `"type"`
with a value of `"module"`.
Files ending with `.js` will be loaded as ES modules when the nearest parent
`package.json` file contains a top-level field `"type"` with a value of
`"module"`.

The nearest parent `package.json` is defined as the first `package.json` found
when searching in the current folder, that folder’s parent, and so on up
Expand All @@ -84,14 +82,12 @@ node --experimental-modules my-app.js # Runs as ES module
```

If the nearest parent `package.json` lacks a `"type"` field, or contains
`"type": "commonjs"`, extensionless and `.js` files are treated as CommonJS.
If the volume root is reached and no `package.json` is found,
Node.js defers to the default, a `package.json` with no `"type"`
field. "Extensionless" refers to file paths which do not contain
an extension as opposed to optionally dropping a file extension in a specifier.
`"type": "commonjs"`, `.js` files are treated as CommonJS. If the volume root is
reached and no `package.json` is found, Node.js defers to the default, a
`package.json` with no `"type"` field.

`import` statements of `.js` and extensionless files are treated as ES modules
if the nearest parent `package.json` contains `"type": "module"`.
`import` statements of `.js` files are treated as ES modules if the nearest
parent `package.json` contains `"type": "module"`.

```js
// my-app.js, part of the same example as above
Expand All @@ -109,14 +105,13 @@ as ES modules and `.cjs` files are always treated as CommonJS.

### Package Scope and File Extensions

A folder containing a `package.json` file, and all subfolders below that
folder down until the next folder containing another `package.json`, is
considered a _package scope_. The `"type"` field defines how `.js` and
extensionless files should be treated within a particular `package.json` file’s
package scope. Every package in a project’s `node_modules` folder contains its
own `package.json` file, so each project’s dependencies have their own package
scopes. A `package.json` lacking a `"type"` field is treated as if it contained
`"type": "commonjs"`.
A folder containing a `package.json` file, and all subfolders below that folder
down until the next folder containing another `package.json`, is considered a
_package scope_. The `"type"` field defines how `.js` files should be treated
within a particular `package.json` file’s package scope. Every package in a
project’s `node_modules` folder contains its own `package.json` file, so each
project’s dependencies have their own package scopes. A `package.json` lacking a
`"type"` field is treated as if it contained `"type": "commonjs"`.

The package scope applies not only to initial entry points (`node
--experimental-modules my-app.js`) but also to files referenced by `import`
Expand Down Expand Up @@ -1610,12 +1605,10 @@ _defaultEnv_ is the conditional environment name priority array,
> 1. If _url_ ends in _".cjs"_, then
> 1. Return _"commonjs"_.
> 1. If _pjson?.type_ exists and is _"module"_, then
> 1. If _url_ ends in _".js"_ or lacks a file extension, then
> 1. If _url_ ends in _".js"_, then
> 1. Return _"module"_.
> 1. Throw an _Unsupported File Extension_ error.
> 1. Otherwise,
> 1. If _url_ lacks a file extension, then
> 1. Return _"commonjs"_.
> 1. Throw an _Unsupported File Extension_ error.
**READ_PACKAGE_SCOPE**(_url_)
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/modules/esm/get_format.js
Expand Up @@ -55,7 +55,7 @@ function defaultGetFormat(url, context, defaultGetFormat) {
} else if (parsed.protocol === 'file:') {
const ext = extname(parsed.pathname);
let format;
if (ext === '.js' || ext === '') {
if (ext === '.js') {
format = getPackageType(parsed.href) === TYPE_MODULE ?
'module' : 'commonjs';
} else {
Expand Down
27 changes: 0 additions & 27 deletions test/es-module/test-esm-no-extension.js

This file was deleted.

81 changes: 0 additions & 81 deletions test/es-module/test-esm-unknown-main.js

This file was deleted.

36 changes: 36 additions & 0 deletions test/es-module/test-esm-unknown-or-no-extension.js
@@ -0,0 +1,36 @@
'use strict';

const common = require('../common');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const assert = require('assert');

// In a "type": "module" package scope, files with unknown extensions or no
// extensions should throw; both when used as a main entry point and also when
// referenced via `import`.

[
'/es-modules/package-type-module/noext-esm',
'/es-modules/package-type-module/imports-noext.mjs',
'/es-modules/package-type-module/extension.unknown',
'/es-modules/package-type-module/imports-unknownext.mjs',
].forEach((fixturePath) => {
const entry = fixtures.path(fixturePath);
const child = spawn(process.execPath, ['--experimental-modules', entry]);
let stdout = '';
let stderr = '';
child.stderr.setEncoding('utf8');
child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => {
stdout += data;
});
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', common.mustCall((code, signal) => {
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, '');
assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1);
}));
});

0 comments on commit 4ee41c5

Please sign in to comment.