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

esm: refactor dynamic modules #24560

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -623,23 +623,24 @@ Module.prototype.load = function(filename) {
if (experimentalModules) {
if (asyncESM === undefined) lazyLoadESM();
const ESMLoader = asyncESM.ESMLoader;
const url = pathToFileURL(filename);
const urlString = `${url}`;
const url = `${pathToFileURL(filename)}`;
const module = ESMLoader.moduleMap.get(url);
// create module entry at load time to snapshot exports correctly
const exports = this.exports;
if (ESMLoader.moduleMap.has(urlString) !== true) {
if (module !== undefined) { // called from cjs translator
module.reflect.onReady((reflect) => {
reflect.exports.default.set(exports);
});
} else { // preemptively cache
ESMLoader.moduleMap.set(
urlString,
url,
new ModuleJob(ESMLoader, url, async () => {
const ctx = createDynamicModule(
['default'], url);
ctx.reflect.exports.default.set(exports);
return ctx;
return createDynamicModule(
['default'], url, (reflect) => {
reflect.exports.default.set(exports);
});
})
);
} else {
const job = ESMLoader.moduleMap.get(urlString);
if (job.reflect)
job.reflect.exports.default.set(exports);
}
}
};
Expand Down
85 changes: 41 additions & 44 deletions lib/internal/modules/esm/create_dynamic_module.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const { ModuleWrap } = internalBinding('module_wrap');
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
const debug = require('util').debuglog('esm');
const ArrayJoin = Function.call.bind(Array.prototype.join);
const ArrayMap = Function.call.bind(Array.prototype.map);
Expand All @@ -10,50 +10,47 @@ const createDynamicModule = (exports, url = '', evaluate) => {
`creating ESM facade for ${url} with exports: ${ArrayJoin(exports, ', ')}`
);
const names = ArrayMap(exports, (name) => `${name}`);
// Create two modules: One whose exports are get- and set-able ('reflective'),
// and one which re-exports all of these but additionally may
// run an executor function once everything is set up.
const src = `
export let executor;
${ArrayJoin(ArrayMap(names, (name) => `export let $${name};`), '\n')}
/* This function is implicitly returned as the module's completion value */
(() => ({
setExecutor: fn => executor = fn,
reflect: {
exports: { ${
ArrayJoin(ArrayMap(names, (name) => `
${name}: {
get: () => $${name},
set: v => $${name} = v
}`), ', \n')}
}
}
}));`;
const reflectiveModule = new ModuleWrap(src, `cjs-facade:${url}`);
reflectiveModule.instantiate();
const { setExecutor, reflect } = reflectiveModule.evaluate(-1, false)();
// public exposed ESM
const reexports = `
import {
executor,
${ArrayMap(names, (name) => `$${name}`)}
} from "";
export {
${ArrayJoin(ArrayMap(names, (name) => `$${name} as ${name}`), ', ')}
}
if (typeof executor === "function") {
// add await to this later if top level await comes along
executor()
}`;
if (typeof evaluate === 'function') {
setExecutor(() => evaluate(reflect));
}
const module = new ModuleWrap(reexports, `${url}`);
module.link(async () => reflectiveModule);
module.instantiate();
reflect.namespace = module.namespace();

const source = `
${ArrayJoin(ArrayMap(names, (name) =>
`let $${name};
export { $${name} as ${name} };
import.meta.exports.${name} = {
get: () => $${name},
set: (v) => $${name} = v,
};`), '\n')
}

import.meta.done();
`;

const m = new ModuleWrap(source, `${url}`);
m.link(() => 0);
m.instantiate();

const readyfns = new Set();
const reflect = {
namespace: m.namespace(),
exports: {},
onReady: (cb) => { readyfns.add(cb); },
};

callbackMap.set(m, {
initializeImportMeta: (meta, wrap) => {
meta.exports = reflect.exports;
meta.done = () => {
evaluate(reflect);
reflect.onReady = (cb) => cb(reflect);
for (const fn of readyfns) {
readyfns.delete(fn);
fn(reflect);
}
};
},
});

return {
module,
module: m,
reflect,
};
};
Expand Down
7 changes: 4 additions & 3 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ translators.set('cjs', async (url, isMain) => {
const module = CJSModule._cache[
isWindows ? StringReplace(pathname, winSepRegEx, '\\') : pathname];
if (module && module.loaded) {
const ctx = createDynamicModule(['default'], url);
ctx.reflect.exports.default.set(module.exports);
return ctx;
const exports = module.exports;
return createDynamicModule(['default'], url, (reflect) => {
reflect.exports.default.set(exports);
});
}
return createDynamicModule(['default'], url, () => {
debug(`Loading CJSModule ${url}`);
Expand Down