Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module: remove dynamicInstantiate loader hook #33501

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 0 additions & 14 deletions doc/api/errors.md
Expand Up @@ -1563,14 +1563,6 @@ strict compliance with the API specification (which in some cases may accept
`func(undefined)` and `func()` are treated identically, and the
[`ERR_INVALID_ARG_TYPE`][] error code may be used instead.

<a id="ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK"></a>
### `ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK`

> Stability: 1 - Experimental

An [ES Module][] loader hook specified `format: 'dynamic'` but did not provide
a `dynamicInstantiate` hook.

<a id="ERR_MISSING_OPTION"></a>
### `ERR_MISSING_OPTION`

Expand Down Expand Up @@ -2512,12 +2504,6 @@ while trying to read and parse it.

The `--entry-type=...` flag is not compatible with the Node.js REPL.

<a id="ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK"></a>
#### `ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK`

Used when an [ES Module][] loader hook specifies `format: 'dynamic'` but does
not provide a `dynamicInstantiate` hook.

<a id="ERR_FEATURE_UNAVAILABLE_ON_PLATFORM"></a>
#### `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM`

Expand Down
35 changes: 0 additions & 35 deletions doc/api/esm.md
Expand Up @@ -1204,7 +1204,6 @@ of the following:
| --- | --- | --- |
| `'builtin'` | Load a Node.js builtin module | Not applicable |
| `'commonjs'` | Load a Node.js CommonJS module | Not applicable |
| `'dynamic'` | Use a [dynamic instantiate hook][] | Not applicable |
| `'json'` | Load a JSON file | { [ArrayBuffer][], [string][], [TypedArray][] } |
| `'module'` | Load an ES module | { [ArrayBuffer][], [string][], [TypedArray][] } |
| `'wasm'` | Load a WebAssembly module | { [ArrayBuffer][], [string][], [TypedArray][] } |
Expand Down Expand Up @@ -1345,38 +1344,6 @@ const require = createRequire(process.cwd() + '/<preload>');
}
```

#### <code>dynamicInstantiate</code> hook

> Note: The loaders API is being redesigned. This hook may disappear or its
> signature may change. Do not rely on the API described below.

To create a custom dynamic module that doesn't correspond to one of the
existing `format` interpretations, the `dynamicInstantiate` hook can be used.
This hook is called only for modules that return `format: 'dynamic'` from
the [`getFormat` hook][].

```js
/**
* @param {string} url
* @returns {object} response
* @returns {array} response.exports
* @returns {function} response.execute
*/
export async function dynamicInstantiate(url) {
return {
exports: ['customExportName'],
execute: (exports) => {
// Get and set functions provided for pre-allocated export names
exports.customExportName.set('value');
}
};
}
```

With the list of module exports provided upfront, the `execute` function will
then be called at the exact point of module evaluation order for that module
in the import tree.

### Examples

The various loader hooks can be used together to accomplish wide-ranging
Expand Down Expand Up @@ -1846,7 +1813,6 @@ success!
[`data:` URLs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
[`esm`]: https://github.com/standard-things/esm#readme
[`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
[`getFormat` hook]: #esm_code_getformat_code_hook
[`import()`]: #esm_import_expressions
[`import.meta.url`]: #esm_import_meta
[`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Expand All @@ -1859,7 +1825,6 @@ success!
[TypedArray]: http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects
[Uint8Array]: http://www.ecma-international.org/ecma-262/6.0/#sec-uint8array
[`util.TextDecoder`]: util.html#util_class_util_textdecoder
[dynamic instantiate hook]: #esm_code_dynamicinstantiate_code_hook
[import an ES or CommonJS module for its side effects only]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Import_a_module_for_its_side_effects_only
[special scheme]: https://url.spec.whatwg.org/#special-scheme
[the full specifier path]: #esm_mandatory_file_extensions
Expand Down
3 changes: 0 additions & 3 deletions lib/internal/errors.js
Expand Up @@ -1254,9 +1254,6 @@ E('ERR_MISSING_ARGS',
}
return `${msg} must be specified`;
}, TypeError);
E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
'The ES Module loader may not return a format of \'dynamic\' when no ' +
'dynamicInstantiate function was provided', Error);
E('ERR_MISSING_OPTION', '%s is required', TypeError);
E('ERR_MODULE_NOT_FOUND', (path, base, type = 'package') => {
return `Cannot find ${type} '${path}' imported from ${base}`;
Expand Down
41 changes: 5 additions & 36 deletions lib/internal/modules/esm/loader.js
Expand Up @@ -11,7 +11,6 @@ const {
ERR_INVALID_RETURN_PROPERTY,
ERR_INVALID_RETURN_PROPERTY_VALUE,
ERR_INVALID_RETURN_VALUE,
ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK,
ERR_UNKNOWN_MODULE_FORMAT
} = require('internal/errors').codes;
const { URL, pathToFileURL } = require('internal/url');
Expand All @@ -28,14 +27,10 @@ const { defaultGetSource } = require(
'internal/modules/esm/get_source');
const { defaultTransformSource } = require(
'internal/modules/esm/transform_source');
const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module');
const { translators } = require(
'internal/modules/esm/translators');
const { getOptionValue } = require('internal/options');

const debug = require('internal/util/debuglog').debuglog('esm');

/* A Loader instance is used as the main entry point for loading ES modules.
* Currently, this is a singleton -- there is only one used for loading
* the main module and everything in its dependency graph. */
Expand Down Expand Up @@ -68,23 +63,13 @@ class Loader {
// This hook is called after the module is resolved but before a translator
// is chosen to load it; the format returned by this function is the name
// of a translator.
// If `.format` on the returned value is 'dynamic', .dynamicInstantiate
// will be used as described below.
this._getFormat = defaultGetFormat;
// This hook is called just before the source code of an ES module file
// is loaded.
this._getSource = defaultGetSource;
// This hook is called just after the source code of an ES module file
// is loaded, but before anything is done with the string.
this._transformSource = defaultTransformSource;
// This hook is only called when getFormat is 'dynamic' and
// has the signature
// (url : string) -> Promise<{ exports: { ... }, execute: function }>
// Where `exports` is an object whose property names define the exported
// names of the generated module. `execute` is a function that receives
// an object with the same keys as `exports`, whose values are get/set
// functions for the actual exported values.
this._dynamicInstantiate = undefined;
// The index for assigning unique URLs to anonymous module evaluation
this.evalIndex = 0;
}
Expand Down Expand Up @@ -138,7 +123,6 @@ class Loader {
}

if (this._resolve === defaultResolve &&
format !== 'dynamic' &&
!url.startsWith('file:') &&
!url.startsWith('data:')
) {
Expand Down Expand Up @@ -193,8 +177,8 @@ class Loader {
if (resolve !== undefined)
this._resolve = FunctionPrototypeBind(resolve, null);
if (dynamicInstantiate !== undefined) {
this._dynamicInstantiate =
FunctionPrototypeBind(dynamicInstantiate, null);
process.emitWarning(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't needed

Copy link
Contributor Author

@jkrems jkrems May 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I started without it. But then the test still passed and I was super confused if anything happened. Having the warning did help me be confident that it actually ignored the hook.

That being said - happy to remove this last trace as well.

'The dynamicInstantiate loader hook has been removed.');
}
if (getFormat !== undefined) {
this._getFormat = FunctionPrototypeBind(getFormat, null);
Expand Down Expand Up @@ -248,25 +232,10 @@ class Loader {
if (job !== undefined)
return job;

let loaderInstance;
if (format === 'dynamic') {
if (typeof this._dynamicInstantiate !== 'function')
throw new ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK();

loaderInstance = async (url) => {
debug(`Translating dynamic ${url}`);
const { exports, execute } = await this._dynamicInstantiate(url);
return createDynamicModule([], exports, url, (reflect) => {
debug(`Loading dynamic ${url}`);
execute(reflect.exports);
}).module;
};
} else {
if (!translators.has(format))
throw new ERR_UNKNOWN_MODULE_FORMAT(format);
if (!translators.has(format))
throw new ERR_UNKNOWN_MODULE_FORMAT(format);

loaderInstance = translators.get(format);
}
const loaderInstance = translators.get(format);

const inspectBrk = parentURL === undefined &&
format === 'module' && getOptionValue('--inspect-brk');
Expand Down

This file was deleted.

3 changes: 2 additions & 1 deletion test/es-module/test-esm-named-exports.mjs
@@ -1,8 +1,9 @@
// Flags: --experimental-loader ./test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs
import '../common/index.mjs';
import { readFile } from 'fs';
import { readFile, __fromLoader } from 'fs';
import assert from 'assert';
import ok from '../fixtures/es-modules/test-esm-ok.mjs';

assert(ok);
assert(readFile);
assert(__fromLoader);
67 changes: 53 additions & 14 deletions test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs
@@ -1,23 +1,62 @@
import module from 'module';

export function getFormat(url, context, defaultGetFormat) {
if (module.builtinModules.includes(url)) {
const GET_BUILTIN = `$__get_builtin_hole_${Date.now()}`;

export function getGlobalPreloadCode() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason for needing this is because this hook is messing with the builtin module module. Otherwise the generated code could import {createRequire} from 'module' without needing a global.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are going to push users into this hook now, then I think we should aim to work on this usability first - you shouldn't need to define a secret global for the common use case...

return `Object.defineProperty(globalThis, ${JSON.stringify(GET_BUILTIN)}, {
value: (builtinName) => {
return getBuiltin(builtinName);
},
enumerable: false,
configurable: false,
});
`;
}

export function resolve(specifier, context, defaultResolve) {
const def = defaultResolve(specifier, context);
if (def.url.startsWith('nodejs:')) {
return {
url: `custom-${def.url}`,
};
}
return def;
}

export function getSource(url, context, defaultGetSource) {
if (url.startsWith('custom-nodejs:')) {
const urlObj = new URL(url);
return {
format: 'dynamic'
source: generateBuiltinModule(urlObj.pathname),
format: 'module',
};
}
return defaultGetSource(url, context);
}

export function getFormat(url, context, defaultGetFormat) {
if (url.startsWith('custom-nodejs:')) {
return { format: 'module' };
}
return defaultGetFormat(url, context, defaultGetFormat);
}

export function dynamicInstantiate(url) {
const builtinInstance = module._load(url);
const builtinExports = ['default', ...Object.keys(builtinInstance)];
return {
exports: builtinExports,
execute: exports => {
for (let name of builtinExports)
exports[name].set(builtinInstance[name]);
exports.default.set(builtinInstance);
}
};
function generateBuiltinModule(builtinName) {
const builtinInstance = module._load(builtinName);
const builtinExports = [
...Object.keys(builtinInstance),
];
return `\
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems horrible enough to constitute figuring out some sort of replacement for dynamicInstantiate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the hackiest version of generating the code. I don't agree that the loader hooks providing proper JS code is horrible in general. It's possible to create a dynamicInstantiate-like API on top of this primitive.

const $builtinInstance = ${GET_BUILTIN}(${JSON.stringify(builtinName)});

export const __fromLoader = true;

export default $builtinInstance;

${
builtinExports
.map(name => `export const ${name} = $builtinInstance.${name};`)
.join('\n')
}
`;
}