module: error for CJS .js load within type: module

PR-URL: https://github.com/nodejs/node/pull/29492
Reviewed-By: Jan Krems <jan.krems@gmail.com>
This commit is contained in:
Guy Bedford 2019-09-08 11:20:43 -04:00 committed by Jan Krems
parent ac41959922
commit 4396bebfe1
5 changed files with 87 additions and 34 deletions

View File

@ -212,16 +212,17 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
// -> a.<ext> // -> a.<ext>
// -> a/index.<ext> // -> a/index.<ext>
// Check if the directory is a package.json dir. const packageJsonCache = new SafeMap();
const packageMainCache = Object.create(null);
// Explicit exports from package.json files
const packageExportsCache = new SafeMap();
function readPackageRaw(requestPath) { function readPackage(requestPath) {
const jsonPath = path.resolve(requestPath, 'package.json'); 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) { if (json === undefined) {
packageJsonCache.set(jsonPath, false);
return false; return false;
} }
@ -232,11 +233,13 @@ function readPackageRaw(requestPath) {
try { try {
const parsed = JSON.parse(json); const parsed = JSON.parse(json);
packageMainCache[requestPath] = parsed.main; const filtered = {
if (experimentalExports) { main: parsed.main,
packageExportsCache.set(requestPath, parsed.exports); exports: parsed.exports,
} type: parsed.type
return parsed; };
packageJsonCache.set(jsonPath, filtered);
return filtered;
} catch (e) { } catch (e) {
e.path = jsonPath; e.path = jsonPath;
e.message = 'Error parsing ' + jsonPath + ': ' + e.message; e.message = 'Error parsing ' + jsonPath + ': ' + e.message;
@ -244,33 +247,33 @@ function readPackageRaw(requestPath) {
} }
} }
function readPackage(requestPath) { function readPackageScope(checkPath) {
const entry = packageMainCache[requestPath]; const rootSeparatorIndex = checkPath.indexOf(path.sep);
if (entry) let separatorIndex;
return entry; while (
(separatorIndex = checkPath.lastIndexOf(path.sep)) > rootSeparatorIndex
const pkg = readPackageRaw(requestPath); ) {
if (pkg === false) return false; checkPath = checkPath.slice(0, separatorIndex);
if (checkPath.endsWith(path.sep + 'node_modules'))
return pkg.main; return false;
const pjson = readPackage(checkPath);
if (pjson) return pjson;
}
return false;
} }
function readExports(requestPath) { function readPackageMain(requestPath) {
if (packageExportsCache.has(requestPath)) { const pkg = readPackage(requestPath);
return packageExportsCache.get(requestPath); return pkg ? pkg.main : undefined;
} }
const pkg = readPackageRaw(requestPath); function readPackageExports(requestPath) {
if (!pkg) { const pkg = readPackage(requestPath);
packageExportsCache.set(requestPath, null); return pkg ? pkg.exports : undefined;
return null;
}
return pkg.exports;
} }
function tryPackage(requestPath, exts, isMain, originalPath) { function tryPackage(requestPath, exts, isMain, originalPath) {
const pkg = readPackage(requestPath); const pkg = readPackageMain(requestPath);
if (!pkg) { if (!pkg) {
return tryExtensions(path.resolve(requestPath, 'index'), exts, isMain); return tryExtensions(path.resolve(requestPath, 'index'), exts, isMain);
@ -371,7 +374,7 @@ function resolveExports(nmPath, request, absoluteRequest) {
} }
const basePath = path.resolve(nmPath, name); const basePath = path.resolve(nmPath, name);
const pkgExports = readExports(basePath); const pkgExports = readPackageExports(basePath);
const mappingKey = `.${expansion}`; const mappingKey = `.${expansion}`;
if (typeof pkgExports === 'object' && pkgExports !== null) { if (typeof pkgExports === 'object' && pkgExports !== null) {
@ -947,6 +950,12 @@ Module.prototype._compile = function(content, filename) {
// Native extension for .js // Native extension for .js
Module._extensions['.js'] = function(module, filename) { 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'); const content = fs.readFileSync(filename, 'utf8');
module._compile(content, filename); module._compile(content, filename);
}; };

View File

@ -875,7 +875,8 @@ static void InternalModuleReadJSON(const FunctionCallbackInfo<Value>& args) {
const size_t size = offset - start; const size_t size = offset - start;
if (size == 0 || ( if (size == 0 || (
size == SearchString(&chars[start], size, "\"main\"") && 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; return;
} else { } else {
Local<String> chars_string = Local<String> chars_string =

View File

@ -23,6 +23,13 @@ expect('', packageWithoutTypeMain, 'package-without-type');
expect('--input-type=module', packageTypeModuleMain, expect('--input-type=module', packageTypeModuleMain,
'ERR_INPUT_TYPE_NOT_ALLOWED', true); '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) { function expect(opt = '', inputFile, want, wantsError = false) {
// TODO: Remove when --experimental-modules is unflagged // TODO: Remove when --experimental-modules is unflagged
opt = `--experimental-modules ${opt}`; opt = `--experimental-modules ${opt}`;

View File

@ -212,6 +212,10 @@ test({
shouldFail: false, shouldFail: false,
entry: parentFilepath, entry: parentFilepath,
resources: { resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[parentURL]: { [parentURL]: {
body: parentBody, body: parentBody,
match: true, match: true,
@ -227,6 +231,10 @@ test({
preload: [depFilepath], preload: [depFilepath],
entry: parentFilepath, entry: parentFilepath,
resources: { resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[parentURL]: { [parentURL]: {
body: parentBody, body: parentBody,
match: true, match: true,
@ -279,6 +287,10 @@ test({
shouldFail: false, shouldFail: false,
entry: depFilepath, entry: depFilepath,
resources: { resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[depURL]: { [depURL]: {
body: depBody, body: depBody,
match: true, match: true,
@ -289,6 +301,10 @@ test({
shouldFail: false, shouldFail: false,
entry: depFilepath, entry: depFilepath,
resources: { resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[policyToDepRelativeURLString]: { [policyToDepRelativeURLString]: {
body: depBody, body: depBody,
match: true, match: true,
@ -309,6 +325,10 @@ test({
shouldFail: false, shouldFail: false,
entry: depFilepath, entry: depFilepath,
resources: { resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[policyToDepRelativeURLString]: { [policyToDepRelativeURLString]: {
body: depBody, body: depBody,
match: true, match: true,
@ -351,6 +371,10 @@ test({
shouldFail: false, shouldFail: false,
entry: workerSpawningFilepath, entry: workerSpawningFilepath,
resources: { resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[workerSpawningURL]: { [workerSpawningURL]: {
body: workerSpawningBody, body: workerSpawningBody,
match: true, match: true,
@ -370,6 +394,10 @@ test({
entry: workerSpawningFilepath, entry: workerSpawningFilepath,
preload: [parentFilepath], preload: [parentFilepath],
resources: { resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[workerSpawningURL]: { [workerSpawningURL]: {
body: workerSpawningBody, body: workerSpawningBody,
match: true, match: true,

View File

@ -36,8 +36,16 @@ if (!tmpdirURL.pathname.endsWith('/')) {
tmpdirURL.pathname += '/'; tmpdirURL.pathname += '/';
} }
const packageFilepath = path.join(tmpdir.path, 'package.json');
const packageURL = pathToFileURL(packageFilepath);
const packageBody = '{"main": "dep.js"}';
function test({ shouldFail, integrity }) { function test({ shouldFail, integrity }) {
const resources = { const resources = {
[packageURL]: {
body: packageBody,
integrity: `sha256-${hash('sha256', packageBody)}`
},
[depURL]: { [depURL]: {
body: depBody, body: depBody,
integrity integrity