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

Types/Dual-Mode #4

Closed
wants to merge 3 commits into from
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
128 changes: 57 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,119 +1,105 @@
# Package Mode Proposal
# Package Type Proposal

This is a [Phase 2 proposal](https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md#phase-2) for the Node.js [ECMAScript modules project](https://github.com/nodejs/ecmascript-modules).

It builds on and is complementary to Phase 2 work currently done with the [Node import file specifier resolution proposal](https://github.com/GeoffreyBooth/node-import-file-specifier-resolution-proposal) and the [package export maps proposal](https://github.com/jkrems/proposal-pkg-exports).

## Problem Statement
## Goals

To provide a simple mechanism for supporting `.js` ES Modules in Node.js.
This proposal aims to define the configuration in `package.json` that will be used to:

## Current Status
- Specify the package type, which for the most part will correspond with its intended method of consumption or target environment: CommonJS, ESM, browser, etc.
- A package can be multiple types.
- An ESM package type tells Node to treat the package’s `.js` files as ESM.
- Specify the entry point and/or exported paths for each of a package’s types.

### Example of `"exports"` as the Mode Boundary
## Motivation

The [Node import file specifier resolution proposal](https://github.com/GeoffreyBooth/node-import-file-specifier-resolution-proposal) and the [package export maps proposal](https://github.com/jkrems/proposal-pkg-exports) allow `.js` files as ESM whenever the `"exports"` property is used in a package.
### `"exports"` as the ESM Signifier

`"exports"` therefore acts as a package ESM signifier in the file import specifier proposal:
The [Node import file specifier resolution proposal](https://github.com/GeoffreyBooth/node-import-file-specifier-resolution-proposal) and the [package export maps proposal](https://github.com/jkrems/proposal-pkg-exports) allow `.js` files as ESM whenever the `"exports"` property is used in a `package.json`. `"exports"` therefore acts as a package ESM signifier in the file import specifier proposal.

_package.json_
A `package.json` of simply `{}` will cause `node file.js` inside the same folder to load `file.js` as CommonJS.

```json
{}
```

will cause `node file.js` inside the same folder to load `file.js` as CommonJS.

If I then set:

_package.json_

```json
{
"exports": "./file.js"
}
```

then `node file.js` will now load `file.js` as an ES module, while also locking down the package subpaths.

### `"exports"` Mode Boundary as a Conflation of Concerns
However, if one then sets that `package.json` to be `{ "exports": "./file.js" }`, then `node file.js` will now load `file.js` as an ES module, while also locking down the package subpaths.

This is a conflation of three separate concerns:

1. A user wants to load `.js` files within a package as ESM.
2. A user wants to set the ESM package entry point.
3. A user wants package export maps or encapsulation.

If a user wants to achieve just one of the above, they are immediately tied into the others.

Instead we should try to break up these cases into the simplest orthogonal primitives so that they are easy to understand and don't unnecessarily bundle up concerns for users.
Users should be able to trigger `.js`-as-ESM without necessarily needing to also enable `"exports"`’ encapsulation, or needing to type the verbose `{ "exports": { "./": "./" } }`.

## Proposal

The proposal, as presented before one year ago to this group (in 5 mins!), is to introduce the `"mode": "esm"` package boundary indicator
to know when `.js` files should be treated as ESM.
### `"type"` Field

_package.json_
We propose the creation of a `package.json` `type` field that takes a string or array of strings, for example:

```json
{}
{
"type": "esm"
}
```

will load `file.js` in the same folder as CommonJS.

_packge.json_

```json
{
"mode": "esm"
"type": ["esm", "commonjs", "browser"]
}
```

will now load `file.js` as an ES module.
Node will initially only support the types `"esm"` and `"commonjs"`. Other types that users may specify can be used by build tools or loaders. For example, a loader could be written to enable support for a `"browser"` type by emulating a DOM environment and supplying `window` and other browser globals.

Thus a user can now achieve (1), `.js` as ESM, without necessarily opting in to the other features.
A `package.json` without a `type` field is equivalent to `"type": "commonjs"`. This corresponds with Node’s current behavior for loading packages.

### ESM entry point
A `package.json` with `"type": "esm"` will cause Node to treat the package as ESM: not only will `.js` files within the package be loaded as ESM, but the CommonJS globals such as `__filename` etc. will not be available.

When it comes to setting the entry point, `"mode": "esm"` is actually fully compatible with the existing `"main"` property in the `package.json`.
### Exports

So instead of `{ "exports": "./file.js" }` a user can write:
The `"exports"` key from the [package export maps proposal](https://github.com/jkrems/proposal-pkg-exports) behaves as that proposal describes, however a package with multiple types can have objects as values:

```json
{
"mode": "esm",
"main": "file.js"
"type": ["esm", "commonjs"],
"exports": {
"./": {
"esm": "./src/",
"commonjs": "./dist/"
}
}
}
```

to have a `.js` main ES module be supported.
If a path is given only one value, such as `"./": "./"`, it is applied for all of the package’s types.

If they wrote `{ "main": "file.mjs" }` then they can still have ESM support fine without setting a mode boundary.
Note that if a package has only one type, there is no need for the object form as shown here; the simpler `".": "./src/index.js"` is enough.

Thus (2), defining the ESM package entry point, is now fully separated as well.
### Entry points

### Compatibility with the Package Exports Proposal
`"main"` remains reserved exclusively for defining the CommonJS entry point. We cannot change its signature, for example to accept an object of multiple entry points, without breaking backward compatibility.

In terms of bringing back the `"exports"` proposal here, this can be done in a compatible way to provide package export maps and encapsulation, and possibly even dual mode package support too.
Therefore, entry points must be defined within `"exports"`:

There are some implementation questions that would need to be worked out further:
```json
{
"type": "esm",
"exports": "./index.mjs"
}
```
```json
{
"type": ["typescript", "esm", "commonjs"],
"exports": {
".": {
"typescript": "./src/index.ts",
"esm": "./esm/index.js",
"commonjs": "./cjs/index.js"
}
}
}
```

* Should `"exports"` automatically act as if `"mode": "esm"` is present?
* If there is both a `"main"` and an `"exports"` property does `"exports"` always win?
If the user wants to additionally support older versions of Node, `"main"` must also be specified. For newer versions of Node that support both `"exports"` and `"main"`, the entry point specified in `"exports"` shall take precedence.

## Specification

The specification for this feature is defined here - https://github.com/guybedford/ecmascript-modules/commit/25b493c369cb430b4eac3a69ecdabe7e6cdc41c3.

This is a diff on top of the current import file specifier resolution proposal spec is available at https://github.com/nodejs/ecmascript-modules/pull/19.

All the defined behaviours remain, except for:

1. Delegating the mode to the `"mode": "esm"` signifier.
2. Supporting the package.json `"main"` as the main entry point in ESM packages.
TBD.

### Draft Implementation

A draft implementation of the approach is available at https://github.com/guybedford/node/tree/irp-mode-implementation.

This does not yet provide the package export maps proposal support as the exact behaviours of this interaction still need to be worked out.
TBD.