module: self referential modules in repl or -r

Load self referential modules from the repl and using the preload flag
`-r`. In both cases the base path used for resolution is the current
`process.cwd()`. Also fixes an internal cycle bug in the REPL exports
resolution.

PR-URL: https://github.com/nodejs/node/pull/32261
Fixes: https://github.com/nodejs/node/issues/31595
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
This commit is contained in:
Daniele Belardi 2020-07-16 09:27:53 +02:00 committed by Guy Bedford
parent 6d5c33525a
commit ec2ffd6b9d
7 changed files with 94 additions and 8 deletions

View File

@ -29,6 +29,7 @@ module.exports = {
const { const {
ArrayIsArray, ArrayIsArray,
ArrayPrototypeJoin,
Error, Error,
JSONParse, JSONParse,
Map, Map,
@ -422,7 +423,23 @@ function findLongestRegisteredExtension(filename) {
return '.js'; return '.js';
} }
function trySelfParentPath(parent) {
if (!parent) return false;
if (parent.filename) {
return parent.filename;
} else if (parent.id === '<repl>' || parent.id === 'internal/preload') {
try {
return process.cwd() + path.sep;
} catch {
return false;
}
}
}
function trySelf(parentPath, request) { function trySelf(parentPath, request) {
if (!parentPath) return false;
const { data: pkg, path: basePath } = readPackageScope(parentPath) || {}; const { data: pkg, path: basePath } = readPackageScope(parentPath) || {};
if (!pkg || pkg.exports === undefined) return false; if (!pkg || pkg.exports === undefined) return false;
if (typeof pkg.name !== 'string') return false; if (typeof pkg.name !== 'string') return false;
@ -1053,13 +1070,16 @@ Module._resolveFilename = function(request, parent, isMain, options) {
} }
} }
} }
const filename = trySelf(parent.filename, request); }
if (filename) {
const cacheKey = request + '\x00' + // Try module self resoultion first
(paths.length === 1 ? paths[0] : paths.join('\x00')); const parentPath = trySelfParentPath(parent);
Module._pathCache[cacheKey] = filename; const selfResolved = trySelf(parentPath, request);
return filename; if (selfResolved) {
} const cacheKey = request + '\x00' +
(paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, '\x00'));
Module._pathCache[cacheKey] = selfResolved;
return selfResolved;
} }
// Look up the filename first, since that's the cache key. // Look up the filename first, since that's the cache key.

View File

@ -1,5 +1,8 @@
'use strict'; 'use strict';
// This is needed to avoid cycles in esm/resolve <-> cjs/loader
require('internal/modules/cjs/loader');
const { const {
FunctionPrototypeBind, FunctionPrototypeBind,
ObjectSetPrototypeOf, ObjectSetPrototypeOf,

View File

@ -32,7 +32,6 @@ const {
} = require('fs'); } = require('fs');
const { getOptionValue } = require('internal/options'); const { getOptionValue } = require('internal/options');
const { sep, relative } = require('path'); const { sep, relative } = require('path');
const { Module: CJSModule } = require('internal/modules/cjs/loader');
const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const typeFlag = getOptionValue('--input-type'); const typeFlag = getOptionValue('--input-type');
@ -49,11 +48,13 @@ const {
ERR_UNSUPPORTED_DIR_IMPORT, ERR_UNSUPPORTED_DIR_IMPORT,
ERR_UNSUPPORTED_ESM_URL_SCHEME, ERR_UNSUPPORTED_ESM_URL_SCHEME,
} = require('internal/errors').codes; } = require('internal/errors').codes;
const { Module: CJSModule } = require('internal/modules/cjs/loader');
const packageJsonReader = require('internal/modules/package_json_reader'); const packageJsonReader = require('internal/modules/package_json_reader');
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import']); const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import']);
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS); const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
function getConditionsSet(conditions) { function getConditionsSet(conditions) {
if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) { if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) {
if (!ArrayIsArray(conditions)) { if (!ArrayIsArray(conditions)) {

View File

@ -0,0 +1,4 @@
'use strict'
module.exports = 'Self resolution working';

View File

@ -0,0 +1,13 @@
{
"name": "self_ref",
"version": "1.0.0",
"description": "",
"main": "index.js",
"exports": "./index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { exec } = require('child_process');
const nodeBinary = process.argv[0];
if (!common.isMainThread)
common.skip('process.chdir is not available in Workers');
const selfRefModule = fixtures.path('self_ref_module');
const fixtureA = fixtures.path('printA.js');
exec(`"${nodeBinary}" -r self_ref "${fixtureA}"`, { cwd: selfRefModule },
(err, stdout, stderr) => {
assert.ifError(err);
assert.strictEqual(stdout, 'A\n');
});

View File

@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { spawn } = require('child_process');
if (!common.isMainThread)
common.skip('process.chdir is not available in Workers');
const selfRefModule = fixtures.path('self_ref_module');
const child = spawn(process.execPath,
['--interactive'],
{ cwd: selfRefModule }
);
let output = '';
child.stdout.on('data', (chunk) => output += chunk);
child.on('exit', common.mustCall(() => {
const results = output.replace(/^> /mg, '').split('\n').slice(2);
assert.deepStrictEqual(results, [ "'Self resolution working'", '' ]);
}));
child.stdin.write('require("self_ref");\n');
child.stdin.write('.exit');
child.stdin.end();