From 50480c5f766d481252ae876eb5c2db29799f162a Mon Sep 17 00:00:00 2001 From: Bradley Farias Date: Wed, 1 Mar 2017 09:35:58 -0600 Subject: [PATCH 1/4] 002 - rewrite after discussions due to breakages / problems with VM support, timing, etc. --- 002-es-modules.md | 511 +++++++++++++++++++++++++++++++ 002-es6-modules.md | 732 --------------------------------------------- 2 files changed, 511 insertions(+), 732 deletions(-) create mode 100644 002-es-modules.md delete mode 100644 002-es6-modules.md diff --git a/002-es-modules.md b/002-es-modules.md new file mode 100644 index 0000000..0f8b4de --- /dev/null +++ b/002-es-modules.md @@ -0,0 +1,511 @@ +| Title | ES Module Interoperability | +|--------|-----------------------------| +| Author | @bmeck | +| Status | DRAFT | +| Date | 2017-03-01 | + +**NOTE:** `DRAFT` status does not mean ESM will be implemented in Node +core. Instead that this is the standard, should Node core decide to implement +ESM. At which time this draft would be moved to `ACCEPTED`. + +--- + +Abbreviations: +* `ESM` - Ecma262 Modules (ES Modules) +* `CJS` - Node Modules (a CommonJS variant) + +The intent of this standard is to: + +* implement interoperability for ESM and Node's existing CommonJS module system (CJS) + +## 1. Purpose + +1. Allow a common module syntax for Browser and Server. +2. Allow a common set of context variables for Browser and Server. + +## 2. Related + +[ECMA262](https://tc39.github.io/ecma262/) discusses the syntax and semantics of +related syntax, and introduces ESM. + +[Dynamic Import](https://github.com/tc39/proposal-dynamic-import) introduces `import()` which will be available in all parsing goals. + +### 2.1. Types + +* **[ModuleRecord] +(https://tc39.github.io/ecma262/#sec-abstract-module-records)** + - Defines the list of imports via `[[ImportEntry]]`. + - Defines the list of exports via `[[ExportEntry]]`. + +* **[ModuleNamespace] +(https://tc39.github.io/ecma262/#sec-module-namespace-objects)** + - Represents a read-only static set of bindings to a module's exports. + +### 2.2. Operations + +* **[ParseModule](https://tc39.github.io/ecma262/#sec-parsemodule)** + - Creates a [SourceTextModuleRecord] + (https://tc39.github.io/ecma262/#sec-source-text-module-records) from + source code. + +* **[HostResolveImportedModule] +(https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule)** + - A hook for when an import is exactly performed. This returns a + `ModuleRecord`. Used as a means to grab modules from Node's loader/cache. + +## 3. Semantics + +### 3.1. Async loading + +ESM imports will be loaded asynchronously. This matches browser behavior. This means: + +* If a new `import` is queued up, it will never evaluate synchronously. +* Between module evaluation within the same graph there may be other work done. Order of module evaluation within a module graph will always be preserved. +* Multiple module graphs could be loading at the same time concurrently. + +### 3.2. Determining if source is an ES Module + +A new file type will be recognised, `.mjs`, for ES modules. This file type will be registered with IANA as an official file type, see TC39 issue. There are no known issues with browsers since they [do not determine MIME type using file extensions](https://mimesniff.spec.whatwg.org/#interpreting-the-resource-metadata). + +The `.mjs` file extension will not be loadable via `require()`. This means that, once the Node resolution algorithm reaches file expansion, the path for path + `.mjs` would throw an error. In order to support loading ESM in CJS files please use `import()`. + +### 3.3. ES Import Path Resolution + + +ES `import` statements will perform non-exact searches on relative or +absolute paths, like `require()`. This means that file extensions, and +index files will be searched. However, ESM import specifier resolution will be done using URLs which match closer to the browser. Unlike browsers, only the `file:` protocol will be supported until network and security issues can be researched for other protocols. + +With `import` being URL based encoding and decoding will automatically be performed. This may affect file paths containing any of the following characters: `:`,`?`,`#`, or `%`. Details of the parsing algorithm are at the [WHATWG URL Spec](https://url.spec.whatwg.org/). + +* paths with `:` face multiple variations of path mutation +* paths with `%` in their path segments would be decoded +* paths with `?`, or `#` in their paths would face truncation of pathname + +All behavior differing from the [`type=module` path resolution algorithm](https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier) will be places in locations that would throw errors in the browser. + +#### 3.3.1. Algorithms + +##### 3.3.1.1. HostResolveImportedModule Search + +Notes: + +* The CLI has a location URL of the process working directory. +* Paths are resolved to realpaths normally *after* all these steps. + +1. Apply the URL parser to `specifier`. If the result is not failure, return the result. +2. If `specifier` does start with the character U+002F SOLIDUS (`/`), the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (`./`), or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F SOLIDUS (`../`) + 1. Let `specifier` be the result of applying the URL parser to `specifier` with importing location's URL as the base URL. + 2. Return the result of applying the path search algorithm to `specifier`. +3. Return the result of applying the module search algorithm to `specifier`. + +##### 3.3.1.2. Path Search + +1. If it does not throw an error, return the result of applying the file search algorithm to `specifier`. +2. If it does not throw an error, return the result of applying the directory search algorithm to `specifier`. +3. Throw an error. + +##### 3.3.1.3. File Search + +1. If the resource for `specifier` exists, return `specifier`. +2. For each file extension `[".mjs", ".js", ".json", ".node"]` + 1. Let `searchable` be a new URL from `specifier`. + 2. Append the file extension to the pathname of `searchable`. + 3. If the resource for `searchable` exists, return `searchable`. +3. Throw an error. + +##### 3.3.1.4. Package Main Search + +1. If it does not throw an error, return the result of applying the file search algorithm to `specifier`. +2. If it does not throw an error, return the result of applying the index search algorithm to `specifier`. +3. Throw an error. + +##### 3.3.1.5. Index Search + +1. Let `searchable` be a new URL from `specifier`. +2. If `searchable` does not have a trailing `/` in its pathname append one. +3. Let `searchable` be the result of applying the URL parser to `./index` with `specifier` as the base URL. +4. If it does not throw an error, return the result of applying the file search algorithm to `searchable`. +5. Throw an error. + +##### 3.3.1.6. Directory Search + +1. Let `dir` be a new URL from `specifier`. +2. If `dir` does not have a trailing `/` in its pathname append one. +3. Let `searchable` be the result of applying the URL parser to `./package.json` with `dir` as the base URL. +5. If the resource for `searchable` exists and it contains a "main" field. + 1. Let `main` be the result of applying the URL parser to the `main` field with `dir` as the base URL. + 2. If it does not throw an error, return the result of applying the package main search algorithm to `main`. +6. If it does not throw an error, return the result of applying the index main search algorithm to `dir`. +7. Throw an error. + +##### 3.3.1.7. Module Search + +1. Let `package` be a new URL from the directory containing the importing location. +2. If `package` does not have a trailing `/` in its pathname append one. +3. Let `searchable` be the result of applying the URL parser to `./node_modules/${specifier}` with `package` as the base URL. +4. If it does not throw an error, return the result of applying the file search algorithm to `searchable`. +5. If it does not throw an error, return the result of applying the directory search algorithm to `searchable`. +6. Let `parent` be the result of applying the URL parser to `../` with `package` as the base URL. +7. If it does not throw an error, return the result of applying the module search algorithm to `specifier` with an importing location of `parent`. +8. Throw an error. + +##### 3.3.1.7. Examples + +```javascript +import 'file:///etc/config/app.json'; +``` + +Parseable with the URL parser. No searching. + +```javascript +import './foo'; +import './foo?search'; +import './foo#hash'; +import '../bar'; +import '/baz'; +``` + +Applies the URL parser to the specifiers with a base url of the importing location. Then performs the path search algorithm. + +```javascript +import 'baz'; +import 'abc/123'; +``` + +Performs the module search algorithm. + +### 4. Compatibility + +#### 4.3.2 Removal of non-local dependencies + +All of the following will not be supported by the `import` statement: + +* `$NODE_PATH` +* `$HOME/.node_modules` +* `$HOME/.node_libraries` +* `$PREFIX/lib/node` + +Use local dependencies, and symbolic links as needed. + +##### 4.3.2.1 How to support non-local dependencies + +Although not recommended, and in fact discouraged, there is a way to support +non-local dependencies. **USE THIS AT YOUR OWN DISCRETION**. + +Symlinks of `node_modules -> $HOME/.node_modules`, `node_modules/foo/ -> +$HOME/.node_modules/foo/`, etc. will continue to be supported. + +Adding a parent directory with `node_modules` symlinked will be an effective +strategy for recreating these functionalities. This will incur the known +problems with non-local dependencies, but now leaves the problems in the hands +of the user, allowing Node to give more clear insight to your modules by +reducing complexity. + +Given: + +```sh +/opt/local/myapp +``` + +Transform to: + +```sh +/opt/local/non-local-deps/myapp +/opt/local/non-local-deps/node_modules -> $PREFIX/lib/node (etc.) +``` + +And nest as many times as needed. + +#### 4.3.3. Removal of fallthrough behavior. + +Exact algorithm TBD. + +#### 4.3.4. Errors from new path behavior. + +In the case that an `import` statement is unable to find a module, Node should +make a **best effort** to see if `require` would have found the module and +print out where it was found, if `NODE_PATH` was used, if `HOME` was used, etc. + +#### 4.4. Shipping both ESM and CJS + +When a `package.json` main is encountered, file extension searches are used to +provide a means to ship both ESM and CJS variants of packages. If we have two +entry points `index.mjs` and `index.js` setting `"main":"./index"` in +`package.json` will make Node pick up either, depending on what is supported. + +##### 4.4.1. Excluding main + +Since `main` in `package.json` is entirely optional even inside of npm +packages, some people may prefer to exclude main entirely in the case of using +`./index` as that is still in the Node module search algorithm. + +### 4.5. ESM Evaluation + +#### 4.5.1. Environment Variables + +ESM will not be bootstrapped with magic variables and will await upcoming specifications in order to provide such behaviors in a standard way. As such, the following variables are changed: + +| Variable | Exists | Value | +| ---- | ---- | --- | +| this | y | [`undefined`](https://tc39.github.io/ecma262/#sec-module-environment-records-getthisbinding) | +| arguments | n | | +| require | n | | +| module | n | | +| exports | n | | +| __filename | n | | +| __dirname | n | | + +Like normal scoping rules, if a variable does not exist in a scope, the outer scope is used to find the variable. Since ESM are always strict, errors may be thrown upon trying to use variables that do not exist globally when using ESM. + +##### 4.5.1.1. Standardization Effort + +[Efforts are ongoing to reserve a specifier](https://github.com/whatwg/html/issues/1013) that will be compatible in both Browsers and Node. Tentatively it will be `js:context` and export a single `{url}` value. + +##### 4.5.1.2. Getting CJS Variables Workaround + +Although heavily advised against, you can have a CJS module sibling for your ESM that can export these things: + +```js +// expose.js +module.exports = {__dirname}; +``` + +```js +// use.mjs +import expose from './expose.js'; +const {__dirname} = expose; +``` + +### 4.6. ES consuming CommonJS + +After *any* CJS finishes evaluation, it will be placed into the same cache as ESM. +The value of what is placed in the cache will reflect a single default export pointing to the value of `module.exports` at the time evaluation ended. + +Essentially after any CJS completes evaluation: + +1. if there was an error, place the error in the ESM cache and return +2. let `export` be the value of `module.exports` +3. if there was an error, place the error in the ESM cache and return +4. create an ESM with `{default:module.exports}` as its namespace +5. place the ESM in the ESM cache + +#### 4.6.1. default imports + +`module.exports` is a single value. As such it does not have the dictionary +like properties of ES module exports. In order to transform a CJS module into ESM a `default` export which will +point to the value of `module.exports` that was snapshotted *imediately after the CJS finished evaluation*. Due to problems in supporting named imports, they will not be enabled by default. Space is intentionally left open to allow named properties to be supported through future explorations. + +##### 4.6.1.1. Examples + +Given: + +```javascript +// cjs.js +module.exports = { + default:'my-default', + thing:'stuff' +}; +``` + +You will grab `module.exports` when performing an ESM import of `cjs.js`. + +```javascript +// es.mjs + +import * as baz from './cjs.js'; +// baz = { +// get default() {return module.exports;}, +// } + +import foo from './cjs.js'; +// foo = module.exports; + +import {default as bar} from './cjs.js'; +// bar = module.exports +``` + +------ + +Given: + +```javascript +// cjs.js +module.exports = null; +``` + +You will grab `module.exports` when performing an ES import. + +```javascript +// es.mjs +import foo from './cjs.js'; +// foo = null; + +import * as bar from './cjs.js'; +// bar = {default:null}; +``` + +------ + +Given: + +```javascript +// cjs.js +module.exports = function two() { + return 2; +}; +``` + +You will grab `module.exports` when performing an ESM import. + +```javascript +// es.mjs +import foo from './cjs.js'; +foo(); // 2 + +import * as bar from './cjs.js'; +bar.default(); // 2 +bar(); // throws, bar is not a function +``` + +------ + +Given: + +```javascript +// cjs.js +module.exports = Promise.resolve(3); +``` + +You will grab `module.exports` when performing an ES import. + +```javascript +// es.mjs +import foo from './cjs.js'; +foo.then(console.log); // outputs 3 + +import * as bar from './cjs.js'; +bar.default.then(console.log); // outputs 3 +bar.then(console.log); // throws, bar is not a Promise +``` + +### 4.7. CommonJS consuming ES + +#### 4.7.1. default exports + +ES modules only export named values. A "default" export is an export that uses +the property named `default`. + +##### 4.7.1.1. Examples + +Given: + +```javascript +// es.mjs +let foo = {bar:'my-default'}; +// note: +// this is a value +// it is not a binding like `export {foo}` +export default foo; +foo = null; +``` + +```javascript +// cjs.js +const es_namespace = await import('./es'); +// es_namespace ~= { +// get default() { +// return result_from_evaluating_foo; +// } +// } +console.log(es_namespace.default); +// {bar:'my-default'} +``` + +------ + +Given: + +```javascript +// es.mjs +export let foo = {bar:'my-default'}; +export {foo as bar}; +export function f() {}; +export class c {}; +``` + +```javascript +// cjs.js +const es_namespace = await import('./es'); +// es_namespace ~= { +// get foo() {return foo;} +// get bar() {return foo;} +// get f() {return f;} +// get c() {return c;} +// } +``` + +### 4.8. Known Gotchas + +All of these gotchas relate to opt-in semantics and the fact that CommonJS is a +dynamic loader while ES is a static loader. + +No existing code will be affected. + +#### 4.8.1. ES exports are read only + +The objects create by an ES module are [ModuleNamespace Objects][5]. + +These have `[[Set]]` be a no-op and are read only views of the exports of an ES +module. Attempting to reassign any named export will not work, but assigning to +the properties of the exports follows normal rules. + +### 4.9. CJS modules allow mutation of imported modules + +CJS modules have allowed mutation on imported modules. When ES modules are +integrating against CJS systems like Grunt, it may be necessary to mutate a +`module.exports`. + +Remember that `module.exports` from CJS is directly available under `default` +for `import`. This means that if you use: + +```javascript +import * as namespace from 'grunt'; +``` + +According to ES `*` grabs the namespace directly whose properties will be +read-only. + +However, doing: + +```javascript +import grunt_default from 'grunt'; +``` + +Grabs the `default` which is exactly what `module.exports` is, and all the +properties will be mutable. + +#### 4.9.1. ES will not honor reassigning `module.exports` after evaluation + +Since we need a consistent time to snapshot the `module.exports` of a CJS +module. We will execute it immediately after evaluation. Code such as: + +```javascript +// bad-cjs.js +module.exports = 123; +setTimeout(_ => module.exports = null); +``` + +Will not see `module.exports` change to `null`. All ES module `import`s of the +module will always see `123`. + +## 5. JS APIs + +* `vm.Module` and ways to create custom ESM implementations such as those in [jsdom](https://github.com/tmpvar/jsdom). +* `vm.ReflectiveModule` as a means to declare a list of exports and expose a reflection API to those exports. +* Providing an option to both `vm.Script` and `vm.Module` to intercept `import()`. + +* Loader hooks for: + * Rewriting the URL of an `import` request *prior* to loader resolution. + * Way to insert Modules a module's local ESM cache. + * Way to insert Modules the global ESM cache. \ No newline at end of file diff --git a/002-es6-modules.md b/002-es6-modules.md deleted file mode 100644 index 69a78d9..0000000 --- a/002-es6-modules.md +++ /dev/null @@ -1,732 +0,0 @@ -| Title | ES6 Module Interoperability | -|--------|-----------------------------| -| Author | @bmeck | -| Status | DRAFT | -| Date | 2016-01-07 | - -**NOTE:** `DRAFT` status does not mean ES6 modules will be implemented in Node -core. Instead that this is the standard, should Node core decide to implement -ES6 modules. At which time this draft would be moved to `ACCEPTED`. - ---- - -The intent of this standard is to: - -* implement interoperability for ES modules and Node's existing module system -* create a **Registry Object** (see WHATWG section below) compatible with - the [WHATWG Loader](http://whatwg.github.io/loader/) Registry - -## 1. Purpose - -1. Allow a common module syntax for Browser and Server. -2. Allow a common registry for inspection by Browser and Server - environments/tools. - * These will most likely be represented by metaproperties like - `import.context`, but the spec is not yet fully in place. - -## 2. Related - - -[ECMA262](https://tc39.github.io/ecma262/) discusses the syntax and semantics of -related syntax, and introduces: - -### 2.1. Types - -* **[ModuleRecord] -(https://tc39.github.io/ecma262/#sec-abstract-module-records)** - - Defines the list of imports via `[[ImportEntry]]`. - - Defines the list of exports via `[[ExportEntry]]`. - -* **[ModuleNamespace] -(https://tc39.github.io/ecma262/#sec-module-namespace-objects)** - - Represents a read-only static set of bindings to a module's exports. - -### 2.2. Operations - -* **[ParseModule](https://tc39.github.io/ecma262/#sec-parsemodule)** - - Creates a [SourceTextModuleRecord] - (https://tc39.github.io/ecma262/#sec-source-text-module-records) from - source code. - -* **[HostResolveImportedModule] -(https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule)** - - A hook for when an import is exactly performed. This returns a - `ModuleRecord`. Used as a means to grab modules from Node's loader/cache. - -* **[CreateImportBinding] -(https://tc39.github.io/ecma262/#sec-createimportbinding)** - - A means to create a shared binding (variable) from an export to an - import. Required for the "live" binding of imports. - -* **[ModuleNamespaceCreate] -(https://tc39.github.io/ecma262/#sec-modulenamespacecreate)** - - Provides a means of creating a list of exports manually, used so that - CommonJS `module.exports` can create `ModuleRecord`s that are - prepopulated. - - -[WHATWG Loader](https://github.com/whatwg/loader) discusses the design of -module metadata in a [Registry](https://whatwg.github.io/loader/#registry). All -actions regarding the Registry can be done -synchronously, though JS level API uses Promises. - -**NOTE:** It is not Node's intent to implement the asynchronous pipeline in the -WHATWG Loader specification. - -## 3. Additional Structures Required - -### 3.1. **DynamicModuleRecord** - -A Module Record that represents a view of an Object for its `[[Namespace]]` -rather than coming from an environment record. - -`DynamicModuleRecord` preserves the feature that exported values are known when -it comes time for `HostResolveImportedModule` to return. That means that they -are known after the -file is parsed, but before it is evaluated. This behavior is preserved by Node -synchronously executing CJS files when they are encountered during -`HostResolveImportedModule`. - -When creating a `DynamicModuleRecord` the [`[[Exports]]`] -(https://tc39.github.io/ecma262/#table-29) is frozen upon construction. No new -exports may be added. No exports may be removed. The values of the exports will -continue to be mutable however. - -### 3.1.1 DynamicModuleCreate(O) - -The abstract operation `DynamicModuleCreate` with arguments `namespace` is used -to allow creation of new `DynamicModuleRecord`s. It performs the following -steps: - -1. Let M be a newly created object. -2. Set M's essential internal methods to the definitions specified in - [15.2.1.15 Abstract Module Records] - (https://tc39.github.io/ecma262/#sec-abstract-module-records) -3. Set M's [[Realm]] internal slot to the current Realm Record. -4. Set M's [[Namespace]] internal slot to DelegatedModuleNamespaceObjectCreate - (`M`, `O`) -5. Set M's [[Environment]] internal slot to NewObjectEnvironment(`M`.[[Namespace]], **null**) -6. Set M's [[Evaluated]] internal slot to **true** -7. Return M - -### 3.2. **DelegatedModuleNamespaceObject** - -A `ModuleNamespaceObject` that performs delegation to an Object when accessing -properties. This is used for delegation behavior from CJS `module.exports` when -imported by ES modules. - -#### Table 1: Internal Slots of DelegatedModuleNamespaceObject Namespace Exotic -Objects - -Field Name | Value Type | Meaning ----| --- | --- -[[Delegate]] | Object | The Object from which to delegate access - -#### 3.2.1. `[[Get]] (P, Receiver)` - -When the [[Get]] internal method of a module namespace exotic object O is -called with property key P and ECMAScript language value Receiver, the -following steps are taken: - -1. Assert: IsPropertyKey(`P`) is true. -2. If Type(`P`) is Symbol, then -3. Return ? OrdinaryGet(`O`,`P`,` Receiver`). -4. Let exports be the value of `O`'s `[[Exports]]` internal slot. -5. If `P` is not an element of exports, return **undefined**. -6. Let m be the value of `O`'s `[[Object]]` internal slot. -7. If `P` equals **"default"**, return `m`. -7. Let `value` be ! `O`.`[[Get]]`(`P`,` O`) -8. Return `value` - -#### 3.2.2. `DelegatedModuleNamespaceObjectCreate(module, O)` - -The abstract operation `DelegatedModuleNamespaceObjectCreate` with arguments `O` -is used to create a `DelegatedModuleNamespaceObject`. It performs the following -steps: - -1. Assert: `module` is a Module Record. -2. Assert: `module`.[[Namespace]] is **undefined**. -3. Let `NS` be a newly created object. -4. Set `NS`'s essential internal methods to the definitions specified in [9.4.6 - Module Namespace Exotic Objects] - (https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects) -5. Set `NS`'s [[Module]] internal slot to `module` -6. Let `exports` be a new List -7. Let `p` be `O`. -8. Let `done` be **false**. -9. Repeat while `done` is **false**, - 1. If `p` is null, let `done `be **true**. - 2. Else, - 1. For each `property` in OwnPropertyKeys(`p`) - 1. If `property` does not equal **"default"** and `exports` does - not contain `property`, add `property` to exports - 2. If the [[GetPrototypeOf]] internal method of `p` is not the ordinary - object internal method defined in [9.1.1] - (https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof) - , let `done` be **true**. - 3. Else, let `p` be the value of p's [[Prototype]] internal slot. -10. Set the value of the [[Exports]] internal slot of `NS` to `exports`. -11. Return `NS` - -## 4. Algorithm - -### 4.1. NodeModuleEvaluationJob(source, mode) - -The abstractjob operation `NodeModuleEvaluationJob` with parameters `source` and -`mode`. The results of this should be placed in the cache that -`HostResolveImportedModule` uses. - -1. If `mode` equals CJS - 1. Let `body` be the bootstraped form of `source` with necessary CJS - wrapper code. - 2. Call ! ScriptEvaluationJob(`body`, **undefined**) - 3. Return ! DynamicModuleCreate from `module.exports` -2. Else if `mode` equals ES - 1. Let `M` be ! ParseModule(`source`) - 2. Perform the algorithm listed in **4.** on `M`.[[RequestedModules]] while - respecting the semantics in **5.** - 3. Connect `import` bindings for all relevant submodules using - [ModuleDeclarationInstantiation from the Abstract Module Record M] - (https://tc39.github.io/ecma262/#table-37) - 4. Call ! `M`.[[ModuleEvaluation]] - 5. Return `M` - -**NOTE:** This still guarantees: - -* ES module dependencies are all executed prior to the module itself -* CJS modules have a full shape prior to being handed to ES modules -* CJS modules can imperatively start loading other modules, including ES modules - -## 5. Semantics - -### 5.1. Determining if source is an ES Module - -Require that Module source text has at least one `import` or `export` declaration. -A module with only an `import` declaration and no `export` declaration is valid. -Modules, that do not export anything, should specify an `export {}` to make -intentions clear and avoid accidental parse errors while removing `import` -declarations. The `export {}` is **not** new syntax and does **not** export an -empty object. It is simply the standard way to specify exporting nothing. - -A package opts-in to the Module goal by specifying `"module"` as the parse goal -field *(name not final)* in its `package.json`. Package dependencies are not -affected by the opt-in and may be a mix of CJS and ES module packages. If a parse -goal is not specified, then attempt to parse source text as the preferred goal -*(Script for now since most modules are CJS)*. If there is a parse error that -may allow another goal to parse, then parse as the other goal, and so on. After -this, the goal is known unambiguously and the environment can safely perform -initialization without the possibility of the source text being run in the wrong -goal. - -Note: While the ES2015 specification -[does not forbid](http://www.ecma-international.org/ecma-262/6.0/#sec-forbidden-extensions) -this extension, Node wants to avoid acting as a rogue agent. Node has a TC39 -representative, [@bmeck](https://github.com/bmeck), to champion this proposal. -A specification change or at least an official endorsement of this Node proposal -would be welcomed. If a resolution is not possible, this proposal will fallback -to the previous [`.mjs` file extension proposal](https://github.com/nodejs/node-eps/blob/5dae5a537c2d56fbaf23aaf2ae9da15e74474021/002-es6-modules.md#51-determining-if-source-is-an-es-module). - -#### 5.1.1 Goal Detection - -##### Parse (source, goal, throws) - - The abstract operation to parse source text as a given goal. - - 1. Bootstrap `source` for `goal`. - 2. Parse `source` as `goal`. - 3. If success, return `true`. - 4. If `throws`, throw exception. - 5. Return `false`. - -##### Operation - -1. If a package parse goal is specified, then - 1. Let `goal` be the resolved parse goal. - 2. Call `Parse(source, goal, true)` and return. - -2. Else fallback to multiple parse. - 1. If `Parse(Source, Script, false)` is `true`, then - 1. Return. - 2. Else - 1. Call `Parse(Source, Module, true)`. - - *Note: A host can choose either goal to parse first and may change their order - over time or as new parse goals are introduced. Feel free to swap the order of - Script and Module.* - -#### 5.1.2 Implementation - -To improve performance, host environments may want to specify a goal to parse -first. This can be done in several ways:
-cache on disk, a command line flag, a manifest file, HTTP header, file extension, etc. - -#### 5.1.3 Tooling Concerns - -Some tools, outside of Node, may not have access to a JS parser *(Bash programs, -some asset pipelines, etc.)*. These tools generally operate on files as opaque -blobs / plain text files and can use the techniques, listed under -[Implementation](#512-implementation), to get parse goal information. - -### 5.2. ES Import Path Resolution - -ES `import` statements will perform non-exact searches on relative or -absolute paths, like `require()`. This means that file extensions, and -index files will be searched, - -In summary: - -```javascript -// looks at -// ./foo.js -// ./foo/package.json -// ./foo/index.js -// etc. -import './foo'; -``` - -```javascript -// looks at -// /bar.js -// /bar/package.json -// /bar/index.js -// etc. -import '/bar'; -``` - -```javascript -// looks at: -// ./node_modules/baz.js -// ./node_modules/baz/package.json -// ./node_modules/baz/index.js -// and parent node_modules: -// ../node_modules/baz.js -// ../node_modules/baz/package.json -// ../node_modules/baz/index.js -// etc. -import 'baz'; -``` - -```javascript -// looks at: -// ./node_modules/abc/123.js -// ./node_modules/abc/123/package.json -// ./node_modules/abc/123/index.js -// and parent node_modules: -// ../node_modules/abc/123.js -// ../node_modules/abc/123/package.json -// ../node_modules/abc/123/index.js -// etc. -import 'abc/123'; -``` - -#### 5.2.1. Removal of non-local dependencies - -All of the following will not be supported by the `import` statement: - -* `$NODE_PATH` -* `$HOME/.node_modules` -* `$HOME/.node_libraries` -* `$PREFIX/lib/node` - -Use local dependencies, and symbolic links as needed. - -##### 5.2.1.1. How to support non-local dependencies - -Although not recommended, and in fact discouraged, there is a way to support -non-local dependencies. **USE THIS AT YOUR OWN DISCRETION**. - -Symlinks of `node_modules -> $HOME/.node_modules`, `node_modules/foo/ -> -$HOME/.node_modules/foo/`, etc. will continue to be supported. - -Adding a parent directory with `node_modules` symlinked will be an effective -strategy for recreating these functionalities. This will incur the known -problems with non-local dependencies, but now leaves the problems in the hands -of the user, allowing Node to give more clear insight to your modules by -reducing complexity. - -Given: - -```sh -/opt/local/myapp -``` - -Transform to: - -```sh -/opt/local/non-local-deps/myapp -/opt/local/non-local-deps/node_modules -> $PREFIX/lib/node (etc.) -``` - -And nest as many times as needed. - -#### 5.2.2. Errors from new path behavior. - -In the case that an `import` statement is unable to find a module, Node should -make a **best effort** to see if `require` would have found the module and -print out where it was found, if `NODE_PATH` was used, if `HOME` was used, etc. - -### 5.3. `this` in ES modules - -ES modules will have a `this` value set to `undefined`. This -is a breaking change. CJS modules have a `this` value set to their `module` -binding. - -See ECMA262's [Module Environment Record]( -https://tc39.github.io/ecma262/#sec-module-environment-records-getthisbinding -) for this semantic. - -### 5.4. ES consuming CommonJS - -####5.4.1. default imports - -`module.exports` is a single value. As such it does not have the dictionary -like properties of ES module exports. In order to facilitate named imports for -ES modules, all properties of `module.exports` will be hoisted to named exports -after evaluation of CJS modules with the exception of `default` which will -point to `module.exports` directly. - -##### 5.4.1.1. Examples - -Given: - -```javascript -// cjs.js -module.exports = { - default:'my-default', - thing:'stuff' -}; -``` - -You will grab `module.exports` when performing an ES import. - -```javascript -// es.js - -// grabs the namespace -import * as baz from './cjs.js'; -// baz = { -// get default() {return module.exports;}, -// get thing() {return this.default.thing}.bind(baz) -// } - -// grabs "default", aka module.exports directly -import foo from './cjs.js'; -// foo = {default:'my-default', thing:'stuff'}; - -// grabs "default", aka module.exports directly -import {default as bar} from './cjs.js'; -// bar = {default:'my-default', thing:'stuff'}; -``` - ------- - -Given: - -```javascript -// cjs.js -module.exports = null; -``` - -You will grab `module.exports` when performing an ES import. - -```javascript -// es.js -import foo from './cjs.js'; -// foo = null; - -import * as bar from './cjs.js'; -// bar = {default:null}; -``` - ------- - -Given: - -```javascript -// cjs.js -module.exports = function two() { - return 2; -}; -``` - -You will grab `module.exports` when performing an ES import. - -```javascript -// es.js -import foo from './cjs.js'; -foo(); // 2 - -import * as bar from './cjs.js'; -bar.name; // 'two' -bar.default(); // 2 -bar(); // throws, bar is not a function -``` - ------- - -Given: - -```javascript -// cjs.js -module.exports = Promise.resolve(3); -``` - -You will grab `module.exports` when performing an ES import. - -```javascript -// es.js -import foo from './cjs.js'; -foo.then(console.log); // outputs 3 - -import * as bar from './cjs.js'; -bar.default.then(console.log); // outputs 3 -bar.then(console.log); // throws, bar is not a Promise -``` - -### 5.5. CommonJS consuming ES - -#### 5.5.1. default exports - -ES modules only export named values. A "default" export is an export that uses -the property named `default`. - -##### 5.5.1.1. Examples - -Given: - -```javascript -// es.js -let foo = {bar:'my-default'}; -// note: -// this is a value -// it is not a binding like `export {foo}` -export default foo; -foo = null; -``` - -```javascript -// cjs.js -const es_namespace = require('./es'); -// es_namespace ~= { -// get default() { -// return result_from_evaluating_foo; -// } -// } -console.log(es_namespace.default); -// {bar:'my-default'} -``` - ------- - -Given: - -```javascript -// es.js -export let foo = {bar:'my-default'}; -export {foo as bar}; -export function f() {}; -export class c {}; -``` - -```javascript -// cjs.js -const es_namespace = require('./es'); -// es_namespace ~= { -// get foo() {return foo;} -// get bar() {return foo;} -// get f() {return f;} -// get c() {return c;} -// } -``` - -### 5.6. Known Gotchas - -All of these gotchas relate to opt-in semantics and the fact that CommonJS is a -dynamic loader while ES is a static loader. - -No existing code will be affected. - -#### 5.6.1. ES exports are read only - -The objects create by an ES module are [ModuleNamespace Objects][5]. - -These have `[[Set]]` be a no-op and are read only views of the exports of an ES -module. Attempting to reassign any named export will not work, but assigning to -the properties of the exports follows normal rules. - -### 5.7. CJS modules allow mutation of imported modules - -CJS modules have allowed mutation on imported modules. When ES modules are -integrating against CJS systems like Grunt, it may be necessary to mutate a -`module.exports`. - -Remember that `module.exports` from CJS is directly available under `default` -for `import`. This means that if you use: - -```javascript -import * as namespace from 'grunt'; -``` - -According to ES `*` grabs the namespace directly whose properties will be -read-only. - -However, doing: - -```javascript -import grunt_default from 'grunt'; -``` - -Grabs the `default` which is exactly what `module.exports` is, and all the -properties will be mutable. - -#### 5.7.1. ES will not honor reassigning `module.exports` after evaluation - -Since we need a consistent time to snapshot the `module.exports` of a CJS -module. We will execute it immediately after evaluation. Code such as: - -```javascript -// bad-cjs.js -module.exports = 123; -setTimeout(_ => module.exports = null); -``` - -Will not see `module.exports` change to `null`. All ES module `import`s of the -module will always see `123`. - -#### 5.7.2. ES export list for CJS are snapshot immediately after execution. - -Since `module.exports` is snapshot immediately after execution, that is the -point when hoisting of properties occurs, adding and removing properties later -will not have an effect on the list of exports. - -```javascript -// bad-cjs.js -module.exports = { - yo: 'lo' -}; -setTimeout(_ => { - delete module.exports.yo; - module.exports.foo = 'bar'; - require('./es.js'); -}); -``` - -```javascript -// es.js -import * as namespace from './bad-cjs.js'; -console.log(Object.keys(namespace)); // ['yo'] -console.log(namespace.foo); // undefined - -// mutate to show 'yo' still exists as a binding -import cjs_exports from './bad-cjs.js'; -cjs_exports.yo = 'lo again'; -console.log(namespace.yo); // 'yolo again' -``` - -#### 5.7.3. Circular Dep CJS => ES => CJS Causes Throw - -Due to the following explanation we want to avoid a very specific problem. -Given: - -```javascript -// cjs.js -module.exports = {x:0}; -require('./es'); -``` - -```javascript -// es.js -import * as ns from './cjs.js'; -// ns = ? -import cjs from './cjs.js'; -// cjs = ? -``` - -ES modules must know the list of exported bindings of all dependencies prior to -evaluating. The value being exported for CJS modules is not stable to snapshot -until `cjs.js` finishes executing. The result is that there are no properties -to import and you receive an empty module. - -In order to prevent this sticky situation we will throw on this case. - -Since this case is coming from ES, we will not break any existing circular -dependencies in CJS <-> CJS. It may be easier to think of this change as -similar to how you cannot affect a generator while it is running. - -This would change the ES module behavior to: - -```javascript -// es.js -import * as ns from './cjs.js'; -// throw new EvalError('./cjs is not an ES module and has not finished evaluation'); -``` - -## 6. Example Implementations - -These are written with the expectation that: - -* ModuleNamespaces can be created from existing Objects. -* WHATWG Loader spec Registry is available as a ModuleRegistry. -* ModuleStatus Objects can be created. - -The variable names should be hidden from user code using various techniques -left out here. - -### 6.1. CJS Modules - -#### 6.1.1. Pre Evaluation - -```javascript -// for posterity, will still throw on circular deps -ModuleRegistry.set(__filename, new ModuleStatus({ - 'ready': {'[[Result]]':undefined}; -})); -``` - -#### 6.1.2. Immediately Post Evaluation - -##### 6.1.2.1. On Error - -```javascript -ModuleRegistry.delete(__filename); -``` - -##### 6.1.2.2. On Normal Completion - -```javascript -let module_namespace = module.exports; -ModuleRegistry.set(__filename, new ModuleStatus({ - 'ready': {'[[Result]]':DynamicModuleCreate(module_namespace)[[Namespace]]}; -})); -``` - -### 6.2. ES Modules - -#### 6.2.1. Post Parsing - -```javascript -Object.defineProperty(module, 'exports', { - get() {return module[[Namespace]]}; - set(v) {throw new Error(`${__filename} is an ES module and cannot assign to module.exports`)} - configurable: false, - enumerable: false -}); -``` - -Parsing occurs prior to evaluation, and CJS may execute once we start to -resolve `import`. - -#### 6.2.2. Header - -```javascript -// we will intercept this to inject the values -import {__filename,__dirname,require,module,exports} from ''; -``` - -#### 6.2.3. Immediately Post Evaluation - -##### 6.2.3.1. On Error - -```javascript -delete require.cache[__filename]; -``` From 26c14c188162ba06edd9e3fbc53ea6b35e86de27 Mon Sep 17 00:00:00 2001 From: Bradley Farias Date: Tue, 2 May 2017 02:22:09 -0500 Subject: [PATCH 2/4] fixes --- 002-es-modules.md | 158 +++++++++++++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 50 deletions(-) diff --git a/002-es-modules.md b/002-es-modules.md index 0f8b4de..a2d7e13 100644 --- a/002-es-modules.md +++ b/002-es-modules.md @@ -16,7 +16,7 @@ Abbreviations: The intent of this standard is to: -* implement interoperability for ESM and Node's existing CommonJS module system (CJS) +* implement interoperability for ESM and Node's existing CJS module system ## 1. Purpose @@ -28,7 +28,8 @@ The intent of this standard is to: [ECMA262](https://tc39.github.io/ecma262/) discusses the syntax and semantics of related syntax, and introduces ESM. -[Dynamic Import](https://github.com/tc39/proposal-dynamic-import) introduces `import()` which will be available in all parsing goals. +[Dynamic Import](https://github.com/tc39/proposal-dynamic-import) introduces +`import()` which will be available in all parsing goals. ### 2.1. Types @@ -57,32 +58,48 @@ related syntax, and introduces ESM. ### 3.1. Async loading -ESM imports will be loaded asynchronously. This matches browser behavior. This means: +ESM imports will be loaded asynchronously. This matches browser behavior. +This means: * If a new `import` is queued up, it will never evaluate synchronously. -* Between module evaluation within the same graph there may be other work done. Order of module evaluation within a module graph will always be preserved. +* Between module evaluation within the same graph there may be other work done. +Order of module evaluation within a module graph will always be preserved. * Multiple module graphs could be loading at the same time concurrently. ### 3.2. Determining if source is an ES Module -A new file type will be recognised, `.mjs`, for ES modules. This file type will be registered with IANA as an official file type, see TC39 issue. There are no known issues with browsers since they [do not determine MIME type using file extensions](https://mimesniff.spec.whatwg.org/#interpreting-the-resource-metadata). +A new file type will be recognised, `.mjs`, for ES modules. This file type will +be registered with IANA as an official file type, see TC39 issue. There are no +known issues with browsers since they +[do not determine MIME type using file extensions](https://mimesniff.spec.whatwg.org/#interpreting-the-resource-metadata). -The `.mjs` file extension will not be loadable via `require()`. This means that, once the Node resolution algorithm reaches file expansion, the path for path + `.mjs` would throw an error. In order to support loading ESM in CJS files please use `import()`. +The `.mjs` file extension will not be loadable via `require()`. This means that, +once the Node resolution algorithm reaches file expansion, the path for +path + `.mjs` would throw an error. In order to support loading ESM in CJS files +please use `import()`. ### 3.3. ES Import Path Resolution ES `import` statements will perform non-exact searches on relative or absolute paths, like `require()`. This means that file extensions, and -index files will be searched. However, ESM import specifier resolution will be done using URLs which match closer to the browser. Unlike browsers, only the `file:` protocol will be supported until network and security issues can be researched for other protocols. +index files will be searched. However, ESM import specifier resolution will be +done using URLs which match closer to the browser. Unlike browsers, only the +`file:` protocol will be supported until network and security issues can be +researched for other protocols. -With `import` being URL based encoding and decoding will automatically be performed. This may affect file paths containing any of the following characters: `:`,`?`,`#`, or `%`. Details of the parsing algorithm are at the [WHATWG URL Spec](https://url.spec.whatwg.org/). +With `import` being URL based encoding and decoding will automatically be +performed. This may affect file paths containing any of the following +characters: `:`,`?`,`#`, or `%`. Details of the parsing algorithm are at the +[WHATWG URL Spec](https://url.spec.whatwg.org/). * paths with `:` face multiple variations of path mutation * paths with `%` in their path segments would be decoded * paths with `?`, or `#` in their paths would face truncation of pathname -All behavior differing from the [`type=module` path resolution algorithm](https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier) will be places in locations that would throw errors in the browser. +All behavior differing from the +[`type=module` path resolution algorithm](https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier) +will be places in locations that would throw errors in the browser. #### 3.3.1. Algorithms @@ -93,61 +110,82 @@ Notes: * The CLI has a location URL of the process working directory. * Paths are resolved to realpaths normally *after* all these steps. -1. Apply the URL parser to `specifier`. If the result is not failure, return the result. -2. If `specifier` does start with the character U+002F SOLIDUS (`/`), the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (`./`), or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F SOLIDUS (`../`) - 1. Let `specifier` be the result of applying the URL parser to `specifier` with importing location's URL as the base URL. - 2. Return the result of applying the path search algorithm to `specifier`. +1. Apply the URL parser to `specifier`. If the result is not failure, +return the result. +2. If `specifier` does start with the character U+002F SOLIDUS (`/`), the +two-character sequence U+002E FULL STOP, U+002F SOLIDUS (`./`), or the +three-character sequence U+002E FULL STOP, U+002E FULL STOP, +U+002F SOLIDUS (`../`) + 1. Let `specifier` be the result of applying the URL parser to `specifier` + with importing location's URL as the base URL. + 2. Return the result of applying the path search algorithm to `specifier`. 3. Return the result of applying the module search algorithm to `specifier`. ##### 3.3.1.2. Path Search -1. If it does not throw an error, return the result of applying the file search algorithm to `specifier`. -2. If it does not throw an error, return the result of applying the directory search algorithm to `specifier`. +1. If it does not throw an error, return the result of applying the file search +algorithm to `specifier`. +2. If it does not throw an error, return the result of applying the directory +search algorithm to `specifier`. 3. Throw an error. ##### 3.3.1.3. File Search 1. If the resource for `specifier` exists, return `specifier`. 2. For each file extension `[".mjs", ".js", ".json", ".node"]` - 1. Let `searchable` be a new URL from `specifier`. - 2. Append the file extension to the pathname of `searchable`. - 3. If the resource for `searchable` exists, return `searchable`. + 1. Let `searchable` be a new URL from `specifier`. + 2. Append the file extension to the pathname of `searchable`. + 3. If the resource for `searchable` exists, return `searchable`. 3. Throw an error. ##### 3.3.1.4. Package Main Search -1. If it does not throw an error, return the result of applying the file search algorithm to `specifier`. -2. If it does not throw an error, return the result of applying the index search algorithm to `specifier`. +1. If it does not throw an error, return the result of applying the file search +algorithm to `specifier`. +2. If it does not throw an error, return the result of applying the index +search algorithm to `specifier`. 3. Throw an error. ##### 3.3.1.5. Index Search 1. Let `searchable` be a new URL from `specifier`. 2. If `searchable` does not have a trailing `/` in its pathname append one. -3. Let `searchable` be the result of applying the URL parser to `./index` with `specifier` as the base URL. -4. If it does not throw an error, return the result of applying the file search algorithm to `searchable`. +3. Let `searchable` be the result of applying the URL parser to `./index` with +`specifier` as the base URL. +4. If it does not throw an error, return the result of applying the file search +algorithm to `searchable`. 5. Throw an error. ##### 3.3.1.6. Directory Search 1. Let `dir` be a new URL from `specifier`. 2. If `dir` does not have a trailing `/` in its pathname append one. -3. Let `searchable` be the result of applying the URL parser to `./package.json` with `dir` as the base URL. -5. If the resource for `searchable` exists and it contains a "main" field. - 1. Let `main` be the result of applying the URL parser to the `main` field with `dir` as the base URL. - 2. If it does not throw an error, return the result of applying the package main search algorithm to `main`. -6. If it does not throw an error, return the result of applying the index main search algorithm to `dir`. -7. Throw an error. +3. Let `searchable` be the result of applying the URL parser to `./package.json` +with `dir` as the base URL. +4. If the resource for `searchable` exists and it contains a "main" field. + 1. Let `main` be the result of applying the URL parser to the `main` field + with `dir` as the base URL. + 2. If it does not throw an error, return the result of applying the package + main search algorithm to `main`. +5. If it does not throw an error, return the result of applying the index +search algorithm to `dir`. +6. Throw an error. ##### 3.3.1.7. Module Search -1. Let `package` be a new URL from the directory containing the importing location. +1. Let `package` be a new URL from the directory containing the importing +location. 2. If `package` does not have a trailing `/` in its pathname append one. -3. Let `searchable` be the result of applying the URL parser to `./node_modules/${specifier}` with `package` as the base URL. -4. If it does not throw an error, return the result of applying the file search algorithm to `searchable`. -5. If it does not throw an error, return the result of applying the directory search algorithm to `searchable`. -6. Let `parent` be the result of applying the URL parser to `../` with `package` as the base URL. -7. If it does not throw an error, return the result of applying the module search algorithm to `specifier` with an importing location of `parent`. +3. Let `searchable` be the result of applying the URL parser to +`./node_modules/${specifier}` with `package` as the base URL. +4. If it does not throw an error, return the result of applying the file search +algorithm to `searchable`. +5. If it does not throw an error, return the result of applying the directory +search algorithm to `searchable`. +6. Let `parent` be the result of applying the URL parser to `../` with +`package` as the base URL. +7. If it does not throw an error, return the result of applying the module +search algorithm to `specifier` with an importing location of `parent`. 8. Throw an error. ##### 3.3.1.7. Examples @@ -166,7 +204,8 @@ import '../bar'; import '/baz'; ``` -Applies the URL parser to the specifiers with a base url of the importing location. Then performs the path search algorithm. +Applies the URL parser to the specifiers with a base url of the importing +location. Then performs the path search algorithm. ```javascript import 'baz'; @@ -244,7 +283,9 @@ packages, some people may prefer to exclude main entirely in the case of using #### 4.5.1. Environment Variables -ESM will not be bootstrapped with magic variables and will await upcoming specifications in order to provide such behaviors in a standard way. As such, the following variables are changed: +ESM will not be bootstrapped with magic variables and will await upcoming +specifications in order to provide such behaviors in a standard way. As such, +the following variables are changed: | Variable | Exists | Value | | ---- | ---- | --- | @@ -256,15 +297,20 @@ ESM will not be bootstrapped with magic variables and will await upcoming specif | __filename | n | | | __dirname | n | | -Like normal scoping rules, if a variable does not exist in a scope, the outer scope is used to find the variable. Since ESM are always strict, errors may be thrown upon trying to use variables that do not exist globally when using ESM. +Like normal scoping rules, if a variable does not exist in a scope, the outer +scope is used to find the variable. Since ESM are always strict, errors may be +thrown upon trying to use variables that do not exist globally when using ESM. ##### 4.5.1.1. Standardization Effort -[Efforts are ongoing to reserve a specifier](https://github.com/whatwg/html/issues/1013) that will be compatible in both Browsers and Node. Tentatively it will be `js:context` and export a single `{url}` value. +[Efforts are ongoing to reserve a specifier](https://github.com/whatwg/html/issues/1013) +that will be compatible in both Browsers and Node. Tentatively it will be +`js:context` and export a single `{url}` value. ##### 4.5.1.2. Getting CJS Variables Workaround -Although heavily advised against, you can have a CJS module sibling for your ESM that can export these things: +Although heavily advised against, you can have a CJS module sibling for your +ESM that can export these things: ```js // expose.js @@ -279,8 +325,9 @@ const {__dirname} = expose; ### 4.6. ES consuming CommonJS -After *any* CJS finishes evaluation, it will be placed into the same cache as ESM. -The value of what is placed in the cache will reflect a single default export pointing to the value of `module.exports` at the time evaluation ended. +After *any* CJS finishes evaluation, it will be placed into the same cache as +ESM. The value of what is placed in the cache will reflect a single default +export pointing to the value of `module.exports` at the time evaluation ended. Essentially after any CJS completes evaluation: @@ -290,11 +337,18 @@ Essentially after any CJS completes evaluation: 4. create an ESM with `{default:module.exports}` as its namespace 5. place the ESM in the ESM cache +Note: step 4 is the only time the value of `module.exports` is assigned to the +ESM. + #### 4.6.1. default imports `module.exports` is a single value. As such it does not have the dictionary -like properties of ES module exports. In order to transform a CJS module into ESM a `default` export which will -point to the value of `module.exports` that was snapshotted *imediately after the CJS finished evaluation*. Due to problems in supporting named imports, they will not be enabled by default. Space is intentionally left open to allow named properties to be supported through future explorations. +like properties of ES module exports. In order to transform a CJS module into +ESM a `default` export which will point to the value of `module.exports` that +was snapshotted *imediately after the CJS finished evaluation*. Due to problems +in supporting named imports, they will not be enabled by default. Space is +intentionally left open to allow named properties to be supported through +future explorations. ##### 4.6.1.1. Examples @@ -458,7 +512,8 @@ The objects create by an ES module are [ModuleNamespace Objects][5]. These have `[[Set]]` be a no-op and are read only views of the exports of an ES module. Attempting to reassign any named export will not work, but assigning to -the properties of the exports follows normal rules. +the properties of the exports follows normal rules. This also means that keys +cannot be added. ### 4.9. CJS modules allow mutation of imported modules @@ -501,11 +556,14 @@ module will always see `123`. ## 5. JS APIs -* `vm.Module` and ways to create custom ESM implementations such as those in [jsdom](https://github.com/tmpvar/jsdom). -* `vm.ReflectiveModule` as a means to declare a list of exports and expose a reflection API to those exports. -* Providing an option to both `vm.Script` and `vm.Module` to intercept `import()`. +* `vm.Module` and ways to create custom ESM implementations such as those in +[jsdom](https://github.com/tmpvar/jsdom). +* `vm.ReflectiveModule` as a means to declare a list of exports and expose a +reflection API to those exports. +* Providing an option to both `vm.Script` and `vm.Module` to intercept +`import()`. * Loader hooks for: - * Rewriting the URL of an `import` request *prior* to loader resolution. - * Way to insert Modules a module's local ESM cache. - * Way to insert Modules the global ESM cache. \ No newline at end of file + * Rewriting the URL of an `import` request *prior* to loader resolution. + * Way to insert Modules a module's local ESM cache. + * Way to insert Modules the global ESM cache. \ No newline at end of file From ecc9f88677523ea843f9f4c00442abffd2b7270c Mon Sep 17 00:00:00 2001 From: Bradley Farias Date: Tue, 2 May 2017 02:33:20 -0500 Subject: [PATCH 3/4] clarify to avoid infinite loop in module search --- 002-es-modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/002-es-modules.md b/002-es-modules.md index a2d7e13..4271767 100644 --- a/002-es-modules.md +++ b/002-es-modules.md @@ -174,7 +174,7 @@ search algorithm to `dir`. ##### 3.3.1.7. Module Search 1. Let `package` be a new URL from the directory containing the importing -location. +location. If `package` is the same as the importing location, throw an error. 2. If `package` does not have a trailing `/` in its pathname append one. 3. Let `searchable` be the result of applying the URL parser to `./node_modules/${specifier}` with `package` as the base URL. From a1eab9bf023bbe13a79ddb18f0622a5d57215f9b Mon Sep 17 00:00:00 2001 From: Bradley Farias Date: Tue, 2 May 2017 02:36:29 -0500 Subject: [PATCH 4/4] link tc39 issue --- 002-es-modules.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/002-es-modules.md b/002-es-modules.md index 4271767..5000bbd 100644 --- a/002-es-modules.md +++ b/002-es-modules.md @@ -69,8 +69,9 @@ Order of module evaluation within a module graph will always be preserved. ### 3.2. Determining if source is an ES Module A new file type will be recognised, `.mjs`, for ES modules. This file type will -be registered with IANA as an official file type, see TC39 issue. There are no -known issues with browsers since they +be registered with IANA as an official file type, see +[TC39 issue](https://github.com/tc39/ecma262/issues/322). There are no known +issues with browsers since they [do not determine MIME type using file extensions](https://mimesniff.spec.whatwg.org/#interpreting-the-resource-metadata). The `.mjs` file extension will not be loadable via `require()`. This means that,