diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 08ba06101ca92c..3b79ac2df5ceef 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -1,10 +1,14 @@ 'use strict'; const { + ArrayPrototypeJoin, ObjectSetPrototypeOf, PromiseAll, SafeSet, SafePromise, + StringPrototypeIncludes, + StringPrototypeMatch, + StringPrototypeSplit, } = primordials; const { ModuleWrap } = internalBinding('module_wrap'); @@ -93,6 +97,29 @@ class ModuleJob { } } catch (e) { decorateErrorStack(e); + if (StringPrototypeIncludes(e.message, + ' does not provide an export named')) { + const splitStack = StringPrototypeSplit(e.stack, '\n'); + const parentFileUrl = splitStack[0]; + const childSpecifier = StringPrototypeMatch(e.message, /module '(.*)' does/)[1]; + const childFileURL = + await this.loader.resolve(childSpecifier, parentFileUrl); + const format = await this.loader.getFormat(childFileURL); + if (format === 'commonjs') { + const importStatement = splitStack[1]; + const namedImports = StringPrototypeMatch(importStatement, /{.*}/)[0]; + e.message = `The requested module '${childSpecifier}' is expected ` + + 'to be of type CommonJS, which does not support named exports. ' + + 'CommonJS modules can be imported by importing the default ' + + 'export.\n' + + 'For example:\n' + + `import pkg from '${childSpecifier}';\n` + + `const ${namedImports} = pkg;`; + const newStack = StringPrototypeSplit(e.stack, '\n'); + newStack[3] = `SyntaxError: ${e.message}`; + e.stack = ArrayPrototypeJoin(newStack, '\n'); + } + } throw e; } for (const dependencyJob of jobsInGraph) { diff --git a/test/es-module/test-esm-cjs-named-error.mjs b/test/es-module/test-esm-cjs-named-error.mjs new file mode 100644 index 00000000000000..d34f762dfb4e26 --- /dev/null +++ b/test/es-module/test-esm-cjs-named-error.mjs @@ -0,0 +1,64 @@ +import '../common/index.mjs'; +import { rejects } from 'assert'; + +const fixtureBase = '../fixtures/es-modules/package-cjs-named-error'; + +const expectedRelative = 'The requested module \'./fail.cjs\' is expected to ' + + 'be of type CommonJS, which does not support named exports. CommonJS ' + + 'modules can be imported by importing the default export.\n' + + 'For example:\n' + + 'import pkg from \'./fail.cjs\';\n' + + 'const { comeOn } = pkg;'; + +const expectedPackageHack = 'The requested module \'./json-hack/fail.js\' is ' + + 'expected to be of type CommonJS, which does not support named exports. ' + + 'CommonJS modules can be imported by importing the default export.\n' + + 'For example:\n' + + 'import pkg from \'./json-hack/fail.js\';\n' + + 'const { comeOn } = pkg;'; + +const expectedBare = 'The requested module \'deep-fail\' is expected to ' + + 'be of type CommonJS, which does not support named exports. CommonJS ' + + 'modules can be imported by importing the default export.\n' + + 'For example:\n' + + 'import pkg from \'deep-fail\';\n' + + 'const { comeOn } = pkg;'; + +rejects(async () => { + await import(`${fixtureBase}/single-quote.mjs`); +}, { + name: 'SyntaxError', + message: expectedRelative +}, 'should support relative specifiers with single quotes'); + +rejects(async () => { + await import(`${fixtureBase}/double-quote.mjs`); +}, { + name: 'SyntaxError', + message: expectedRelative +}, 'should support relative specifiers with double quotes'); + +rejects(async () => { + await import(`${fixtureBase}/json-hack.mjs`); +}, { + name: 'SyntaxError', + message: expectedPackageHack +}, 'should respect recursive package.json for module type'); + +rejects(async () => { + await import(`${fixtureBase}/bare-import-single.mjs`); +}, { + name: 'SyntaxError', + message: expectedBare +}, 'should support bare specifiers with single quotes'); + +rejects(async () => { + await import(`${fixtureBase}/bare-import-double.mjs`); +}, { + name: 'SyntaxError', + message: expectedBare +}, 'should support bare specifiers with double quotes'); + +rejects(async () => { + await import(`${fixtureBase}/escaped-single-quote.mjs`); +}, /import pkg from '\.\/oh'no\.cjs'/, 'should support relative specifiers with escaped single quote'); diff --git a/test/fixtures/es-modules/package-cjs-named-error/bare-import-double.mjs b/test/fixtures/es-modules/package-cjs-named-error/bare-import-double.mjs new file mode 100644 index 00000000000000..45b8e83a84b795 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/bare-import-double.mjs @@ -0,0 +1 @@ +import { comeOn } from "deep-fail"; diff --git a/test/fixtures/es-modules/package-cjs-named-error/bare-import-single.mjs b/test/fixtures/es-modules/package-cjs-named-error/bare-import-single.mjs new file mode 100644 index 00000000000000..24c6b528b723a7 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/bare-import-single.mjs @@ -0,0 +1 @@ +import { comeOn } from 'deep-fail'; diff --git a/test/fixtures/es-modules/package-cjs-named-error/double-quote.mjs b/test/fixtures/es-modules/package-cjs-named-error/double-quote.mjs new file mode 100644 index 00000000000000..3015ef5bfa88db --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/double-quote.mjs @@ -0,0 +1 @@ +import { comeOn } from "./fail.cjs"; diff --git a/test/fixtures/es-modules/package-cjs-named-error/escaped-single-quote.mjs b/test/fixtures/es-modules/package-cjs-named-error/escaped-single-quote.mjs new file mode 100644 index 00000000000000..07165c8b54d137 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/escaped-single-quote.mjs @@ -0,0 +1 @@ +import { value } from "./oh'no.cjs"; diff --git a/test/fixtures/es-modules/package-cjs-named-error/fail.cjs b/test/fixtures/es-modules/package-cjs-named-error/fail.cjs new file mode 100644 index 00000000000000..40c512ab0e5ad2 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/fail.cjs @@ -0,0 +1,3 @@ +module.exports = { + comeOn: 'fhqwhgads' +}; diff --git a/test/fixtures/es-modules/package-cjs-named-error/json-hack.mjs b/test/fixtures/es-modules/package-cjs-named-error/json-hack.mjs new file mode 100644 index 00000000000000..8bcb98462aef78 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/json-hack.mjs @@ -0,0 +1 @@ +import { comeOn } from './json-hack/fail.js'; diff --git a/test/fixtures/es-modules/package-cjs-named-error/json-hack/fail.js b/test/fixtures/es-modules/package-cjs-named-error/json-hack/fail.js new file mode 100644 index 00000000000000..40c512ab0e5ad2 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/json-hack/fail.js @@ -0,0 +1,3 @@ +module.exports = { + comeOn: 'fhqwhgads' +}; diff --git a/test/fixtures/es-modules/package-cjs-named-error/json-hack/package.json b/test/fixtures/es-modules/package-cjs-named-error/json-hack/package.json new file mode 100644 index 00000000000000..5bbefffbabee39 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/json-hack/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/index.js b/test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/index.js new file mode 100644 index 00000000000000..40c512ab0e5ad2 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/index.js @@ -0,0 +1,3 @@ +module.exports = { + comeOn: 'fhqwhgads' +}; diff --git a/test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/package.json b/test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/package.json new file mode 100644 index 00000000000000..a52e9923b0c910 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/package.json @@ -0,0 +1,4 @@ +{ + "name": "deep-fail", + "main": "index.mjs" +} diff --git a/test/fixtures/es-modules/package-cjs-named-error/oh'no.cjs b/test/fixtures/es-modules/package-cjs-named-error/oh'no.cjs new file mode 100644 index 00000000000000..a508e568a888fd --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/oh'no.cjs @@ -0,0 +1,3 @@ +module.exports = { + value: 123 +}; diff --git a/test/fixtures/es-modules/package-cjs-named-error/package.json b/test/fixtures/es-modules/package-cjs-named-error/package.json new file mode 100644 index 00000000000000..e0ec22311d216a --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-cjs-named-error", + "main": "index.mjs", + "type": "module" +} diff --git a/test/fixtures/es-modules/package-cjs-named-error/single-quote.mjs b/test/fixtures/es-modules/package-cjs-named-error/single-quote.mjs new file mode 100644 index 00000000000000..c6e923162c2e8c --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/single-quote.mjs @@ -0,0 +1 @@ +import { comeOn } from './fail.cjs';