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

[v16.x backport] esm: add HTTPS imports and unflag JSON modules #42726

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 10 additions & 7 deletions doc/api/cli.md
Expand Up @@ -282,22 +282,24 @@ added:

Enable experimental `import.meta.resolve()` support.

### `--experimental-json-modules`
### `--experimental-loader=module`

<!-- YAML
added: v12.9.0
added: v9.0.0
Copy link
Member

Choose a reason for hiding this comment

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

are we sure this was added in node 9?

Running node v9.11.2 (npm v5.6.0)
node: bad option: --experimental-loader=module

Copy link
Contributor Author

@aduh95 aduh95 Apr 18, 2022

Choose a reason for hiding this comment

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

Don't know, could be wrong, this info is from #22104 and not related to this backport.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've opened #42778 to fix that on master.

-->

Enable experimental JSON support for the ES Module loader.
Specify the `module` of a custom experimental [ECMAScript module loader][].
`module` may be any string accepted as an [`import` specifier][].

### `--experimental-loader=module`
### `--experimental-network-imports`

<!-- YAML
added: v9.0.0
added: REPLACEME
-->

Specify the `module` of a custom experimental [ECMAScript module loader][].
`module` may be any string accepted as an [`import` specifier][].
> Stability: 1 - Experimental

Enable experimental support for the `https:` protocol in `import` specifiers.

### `--experimental-policy`

Expand Down Expand Up @@ -1542,6 +1544,7 @@ Node.js options that are allowed are:
* `--experimental-json-modules`
* `--experimental-loader`
* `--experimental-modules`
* `--experimental-network-imports`
* `--experimental-policy`
* `--experimental-specifier-resolution`
* `--experimental-top-level-await`
Expand Down
17 changes: 17 additions & 0 deletions doc/api/errors.md
Expand Up @@ -3105,6 +3105,23 @@ removed: v10.0.0

Used by the `Node-API` when `Constructor.prototype` is not an object.

<a id="ERR_NETWORK_IMPORT_BAD_RESPONSE"></a>

### `ERR_NETWORK_IMPORT_BAD_RESPONSE`

> Stability: 1 - Experimental

Response was received but was invalid when importing a module over the network.

<a id="ERR_NETWORK_IMPORT_DISALLOWED"></a>

### `ERR_NETWORK_IMPORT_DISALLOWED`

> Stability: 1 - Experimental

A network module attempted to load another module that it is not allowed to
load. Likely this restriction is for security reasons.

<a id="ERR_NO_LONGER_SUPPORTED"></a>

### `ERR_NO_LONGER_SUPPORTED`
Expand Down
110 changes: 71 additions & 39 deletions doc/api/esm.md
Expand Up @@ -471,22 +471,6 @@ These CommonJS variables are not available in ES modules.
`__filename` and `__dirname` use cases can be replicated via
[`import.meta.url`][].

#### No JSON Module Loading

JSON imports are still experimental and only supported via the
`--experimental-json-modules` flag.

Local JSON files can be loaded relative to `import.meta.url` with `fs` directly:

<!-- eslint-skip -->

```js
import { readFile } from 'fs/promises';
const json = JSON.parse(await readFile(new URL('./dat.json', import.meta.url)));
```

Alternatively `module.createRequire()` can be used.

#### No Native Module Loading

Native modules are not currently supported with ES module imports.
Expand Down Expand Up @@ -524,35 +508,19 @@ separate cache.

> Stability: 1 - Experimental

Currently importing JSON modules are only supported in the `commonjs` mode
and are loaded using the CJS loader. [WHATWG JSON modules specification][] are
still being standardized, and are experimentally supported by including the
additional flag `--experimental-json-modules` when running Node.js.

When the `--experimental-json-modules` flag is included, both the
`commonjs` and `module` mode use the new experimental JSON
loader. The imported JSON only exposes a `default`. There is no
support for named exports. A cache entry is created in the CommonJS
cache to avoid duplication. The same object is returned in
CommonJS if the JSON module has already been imported from the
same path.

Assuming an `index.mjs` with
JSON files can be referenced by `import`:

```js
import packageConfig from './package.json' assert { type: 'json' };
```

The `--experimental-json-modules` flag is needed for the module
to work.

```bash
node index.mjs # fails
node --experimental-json-modules index.mjs # works
```

The `assert { type: 'json' }` syntax is mandatory; see [Import Assertions][].

The imported JSON only exposes a `default` export. There is no support for named
exports. A cache entry is created in the CommonJS cache to avoid duplication.
The same object is returned in CommonJS if the JSON module has already been
imported from the same path.

<i id="esm_experimental_wasm_modules"></i>

## Wasm modules
Expand Down Expand Up @@ -628,6 +596,71 @@ spawn(execPath, [
});
```

## HTTPS and HTTP imports

> Stability: 1 - Experimental

Importing network based modules using `https:` and `http:` is supported under
the `--experimental-network-imports` flag. This allows web browser-like imports
to work in Node.js with a few differences due to application stability and
security concerns that are different when running in a privileged environment
instead of a browser sandbox.

### Imports are limited to HTTP/1

Automatic protocol negotiation for HTTP/2 and HTTP/3 is not yet supported.

### HTTP is limited to loopback addresses

`http:` is vulnerable to man-in-the-middle attacks and is not allowed to be
used for addresses outside of the IPv4 address `127.0.0.0/8` (`127.0.0.1` to
`127.255.255.255`) and the IPv6 address `::1`. Support for `http:` is intended
to be used for local development.

### Authentication is never sent to the destination server.

`Authorization`, `Cookie`, and `Proxy-Authorization` headers are not sent to the
server. Avoid including user info in parts of imported URLs. A security model
for safely using these on the server is being worked on.

### CORS is never checked on the destination server

CORS is designed to allow a server to limit the consumers of an API to a
specific set of hosts. This is not supported as it does not make sense for a
server-based implementation.

### Cannot load non-network dependencies

These modules cannot access other modules that are not over `http:` or `https:`.
To still access local modules while avoiding the security concern, pass in
references to the local dependencies:

```mjs
// file.mjs
import worker_threads from 'worker_threads';
import { configure, resize } from 'https://example.com/imagelib.mjs';
configure({ worker_threads });
```

```mjs
// https://example.com/imagelib.mjs
let worker_threads;
export function configure(opts) {
worker_threads = opts.worker_threads;
}
export function resize(img, size) {
// Perform resizing in worker_thread to avoid main thread blocking
}
```

### Network-based loading is not enabled by default

For now, the `--experimental-network-imports` flag is required to enable loading
resources over `http:` or `https:`. In the future, a different mechanism will be
used to enforce this. Opt-in is required to prevent transitive dependencies
inadvertently using potentially mutable state that could affect reliability
of Node.js applications.

<i id="esm_experimental_loaders"></i>

## Loaders
Expand Down Expand Up @@ -1467,7 +1500,6 @@ success!
[Node.js Module Resolution Algorithm]: #resolver-algorithm-specification
[Terminology]: #terminology
[URL]: https://url.spec.whatwg.org/
[WHATWG JSON modules specification]: https://html.spec.whatwg.org/#creating-a-json-module-script
[`"exports"`]: packages.md#exports
[`"type"`]: packages.md#type
[`--input-type`]: cli.md#--input-typetype
Expand Down
3 changes: 1 addition & 2 deletions doc/api/packages.md
Expand Up @@ -117,8 +117,7 @@ There is the ECMAScript module loader:
`'./startup/index.js'`) must be fully specified.
* It does no extension searching. A file extension must be provided
when the specifier is a relative or absolute file URL.
* It can load JSON modules, but an import assertion is required (behind
`--experimental-json-modules` flag).
* It can load JSON modules, but an import assertion is required.
* It accepts only `.js`, `.mjs`, and `.cjs` extensions for JavaScript text
files.
* It can be used to load JavaScript CommonJS modules. Such modules
Expand Down
6 changes: 3 additions & 3 deletions doc/node.1
Expand Up @@ -142,14 +142,14 @@ Enable Source Map V3 support for stack traces.
.It Fl -experimental-import-meta-resolve
Enable experimental ES modules support for import.meta.resolve().
.
.It Fl -experimental-json-modules
Enable experimental JSON interop support for the ES Module loader.
.
.It Fl -experimental-loader Ns = Ns Ar module
Specify the
.Ar module
to use as a custom module loader.
.
.It Fl -experimental-network-imports
Enable experimental support for loading modules using `import` over `https:`.
.
.It Fl -experimental-policy
Use the specified file as a security policy.
.
Expand Down
11 changes: 8 additions & 3 deletions lib/internal/errors.js
Expand Up @@ -1414,6 +1414,10 @@ E('ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT',
'start offset of %s should be a multiple of %s', RangeError);
E('ERR_NAPI_INVALID_TYPEDARRAY_LENGTH',
'Invalid typed array length', RangeError);
E('ERR_NETWORK_IMPORT_BAD_RESPONSE',
"import '%s' received a bad response: %s", Error);
E('ERR_NETWORK_IMPORT_DISALLOWED',
"import of '%s' by %s is not supported: %s", Error);
E('ERR_NO_CRYPTO',
'Node.js is not compiled with OpenSSL crypto support', Error);
E('ERR_NO_ICU',
Expand Down Expand Up @@ -1575,12 +1579,13 @@ E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError);
E('ERR_UNKNOWN_FILE_EXTENSION',
'Unknown file extension "%s" for %s',
TypeError);
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError);
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s for URL %s',
RangeError);
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);
E('ERR_UNSUPPORTED_DIR_IMPORT', "Directory import '%s' is not supported " +
'resolving ES modules imported from %s', Error);
E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url) => {
let msg = 'Only file and data URLs are supported by the default ESM loader';
E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url, supported) => {
let msg = `Only URLs with a scheme in: ${ArrayPrototypeJoin(supported, ', ')} are supported by the default ESM loader`;
if (isWindows && url.protocol.length === 2) {
msg +=
'. On Windows, absolute paths must be valid file:// URLs';
Expand Down
6 changes: 3 additions & 3 deletions lib/internal/main/check_syntax.js
Expand Up @@ -45,16 +45,16 @@ if (process.argv[1] && process.argv[1] !== '-') {
});
}

function checkSyntax(source, filename) {
async function checkSyntax(source, filename) {
const { getOptionValue } = require('internal/options');
let isModule = false;
if (filename === '[stdin]' || filename === '[eval]') {
isModule = getOptionValue('--input-type') === 'module';
} else {
const { defaultResolve } = require('internal/modules/esm/resolve');
const { defaultGetFormat } = require('internal/modules/esm/get_format');
const { url } = defaultResolve(pathToFileURL(filename).toString());
const format = defaultGetFormat(url);
const { url } = await defaultResolve(pathToFileURL(filename).toString());
const format = await defaultGetFormat(url);
isModule = format === 'module';
}
if (isModule) {
Expand Down