in internal/node/require_patch.js [354:487]
function(request, parent, isMain, options) {
const parentFilename = (parent && parent.filename) ? parent.filename : undefined;
if (SILLY_VERBOSE) log_verbose(`resolve ${request} from ${parentFilename}`);
const failedResolutions = [];
// Attempt to resolve to module root.
// This should be the first attempted resolution because:
// - it's fairly cheap to check (regex over a small array);
// - it is be very common when there are a lot of packages built from source;
if (!isMain) {
// Don't resolve to module root if this is the main entry point
// as the main entry point will always be fully qualified with the
// workspace name and full path.
// See https://github.com/bazelbuild/rules_nodejs/issues/834
const moduleRoot = resolveToModuleRoot(request);
if (moduleRoot) {
const moduleRootInRunfiles = resolveRunfiles(undefined, moduleRoot);
const filename = module.constructor._findPath(moduleRootInRunfiles, []);
if (filename) {
return filename;
} else {
failedResolutions.push(
`module root ${moduleRoot} - No file ${request} found in module root ${moduleRoot}`);
}
}
}
// Built-in modules, relative, absolute imports and npm dependencies
// can be resolved using request
try {
const resolved = originalResolveFilename(request, parent, isMain, options);
if (resolved === request || request.startsWith('.') || request.startsWith('/') ||
request.match(/^[A-Z]\:[\\\/]/i)) {
if (SILLY_VERBOSE)
log_verbose(
`resolved ${request} to built-in, relative or absolute import ` +
`${resolved} from ${parentFilename}`);
return resolved;
} else {
// Resolved is not a built-in module, relative or absolute import
// but also allow imports within npm packages that are within the parent files
// node_modules, meaning it is a dependency of the npm package making the import.
const parentSegments = parentFilename ? parentFilename.replace(/\\/g, '/').split('/') : [];
const parentNodeModulesSegment = parentSegments.indexOf('node_modules');
if (parentNodeModulesSegment != -1) {
const parentRoot = parentSegments.slice(0, parentNodeModulesSegment).join('/');
const relative = path.relative(parentRoot, resolved);
if (!relative.startsWith('..')) {
// Resolved within parent node_modules
log_verbose(
`resolved ${request} within parent node_modules to ` +
`${resolved} from ${parentFilename}`);
return resolved;
} else {
throw new Error(
`Resolved to ${resolved} outside of parent node_modules ${parentFilename}`);
}
}
throw new Error('Not a built-in module, relative or absolute import');
}
} catch (e) {
failedResolutions.push(`built-in, relative, absolute, nested node_modules - ${e.toString()}`);
}
// If the import is not a built-in module, an absolute, relative import or a
// dependency of an npm package, attempt to resolve against the runfiles location
try {
const resolved =
originalResolveFilename(resolveRunfiles(parentFilename, request), parent, isMain, options);
log_verbose(`resolved ${request} within runfiles to ${resolved} from ${parentFilename}`);
return resolved;
} catch (e) {
failedResolutions.push(`runfiles - ${e.toString()}`);
}
// If the parent file is from an external repository, attempt to resolve against
// the external repositories node_modules (if they exist)
let relativeParentFilename =
parentFilename ? path.relative(process.env.RUNFILES, parent.filename) : undefined;
if (relativeParentFilename && !relativeParentFilename.startsWith('..')) {
// Remove leading USER_WORKSPACE_NAME/external so that external workspace name is
// always the first segment
// TODO(gregmagolan): should not be needed when --nolegacy_external_runfiles is default
const externalPrefix = `${USER_WORKSPACE_NAME}/external/`;
if (relativeParentFilename.startsWith(externalPrefix)) {
relativeParentFilename = relativeParentFilename.substr(externalPrefix.length);
}
const parentSegments = relativeParentFilename.split('/');
if (parentSegments[0] !== USER_WORKSPACE_NAME) {
try {
const resolved = originalResolveFilename(
resolveRunfiles(undefined, parentSegments[0], 'node_modules', request), parent, isMain,
options);
log_verbose(
`resolved ${request} within node_modules ` +
`(${parentSegments[0]}/node_modules) to ${resolved} from ${relativeParentFilename}`);
return resolved;
} catch (e) {
failedResolutions.push(`${parentSegments[0]}/node_modules - ${e.toString()}`);
}
}
}
// If import was not resolved above then attempt to resolve
// within the node_modules filegroup in use
try {
const resolved = originalResolveFilename(
resolveRunfiles(undefined, NODE_MODULES_ROOT, request), parent, isMain, options);
log_verbose(
`resolved ${request} within node_modules (${NODE_MODULES_ROOT}) to ` +
`${resolved} from ${parentFilename}`);
return resolved;
} catch (e) {
failedResolutions.push(`node_modules attribute (${NODE_MODULES_ROOT}) - ${e.toString()}`);
}
// Print the same error message that vanilla nodejs does.
// See https://github.com/bazelbuild/rules_nodejs/issues/1015
let moduleNotFoundError = `Cannot find module '${request}'. ` +
'Please verify that the package.json has a valid "main" entry';
if (VERBOSE_LOGS) {
moduleNotFoundError += `\nrequired in target ${TARGET} by '${parentFilename}'\n looked in:\n` +
failedResolutions.map(r => ` ${r}`).join('\n') + '\n';
}
const error = new Error(moduleNotFoundError);
error.code = 'MODULE_NOT_FOUND';
// todo - error.path = ?;
error.requestPath = parentFilename;
error.bazelTarget = TARGET;
error.failedResolutions = failedResolutions;
throw error;
}