Skip to content
This repository was archived by the owner on Apr 16, 2020. It is now read-only.

Commit 9377b27

Browse files
committed
named exports support through two phase execution
1 parent 66aebb1 commit 9377b27

File tree

11 files changed

+246
-92
lines changed

11 files changed

+246
-92
lines changed

doc/api/errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,12 @@ An attempt was made to load a module with an unknown or unsupported format.
18551855
An invalid or unknown process signal was passed to an API expecting a valid
18561856
signal (such as [`subprocess.kill()`][]).
18571857

1858+
<a id="ERR_UNSUPPORTED_FILE_EXTENSION"></a>
1859+
### ERR_UNSUPPORTED_FILE_EXTENSION
1860+
1861+
The given file extension is not of a supported format for loading through
1862+
the Node.js ES module loader.
1863+
18581864
<a id="ERR_V8BREAKITERATOR"></a>
18591865
### ERR_V8BREAKITERATOR
18601866

lib/internal/modules/cjs/loader.js

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,9 @@ const { validateString } = require('internal/validators');
5858
module.exports = Module;
5959

6060
let asyncESM;
61-
let ModuleJob;
62-
let createDynamicModule;
6361

6462
function lazyLoadESM() {
6563
asyncESM = require('internal/process/esm_loader');
66-
ModuleJob = require('internal/modules/esm/module_job');
67-
createDynamicModule = require(
68-
'internal/modules/esm/create_dynamic_module');
6964
}
7065

7166
const {
@@ -631,23 +626,9 @@ Module.prototype.load = function(filename) {
631626
if (asyncESM === undefined) lazyLoadESM();
632627
const ESMLoader = asyncESM.ESMLoader;
633628
const url = `${pathToFileURL(filename)}`;
634-
const module = ESMLoader.moduleMap.get(url);
635-
// Create module entry at load time to snapshot exports correctly
636-
const exports = this.exports;
637-
if (module !== undefined) { // called from cjs translator
638-
module.reflect.onReady((reflect) => {
639-
reflect.exports.default.set(exports);
640-
});
641-
} else { // preemptively cache
642-
ESMLoader.moduleMap.set(
643-
url,
644-
new ModuleJob(ESMLoader, url, async () => {
645-
return createDynamicModule(
646-
['default'], url, (reflect) => {
647-
reflect.exports.default.set(exports);
648-
});
649-
})
650-
);
629+
if (!ESMLoader.moduleMap.has(url)) {
630+
// Create module entry at load time to snapshot exports correctly
631+
ESMLoader.cjsCache.set(url, this.exports);
651632
}
652633
}
653634
};

lib/internal/modules/esm/create_dynamic_module.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,16 @@ import.meta.done();
2626
m.link(() => 0);
2727
m.instantiate();
2828

29-
const readyfns = new Set();
3029
const reflect = {
3130
namespace: m.namespace(),
32-
exports: {},
33-
onReady: (cb) => { readyfns.add(cb); },
31+
exports: {}
3432
};
3533

3634
callbackMap.set(m, {
3735
initializeImportMeta: (meta, wrap) => {
3836
meta.exports = reflect.exports;
3937
meta.done = () => {
4038
evaluate(reflect);
41-
reflect.onReady = (cb) => cb(reflect);
42-
for (const fn of readyfns) {
43-
readyfns.delete(fn);
44-
fn(reflect);
45-
}
4639
};
4740
},
4841
});

lib/internal/modules/esm/module_job.js

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,40 @@ class ModuleJob {
2121
this.loader = loader;
2222
this.isMain = isMain;
2323

24-
// This is a Promise<{ module, reflect }>, whose fields will be copied
25-
// onto `this` by `link()` below once it has been resolved.
26-
this.modulePromise = moduleProvider.call(loader, url, isMain);
24+
// moduleProvider returns { preExec, modulePromise }, where
25+
// modulePromise a Promise<{ module, reflect }>.
26+
// These fields will be copied `this` by `link()` once resolved.
27+
({ preExec: this.preExec, modulePromise: this.modulePromise } =
28+
moduleProvider.call(loader, url, isMain));
2729
this.module = undefined;
2830
this.reflect = undefined;
2931

3032
// Wait for the ModuleWrap instance being linked with all dependencies.
3133
const link = async () => {
32-
({ module: this.module,
33-
reflect: this.reflect } = await this.modulePromise);
34-
assert(this.module instanceof ModuleWrap);
34+
const translator = await this.modulePromise;
35+
36+
if (typeof translator === 'function') {
37+
this.preExec = translator;
38+
} else {
39+
({ module: this.module, reflect: this.reflect } = translator);
40+
assert(this.module instanceof ModuleWrap);
41+
}
3542

3643
const dependencyJobs = [];
3744
const promises = this.module.link(async (specifier) => {
3845
const jobPromise = this.loader.getModuleJob(specifier, url);
3946
dependencyJobs.push(jobPromise);
4047
return (await (await jobPromise).modulePromise).module;
4148
});
42-
43-
if (promises !== undefined)
49+
this.dependencyJobsPromise = SafePromise.all(dependencyJobs);
50+
if (promises)
4451
await SafePromise.all(promises);
45-
46-
return SafePromise.all(dependencyJobs);
4752
};
48-
// Promise for the list of all dependencyJobs.
53+
// Promise for linking this module.
4954
this.linked = link();
55+
// Promise for the population of dependencyJobs.
56+
// This promise is only available after modulePromise.
57+
this.dependencyJobsPromise = undefined;
5058
// This promise is awaited later anyway, so silence
5159
// 'unhandled rejection' warnings.
5260
this.linked.catch(noop);
@@ -68,14 +76,19 @@ class ModuleJob {
6876
// entire dependency graph, i.e. creates all the module namespaces and the
6977
// exported/imported variables.
7078
async _instantiate() {
79+
const preExecJobs = await getPostOrderPreExecs(this, new SafeSet());
80+
for (const preExecJob of preExecJobs) {
81+
preExecJob.preExec();
82+
}
7183
const jobsInGraph = new SafeSet();
72-
7384
const addJobsToDependencyGraph = async (moduleJob) => {
7485
if (jobsInGraph.has(moduleJob)) {
7586
return;
7687
}
7788
jobsInGraph.add(moduleJob);
78-
const dependencyJobs = await moduleJob.linked;
89+
await moduleJob.modulePromise;
90+
const dependencyJobs = await moduleJob.dependencyJobsPromise;
91+
await moduleJob.linked;
7992
return Promise.all(dependencyJobs.map(addJobsToDependencyGraph));
8093
};
8194
await addJobsToDependencyGraph(this);
@@ -105,5 +118,23 @@ class ModuleJob {
105118
return module;
106119
}
107120
}
121+
122+
// Executes CommonJS modules with a preExec synchronously in graph
123+
// post-order at the end of instantiate, before job.link has resolved.
124+
async function getPostOrderPreExecs(job, seen, preExecJobs = []) {
125+
if (seen.has(job)) return;
126+
seen.add(job);
127+
if (job.preExec) {
128+
preExecJobs.push(job);
129+
return preExecJobs;
130+
}
131+
await job.modulePromise;
132+
const dependencyJobs = await job.dependencyJobsPromise;
133+
for (const depJob of dependencyJobs) {
134+
await getPostOrderPreExecs(depJob, seen, preExecJobs);
135+
}
136+
return preExecJobs;
137+
}
138+
108139
Object.setPrototypeOf(ModuleJob.prototype, null);
109140
module.exports = ModuleJob;

lib/internal/modules/esm/translators.js

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,18 @@ const {
66
stripShebang
77
} = require('internal/modules/cjs/helpers');
88
const CJSModule = require('internal/modules/cjs/loader');
9-
const internalURLModule = require('internal/url');
9+
const { URL, fileURLToPath } = require('url');
1010
const createDynamicModule = require(
1111
'internal/modules/esm/create_dynamic_module');
1212
const fs = require('fs');
1313
const {
1414
SafeMap,
1515
} = primordials;
16-
const { URL } = require('url');
1716
const { debuglog, promisify } = require('util');
1817
const esmLoader = require('internal/process/esm_loader');
18+
const { isValidIdentifier } = require('internal/validators');
1919

2020
const readFileAsync = promisify(fs.readFile);
21-
const StringReplace = Function.call.bind(String.prototype.replace);
2221

2322
const debug = debuglog('esm');
2423

@@ -35,62 +34,82 @@ async function importModuleDynamically(specifier, { url }) {
3534
}
3635

3736
// Strategy for loading a standard JavaScript module
38-
translators.set('esm', async function(url) {
39-
const source = `${await readFileAsync(new URL(url))}`;
40-
debug(`Translating StandardModule ${url}`);
41-
const module = new ModuleWrap(stripShebang(source), url);
42-
callbackMap.set(module, {
43-
initializeImportMeta,
44-
importModuleDynamically,
45-
});
46-
return {
47-
module,
48-
reflect: undefined,
49-
};
37+
translators.set('esm', function(url) {
38+
const modulePromise = (async () => {
39+
const source = `${await readFileAsync(new URL(url))}`;
40+
debug(`Translating StandardModule ${url}`);
41+
const module = new ModuleWrap(stripShebang(source), url);
42+
callbackMap.set(module, {
43+
initializeImportMeta,
44+
importModuleDynamically,
45+
});
46+
return {
47+
module,
48+
reflect: undefined,
49+
};
50+
})();
51+
return { modulePromise };
5052
});
5153

54+
function createCommonJSModule(url, exports) {
55+
// Snapshot the namespace values first
56+
const namedExports = Reflect.ownKeys(exports).filter((exportName) =>
57+
isValidIdentifier(exportName, false)
58+
);
59+
const ns = Object.create(null);
60+
ns.default = exports;
61+
for (const exportName of namedExports) {
62+
if (exportName === 'default')
63+
continue;
64+
try {
65+
ns[exportName] = exports[exportName];
66+
} catch {}
67+
}
68+
return createDynamicModule([...namedExports, 'default'], url, (reflect) => {
69+
for (const exportName in ns) {
70+
reflect.exports[exportName].set(ns[exportName]);
71+
}
72+
});
73+
}
74+
5275
// Strategy for loading a node-style CommonJS module
53-
const isWindows = process.platform === 'win32';
54-
const winSepRegEx = /\//g;
55-
translators.set('cjs', async function(url, isMain) {
76+
translators.set('cjs', function(url, isMain) {
5677
debug(`Translating CJSModule ${url}`);
57-
const pathname = internalURLModule.fileURLToPath(new URL(url));
5878
const cached = this.cjsCache.get(url);
5979
if (cached) {
6080
this.cjsCache.delete(url);
61-
return cached;
62-
}
63-
const module = CJSModule._cache[
64-
isWindows ? StringReplace(pathname, winSepRegEx, '\\') : pathname];
65-
if (module && module.loaded) {
66-
const exports = module.exports;
67-
return createDynamicModule(['default'], url, (reflect) => {
68-
reflect.exports.default.set(exports);
69-
});
81+
const modulePromise = (async () => createCommonJSModule(url, cached))();
82+
return { modulePromise };
7083
}
71-
return createDynamicModule(['default'], url, () => {
72-
debug(`Loading CJSModule ${url}`);
73-
// We don't care about the return val of _load here because Module#load
74-
// will handle it for us by checking the loader registry and filling the
75-
// exports like above
76-
CJSModule._load(pathname, undefined, isMain);
77-
});
84+
let modulePromiseResolve;
85+
const modulePromise = new Promise(
86+
(resolve) => modulePromiseResolve = resolve
87+
);
88+
const preExec = () => {
89+
debug(`Executing CJSModule ${url}`);
90+
const module = CJSModule._load(fileURLToPath(url), undefined, isMain);
91+
modulePromiseResolve(createCommonJSModule(url, module));
92+
};
93+
return { preExec, modulePromise };
7894
});
7995

8096
// Strategy for loading a node builtin CommonJS module that isn't
8197
// through normal resolution
82-
translators.set('builtin', async function(url) {
98+
translators.set('builtin', function(url) {
8399
debug(`Translating BuiltinModule ${url}`);
84-
// slice 'node:' scheme
85-
const id = url.slice(5);
86-
NativeModule.require(id);
87-
const module = NativeModule.map.get(id);
88-
return createDynamicModule(
89-
[...module.exportKeys, 'default'], url, (reflect) => {
90-
debug(`Loading BuiltinModule ${url}`);
91-
module.reflect = reflect;
92-
for (const key of module.exportKeys)
93-
reflect.exports[key].set(module.exports[key]);
94-
reflect.exports.default.set(module.exports);
95-
});
100+
const modulePromise = (async () => {
101+
// slice 'node:' scheme
102+
const id = url.slice(5);
103+
NativeModule.require(id);
104+
const module = NativeModule.map.get(id);
105+
return createDynamicModule(
106+
[...module.exportKeys, 'default'], url, (reflect) => {
107+
debug(`Loading BuiltinModule ${url}`);
108+
module.reflect = reflect;
109+
for (const key of module.exportKeys)
110+
reflect.exports[key].set(module.exports[key]);
111+
reflect.exports.default.set(module.exports);
112+
});
113+
})();
114+
return { modulePromise };
96115
});

0 commit comments

Comments
 (0)