Skip to content

Latest commit

 

History

History
116 lines (82 loc) · 5.7 KB

esm-in-js-ux.md

File metadata and controls

116 lines (82 loc) · 5.7 KB

These are proposals for the user experience of an addition to package.json that configures Node to treat .js files as ESM within a package boundary. This assumes the --experimental-modules implementation as a baseline. This is spun off from #150, and relates to nodejs/node/pull/18392.

This pull request adds support for a "mode": "esm" flag to be added to package.json, that tells Node to treat all .js files within the package boundary of that package.json file as ESM. The package boundary of a package.json file is deemed to be all modules in that folder and subfolders until the next nested package.json file.

In #150 we found consensus on adding a little more customizability than what would be offered by a simple boolean flag, but I wanted to include this here for completeness. Also, I expect that this PR would serve as the basis for a more full-featured implementation.

MIME types/webserver as metaphor

In browsers, users must configure their webservers to serve .js or .mjs files with a JavaScript MIME type like text/javascript in order for the browser to recognize the file as ESM. The browsers pay no attention to file extensions, but most webservers use file extensions to determine what MIME types to use to serve files. This is similar to AddType video/webm .webm that you may have seen in Apache webserver configuration files.

The idea here is to mimic this webserver configuration in a new package.json section called mimes:

"mimes": {
  "js": "application/node",
  "mjs": "text/javascript",
  "json": "application/json"
}

In this example, .js files would be treated as CommonJS, which has the MIME type application/node. .mjs files would be treated as ESM (text/javascript, or application/javascript) and .json files would be treated as JSON. The configuration in this example would be the default, what Node uses if a mimes field was missing, as historically .js files were always treated as CommonJS.

Here’s another example:

"mimes": {
  "js": "text/javascript",
  "cjs": "application/node"
}

This tells Node to treat .js files within this package boundary as ESM, and a new .cjs file extension as CommonJS. The .cjs extension could be anything—just as in Apache a user could add the configuration AddType video/webm .foo, this mimes block provides the flexibility for new file extensions to be defined and mapped to MIME types that Node understands. Since .mjs isn’t listed here, Node falls back to the default mapping for it (text/javascript), and likewise for any other file extensions that Node recognizes by default (.json, etc.).

Preconfigured MIMEs databases

This is based on this proposal by @bmeck, and is similar to the previous proposal. In this version, the mimes field takes a string or array of strings, where each string indicates a preset MIME database:

"mimes": "cjs"

This would be the default, which corresponds to the first mimes block in the previous proposal (and to the --experimental-modules current behavior).

"mimes": [null, "esm"]

This would blank out any default extension/MIME mapping within the current package boundary, and then apply the "esm" preset (which maps both .mjs and .js to ESM).

Map from extension to extension

This is based on this comment from @ljharb. It would map file extensions to other file extensions (@ljharb please expand upon your idea and I’ll update this section, as there wasn’t an example in the comment):

"extensions": {
  "js": "mjs"
}

This would tell Node to treat all .js files as .mjs files within the package boundary—in other words, to treat all .js files as ESM.

Map from extension to parse goal

Similar to the previous, this would define parse goals for extensions directly:

"parseGoals": {
  "mjs": "module",
  "js": "commonjs"
}

I presume that this would be limited to JavaScript-like files, unless things like .json or .node could have parse goals.

Consensus: “MIME types/webserver as metaphor” with links to external JSON files

Start with the second proposal from this list, “MIME types/webserver as metaphor”, and its new mimes section in package.json that can be an object, e.g.:

"mimes": {
  "js": "text/javascript",
  "cjs": "application/node"
}

That mimes section could alternatively take an array of strings, which would be relative or resolved paths to JSON files:

"mimes": [
  "./my-mimes.json",
  "typescript/mimes.json"
]

Each JSON file would be an object with “extension: mime” mappings like the first example. They would be combined using Object.assign. If the first element in the array is null, Node’s default MIME mappings are discarded first before the array elements are merged, e.g. something like:

const nodeDefaultMimeMappings = require('module').mimeMappings;

const packageJsonMimes = require('./package.json').mimes;
if (Array.isArray(packageJsonMimes)) {
  let mimeMappings = (packageJsonMimes[0] === null) ? Object.create(null) : nodeDefaultMimeMappings;
  packageJsonMimes.forEach((filePath, index) => {
    if (filePath === null && index !== 0) {
      throw new Error('Only the first element of a mimes array may be null');
    }
    Object.assign(mimeMappings, require(filePath));
  });
  return mimeMappings;
} else {
  return Object.assign(nodeDefaultMimeMappings, packageJsonMimes);
}