mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
esm: add support for JSON import assertion
Remove V8 flag for import assertions, enabling support for the syntax; require the import assertion syntax for imports of JSON. Support import assertions in user loaders. Use both resolved module URL and import assertion type as the key for caching modules. Co-authored-by: Geoffrey Booth <webadmin@geoffreybooth.com> PR-URL: https://github.com/nodejs/node/pull/40250 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
This commit is contained in:
parent
2e2a6fecd9
commit
2cc7a91a5d
|
|
@ -1689,6 +1689,36 @@ is set for the `Http2Stream`.
|
|||
|
||||
An attempt was made to construct an object using a non-public constructor.
|
||||
|
||||
<a id="ERR_IMPORT_ASSERTION_TYPE_FAILED"></a>
|
||||
|
||||
### `ERR_IMPORT_ASSERTION_TYPE_FAILED`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
An import assertion has failed, preventing the specified module to be imported.
|
||||
|
||||
<a id="ERR_IMPORT_ASSERTION_TYPE_MISSING"></a>
|
||||
|
||||
### `ERR_IMPORT_ASSERTION_TYPE_MISSING`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
An import assertion is missing, preventing the specified module to be imported.
|
||||
|
||||
<a id="ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED"></a>
|
||||
|
||||
### `ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
An import assertion is not supported by this version of Node.js.
|
||||
|
||||
<a id="ERR_INCOMPATIBLE_OPTION_PAIR"></a>
|
||||
|
||||
### `ERR_INCOMPATIBLE_OPTION_PAIR`
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
<!-- YAML
|
||||
added: v8.5.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/40250
|
||||
description: Add support for import assertions.
|
||||
- version:
|
||||
- v17.0.0
|
||||
- v16.12.0
|
||||
|
|
@ -220,6 +223,28 @@ absolute URL strings.
|
|||
import fs from 'node:fs/promises';
|
||||
```
|
||||
|
||||
## Import assertions
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
The [Import Assertions proposal][] adds an inline syntax for module import
|
||||
statements to pass on more information alongside the module specifier.
|
||||
|
||||
```js
|
||||
import fooData from './foo.json' assert { type: 'json' };
|
||||
|
||||
const { default: barData } =
|
||||
await import('./bar.json', { assert: { type: 'json' } });
|
||||
```
|
||||
|
||||
Node.js supports the following `type` values:
|
||||
|
||||
| `type` | Resolves to |
|
||||
| -------- | ---------------- |
|
||||
| `'json'` | [JSON modules][] |
|
||||
|
||||
## Builtin modules
|
||||
|
||||
[Core modules][] provide named exports of their public API. A
|
||||
|
|
@ -517,10 +542,8 @@ same path.
|
|||
|
||||
Assuming an `index.mjs` with
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
import packageConfig from './package.json';
|
||||
import packageConfig from './package.json' assert { type: 'json' };
|
||||
```
|
||||
|
||||
The `--experimental-json-modules` flag is needed for the module
|
||||
|
|
@ -608,12 +631,20 @@ CommonJS modules loaded.
|
|||
|
||||
#### `resolve(specifier, context, defaultResolve)`
|
||||
|
||||
<!-- YAML
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/40250
|
||||
description: Add support for import assertions.
|
||||
-->
|
||||
|
||||
> Note: The loaders API is being redesigned. This hook may disappear or its
|
||||
> signature may change. Do not rely on the API described below.
|
||||
|
||||
* `specifier` {string}
|
||||
* `context` {Object}
|
||||
* `conditions` {string\[]}
|
||||
* `importAssertions` {Object}
|
||||
* `parentURL` {string|undefined}
|
||||
* `defaultResolve` {Function} The Node.js default resolver.
|
||||
* Returns: {Object}
|
||||
|
|
@ -690,13 +721,15 @@ export async function resolve(specifier, context, defaultResolve) {
|
|||
* `context` {Object}
|
||||
* `format` {string|null|undefined} The format optionally supplied by the
|
||||
`resolve` hook.
|
||||
* `importAssertions` {Object}
|
||||
* `defaultLoad` {Function}
|
||||
* Returns: {Object}
|
||||
* `format` {string}
|
||||
* `source` {string|ArrayBuffer|TypedArray}
|
||||
|
||||
The `load` hook provides a way to define a custom method of determining how
|
||||
a URL should be interpreted, retrieved, and parsed.
|
||||
a URL should be interpreted, retrieved, and parsed. It is also in charge of
|
||||
validating the import assertion.
|
||||
|
||||
The final value of `format` must be one of the following:
|
||||
|
||||
|
|
@ -1358,6 +1391,8 @@ success!
|
|||
[Dynamic `import()`]: https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports
|
||||
[ECMAScript Top-Level `await` proposal]: https://github.com/tc39/proposal-top-level-await/
|
||||
[ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration
|
||||
[Import Assertions proposal]: https://github.com/tc39/proposal-import-assertions
|
||||
[JSON modules]: #json-modules
|
||||
[Node.js Module Resolution Algorithm]: #resolver-algorithm-specification
|
||||
[Terminology]: #terminology
|
||||
[URL]: https://url.spec.whatwg.org/
|
||||
|
|
|
|||
|
|
@ -1083,6 +1083,12 @@ E('ERR_HTTP_SOCKET_ENCODING',
|
|||
E('ERR_HTTP_TRAILER_INVALID',
|
||||
'Trailers are invalid with this transfer encoding', Error);
|
||||
E('ERR_ILLEGAL_CONSTRUCTOR', 'Illegal constructor', TypeError);
|
||||
E('ERR_IMPORT_ASSERTION_TYPE_FAILED',
|
||||
'Module "%s" is not of type "%s"', TypeError);
|
||||
E('ERR_IMPORT_ASSERTION_TYPE_MISSING',
|
||||
'Module "%s" needs an import assertion of type "%s"', TypeError);
|
||||
E('ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
|
||||
'Import assertion type "%s" is unsupported', TypeError);
|
||||
E('ERR_INCOMPATIBLE_OPTION_PAIR',
|
||||
'Option "%s" cannot be used in combination with option "%s"', TypeError);
|
||||
E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' +
|
||||
|
|
|
|||
|
|
@ -1015,9 +1015,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
|
|||
filename,
|
||||
lineOffset: 0,
|
||||
displayErrors: true,
|
||||
importModuleDynamically: async (specifier) => {
|
||||
importModuleDynamically: async (specifier, _, importAssertions) => {
|
||||
const loader = asyncESM.esmLoader;
|
||||
return loader.import(specifier, normalizeReferrerURL(filename));
|
||||
return loader.import(specifier, normalizeReferrerURL(filename),
|
||||
importAssertions);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1030,9 +1031,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
|
|||
'__dirname',
|
||||
], {
|
||||
filename,
|
||||
importModuleDynamically(specifier) {
|
||||
importModuleDynamically(specifier, _, importAssertions) {
|
||||
const loader = asyncESM.esmLoader;
|
||||
return loader.import(specifier, normalizeReferrerURL(filename));
|
||||
return loader.import(specifier, normalizeReferrerURL(filename),
|
||||
importAssertions);
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
|
|||
102
lib/internal/modules/esm/assert.js
Normal file
102
lib/internal/modules/esm/assert.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
ArrayPrototypeIncludes,
|
||||
ObjectCreate,
|
||||
ObjectValues,
|
||||
ObjectPrototypeHasOwnProperty,
|
||||
Symbol,
|
||||
} = primordials;
|
||||
const { validateString } = require('internal/validators');
|
||||
|
||||
const {
|
||||
ERR_IMPORT_ASSERTION_TYPE_FAILED,
|
||||
ERR_IMPORT_ASSERTION_TYPE_MISSING,
|
||||
ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED,
|
||||
} = require('internal/errors').codes;
|
||||
|
||||
const kImplicitAssertType = Symbol('implicit assert type');
|
||||
|
||||
/**
|
||||
* Define a map of module formats to import assertion types (the value of `type`
|
||||
* in `assert { type: 'json' }`).
|
||||
* @type {Map<string, string | typeof kImplicitAssertType}
|
||||
*/
|
||||
const formatTypeMap = {
|
||||
'__proto__': null,
|
||||
'builtin': kImplicitAssertType,
|
||||
'commonjs': kImplicitAssertType,
|
||||
'json': 'json',
|
||||
'module': kImplicitAssertType,
|
||||
'wasm': kImplicitAssertType, // Should probably be 'webassembly' per https://github.com/tc39/proposal-import-assertions
|
||||
};
|
||||
|
||||
/** @type {Array<string, string | typeof kImplicitAssertType} */
|
||||
const supportedAssertionTypes = ObjectValues(formatTypeMap);
|
||||
|
||||
|
||||
/**
|
||||
* Test a module's import assertions.
|
||||
* @param {string} url The URL of the imported module, for error reporting.
|
||||
* @param {string} format One of Node's supported translators
|
||||
* @param {Record<string, string>} importAssertions Validations for the
|
||||
* module import.
|
||||
* @returns {true}
|
||||
* @throws {TypeError} If the format and assertion type are incompatible.
|
||||
*/
|
||||
function validateAssertions(url, format,
|
||||
importAssertions = ObjectCreate(null)) {
|
||||
const validType = formatTypeMap[format];
|
||||
|
||||
switch (validType) {
|
||||
case undefined:
|
||||
// Ignore assertions for module types we don't recognize, to allow new
|
||||
// formats in the future.
|
||||
return true;
|
||||
|
||||
case importAssertions.type:
|
||||
// The asserted type is the valid type for this format.
|
||||
return true;
|
||||
|
||||
case kImplicitAssertType:
|
||||
// This format doesn't allow an import assertion type, so the property
|
||||
// must not be set on the import assertions object.
|
||||
if (!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
|
||||
return true;
|
||||
}
|
||||
return handleInvalidType(url, importAssertions.type);
|
||||
|
||||
default:
|
||||
// There is an expected type for this format, but the value of
|
||||
// `importAssertions.type` was not it.
|
||||
if (!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
|
||||
// `type` wasn't specified at all.
|
||||
throw new ERR_IMPORT_ASSERTION_TYPE_MISSING(url, validType);
|
||||
}
|
||||
handleInvalidType(url, importAssertions.type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw the correct error depending on what's wrong with the type assertion.
|
||||
* @param {string} url The resolved URL for the module to be imported
|
||||
* @param {string} type The value of the import assertion `type` property
|
||||
*/
|
||||
function handleInvalidType(url, type) {
|
||||
// `type` might have not been a string.
|
||||
validateString(type, 'type');
|
||||
|
||||
// `type` was not one of the types we understand.
|
||||
if (!ArrayPrototypeIncludes(supportedAssertionTypes, type)) {
|
||||
throw new ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED(type);
|
||||
}
|
||||
|
||||
// `type` was the wrong value for this format.
|
||||
throw new ERR_IMPORT_ASSERTION_TYPE_FAILED(url, type);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
kImplicitAssertType,
|
||||
validateAssertions,
|
||||
};
|
||||
|
|
@ -3,14 +3,26 @@
|
|||
const { defaultGetFormat } = require('internal/modules/esm/get_format');
|
||||
const { defaultGetSource } = require('internal/modules/esm/get_source');
|
||||
const { translators } = require('internal/modules/esm/translators');
|
||||
const { validateAssertions } = require('internal/modules/esm/assert');
|
||||
|
||||
/**
|
||||
* Node.js default load hook.
|
||||
* @param {string} url
|
||||
* @param {object} context
|
||||
* @returns {object}
|
||||
*/
|
||||
async function defaultLoad(url, context) {
|
||||
let {
|
||||
format,
|
||||
source,
|
||||
} = context;
|
||||
const { importAssertions } = context;
|
||||
|
||||
if (!translators.has(format)) format = defaultGetFormat(url);
|
||||
if (!format || !translators.has(format)) {
|
||||
format = defaultGetFormat(url);
|
||||
}
|
||||
|
||||
validateAssertions(url, format, importAssertions);
|
||||
|
||||
if (
|
||||
format === 'builtin' ||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const {
|
|||
ArrayPrototypePush,
|
||||
FunctionPrototypeBind,
|
||||
FunctionPrototypeCall,
|
||||
ObjectAssign,
|
||||
ObjectCreate,
|
||||
ObjectSetPrototypeOf,
|
||||
PromiseAll,
|
||||
|
|
@ -202,15 +203,16 @@ class ESMLoader {
|
|||
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
|
||||
const module = new ModuleWrap(url, undefined, source, 0, 0);
|
||||
callbackMap.set(module, {
|
||||
importModuleDynamically: (specifier, { url }) => {
|
||||
return this.import(specifier, url);
|
||||
importModuleDynamically: (specifier, { url }, importAssertions) => {
|
||||
return this.import(specifier, url, importAssertions);
|
||||
}
|
||||
});
|
||||
|
||||
return module;
|
||||
};
|
||||
const job = new ModuleJob(this, url, evalInstance, false, false);
|
||||
this.moduleMap.set(url, job);
|
||||
const job = new ModuleJob(
|
||||
this, url, undefined, evalInstance, false, false);
|
||||
this.moduleMap.set(url, undefined, job);
|
||||
const { module } = await job.run();
|
||||
|
||||
return {
|
||||
|
|
@ -218,20 +220,65 @@ class ESMLoader {
|
|||
};
|
||||
}
|
||||
|
||||
async getModuleJob(specifier, parentURL) {
|
||||
const { format, url } = await this.resolve(specifier, parentURL);
|
||||
let job = this.moduleMap.get(url);
|
||||
/**
|
||||
* Get a (possibly still pending) module job from the cache,
|
||||
* or create one and return its Promise.
|
||||
* @param {string} specifier The string after `from` in an `import` statement,
|
||||
* or the first parameter of an `import()`
|
||||
* expression
|
||||
* @param {string | undefined} parentURL The URL of the module importing this
|
||||
* one, unless this is the Node.js entry
|
||||
* point.
|
||||
* @param {Record<string, string>} importAssertions Validations for the
|
||||
* module import.
|
||||
* @returns {Promise<ModuleJob>} The (possibly pending) module job
|
||||
*/
|
||||
async getModuleJob(specifier, parentURL, importAssertions) {
|
||||
let importAssertionsForResolve;
|
||||
if (this.#loaders.length !== 1) {
|
||||
// We can skip cloning if there are no user provided loaders because
|
||||
// the Node.js default resolve hook does not use import assertions.
|
||||
importAssertionsForResolve =
|
||||
ObjectAssign(ObjectCreate(null), importAssertions);
|
||||
}
|
||||
const { format, url } =
|
||||
await this.resolve(specifier, parentURL, importAssertionsForResolve);
|
||||
|
||||
let job = this.moduleMap.get(url, importAssertions.type);
|
||||
|
||||
// CommonJS will set functions for lazy job evaluation.
|
||||
if (typeof job === 'function') this.moduleMap.set(url, job = job());
|
||||
if (typeof job === 'function') {
|
||||
this.moduleMap.set(url, undefined, job = job());
|
||||
}
|
||||
|
||||
if (job !== undefined) return job;
|
||||
if (job === undefined) {
|
||||
job = this.#createModuleJob(url, importAssertions, parentURL, format);
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and cache an object representing a loaded module.
|
||||
* @param {string} url The absolute URL that was resolved for this module
|
||||
* @param {Record<string, string>} importAssertions Validations for the
|
||||
* module import.
|
||||
* @param {string} [parentURL] The absolute URL of the module importing this
|
||||
* one, unless this is the Node.js entry point
|
||||
* @param {string} [format] The format hint possibly returned by the
|
||||
* `resolve` hook
|
||||
* @returns {Promise<ModuleJob>} The (possibly pending) module job
|
||||
*/
|
||||
#createModuleJob(url, importAssertions, parentURL, format) {
|
||||
const moduleProvider = async (url, isMain) => {
|
||||
const { format: finalFormat, source } = await this.load(url, { format });
|
||||
const { format: finalFormat, source } = await this.load(
|
||||
url, { format, importAssertions });
|
||||
|
||||
const translator = translators.get(finalFormat);
|
||||
|
||||
if (!translator) throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat);
|
||||
if (!translator) {
|
||||
throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat);
|
||||
}
|
||||
|
||||
return FunctionPrototypeCall(translator, this, url, source, isMain);
|
||||
};
|
||||
|
|
@ -241,15 +288,16 @@ class ESMLoader {
|
|||
getOptionValue('--inspect-brk')
|
||||
);
|
||||
|
||||
job = new ModuleJob(
|
||||
const job = new ModuleJob(
|
||||
this,
|
||||
url,
|
||||
importAssertions,
|
||||
moduleProvider,
|
||||
parentURL === undefined,
|
||||
inspectBrk
|
||||
);
|
||||
|
||||
this.moduleMap.set(url, job);
|
||||
this.moduleMap.set(url, importAssertions.type, job);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
|
@ -261,11 +309,13 @@ class ESMLoader {
|
|||
* This method must NOT be renamed: it functions as a dynamic import on a
|
||||
* loader module.
|
||||
*
|
||||
* @param {string | string[]} specifiers Path(s) to the module
|
||||
* @param {string} [parentURL] Path of the parent importing the module
|
||||
* @returns {object | object[]} A list of module export(s)
|
||||
* @param {string | string[]} specifiers Path(s) to the module.
|
||||
* @param {string} parentURL Path of the parent importing the module.
|
||||
* @param {Record<string, string>} importAssertions Validations for the
|
||||
* module import.
|
||||
* @returns {Promise<object | object[]>} A list of module export(s).
|
||||
*/
|
||||
async import(specifiers, parentURL) {
|
||||
async import(specifiers, parentURL, importAssertions) {
|
||||
const wasArr = ArrayIsArray(specifiers);
|
||||
if (!wasArr) specifiers = [specifiers];
|
||||
|
||||
|
|
@ -273,7 +323,7 @@ class ESMLoader {
|
|||
const jobs = new Array(count);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
jobs[i] = this.getModuleJob(specifiers[i], parentURL)
|
||||
jobs[i] = this.getModuleJob(specifiers[i], parentURL, importAssertions)
|
||||
.then((job) => job.run())
|
||||
.then(({ module }) => module.getNamespace());
|
||||
}
|
||||
|
|
@ -393,13 +443,16 @@ class ESMLoader {
|
|||
* Resolve the location of the module.
|
||||
*
|
||||
* The internals of this WILL change when chaining is implemented,
|
||||
* depending on the resolution/consensus from #36954
|
||||
* depending on the resolution/consensus from #36954.
|
||||
* @param {string} originalSpecifier The specified URL path of the module to
|
||||
* be resolved
|
||||
* @param {String} parentURL The URL path of the module's parent
|
||||
* @returns {{ url: String }}
|
||||
* be resolved.
|
||||
* @param {string} [parentURL] The URL path of the module's parent.
|
||||
* @param {ImportAssertions} [importAssertions] Assertions from the import
|
||||
* statement or expression.
|
||||
* @returns {{ url: string }}
|
||||
*/
|
||||
async resolve(originalSpecifier, parentURL) {
|
||||
async resolve(originalSpecifier, parentURL,
|
||||
importAssertions = ObjectCreate(null)) {
|
||||
const isMain = parentURL === undefined;
|
||||
|
||||
if (
|
||||
|
|
@ -423,6 +476,7 @@ class ESMLoader {
|
|||
originalSpecifier,
|
||||
{
|
||||
conditions,
|
||||
importAssertions,
|
||||
parentURL,
|
||||
},
|
||||
defaultResolver,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const {
|
|||
ArrayPrototypePush,
|
||||
ArrayPrototypeSome,
|
||||
FunctionPrototype,
|
||||
ObjectCreate,
|
||||
ObjectSetPrototypeOf,
|
||||
PromiseAll,
|
||||
PromiseResolve,
|
||||
|
|
@ -52,8 +53,10 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
|
|||
class ModuleJob {
|
||||
// `loader` is the Loader instance used for loading dependencies.
|
||||
// `moduleProvider` is a function
|
||||
constructor(loader, url, moduleProvider, isMain, inspectBrk) {
|
||||
constructor(loader, url, importAssertions = ObjectCreate(null),
|
||||
moduleProvider, isMain, inspectBrk) {
|
||||
this.loader = loader;
|
||||
this.importAssertions = importAssertions;
|
||||
this.isMain = isMain;
|
||||
this.inspectBrk = inspectBrk;
|
||||
|
||||
|
|
@ -72,8 +75,8 @@ class ModuleJob {
|
|||
// so that circular dependencies can't cause a deadlock by two of
|
||||
// these `link` callbacks depending on each other.
|
||||
const dependencyJobs = [];
|
||||
const promises = this.module.link(async (specifier) => {
|
||||
const jobPromise = this.loader.getModuleJob(specifier, url);
|
||||
const promises = this.module.link(async (specifier, assertions) => {
|
||||
const jobPromise = this.loader.getModuleJob(specifier, url, assertions);
|
||||
ArrayPrototypePush(dependencyJobs, jobPromise);
|
||||
const job = await jobPromise;
|
||||
return job.modulePromise;
|
||||
|
|
@ -144,7 +147,14 @@ class ModuleJob {
|
|||
const { url: childFileURL } = await this.loader.resolve(
|
||||
childSpecifier, parentFileUrl,
|
||||
);
|
||||
const { format } = await this.loader.load(childFileURL);
|
||||
let format;
|
||||
try {
|
||||
// This might throw for non-CommonJS modules because we aren't passing
|
||||
// in the import assertions and some formats require them; but we only
|
||||
// care about CommonJS for the purposes of this error message.
|
||||
({ format } =
|
||||
await this.loader.load(childFileURL));
|
||||
} catch {}
|
||||
|
||||
if (format === 'commonjs') {
|
||||
const importStatement = splitStack[1];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
const ModuleJob = require('internal/modules/esm/module_job');
|
||||
const { kImplicitAssertType } = require('internal/modules/esm/assert');
|
||||
const {
|
||||
ObjectCreate,
|
||||
SafeMap,
|
||||
} = primordials;
|
||||
let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
|
||||
|
|
@ -10,25 +12,35 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
|
|||
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
|
||||
const { validateString } = require('internal/validators');
|
||||
|
||||
const validateAssertType = (type) =>
|
||||
type === kImplicitAssertType || validateString(type, 'type');
|
||||
|
||||
// Tracks the state of the loader-level module cache
|
||||
class ModuleMap extends SafeMap {
|
||||
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
|
||||
get(url) {
|
||||
get(url, type = kImplicitAssertType) {
|
||||
validateString(url, 'url');
|
||||
return super.get(url);
|
||||
validateAssertType(type);
|
||||
return super.get(url)?.[type];
|
||||
}
|
||||
set(url, job) {
|
||||
set(url, type = kImplicitAssertType, job) {
|
||||
validateString(url, 'url');
|
||||
validateAssertType(type);
|
||||
if (job instanceof ModuleJob !== true &&
|
||||
typeof job !== 'function') {
|
||||
throw new ERR_INVALID_ARG_TYPE('job', 'ModuleJob', job);
|
||||
}
|
||||
debug(`Storing ${url} in ModuleMap`);
|
||||
return super.set(url, job);
|
||||
debug(`Storing ${url} (${
|
||||
type === kImplicitAssertType ? 'implicit type' : type
|
||||
}) in ModuleMap`);
|
||||
const cachedJobsForUrl = super.get(url) ?? ObjectCreate(null);
|
||||
cachedJobsForUrl[type] = job;
|
||||
return super.set(url, cachedJobsForUrl);
|
||||
}
|
||||
has(url) {
|
||||
has(url, type = kImplicitAssertType) {
|
||||
validateString(url, 'url');
|
||||
return super.has(url);
|
||||
validateAssertType(type);
|
||||
return super.get(url)?.[type] !== undefined;
|
||||
}
|
||||
}
|
||||
module.exports = ModuleMap;
|
||||
|
|
|
|||
|
|
@ -107,8 +107,8 @@ function errPath(url) {
|
|||
return url;
|
||||
}
|
||||
|
||||
async function importModuleDynamically(specifier, { url }) {
|
||||
return asyncESM.esmLoader.import(specifier, url);
|
||||
async function importModuleDynamically(specifier, { url }, assertions) {
|
||||
return asyncESM.esmLoader.import(specifier, url, assertions);
|
||||
}
|
||||
|
||||
function createImportMetaResolve(defaultParentUrl) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
ObjectCreate,
|
||||
StringPrototypeEndsWith,
|
||||
} = primordials;
|
||||
const CJSLoader = require('internal/modules/cjs/loader');
|
||||
|
|
@ -46,9 +47,8 @@ function runMainESM(mainPath) {
|
|||
|
||||
handleMainPromise(loadESM((esmLoader) => {
|
||||
const main = path.isAbsolute(mainPath) ?
|
||||
pathToFileURL(mainPath).href :
|
||||
mainPath;
|
||||
return esmLoader.import(main);
|
||||
pathToFileURL(mainPath).href : mainPath;
|
||||
return esmLoader.import(main, undefined, ObjectCreate(null));
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@ function evalScript(name, body, breakFirstLine, print) {
|
|||
filename: name,
|
||||
displayErrors: true,
|
||||
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
|
||||
async importModuleDynamically(specifier) {
|
||||
const loader = await asyncESM.esmLoader;
|
||||
return loader.import(specifier, baseUrl);
|
||||
importModuleDynamically(specifier, _, importAssertions) {
|
||||
const loader = asyncESM.esmLoader;
|
||||
return loader.import(specifier, baseUrl, importAssertions);
|
||||
}
|
||||
}));
|
||||
if (print) {
|
||||
|
|
|
|||
10
lib/repl.js
10
lib/repl.js
|
|
@ -454,8 +454,9 @@ function REPLServer(prompt,
|
|||
vm.createScript(fallbackCode, {
|
||||
filename: file,
|
||||
displayErrors: true,
|
||||
importModuleDynamically: async (specifier) => {
|
||||
return asyncESM.esmLoader.import(specifier, parentURL);
|
||||
importModuleDynamically: (specifier, _, importAssertions) => {
|
||||
return asyncESM.esmLoader.import(specifier, parentURL,
|
||||
importAssertions);
|
||||
}
|
||||
});
|
||||
} catch (fallbackError) {
|
||||
|
|
@ -496,8 +497,9 @@ function REPLServer(prompt,
|
|||
script = vm.createScript(code, {
|
||||
filename: file,
|
||||
displayErrors: true,
|
||||
importModuleDynamically: async (specifier) => {
|
||||
return asyncESM.esmLoader.import(specifier, parentURL);
|
||||
importModuleDynamically: (specifier, _, importAssertions) => {
|
||||
return asyncESM.esmLoader.import(specifier, parentURL,
|
||||
importAssertions);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -803,6 +803,13 @@ int ProcessGlobalArgs(std::vector<std::string>* args,
|
|||
return 12;
|
||||
}
|
||||
|
||||
// TODO(aduh95): remove this when the harmony-import-assertions flag
|
||||
// is removed in V8.
|
||||
if (std::find(v8_args.begin(), v8_args.end(),
|
||||
"--no-harmony-import-assertions") == v8_args.end()) {
|
||||
v8_args.push_back("--harmony-import-assertions");
|
||||
}
|
||||
|
||||
auto env_opts = per_process::cli_options->per_isolate->per_env;
|
||||
if (std::find(v8_args.begin(), v8_args.end(),
|
||||
"--abort-on-uncaught-exception") != v8_args.end() ||
|
||||
|
|
|
|||
81
test/es-module/test-esm-assertionless-json-import.js
Normal file
81
test/es-module/test-esm-assertionless-json-import.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Flags: --experimental-json-modules --experimental-loader ./test/fixtures/es-module-loaders/assertionless-json-import.mjs
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { strictEqual } = require('assert');
|
||||
|
||||
async function test() {
|
||||
{
|
||||
const [secret0, secret1] = await Promise.all([
|
||||
import('../fixtures/experimental.json'),
|
||||
import(
|
||||
'../fixtures/experimental.json',
|
||||
{ assert: { type: 'json' } }
|
||||
),
|
||||
]);
|
||||
|
||||
strictEqual(secret0.default.ofLife, 42);
|
||||
strictEqual(secret1.default.ofLife, 42);
|
||||
strictEqual(secret0.default, secret1.default);
|
||||
strictEqual(secret0, secret1);
|
||||
}
|
||||
|
||||
{
|
||||
const [secret0, secret1] = await Promise.all([
|
||||
import('../fixtures/experimental.json?test'),
|
||||
import(
|
||||
'../fixtures/experimental.json?test',
|
||||
{ assert: { type: 'json' } }
|
||||
),
|
||||
]);
|
||||
|
||||
strictEqual(secret0.default.ofLife, 42);
|
||||
strictEqual(secret1.default.ofLife, 42);
|
||||
strictEqual(secret0.default, secret1.default);
|
||||
strictEqual(secret0, secret1);
|
||||
}
|
||||
|
||||
{
|
||||
const [secret0, secret1] = await Promise.all([
|
||||
import('../fixtures/experimental.json#test'),
|
||||
import(
|
||||
'../fixtures/experimental.json#test',
|
||||
{ assert: { type: 'json' } }
|
||||
),
|
||||
]);
|
||||
|
||||
strictEqual(secret0.default.ofLife, 42);
|
||||
strictEqual(secret1.default.ofLife, 42);
|
||||
strictEqual(secret0.default, secret1.default);
|
||||
strictEqual(secret0, secret1);
|
||||
}
|
||||
|
||||
{
|
||||
const [secret0, secret1] = await Promise.all([
|
||||
import('../fixtures/experimental.json?test2#test'),
|
||||
import(
|
||||
'../fixtures/experimental.json?test2#test',
|
||||
{ assert: { type: 'json' } }
|
||||
),
|
||||
]);
|
||||
|
||||
strictEqual(secret0.default.ofLife, 42);
|
||||
strictEqual(secret1.default.ofLife, 42);
|
||||
strictEqual(secret0.default, secret1.default);
|
||||
strictEqual(secret0, secret1);
|
||||
}
|
||||
|
||||
{
|
||||
const [secret0, secret1] = await Promise.all([
|
||||
import('data:application/json,{"ofLife":42}'),
|
||||
import(
|
||||
'data:application/json,{"ofLife":42}',
|
||||
{ assert: { type: 'json' } }
|
||||
),
|
||||
]);
|
||||
|
||||
strictEqual(secret0.default.ofLife, 42);
|
||||
strictEqual(secret1.default.ofLife, 42);
|
||||
}
|
||||
}
|
||||
|
||||
test().then(common.mustCall());
|
||||
|
|
@ -59,21 +59,22 @@ function createBase64URL(mime, body) {
|
|||
assert.strictEqual(ns.default, plainESMURL);
|
||||
}
|
||||
{
|
||||
const ns = await import('data:application/json;foo="test,"this"');
|
||||
const ns = await import('data:application/json;foo="test,"this"',
|
||||
{ assert: { type: 'json' } });
|
||||
assert.deepStrictEqual(Object.keys(ns), ['default']);
|
||||
assert.strictEqual(ns.default, 'this');
|
||||
}
|
||||
{
|
||||
const ns = await import(`data:application/json;foo=${
|
||||
encodeURIComponent('test,')
|
||||
},0`);
|
||||
},0`, { assert: { type: 'json' } });
|
||||
assert.deepStrictEqual(Object.keys(ns), ['default']);
|
||||
assert.strictEqual(ns.default, 0);
|
||||
}
|
||||
{
|
||||
await assert.rejects(async () => {
|
||||
return import('data:application/json;foo="test,",0');
|
||||
}, {
|
||||
await assert.rejects(async () =>
|
||||
import('data:application/json;foo="test,",0',
|
||||
{ assert: { type: 'json' } }), {
|
||||
name: 'SyntaxError',
|
||||
message: /Unexpected end of JSON input/
|
||||
});
|
||||
|
|
@ -81,14 +82,14 @@ function createBase64URL(mime, body) {
|
|||
{
|
||||
const body = '{"x": 1}';
|
||||
const plainESMURL = createURL('application/json', body);
|
||||
const ns = await import(plainESMURL);
|
||||
const ns = await import(plainESMURL, { assert: { type: 'json' } });
|
||||
assert.deepStrictEqual(Object.keys(ns), ['default']);
|
||||
assert.strictEqual(ns.default.x, 1);
|
||||
}
|
||||
{
|
||||
const body = '{"default": 2}';
|
||||
const plainESMURL = createURL('application/json', body);
|
||||
const ns = await import(plainESMURL);
|
||||
const ns = await import(plainESMURL, { assert: { type: 'json' } });
|
||||
assert.deepStrictEqual(Object.keys(ns), ['default']);
|
||||
assert.strictEqual(ns.default.default, 2);
|
||||
}
|
||||
|
|
|
|||
48
test/es-module/test-esm-dynamic-import-assertion.js
Normal file
48
test/es-module/test-esm-dynamic-import-assertion.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Flags: --experimental-json-modules
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { strictEqual } = require('assert');
|
||||
|
||||
async function test() {
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.js', { assert: { type: 'json' } }),
|
||||
import('../fixtures/empty.js'),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'rejected');
|
||||
strictEqual(results[1].status, 'fulfilled');
|
||||
}
|
||||
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.js'),
|
||||
import('../fixtures/empty.js', { assert: { type: 'json' } }),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'fulfilled');
|
||||
strictEqual(results[1].status, 'rejected');
|
||||
}
|
||||
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.json', { assert: { type: 'json' } }),
|
||||
import('../fixtures/empty.json'),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'fulfilled');
|
||||
strictEqual(results[1].status, 'rejected');
|
||||
}
|
||||
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.json'),
|
||||
import('../fixtures/empty.json', { assert: { type: 'json' } }),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'rejected');
|
||||
strictEqual(results[1].status, 'fulfilled');
|
||||
}
|
||||
}
|
||||
|
||||
test().then(common.mustCall());
|
||||
43
test/es-module/test-esm-dynamic-import-assertion.mjs
Normal file
43
test/es-module/test-esm-dynamic-import-assertion.mjs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Flags: --experimental-json-modules
|
||||
import '../common/index.mjs';
|
||||
import { strictEqual } from 'assert';
|
||||
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.js', { assert: { type: 'json' } }),
|
||||
import('../fixtures/empty.js'),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'rejected');
|
||||
strictEqual(results[1].status, 'fulfilled');
|
||||
}
|
||||
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.js'),
|
||||
import('../fixtures/empty.js', { assert: { type: 'json' } }),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'fulfilled');
|
||||
strictEqual(results[1].status, 'rejected');
|
||||
}
|
||||
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.json', { assert: { type: 'json' } }),
|
||||
import('../fixtures/empty.json'),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'fulfilled');
|
||||
strictEqual(results[1].status, 'rejected');
|
||||
}
|
||||
|
||||
{
|
||||
const results = await Promise.allSettled([
|
||||
import('../fixtures/empty.json'),
|
||||
import('../fixtures/empty.json', { assert: { type: 'json' } }),
|
||||
]);
|
||||
|
||||
strictEqual(results[0].status, 'rejected');
|
||||
strictEqual(results[1].status, 'fulfilled');
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Flags: --experimental-json-modules --harmony-import-assertions
|
||||
// Flags: --experimental-json-modules
|
||||
import '../common/index.mjs';
|
||||
import { strictEqual } from 'assert';
|
||||
|
||||
|
|
|
|||
8
test/es-module/test-esm-import-assertion-2.mjs
Normal file
8
test/es-module/test-esm-import-assertion-2.mjs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Flags: --experimental-json-modules
|
||||
import '../common/index.mjs';
|
||||
import { strictEqual } from 'assert';
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
import secret from '../fixtures/experimental.json' assert { type: 'json', unsupportedAssertion: 'should ignore' };
|
||||
|
||||
strictEqual(secret.ofLife, 42);
|
||||
11
test/es-module/test-esm-import-assertion-3.mjs
Normal file
11
test/es-module/test-esm-import-assertion-3.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Flags: --experimental-json-modules
|
||||
import '../common/index.mjs';
|
||||
import { strictEqual } from 'assert';
|
||||
|
||||
import secret0 from '../fixtures/experimental.json' assert { type: 'json' };
|
||||
const secret1 = await import('../fixtures/experimental.json',
|
||||
{ assert: { type: 'json' } });
|
||||
|
||||
strictEqual(secret0.ofLife, 42);
|
||||
strictEqual(secret1.default.ofLife, 42);
|
||||
strictEqual(secret1.default, secret0);
|
||||
12
test/es-module/test-esm-import-assertion-4.mjs
Normal file
12
test/es-module/test-esm-import-assertion-4.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Flags: --experimental-json-modules
|
||||
import '../common/index.mjs';
|
||||
import { strictEqual } from 'assert';
|
||||
|
||||
import secret0 from '../fixtures/experimental.json' assert { type: 'json' };
|
||||
const secret1 = await import('../fixtures/experimental.json', {
|
||||
assert: { type: 'json' },
|
||||
});
|
||||
|
||||
strictEqual(secret0.ofLife, 42);
|
||||
strictEqual(secret1.default.ofLife, 42);
|
||||
strictEqual(secret1.default, secret0);
|
||||
53
test/es-module/test-esm-import-assertion-errors.js
Normal file
53
test/es-module/test-esm-import-assertion-errors.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Flags: --experimental-json-modules
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { rejects } = require('assert');
|
||||
|
||||
const jsModuleDataUrl = 'data:text/javascript,export{}';
|
||||
const jsonModuleDataUrl = 'data:application/json,""';
|
||||
|
||||
async function test() {
|
||||
await rejects(
|
||||
// This rejects because of the unsupported MIME type, not because of the
|
||||
// unsupported assertion.
|
||||
import('data:text/css,', { assert: { type: 'css' } }),
|
||||
{ code: 'ERR_INVALID_MODULE_SPECIFIER' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsModuleDataUrl, { assert: { type: 'json' } }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import('data:text/javascript,', { assert: { type: 'unsupported' } }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl, { assert: {} }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl, { assert: { type: 'unsupported' }}),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
|
||||
);
|
||||
}
|
||||
|
||||
test().then(common.mustCall());
|
||||
48
test/es-module/test-esm-import-assertion-errors.mjs
Normal file
48
test/es-module/test-esm-import-assertion-errors.mjs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Flags: --experimental-json-modules
|
||||
import '../common/index.mjs';
|
||||
import { rejects } from 'assert';
|
||||
|
||||
const jsModuleDataUrl = 'data:text/javascript,export{}';
|
||||
const jsonModuleDataUrl = 'data:application/json,""';
|
||||
|
||||
await rejects(
|
||||
// This rejects because of the unsupported MIME type, not because of the
|
||||
// unsupported assertion.
|
||||
import('data:text/css,', { assert: { type: 'css' } }),
|
||||
{ code: 'ERR_INVALID_MODULE_SPECIFIER' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsModuleDataUrl, { assert: { type: 'json' } }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(import.meta.url, { assert: { type: 'unsupported' } }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl, { assert: {} }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
|
||||
);
|
||||
|
||||
await rejects(
|
||||
import(jsonModuleDataUrl, { assert: { type: 'unsupported' }}),
|
||||
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
|
||||
);
|
||||
37
test/es-module/test-esm-import-assertion-validation.js
Normal file
37
test/es-module/test-esm-import-assertion-validation.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const { validateAssertions } = require('internal/modules/esm/assert');
|
||||
|
||||
const url = 'test://';
|
||||
|
||||
assert.ok(validateAssertions(url, 'builtin', {}));
|
||||
assert.ok(validateAssertions(url, 'commonjs', {}));
|
||||
assert.ok(validateAssertions(url, 'json', { type: 'json' }));
|
||||
assert.ok(validateAssertions(url, 'module', {}));
|
||||
assert.ok(validateAssertions(url, 'wasm', {}));
|
||||
|
||||
assert.throws(() => validateAssertions(url, 'json', {}), {
|
||||
code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING',
|
||||
});
|
||||
|
||||
assert.throws(() => validateAssertions(url, 'module', { type: 'json' }), {
|
||||
code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED',
|
||||
});
|
||||
|
||||
// This should be allowed according to HTML spec. Let's keep it disabled
|
||||
// until WASM module import is sorted out.
|
||||
assert.throws(() => validateAssertions(url, 'module', { type: 'javascript' }), {
|
||||
code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
|
||||
});
|
||||
|
||||
assert.throws(() => validateAssertions(url, 'module', { type: 'css' }), {
|
||||
code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
|
||||
});
|
||||
|
||||
assert.throws(() => validateAssertions(url, 'module', { type: false }), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
|
|
@ -7,7 +7,8 @@ import { createRequire } from 'module';
|
|||
|
||||
import mod from '../fixtures/es-modules/json-cache/mod.cjs';
|
||||
import another from '../fixtures/es-modules/json-cache/another.cjs';
|
||||
import test from '../fixtures/es-modules/json-cache/test.json';
|
||||
import test from '../fixtures/es-modules/json-cache/test.json' assert
|
||||
{ type: 'json' };
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { path } from '../common/fixtures.mjs';
|
|||
import { strictEqual, ok } from 'assert';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import secret from '../fixtures/experimental.json';
|
||||
import secret from '../fixtures/experimental.json' assert { type: 'json' };
|
||||
|
||||
strictEqual(secret.ofLife, 42);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,61 +1,101 @@
|
|||
'use strict';
|
||||
// Flags: --expose-internals
|
||||
|
||||
// This test ensures that the type checking of ModuleMap throws
|
||||
// errors appropriately
|
||||
|
||||
require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const { strictEqual, throws } = require('assert');
|
||||
const { ESMLoader } = require('internal/modules/esm/loader');
|
||||
const ModuleMap = require('internal/modules/esm/module_map');
|
||||
const ModuleJob = require('internal/modules/esm/module_job');
|
||||
const { kImplicitAssertType } = require('internal/modules/esm/assert');
|
||||
const createDynamicModule = require(
|
||||
'internal/modules/esm/create_dynamic_module');
|
||||
|
||||
const stubModuleUrl = new URL('file://tmp/test');
|
||||
const stubModule = createDynamicModule(['default'], stubModuleUrl);
|
||||
const jsModuleDataUrl = 'data:text/javascript,export{}';
|
||||
const jsonModuleDataUrl = 'data:application/json,""';
|
||||
|
||||
const stubJsModule = createDynamicModule([], ['default'], jsModuleDataUrl);
|
||||
const stubJsonModule = createDynamicModule([], ['default'], jsonModuleDataUrl);
|
||||
|
||||
const loader = new ESMLoader();
|
||||
const moduleMap = new ModuleMap();
|
||||
const moduleJob = new ModuleJob(loader, stubModule.module,
|
||||
() => new Promise(() => {}));
|
||||
const jsModuleJob = new ModuleJob(loader, stubJsModule.module, undefined,
|
||||
() => new Promise(() => {}));
|
||||
const jsonModuleJob = new ModuleJob(loader, stubJsonModule.module,
|
||||
{ type: 'json' },
|
||||
() => new Promise(() => {}));
|
||||
|
||||
assert.throws(
|
||||
() => moduleMap.get(1),
|
||||
{
|
||||
|
||||
// ModuleMap.set and ModuleMap.get store and retrieve module jobs for a
|
||||
// specified url/type tuple; ModuleMap.has correctly reports whether such jobs
|
||||
// are stored in the map.
|
||||
{
|
||||
const moduleMap = new ModuleMap();
|
||||
|
||||
moduleMap.set(jsModuleDataUrl, undefined, jsModuleJob);
|
||||
moduleMap.set(jsonModuleDataUrl, 'json', jsonModuleJob);
|
||||
|
||||
strictEqual(moduleMap.get(jsModuleDataUrl), jsModuleJob);
|
||||
strictEqual(moduleMap.get(jsonModuleDataUrl, 'json'), jsonModuleJob);
|
||||
|
||||
strictEqual(moduleMap.has(jsModuleDataUrl), true);
|
||||
strictEqual(moduleMap.has(jsModuleDataUrl, kImplicitAssertType), true);
|
||||
strictEqual(moduleMap.has(jsonModuleDataUrl, 'json'), true);
|
||||
|
||||
strictEqual(moduleMap.has('unknown'), false);
|
||||
|
||||
// The types must match
|
||||
strictEqual(moduleMap.has(jsModuleDataUrl, 'json'), false);
|
||||
strictEqual(moduleMap.has(jsonModuleDataUrl, kImplicitAssertType), false);
|
||||
strictEqual(moduleMap.has(jsonModuleDataUrl), false);
|
||||
strictEqual(moduleMap.has(jsModuleDataUrl, 'unknown'), false);
|
||||
strictEqual(moduleMap.has(jsonModuleDataUrl, 'unknown'), false);
|
||||
}
|
||||
|
||||
// ModuleMap.get, ModuleMap.has and ModuleMap.set should only accept string
|
||||
// values as url argument.
|
||||
{
|
||||
const moduleMap = new ModuleMap();
|
||||
|
||||
const errorObj = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "url" argument must be of type string. Received type number' +
|
||||
' (1)'
|
||||
}
|
||||
);
|
||||
message: /^The "url" argument must be of type string/
|
||||
};
|
||||
|
||||
assert.throws(
|
||||
() => moduleMap.set(1, moduleJob),
|
||||
{
|
||||
[{}, [], true, 1].forEach((value) => {
|
||||
throws(() => moduleMap.get(value), errorObj);
|
||||
throws(() => moduleMap.has(value), errorObj);
|
||||
throws(() => moduleMap.set(value, undefined, jsModuleJob), errorObj);
|
||||
});
|
||||
}
|
||||
|
||||
// ModuleMap.get, ModuleMap.has and ModuleMap.set should only accept string
|
||||
// values (or the kAssertType symbol) as type argument.
|
||||
{
|
||||
const moduleMap = new ModuleMap();
|
||||
|
||||
const errorObj = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "url" argument must be of type string. Received type number' +
|
||||
' (1)'
|
||||
}
|
||||
);
|
||||
message: /^The "type" argument must be of type string/
|
||||
};
|
||||
|
||||
assert.throws(
|
||||
() => moduleMap.set('somestring', 'notamodulejob'),
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "job" argument must be an instance of ModuleJob. ' +
|
||||
"Received type string ('notamodulejob')"
|
||||
}
|
||||
);
|
||||
[{}, [], true, 1].forEach((value) => {
|
||||
throws(() => moduleMap.get(jsModuleDataUrl, value), errorObj);
|
||||
throws(() => moduleMap.has(jsModuleDataUrl, value), errorObj);
|
||||
throws(() => moduleMap.set(jsModuleDataUrl, value, jsModuleJob), errorObj);
|
||||
});
|
||||
}
|
||||
|
||||
assert.throws(
|
||||
() => moduleMap.has(1),
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "url" argument must be of type string. Received type number' +
|
||||
' (1)'
|
||||
}
|
||||
);
|
||||
// ModuleMap.set should only accept ModuleJob values as job argument.
|
||||
{
|
||||
const moduleMap = new ModuleMap();
|
||||
|
||||
[{}, [], true, 1].forEach((value) => {
|
||||
throws(() => moduleMap.set('', undefined, value), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: /^The "job" argument must be an instance of ModuleJob/
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
1
test/fixtures/empty.json
vendored
Normal file
1
test/fixtures/empty.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
17
test/fixtures/es-module-loaders/assertionless-json-import.mjs
vendored
Normal file
17
test/fixtures/es-module-loaders/assertionless-json-import.mjs
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const DATA_URL_PATTERN = /^data:application\/json(?:[^,]*?)(;base64)?,([\s\S]*)$/;
|
||||
const JSON_URL_PATTERN = /\.json(\?[^#]*)?(#.*)?$/;
|
||||
|
||||
export function resolve(url, context, next) {
|
||||
// Mutation from resolve hook should be discarded.
|
||||
context.importAssertions.type = 'whatever';
|
||||
return next(url, context);
|
||||
}
|
||||
|
||||
export function load(url, context, next) {
|
||||
if (context.importAssertions.type == null &&
|
||||
(DATA_URL_PATTERN.test(url) || JSON_URL_PATTERN.test(url))) {
|
||||
const { importAssertions } = context;
|
||||
importAssertions.type = 'json';
|
||||
}
|
||||
return next(url, context);
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ export function resolve(specifier, context, next) {
|
|||
if (def.url.startsWith('node:')) {
|
||||
return {
|
||||
url: `custom-${def.url}`,
|
||||
importAssertions: context.importAssertions,
|
||||
};
|
||||
}
|
||||
return def;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ export function resolve(specifier, context, next) {
|
|||
if (specifier.startsWith('esmHook')) return {
|
||||
format,
|
||||
url: specifier,
|
||||
importAssertions: context.importAssertions,
|
||||
};
|
||||
|
||||
return next(specifier, context, next);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export async function resolve(specifier, { parentURL }, defaultResolve) {
|
||||
export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
|
||||
if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') {
|
||||
return {
|
||||
url: 'file:///asdf'
|
||||
};
|
||||
}
|
||||
return defaultResolve(specifier, {parentURL}, defaultResolve);
|
||||
return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
|
||||
}
|
||||
|
||||
export async function load(url, context, next) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
/* eslint-disable node-core/required-modules */
|
||||
export async function resolve(specifier, { parentURL }, defaultResolve) {
|
||||
export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
|
||||
if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') {
|
||||
return {
|
||||
url: specifier
|
||||
url: specifier,
|
||||
importAssertions,
|
||||
};
|
||||
}
|
||||
return defaultResolve(specifier, {parentURL}, defaultResolve);
|
||||
return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import assert from 'assert';
|
||||
|
||||
import {createRequire} from '../../common/index.mjs';
|
||||
import { createRequire } from '../../common/index.mjs';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const dep = require('./loader-dep.js');
|
||||
|
||||
export function resolve(specifier, { parentURL }, defaultResolve) {
|
||||
export function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
|
||||
assert.strictEqual(dep.format, 'module');
|
||||
return defaultResolve(specifier, {parentURL}, defaultResolve);
|
||||
return defaultResolve(specifier, { parentURL, importAssertions }, defaultResolve);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import {createRequire} from '../../common/index.mjs';
|
|||
const require = createRequire(import.meta.url);
|
||||
const dep = require('./loader-dep.js');
|
||||
|
||||
export function resolve (specifier, { parentURL }, defaultResolve) {
|
||||
export function resolve (specifier, { parentURL, importAssertions }, defaultResolve) {
|
||||
return {
|
||||
url: defaultResolve(specifier, {parentURL}, defaultResolve).url,
|
||||
url: defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve).url,
|
||||
format: dep.format
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,19 @@ import assert from 'assert';
|
|||
// a loader that asserts that the defaultResolve will throw "not found"
|
||||
// (skipping the top-level main of course)
|
||||
let mainLoad = true;
|
||||
export async function resolve(specifier, { parentURL }, defaultResolve) {
|
||||
export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
|
||||
if (mainLoad) {
|
||||
mainLoad = false;
|
||||
return defaultResolve(specifier, {parentURL}, defaultResolve);
|
||||
return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
|
||||
}
|
||||
try {
|
||||
await defaultResolve(specifier, {parentURL}, defaultResolve);
|
||||
await defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
|
||||
}
|
||||
catch (e) {
|
||||
assert.strictEqual(e.code, 'ERR_MODULE_NOT_FOUND');
|
||||
return {
|
||||
url: 'node:fs'
|
||||
url: 'node:fs',
|
||||
importAssertions,
|
||||
};
|
||||
}
|
||||
assert.fail(`Module resolution for ${specifier} should be throw ERR_MODULE_NOT_FOUND`);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const SOURCES = {
|
|||
}
|
||||
export function resolve(specifier, context, next) {
|
||||
if (specifier.startsWith('test:')) {
|
||||
return { url: specifier };
|
||||
return { url: specifier, importAssertions: context.importAssertions };
|
||||
}
|
||||
return next(specifier, context);
|
||||
}
|
||||
|
|
|
|||
2
test/fixtures/es-modules/json-modules.mjs
vendored
2
test/fixtures/es-modules/json-modules.mjs
vendored
|
|
@ -1 +1 @@
|
|||
import secret from '../experimental.json';
|
||||
import secret from '../experimental.json' assert { type: 'json' };
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
// Flags: --experimental-json-modules
|
||||
/* eslint-disable no-unused-vars */
|
||||
import '../common/index.mjs';
|
||||
import { ofLife } from '../fixtures/experimental.json' assert { type: 'json' };
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
file:///*/test/message/esm_display_syntax_error_import_json_named_export.mjs:*
|
||||
import { ofLife } from '../fixtures/experimental.json' assert { type: 'json' };
|
||||
^^^^^^
|
||||
SyntaxError: The requested module '../fixtures/experimental.json' does not provide an export named 'ofLife'
|
||||
at ModuleJob._instantiate (node:internal/modules/esm/module_job:*:*)
|
||||
at async ModuleJob.run (node:internal/modules/esm/module_job:*:*)
|
||||
at async Promise.all (index 0)
|
||||
at async ESMLoader.import (node:internal/modules/esm/loader:*:*)
|
||||
at async loadESM (node:internal/process/esm_loader:*:*)
|
||||
at async handleMainPromise (node:internal/modules/run_main:*:*)
|
||||
|
||||
Node.js *
|
||||
2
test/message/esm_import_assertion_failed.mjs
Normal file
2
test/message/esm_import_assertion_failed.mjs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import '../common/index.mjs';
|
||||
import 'data:text/javascript,export{}' assert {type:'json'};
|
||||
18
test/message/esm_import_assertion_failed.out
Normal file
18
test/message/esm_import_assertion_failed.out
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
node:internal/errors:*
|
||||
ErrorCaptureStackTrace(err);
|
||||
^
|
||||
|
||||
TypeError [ERR_IMPORT_ASSERTION_TYPE_FAILED]: Module "data:text/javascript,export{}" is not of type "json"
|
||||
at new NodeError (node:internal/errors:*:*)
|
||||
at handleInvalidType (node:internal/modules/esm/assert:*:*)
|
||||
at validateAssertions (node:internal/modules/esm/assert:*:*)
|
||||
at defaultLoad (node:internal/modules/esm/load:*:*)
|
||||
at ESMLoader.load (node:internal/modules/esm/loader:*:*)
|
||||
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*)
|
||||
at new ModuleJob (node:internal/modules/esm/module_job:*:*)
|
||||
at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*)
|
||||
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*)
|
||||
at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:*:*) {
|
||||
code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED'
|
||||
}
|
||||
Node.js *
|
||||
3
test/message/esm_import_assertion_missing.mjs
Normal file
3
test/message/esm_import_assertion_missing.mjs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// Flags: --experimental-json-modules
|
||||
import '../common/index.mjs';
|
||||
import 'data:application/json,{}';
|
||||
19
test/message/esm_import_assertion_missing.out
Normal file
19
test/message/esm_import_assertion_missing.out
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
node:internal/errors:*
|
||||
ErrorCaptureStackTrace(err);
|
||||
^
|
||||
|
||||
TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "data:application/json,{}" needs an import assertion of type "json"
|
||||
at new NodeError (node:internal/errors:*:*)
|
||||
at validateAssertions (node:internal/modules/esm/assert:*:*)
|
||||
at defaultLoad (node:internal/modules/esm/load:*:*)
|
||||
at ESMLoader.load (node:internal/modules/esm/loader:*:*)
|
||||
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*)
|
||||
at new ModuleJob (node:internal/modules/esm/module_job:*:*)
|
||||
at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*)
|
||||
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*)
|
||||
at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:*:*)
|
||||
at async Promise.all (index *) {
|
||||
code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
|
||||
}
|
||||
|
||||
Node.js *
|
||||
2
test/message/esm_import_assertion_unsupported.mjs
Normal file
2
test/message/esm_import_assertion_unsupported.mjs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import '../common/index.mjs';
|
||||
import '../fixtures/empty.js' assert { type: 'unsupported' };
|
||||
19
test/message/esm_import_assertion_unsupported.out
Normal file
19
test/message/esm_import_assertion_unsupported.out
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
node:internal/errors:*
|
||||
ErrorCaptureStackTrace(err);
|
||||
^
|
||||
|
||||
TypeError [ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED]: Import assertion type "unsupported" is unsupported
|
||||
at new NodeError (node:internal/errors:*:*)
|
||||
at handleInvalidType (node:internal/modules/esm/assert:*:*)
|
||||
at validateAssertions (node:internal/modules/esm/assert:*:*)
|
||||
at defaultLoad (node:internal/modules/esm/load:*:*)
|
||||
at ESMLoader.load (node:internal/modules/esm/loader:*:*)
|
||||
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*)
|
||||
at new ModuleJob (node:internal/modules/esm/module_job:*:*)
|
||||
at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*)
|
||||
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*)
|
||||
at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:*:*) {
|
||||
code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED'
|
||||
}
|
||||
|
||||
Node.js *
|
||||
|
|
@ -68,6 +68,7 @@ const expectedModules = new Set([
|
|||
'NativeModule internal/modules/package_json_reader',
|
||||
'NativeModule internal/modules/cjs/helpers',
|
||||
'NativeModule internal/modules/cjs/loader',
|
||||
'NativeModule internal/modules/esm/assert',
|
||||
'NativeModule internal/modules/esm/create_dynamic_module',
|
||||
'NativeModule internal/modules/esm/get_format',
|
||||
'NativeModule internal/modules/esm/get_source',
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const ModuleMap = require('internal/modules/esm/module_map');
|
||||
|
||||
// ModuleMap.get, ModuleMap.has and ModuleMap.set should only accept string
|
||||
// values as url argument.
|
||||
{
|
||||
const errorObj = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: /^The "url" argument must be of type string/
|
||||
};
|
||||
|
||||
const moduleMap = new ModuleMap();
|
||||
|
||||
// As long as the assertion of "job" argument is done after the assertion of
|
||||
// "url" argument this test suite is ok. Tried to mock the "job" parameter,
|
||||
// but I think it's useless, and was not simple to mock...
|
||||
const job = undefined;
|
||||
|
||||
[{}, [], true, 1].forEach((value) => {
|
||||
assert.throws(() => moduleMap.get(value), errorObj);
|
||||
assert.throws(() => moduleMap.has(value), errorObj);
|
||||
assert.throws(() => moduleMap.set(value, job), errorObj);
|
||||
});
|
||||
}
|
||||
|
||||
// ModuleMap.set, job argument should only accept ModuleJob values.
|
||||
{
|
||||
const moduleMap = new ModuleMap();
|
||||
|
||||
[{}, [], true, 1].forEach((value) => {
|
||||
assert.throws(() => moduleMap.set('', value), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: /^The "job" argument must be an instance of ModuleJob/
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
// Flags: --experimental-vm-modules --harmony-import-assertions
|
||||
// Flags: --experimental-vm-modules
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
// Flags: --experimental-vm-modules --harmony-import-assertions
|
||||
// Flags: --experimental-vm-modules
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ int main(int argc, char* argv[]) {
|
|||
#endif // _WIN32
|
||||
|
||||
v8::V8::SetFlagsFromString("--random_seed=42");
|
||||
v8::V8::SetFlagsFromString("--harmony-import-assertions");
|
||||
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <path/to/output.cc>\n";
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user