Skip to content

Commit 6e328d0

Browse files
committed
functional providers, format separation, resolve hook prototype
1 parent 1adf7cb commit 6e328d0

File tree

6 files changed

+164
-145
lines changed

6 files changed

+164
-145
lines changed

lib/internal/loader/Loader.js

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const {
99

1010
const ModuleMap = require('internal/loader/ModuleMap');
1111
const ModuleJob = require('internal/loader/ModuleJob');
12-
const resolveRequestUrl = require('internal/loader/resolveRequestUrl');
12+
const { formatProviders, resolve } = require('internal/loader/ModuleRequest');
1313
const errors = require('internal/errors');
1414

1515
function getBase() {
@@ -34,39 +34,41 @@ class Loader {
3434
this.base = base;
3535
}
3636

37-
async resolve(specifier) {
38-
const request = resolveRequestUrl(this.base, specifier);
39-
if (request.url.protocol !== 'file:') {
37+
setModuleResolver(resolver) {
38+
resolve = resolver;
39+
}
40+
41+
async resolve(specifier, parentUrlOrString = this.base) {
42+
const { url, format } = await resolve(specifier, parentUrlOrString);
43+
if (typeof url === 'string') {
44+
url = new URL(url);
45+
}
46+
else if (!(url instanceof URL)) {
47+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'url', 'URL');
48+
}
49+
if (url.protocol !== 'file:') {
4050
throw new errors.Error('ERR_INVALID_PROTOCOL',
4151
request.url.protocol, 'file:');
4252
}
43-
return request.url;
53+
if (!formatProviders.has(format)) {
54+
throw new errors.Error('ERR_INVALID_FORMAT', format);
55+
}
56+
return { url, format };
4457
}
4558

46-
async getModuleJob(dependentJob, specifier) {
47-
if (!this.moduleMap.has(dependentJob.url)) {
48-
throw new errors.Error('ERR_MISSING_MODULE', dependentJob.url);
49-
}
50-
const request = await resolveRequestUrl(dependentJob.url, specifier);
51-
const url = `${request.url}`;
52-
if (this.moduleMap.has(url)) {
53-
return this.moduleMap.get(url);
59+
async getModuleJob(specifier, parentUrlOrString = this.base) {
60+
const { url, format } = await this.resolve(specifier, parentUrlOrString);
61+
const urlString = `${url}`;
62+
let job = this.moduleMap.get(urlString);
63+
if (job === undefined) {
64+
job = new ModuleJob(this, url, urlString, formatProviders.get(format));
65+
this.moduleMap.set(urlString, job);
5466
}
55-
const dependencyJob = new ModuleJob(this, request);
56-
this.moduleMap.set(url, dependencyJob);
57-
return dependencyJob;
67+
return job;
5868
}
5969

60-
async import(specifier) {
61-
const request = await resolveRequestUrl(this.base, specifier);
62-
const url = `${request.url}`;
63-
let job;
64-
if (this.moduleMap.has(url)) {
65-
job = this.moduleMap.get(url);
66-
} else {
67-
job = new ModuleJob(this, request);
68-
this.moduleMap.set(url, job);
69-
}
70+
async import(specifier, parentUrlOrString = this.base) {
71+
const job = await this.getModuleJob(specifier, parentUrlOrString);
7072
const module = await job.run();
7173
return getNamespaceOfModuleWrap(module);
7274
}

lib/internal/loader/ModuleJob.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,28 @@ const resolvedPromise = SafePromise.resolve();
55
const resolvedArrayPromise = SafePromise.resolve([]);
66
const { ModuleWrap } = require('internal/loader/ModuleWrap');
77

8-
const NOOP = () => { /* No-op */ };
98
class ModuleJob {
109
/**
1110
* @param {module: ModuleWrap?, compiled: Promise} moduleProvider
1211
*/
13-
constructor(loader, moduleProvider, url) {
14-
this.url = `${moduleProvider.url}`;
15-
this.moduleProvider = moduleProvider;
12+
constructor(loader, url, urlString, moduleProvider) {
13+
this.url = url;
1614
this.loader = loader;
1715
this.error = null;
1816
this.hadError = false;
1917

2018
if (moduleProvider instanceof ModuleWrap !== true) {
2119
// linked == promise for dependency jobs, with module populated,
2220
// module wrapper linked
23-
this.modulePromise = this.moduleProvider.createModule();
21+
this.moduleProvider = moduleProvider;
22+
this.modulePromise = this.moduleProvider(url);
2423
this.module = undefined;
2524
const linked = async () => {
2625
const dependencyJobs = [];
2726
this.module = await this.modulePromise;
2827
this.module.link(async (dependencySpecifier) => {
2928
const dependencyJobPromise =
30-
this.loader.getModuleJob(this, dependencySpecifier);
29+
this.loader.getModuleJob(dependencySpecifier, urlString);
3130
dependencyJobs.push(dependencyJobPromise);
3231
const dependencyJob = await dependencyJobPromise;
3332
return dependencyJob.modulePromise;
@@ -40,9 +39,8 @@ class ModuleJob {
4039
//module wrapper instantiated
4140
this.instantiated = undefined;
4241
} else {
43-
const getModuleProvider = async () => moduleProvider;
44-
this.modulePromise = getModuleProvider();
45-
this.moduleProvider = { finish: NOOP };
42+
this.moduleProvider = async () => moduleProvider;
43+
this.modulePromise = this.moduleProvider();
4644
this.module = moduleProvider;
4745
this.linked = resolvedArrayPromise;
4846
this.instantiated = this.modulePromise;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
'use strict';
2+
3+
const { URL } = require('url');
4+
const internalCJSModule = require('internal/module');
5+
const internalURLModule = require('internal/url');
6+
const internalFS = require('internal/fs');
7+
const NativeModule = require('native_module');
8+
const { extname } = require('path');
9+
const { realpathSync } = require('fs');
10+
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
11+
const {
12+
ModuleWrap,
13+
createDynamicModule
14+
} = require('internal/loader/ModuleWrap');
15+
const errors = require('internal/errors');
16+
17+
const search = require('internal/loader/search');
18+
const asyncReadFile = require('util').promisify(require('fs').readFile);
19+
const debug = require('util').debuglog('esm');
20+
21+
const realpathCache = new Map();
22+
23+
const formatProviders = new Map();
24+
exports.formatProviders = formatProviders;
25+
26+
// Strategy for loading a standard JavaScript module
27+
formatProviders.set('esm', async (url) => {
28+
const source = `${await asyncReadFile(url)}`;
29+
debug(`Loading StandardModule ${url}`);
30+
return new ModuleWrap(internalCJSModule.stripShebang(source),
31+
`${url}`);
32+
});
33+
34+
// Strategy for loading a node-style CommonJS module
35+
formatProviders.set('cjs', async (url) => {
36+
const ctx = createDynamicModule(['default'], url, (reflect) => {
37+
debug(`Loading CJSModule ${url.pathname}`);
38+
const CJSModule = require('module');
39+
const pathname = internalURLModule.getPathFromURL(url);
40+
ctx.reflect.exports.default.set(CJSModule._load(pathname));
41+
});
42+
return ctx.module;
43+
});
44+
45+
// Strategy for loading a node builtin CommonJS module that isn't
46+
// through normal resolution
47+
formatProviders.set('native', async (url) => {
48+
const ctx = createDynamicModule(['default'], url, (reflect) => {
49+
debug(`Loading CJSModule ${url.pathname}`);
50+
const CJSModule = require('module');
51+
const pathname = internalURLModule.getPathFromURL(url);
52+
ctx.reflect.exports.default.set(CJSModule._load(pathname));
53+
});
54+
return ctx.module;
55+
});
56+
57+
formatProviders.set('json', async (url) => {
58+
const source = `${await asyncReadFile(url)}`;
59+
const ctx = createDynamicModule(['default'], url, (reflect) => {
60+
debug(`Loading JSONModule ${url.pathname}`);
61+
const json = JSON.parse(source);
62+
ctx.reflect.exports.default.set(json);
63+
});
64+
return ctx.module;
65+
});
66+
67+
// TODO: make this native binary handling only
68+
formatProviders.set('binary', async (url) => {
69+
const source = `${await asyncReadFile(url)}`;
70+
const ctx = createDynamicModule(['default'], url, (reflect) => {
71+
debug(`Loading JSONModule ${url.pathname}`);
72+
const json = JSON.parse(source);
73+
ctx.reflect.exports.default.set(json);
74+
});
75+
return ctx.module;
76+
});
77+
78+
function normalizeBaseURL(baseURLOrString) {
79+
if (baseURLOrString instanceof URL) return baseURLOrString;
80+
if (typeof baseURLOrString === 'string') return new URL(baseURLOrString);
81+
return undefined;
82+
}
83+
84+
exports.resolve = resolve;
85+
function resolve(specifier, parentURLOrString) {
86+
if (NativeModule.nonInternalExists(specifier)) {
87+
return {
88+
url: new URL(`node:${specifier}`),
89+
format: 'native'
90+
};
91+
}
92+
93+
const parentURL = normalizeBaseURL(parentURLOrString);
94+
let url = search(specifier, parentURL);
95+
96+
if (url.protocol !== 'file:') {
97+
throw new errors.Error('ERR_INVALID_PROTOCOL', url.protocol, 'file:');
98+
}
99+
100+
if (!preserveSymlinks) {
101+
const real = realpathSync(internalURLModule.getPathFromURL(url), {
102+
[internalFS.realpathCacheKey]: realpathCache
103+
});
104+
const old = url;
105+
url = internalURLModule.getURLFromFilePath(real);
106+
url.search = old.search;
107+
url.hash = old.hash;
108+
}
109+
110+
const ext = extname(url.pathname);
111+
switch (ext) {
112+
case '.mjs':
113+
return { url, format: 'esm' };
114+
case '.json':
115+
return { url, format: 'json' };
116+
case '.node':
117+
return { url, format: 'binary' };
118+
default:
119+
return { url, format: 'cjs' };
120+
}
121+
};

lib/internal/loader/resolveRequestUrl.js

Lines changed: 0 additions & 104 deletions
This file was deleted.

lib/module.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const errors = require('internal/errors');
4040
const Loader = require('internal/loader/Loader');
4141
const ModuleJob = require('internal/loader/ModuleJob');
4242
const { createDynamicModule } = require('internal/loader/ModuleWrap');
43+
const { resolve: defaultResolver } = require('internal/loader/ModuleRequest');
4344
const ESMLoader = new Loader();
4445

4546
function stat(filename) {
@@ -78,6 +79,9 @@ Module._extensions = Object.create(null);
7879
var modulePaths = [];
7980
Module.globalPaths = [];
8081

82+
Module.resolve = defaultResolver;
83+
Module.setModuleResolver = ESMLoader.setModuleResolver;
84+
8185
Module.wrapper = NativeModule.wrapper;
8286
Module.wrap = NativeModule.wrap;
8387
Module._debug = util.debuglog('module');
@@ -547,14 +551,12 @@ Module.prototype.load = function(filename) {
547551

548552
if (experimentalModules) {
549553
const url = getURLFromFilePath(filename);
550-
if (ESMLoader.moduleMap.has(`${url}`) !== true) {
554+
const urlString = `${url}`;
555+
if (ESMLoader.moduleMap.has(urlString) !== true) {
551556
const ctx = createDynamicModule(['default'], url);
552557
ctx.reflect.exports.default.set(this.exports);
553-
ESMLoader.moduleMap.set(`${url}`,
558+
ESMLoader.moduleMap.set(urlString,
554559
new ModuleJob(ESMLoader, ctx.module));
555-
} else {
556-
ESMLoader.moduleMap.get(`${url}`).moduleProvider.finish(
557-
Module._cache[filename]);
558560
}
559561
}
560562
};

node.gyp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
'lib/internal/loader/ModuleMap.js',
9696
'lib/internal/loader/ModuleJob.js',
9797
'lib/internal/loader/ModuleWrap.js',
98-
'lib/internal/loader/resolveRequestUrl.js',
98+
'lib/internal/loader/ModuleRequest.js',
9999
'lib/internal/loader/search.js',
100100
'lib/internal/safe_globals.js',
101101
'lib/internal/net.js',

0 commit comments

Comments
 (0)