node/lib/internal/modules/esm/create_dynamic_module.js
Joyee Cheung d6b57f6629
module: centralize SourceTextModule compilation for builtin loader
This refactors the code that compiles SourceTextModule for the
built-in ESM loader to use a common routine so that it's easier
to customize cache handling for the ESM loader. In addition
this introduces a common symbol for import.meta and import()
so that we don't need to create additional closures as handlers,
since we can get all the information we need from the V8 callback
already. This should reduce the memory footprint of ESM as well.

PR-URL: https://github.com/nodejs/node/pull/52291
Refs: https://github.com/nodejs/node/issues/47472
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2024-04-03 23:31:48 +00:00

95 lines
2.8 KiB
JavaScript

'use strict';
const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
JSONStringify,
SafeSet,
} = primordials;
let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
debug = fn;
});
/**
* Creates an import statement for a given module path and index.
* @param {string} impt - The module path to import.
* @param {number} index - The index of the import statement.
*/
function createImport(impt, index) {
const imptPath = JSONStringify(impt);
return `import * as $import_${index} from ${imptPath};
import.meta.imports[${imptPath}] = $import_${index};`;
}
/**
* Creates an export for a given module.
* @param {string} expt - The name of the export.
* @param {number} index - The index of the export statement.
*/
function createExport(expt, index) {
const nameStringLit = JSONStringify(expt);
return `let $export_${index};
export { $export_${index} as ${nameStringLit} };
import.meta.exports[${nameStringLit}] = {
get: () => $export_${index},
set: (v) => $export_${index} = v,
};`;
}
/**
* Creates a dynamic module with the given imports, exports, URL, and evaluate function.
* @param {string[]} imports - An array of imports.
* @param {string[]} exports - An array of exports.
* @param {string} [url=''] - The URL of the module.
* @param {(reflect: DynamicModuleReflect) => void} evaluate - The function to evaluate the module.
* @typedef {object} DynamicModuleReflect
* @property {string[]} imports - The imports of the module.
* @property {string[]} exports - The exports of the module.
* @property {(cb: (reflect: DynamicModuleReflect) => void) => void} onReady - Callback to evaluate the module.
*/
const createDynamicModule = (imports, exports, url = '', evaluate) => {
debug('creating ESM facade for %s with exports: %j', url, exports);
const source = `
${ArrayPrototypeJoin(ArrayPrototypeMap(imports, createImport), '\n')}
${ArrayPrototypeJoin(ArrayPrototypeMap(exports, createExport), '\n')}
import.meta.done();
`;
const { registerModule, compileSourceTextModule } = require('internal/modules/esm/utils');
const m = compileSourceTextModule(`${url}`, source);
const readyfns = new SafeSet();
/** @type {DynamicModuleReflect} */
const reflect = {
exports: { __proto__: null },
onReady: (cb) => { readyfns.add(cb); },
};
if (imports.length) {
reflect.imports = { __proto__: null };
}
registerModule(m, {
__proto__: null,
initializeImportMeta: (meta, wrap) => {
meta.exports = reflect.exports;
if (reflect.imports) {
meta.imports = reflect.imports;
}
meta.done = () => {
evaluate(reflect);
reflect.onReady = (cb) => cb(reflect);
for (const fn of readyfns) {
readyfns.delete(fn);
fn(reflect);
}
};
},
});
return {
module: m,
reflect,
};
};
module.exports = createDynamicModule;