Skip to content

Commit

Permalink
differences reworking, dynamic import reorder
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Nov 11, 2020
1 parent e38101e commit c4258d2
Showing 1 changed file with 61 additions and 42 deletions.
103 changes: 61 additions & 42 deletions doc/api/esm.md
Expand Up @@ -246,6 +246,11 @@ syncBuiltinESMExports();
fs.readFileSync === readFileSync;
```

## `import()` expressions

[Dynamic `import()`][] is supported in both CommonJS and ES modules. In CommonJS
modules it can be used to load ES modules.

## `import.meta`

* {Object}
Expand Down Expand Up @@ -276,6 +281,9 @@ const buffer = readFileSync(new URL('./data.proto', import.meta.url));
is specified, the value of `import.meta.url` is used as the default.
* Returns: {Promise}
Provides a module-relative resolution function scoped to each module, returning
the URL string.
<!-- eslint-skip -->
```js
const dependencyAsset = await import.meta.resolve('component-lib/asset.css');
Expand All @@ -290,34 +298,29 @@ await import.meta.resolve('./dep', import.meta.url);
```
This function is asynchronous because the ES module resolver in Node.js is
asynchronous.
## `import()` expressions
[Dynamic `import()`][] is supported in both CommonJS and ES modules. In CommonJS
modules it can be used to load ES modules.
allowed to be asynchronous.
## Interoperability with CommonJS
### `require`
`require` always treats the files it references as CommonJS.
Using `require` to load an ES module is not supported because ES modules have
asynchronous execution. Instead, use use [`import()`][] to load an ES module
from a CommonJS module.
### `import` statements
An `import` statement can reference an ES module or a CommonJS module.
`import` statements are permitted only in ES modules. For similar functionality
in CommonJS, see [`import()`][].
`import` statements are permitted only in ES modules, but dynamic [`import()`][]
expressions are supported in CommonJS for loading ES modules.
When importing [CommonJS modules](#esm_commonjs_namespaces), the
`module.exports` object is provided as the default export. Named exports may be
available, provided by static analysis as a convenience for better ecosystem
compatibility.
### `require`
The CommonJS module `require` always treats the files it references as CommonJS.
Using `require` to load an ES module is not supported because ES modules have
asynchronous execution. Instead, use use [`import()`][] to load an ES module
from a CommonJS module.
### CommonJS Namespaces
CommonJS modules consist of a `module.exports` object which can be of any type.
Expand Down Expand Up @@ -401,52 +404,67 @@ Named exports detection covers many common export patterns, reexport patterns
and build tool and transpiler outputs. See [cjs-module-lexer][] for the exact
semantics implemented.
### CommonJS, JSON, and native modules
### Differences between ES modules and CommonJS
#### No `require`, `exports` or `module.exports`
In most cases, the ES module `import` can be used to load CommonJS modules.
CommonJS, JSON, and native modules can be used with
If needed, a `require` function can be constructed within an ES module using
[`module.createRequire()`][].
```js
// cjs.cjs
module.exports = 'cjs';
#### No `__filename` or `__dirname`
// esm.mjs
import { createRequire } from 'module';
These CommonJS variables are not available in ES modules.
const require = createRequire(import.meta.url);
`__filename` and `__dirname` use cases can be replicated via
[`import.meta.url`][].
const cjs = require('./cjs.cjs');
cjs === 'cjs'; // true
```
#### No JSON Module Loading
### Differences between ES modules and CommonJS
JSON imports are still experimental and only supported via the
`--experimental-json-modules` flag.
#### No `NODE_PATH`
Local JSON files can be loaded relative to `import.meta.url` with `fs` directly:
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
if this behavior is desired.
<!-- eslint-skip -->
```js
import { promises as fs } from 'fs';

#### No `require`, `exports`, `module.exports`, `__filename`, `__dirname`
const json = JSON.parse(await fs.readFile('./data.json', import.meta.url));
```
These CommonJS variables are not available in ES modules.
Alterantively `module.createRequire()` can be used.
`require` can be imported into an ES module using [`module.createRequire()`][].
#### No Native Module Loading
Equivalents of `__filename` and `__dirname` can be created inside of each file
via [`import.meta.url`][].
Native modules are not currently supported with ES module imports.
They can be loaded directly with `process.dlopen`:
```js
import process from 'process';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const module = { exports: {} };
process.dlopen(module, fileURLToPath(new URL('./local.node', import.meta.url)));
```

This comment has been minimized.

Copy link
@guybedford

guybedford Nov 11, 2020

Author Contributor

I've suggested a process.dlopen workflow here for native modules. //cc @addaleax if this seems ok?

This comment has been minimized.

Copy link
@addaleax

addaleax Nov 11, 2020

Member

I think it’s fine, yes … although in practice I’d probably rather want to use default imports like CJS?

This comment has been minimized.

Copy link
@guybedford

guybedford Nov 11, 2020

Author Contributor

Yes, perhaps like JSON and Wasm imports this could be behind an --experimental-native-modules flag.

This comment has been minimized.

Copy link
@devsnek

devsnek Nov 11, 2020

Member

In the meantime, it seems better to suggest that people use createRequire, process.dlopen is not very friendly.

This comment has been minimized.

Copy link
@guybedford

guybedford Nov 12, 2020

Author Contributor

I like that this is the bare primitive, but yes that was why I left the comment to get feedback on the approach.

Can you perhaps explain in more detail what's unfriendly about this method in comparison? It would be interesting to hear what others think here as well.

This comment has been minimized.

Copy link
@guybedford

guybedford Nov 12, 2020

Author Contributor

I guess I also like that this is a method without relying on any concept of "require", so that feels more forward looking to me.

This comment has been minimized.

Copy link
@devsnek

devsnek Nov 12, 2020

Member

The api is awkward, but for me the dealbreaker is that it doesn't get put in the require cache. I think that would be very unclear to people and might cause issues.

This comment has been minimized.

Copy link
@guybedford

guybedford Nov 12, 2020

Author Contributor

In terms of the dealbreaker scenario you mentioned - like WebAssembly instantiation, I don't think I've ever come across a case in the wild of more than one JS file wrapping the same binary even.

If anything my concern would be that process is a global capability that has native execution privileges, and that might be a security property worth patching up in Node.js at some point.

Alternatively `module.createRequire()` can be used.
#### No `require.resolve`
Former use cases relying on `require.resolve` to determine the resolved path
of a module can be supported via [`import.meta.resolve`][].
Relative resolution can be handled via `new URL('./local', import.meta.url)`.
For a complete `require.resolve` replacement, there is a flagged experimental
[`import.meta.resolve`][] API.
Alternatively `module.createRequire()` can be used.
#### No `NODE_PATH`
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
if this behavior is desired.
#### No `require.extensions`
Expand All @@ -455,7 +473,8 @@ hooks can provide this workflow in the future.
#### No `require.cache`
`require.cache` is not used by `import`. It has a separate cache.
`require.cache` is not used by `import` as the ES module loader has its own
separate cache.
<i id="esm_experimental_json_modules"></i>
Expand Down

0 comments on commit c4258d2

Please sign in to comment.