diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index bfc4eb6514c327..8f7124be8789cc 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -213,16 +213,17 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077'); // -> a. // -> a/index. -// Check if the directory is a package.json dir. -const packageMainCache = Object.create(null); -// Explicit exports from package.json files -const packageExportsCache = new SafeMap(); +const packageJsonCache = new SafeMap(); -function readPackageRaw(requestPath) { +function readPackage(requestPath) { const jsonPath = path.resolve(requestPath, 'package.json'); - const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath)); + const existing = packageJsonCache.get(jsonPath); + if (existing !== undefined) return existing; + + const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath)); if (json === undefined) { + packageJsonCache.set(jsonPath, false); return false; } @@ -233,11 +234,13 @@ function readPackageRaw(requestPath) { try { const parsed = JSON.parse(json); - packageMainCache[requestPath] = parsed.main; - if (experimentalExports) { - packageExportsCache.set(requestPath, parsed.exports); - } - return parsed; + const filtered = { + main: parsed.main, + exports: parsed.exports, + type: parsed.type + }; + packageJsonCache.set(jsonPath, filtered); + return filtered; } catch (e) { e.path = jsonPath; e.message = 'Error parsing ' + jsonPath + ': ' + e.message; @@ -245,33 +248,33 @@ function readPackageRaw(requestPath) { } } -function readPackage(requestPath) { - const entry = packageMainCache[requestPath]; - if (entry) - return entry; - - const pkg = readPackageRaw(requestPath); - if (pkg === false) return false; - - return pkg.main; -} - -function readExports(requestPath) { - if (packageExportsCache.has(requestPath)) { - return packageExportsCache.get(requestPath); +function readPackageScope(checkPath) { + const rootSeparatorIndex = checkPath.indexOf(path.sep); + let separatorIndex; + while ( + (separatorIndex = checkPath.lastIndexOf(path.sep)) > rootSeparatorIndex + ) { + checkPath = checkPath.slice(0, separatorIndex); + if (checkPath.endsWith(path.sep + 'node_modules')) + return false; + const pjson = readPackage(checkPath); + if (pjson) return pjson; } + return false; +} - const pkg = readPackageRaw(requestPath); - if (!pkg) { - packageExportsCache.set(requestPath, null); - return null; - } +function readPackageMain(requestPath) { + const pkg = readPackage(requestPath); + return pkg ? pkg.main : undefined; +} - return pkg.exports; +function readPackageExports(requestPath) { + const pkg = readPackage(requestPath); + return pkg ? pkg.exports : undefined; } function tryPackage(requestPath, exts, isMain, originalPath) { - const pkg = readPackage(requestPath); + const pkg = readPackageMain(requestPath); if (!pkg) { return tryExtensions(path.resolve(requestPath, 'index'), exts, isMain); @@ -372,7 +375,7 @@ function resolveExports(nmPath, request, absoluteRequest) { } const basePath = path.resolve(nmPath, name); - const pkgExports = readExports(basePath); + const pkgExports = readPackageExports(basePath); const mappingKey = `.${expansion}`; if (typeof pkgExports === 'object' && pkgExports !== null) { @@ -947,6 +950,12 @@ Module.prototype._compile = function(content, filename) { // Native extension for .js Module._extensions['.js'] = function(module, filename) { + if (filename.endsWith('.js')) { + const pkg = readPackageScope(filename); + if (pkg && pkg.type === 'module') { + throw new ERR_REQUIRE_ESM(filename); + } + } const content = fs.readFileSync(filename, 'utf8'); module._compile(stripBOM(content), filename); }; diff --git a/src/node_file.cc b/src/node_file.cc index ad55d501b4e8df..185c7085170e27 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -875,7 +875,8 @@ static void InternalModuleReadJSON(const FunctionCallbackInfo& args) { const size_t size = offset - start; if (size == 0 || ( size == SearchString(&chars[start], size, "\"main\"") && - size == SearchString(&chars[start], size, "\"exports\""))) { + size == SearchString(&chars[start], size, "\"exports\"") && + size == SearchString(&chars[start], size, "\"type\""))) { return; } else { Local chars_string = diff --git a/test/es-module/test-esm-type-flag-errors.js b/test/es-module/test-esm-type-flag-errors.js index 169118e01800ef..a54a018ad9b774 100644 --- a/test/es-module/test-esm-type-flag-errors.js +++ b/test/es-module/test-esm-type-flag-errors.js @@ -23,6 +23,13 @@ expect('', packageWithoutTypeMain, 'package-without-type'); expect('--input-type=module', packageTypeModuleMain, 'ERR_INPUT_TYPE_NOT_ALLOWED', true); +try { + require('../fixtures/es-modules/package-type-module/index.js'); + assert.fail('Expected CJS to fail loading from type: module package.'); +} catch (e) { + assert(e.toString().match(/Error \[ERR_REQUIRE_ESM\]: Must use import to load ES Module:/)); +} + function expect(opt = '', inputFile, want, wantsError = false) { // TODO: Remove when --experimental-modules is unflagged opt = `--experimental-modules ${opt}`; diff --git a/test/parallel/test-policy-integrity.js b/test/parallel/test-policy-integrity.js index 722b45f633174d..2cc1f1280c8059 100644 --- a/test/parallel/test-policy-integrity.js +++ b/test/parallel/test-policy-integrity.js @@ -212,6 +212,10 @@ test({ shouldFail: false, entry: parentFilepath, resources: { + [packageURL]: { + body: packageBody, + match: true, + }, [parentURL]: { body: parentBody, match: true, @@ -227,6 +231,10 @@ test({ preload: [depFilepath], entry: parentFilepath, resources: { + [packageURL]: { + body: packageBody, + match: true, + }, [parentURL]: { body: parentBody, match: true, @@ -279,6 +287,10 @@ test({ shouldFail: false, entry: depFilepath, resources: { + [packageURL]: { + body: packageBody, + match: true, + }, [depURL]: { body: depBody, match: true, @@ -289,6 +301,10 @@ test({ shouldFail: false, entry: depFilepath, resources: { + [packageURL]: { + body: packageBody, + match: true, + }, [policyToDepRelativeURLString]: { body: depBody, match: true, @@ -309,6 +325,10 @@ test({ shouldFail: false, entry: depFilepath, resources: { + [packageURL]: { + body: packageBody, + match: true, + }, [policyToDepRelativeURLString]: { body: depBody, match: true, @@ -351,6 +371,10 @@ test({ shouldFail: false, entry: workerSpawningFilepath, resources: { + [packageURL]: { + body: packageBody, + match: true, + }, [workerSpawningURL]: { body: workerSpawningBody, match: true, @@ -370,6 +394,10 @@ test({ entry: workerSpawningFilepath, preload: [parentFilepath], resources: { + [packageURL]: { + body: packageBody, + match: true, + }, [workerSpawningURL]: { body: workerSpawningBody, match: true, diff --git a/test/parallel/test-policy-parse-integrity.js b/test/parallel/test-policy-parse-integrity.js index 7779f767fb97b7..6fa95416390693 100644 --- a/test/parallel/test-policy-parse-integrity.js +++ b/test/parallel/test-policy-parse-integrity.js @@ -36,8 +36,16 @@ if (!tmpdirURL.pathname.endsWith('/')) { tmpdirURL.pathname += '/'; } +const packageFilepath = path.join(tmpdir.path, 'package.json'); +const packageURL = pathToFileURL(packageFilepath); +const packageBody = '{"main": "dep.js"}'; + function test({ shouldFail, integrity }) { const resources = { + [packageURL]: { + body: packageBody, + integrity: `sha256-${hash('sha256', packageBody)}` + }, [depURL]: { body: depBody, integrity