From 22f700cbc72ebcdb58531ab754f61672a384348a Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 1 Oct 2020 21:24:35 +0200 Subject: [PATCH 01/11] module: improve named cjs import errors When trying to import named exports from a CommonJS module an error is thrown. Unfortunately the V8 error only contains the single line that causes the error, it is therefore impossible to construct an equivalent code consisting of default import + object descructuring assignment. This was the reason why the example code was removed for multi line import statements in https://github.com/nodejs/node/pull/35275 To generate a helpful error messages for any case we can parse the file where the error happens using acorn and construct a valid example code from the parsed ImportDeclaration. This will work for _any_ valid import statement. Since this code is only executed shortly before the node process crashes anyways performance should not be a concern here. Fixes: https://github.com/nodejs/node/issues/35259 Refs: https://github.com/nodejs/node/pull/35275 --- lib/internal/modules/esm/module_job.js | 105 ++++++++++++++---- test/es-module/test-esm-cjs-named-error.mjs | 22 +++- .../default-and-renamed-import.mjs | 4 + 3 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/es-modules/package-cjs-named-error/default-and-renamed-import.mjs diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 6cf968553bae6d..e89eda59c8c576 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -5,6 +5,7 @@ const { ArrayPrototypeMap, ArrayPrototypePush, FunctionPrototype, + Number, ObjectSetPrototypeOf, PromiseAll, PromiseResolve, @@ -14,13 +15,13 @@ const { SafeSet, StringPrototypeIncludes, StringPrototypeMatch, - StringPrototypeReplace, StringPrototypeSplit, } = primordials; const { ModuleWrap } = internalBinding('module_wrap'); const { decorateErrorStack } = require('internal/util'); +const { fileURLToPath } = require('url'); const assert = require('internal/assert'); const resolvedPromise = PromiseResolve(); @@ -28,6 +29,64 @@ const noop = FunctionPrototype; let hasPausedEntry = false; +function extractExample(file, lineNumber) { + const { readFileSync } = require('fs'); + const { parse } = require('internal/deps/acorn/acorn/dist/acorn'); + const { findNodeAt } = require('internal/deps/acorn/acorn-walk/dist/walk'); + + const code = readFileSync(file, { encoding: 'utf8' }); + const parsed = parse(code, { + sourceType: 'module', + locations: true, + }); + let start = 0; + let node; + do { + node = findNodeAt(parsed, start); + start = node.node.end + 1; + + if (node.node.loc.end.line < lineNumber) { + continue; + } + + if (node.node.type !== 'ImportDeclaration') { + continue; + } + + const defaultSpecifier = node.node.specifiers.find( + (specifier) => specifier.type === 'ImportDefaultSpecifier' + ); + const defaultImport = defaultSpecifier + ? defaultSpecifier.local.name + : 'pkg'; + + const joinString = ', '; + let totalLength = 0; + const imports = node.node.specifiers + .filter((specifier) => specifier.type === 'ImportSpecifier') + .map((specifier) => { + const statement = + specifier.local.name === specifier.imported.name + ? `${specifier.imported.name}` + : `${specifier.imported.name}: ${specifier.local.name}`; + totalLength += statement.length + joinString.length; + return statement; + }); + + const boilerplate = `const { } = ${defaultImport};`; + const destructuringAssignment = + totalLength > 80 - boilerplate.length + ? `\n${imports.map((i) => ` ${i}`).join(',\n')}\n` + : ` ${imports.join(joinString)} `; + + return ( + `\n\nimport ${defaultImport} from '${node.node.source.value}';\n` + + `const {${destructuringAssignment}} = ${defaultImport};\n` + ); + } while (node === undefined || node.node.loc.start.line <= lineNumber); + return ''; +} + /* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of * its dependencies, over time. */ class ModuleJob { @@ -91,8 +150,11 @@ class ModuleJob { } jobsInGraph.add(moduleJob); const dependencyJobs = await moduleJob.linked; - return PromiseAll(new SafeArrayIterator( - ArrayPrototypeMap(dependencyJobs, addJobsToDependencyGraph))); + return PromiseAll( + new SafeArrayIterator( + ArrayPrototypeMap(dependencyJobs, addJobsToDependencyGraph) + ) + ); }; await addJobsToDependencyGraph(this); @@ -106,32 +168,35 @@ class ModuleJob { } } catch (e) { decorateErrorStack(e); - if (StringPrototypeIncludes(e.message, - ' does not provide an export named')) { + if ( + StringPrototypeIncludes(e.message, ' does not provide an export named') + ) { const splitStack = StringPrototypeSplit(e.stack, '\n'); const parentFileUrl = splitStack[0]; const { 1: childSpecifier, 2: name } = StringPrototypeMatch( e.message, - /module '(.*)' does not provide an export named '(.+)'/); - const childFileURL = - await this.loader.resolve(childSpecifier, parentFileUrl); + /module '(.*)' does not provide an export named '(.+)'/ + ); + const childFileURL = await this.loader.resolve( + childSpecifier, + parentFileUrl + ); const format = await this.loader.getFormat(childFileURL); if (format === 'commonjs') { - const importStatement = splitStack[1]; - // TODO(@ctavan): The original error stack only provides the single - // line which causes the error. For multi-line import statements we - // cannot generate an equivalent object descructuring assignment by - // just parsing the error stack. - const oneLineNamedImports = StringPrototypeMatch(importStatement, /{.*}/); - const destructuringAssignment = oneLineNamedImports && - StringPrototypeReplace(oneLineNamedImports, /\s+as\s+/g, ': '); - e.message = `Named export '${name}' not found. The requested module` + + const [, fileUrl, lineNumber] = StringPrototypeMatch( + parentFileUrl, + /^(.*):([0-9]+)$/ + ); + const example = extractExample( + fileURLToPath(fileUrl), + Number(lineNumber) + ); + e.message = + `Named export '${name}' not found. The requested module` + ` '${childSpecifier}' is a CommonJS module, which may not support` + ' all module.exports as named exports.\nCommonJS modules can ' + 'always be imported via the default export, for example using:' + - `\n\nimport pkg from '${childSpecifier}';\n${ - destructuringAssignment ? - `const ${destructuringAssignment} = pkg;\n` : ''}`; + example; const newStack = StringPrototypeSplit(e.stack, '\n'); newStack[3] = `SyntaxError: ${e.message}`; e.stack = ArrayPrototypeJoin(newStack, '\n'); diff --git a/test/es-module/test-esm-cjs-named-error.mjs b/test/es-module/test-esm-cjs-named-error.mjs index 4ef75a22f92674..9fdea0f0444a1b 100644 --- a/test/es-module/test-esm-cjs-named-error.mjs +++ b/test/es-module/test-esm-cjs-named-error.mjs @@ -3,21 +3,26 @@ import { rejects } from 'assert'; const fixtureBase = '../fixtures/es-modules/package-cjs-named-error'; -const errTemplate = (specifier, name, namedImports) => +const errTemplate = (specifier, name, namedImports, defaultName) => `Named export '${name}' not found. The requested module` + ` '${specifier}' is a CommonJS module, which may not support ` + 'all module.exports as named exports.\nCommonJS modules can ' + 'always be imported via the default export, for example using:' + - `\n\nimport pkg from '${specifier}';\n` + (namedImports ? - `const ${namedImports} = pkg;\n` : ''); + `\n\nimport ${defaultName || 'pkg'} from '${specifier}';\n` + (namedImports ? + `const ${namedImports} = ${defaultName || 'pkg'};\n` : ''); -const expectedWithoutExample = errTemplate('./fail.cjs', 'comeOn'); +const expectedMultiLine = errTemplate('./fail.cjs', 'comeOn', + '{ comeOn, everybody }'); const expectedRelative = errTemplate('./fail.cjs', 'comeOn', '{ comeOn }'); const expectedRenamed = errTemplate('./fail.cjs', 'comeOn', '{ comeOn: comeOnRenamed }'); +const expectedDefaultRenamed = + errTemplate('./fail.cjs', 'everybody', '{ comeOn: comeOnRenamed, everybody }', + 'abc'); + const expectedPackageHack = errTemplate('./json-hack/fail.js', 'comeOn', '{ comeOn }'); @@ -44,11 +49,18 @@ rejects(async () => { message: expectedRenamed }, 'should correctly format named imports with renames'); +rejects(async () => { + await import(`${fixtureBase}/default-and-renamed-import.mjs`); +}, { + name: 'SyntaxError', + message: expectedDefaultRenamed +}, 'should correctly format hybrid default and named imports with renames'); + rejects(async () => { await import(`${fixtureBase}/multi-line.mjs`); }, { name: 'SyntaxError', - message: expectedWithoutExample, + message: expectedMultiLine, }, 'should correctly format named imports across multiple lines'); rejects(async () => { diff --git a/test/fixtures/es-modules/package-cjs-named-error/default-and-renamed-import.mjs b/test/fixtures/es-modules/package-cjs-named-error/default-and-renamed-import.mjs new file mode 100644 index 00000000000000..5401f4fdef68bc --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/default-and-renamed-import.mjs @@ -0,0 +1,4 @@ +import abc, { + comeOn as comeOnRenamed, + everybody, +} from './fail.cjs'; From e4a018a444dfde0ae76579c7f6fba9f97d876588 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 2 Oct 2020 09:34:12 +0200 Subject: [PATCH 02/11] Update lib/internal/modules/esm/module_job.js Co-authored-by: Antoine du Hamel --- lib/internal/modules/esm/module_job.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index e89eda59c8c576..2baf3a599ffe61 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -1,6 +1,8 @@ 'use strict'; const { + ArrayPrototypeFilter, + ArrayPrototypeFind, ArrayPrototypeJoin, ArrayPrototypeMap, ArrayPrototypePush, From 75c84fd279c1d2fb9cf957ba0e64b8eb513fb90c Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 2 Oct 2020 09:34:40 +0200 Subject: [PATCH 03/11] Update lib/internal/modules/esm/module_job.js Co-authored-by: Antoine du Hamel --- lib/internal/modules/esm/module_job.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 2baf3a599ffe61..e04d7292020df5 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -55,7 +55,8 @@ function extractExample(file, lineNumber) { continue; } - const defaultSpecifier = node.node.specifiers.find( + const defaultSpecifier = ArrayPrototypeFind( + node.node.specifiers, (specifier) => specifier.type === 'ImportDefaultSpecifier' ); const defaultImport = defaultSpecifier From 757d643b1824bc419e2689e6112ad359348c8c67 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 2 Oct 2020 09:35:29 +0200 Subject: [PATCH 04/11] Apply suggestions from code review Co-authored-by: Antoine du Hamel --- lib/internal/modules/esm/module_job.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index e04d7292020df5..55c5c39a26c3e8 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -65,9 +65,12 @@ function extractExample(file, lineNumber) { const joinString = ', '; let totalLength = 0; - const imports = node.node.specifiers - .filter((specifier) => specifier.type === 'ImportSpecifier') - .map((specifier) => { + const imports = ArrayPrototypeMap( + ArrayPrototypeFilter( + node.node.specifiers, + (specifier) => specifier.type === 'ImportSpecifier') + ), + (specifier) => { const statement = specifier.local.name === specifier.imported.name ? `${specifier.imported.name}` @@ -77,15 +80,12 @@ function extractExample(file, lineNumber) { }); const boilerplate = `const { } = ${defaultImport};`; - const destructuringAssignment = - totalLength > 80 - boilerplate.length - ? `\n${imports.map((i) => ` ${i}`).join(',\n')}\n` - : ` ${imports.join(joinString)} `; - - return ( - `\n\nimport ${defaultImport} from '${node.node.source.value}';\n` + - `const {${destructuringAssignment}} = ${defaultImport};\n` - ); + const destructuringAssignment = totalLength > 80 - boilerplate.length ? + `\n${ArrayPrototypeJoin(ArrayPrototypeMap(imports, (i) => ` ${i}`), ',\n')}\n` : + ` ${imports.join(joinString)} `; + + return `\n\nimport ${defaultImport} from '${node.node.source.value}';\n` + + `const {${destructuringAssignment}} = ${defaultImport};\n`; } while (node === undefined || node.node.loc.start.line <= lineNumber); return ''; } From 1fcb340bf90420fe057f679130eebb5a300d91cd Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 2 Oct 2020 09:39:48 +0200 Subject: [PATCH 05/11] Fix syntax errors introduced by commit suggestions --- lib/internal/modules/esm/module_job.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 55c5c39a26c3e8..bd1297ebc094f4 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -68,7 +68,7 @@ function extractExample(file, lineNumber) { const imports = ArrayPrototypeMap( ArrayPrototypeFilter( node.node.specifiers, - (specifier) => specifier.type === 'ImportSpecifier') + (specifier) => (specifier.type === 'ImportSpecifier'), ), (specifier) => { const statement = @@ -81,7 +81,10 @@ function extractExample(file, lineNumber) { const boilerplate = `const { } = ${defaultImport};`; const destructuringAssignment = totalLength > 80 - boilerplate.length ? - `\n${ArrayPrototypeJoin(ArrayPrototypeMap(imports, (i) => ` ${i}`), ',\n')}\n` : + `\n${ArrayPrototypeJoin( + ArrayPrototypeMap(imports, (i) => ` ${i}`), + ',\n', + )}\n` : ` ${imports.join(joinString)} `; return `\n\nimport ${defaultImport} from '${node.node.source.value}';\n` + From 368305c0e6c2a3b27fd6b4ed3697ec31a28b6456 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 16 Oct 2020 11:27:10 +0200 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: Antoine du Hamel --- lib/internal/modules/esm/module_job.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index bd1297ebc094f4..44c76c89fce2ab 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -23,7 +23,7 @@ const { const { ModuleWrap } = internalBinding('module_wrap'); const { decorateErrorStack } = require('internal/util'); -const { fileURLToPath } = require('url'); +const { fileURLToPath } = require('internal/url'); const assert = require('internal/assert'); const resolvedPromise = PromiseResolve(); @@ -85,7 +85,7 @@ function extractExample(file, lineNumber) { ArrayPrototypeMap(imports, (i) => ` ${i}`), ',\n', )}\n` : - ` ${imports.join(joinString)} `; + ` ${ArrayPrototypeJoin(imports, joinString)} `; return `\n\nimport ${defaultImport} from '${node.node.source.value}';\n` + `const {${destructuringAssignment}} = ${defaultImport};\n`; From 067755908a6eb4a5898641af68c0f9c85477e86a Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 16 Oct 2020 13:36:43 +0200 Subject: [PATCH 07/11] Add long multiline test with comment --- test/es-module/test-esm-cjs-named-error.mjs | 24 ++++++++++++++++++- .../package-cjs-named-error/fail.cjs | 10 ++++++++ .../long-multi-line.mjs | 14 +++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/es-modules/package-cjs-named-error/long-multi-line.mjs diff --git a/test/es-module/test-esm-cjs-named-error.mjs b/test/es-module/test-esm-cjs-named-error.mjs index 9fdea0f0444a1b..e70faf425c9048 100644 --- a/test/es-module/test-esm-cjs-named-error.mjs +++ b/test/es-module/test-esm-cjs-named-error.mjs @@ -14,6 +14,21 @@ const errTemplate = (specifier, name, namedImports, defaultName) => const expectedMultiLine = errTemplate('./fail.cjs', 'comeOn', '{ comeOn, everybody }'); +const expectedLongMultiLine = errTemplate('./fail.cjs', 'comeOn', + '{\n' + + ' comeOn,\n' + + ' one,\n' + + ' two,\n' + + ' three,\n' + + ' four,\n' + + ' five,\n' + + ' six,\n' + + ' seven,\n' + + ' eight,\n' + + ' nine,\n' + + ' ten\n' + + '}'); + const expectedRelative = errTemplate('./fail.cjs', 'comeOn', '{ comeOn }'); const expectedRenamed = errTemplate('./fail.cjs', 'comeOn', @@ -61,7 +76,14 @@ rejects(async () => { }, { name: 'SyntaxError', message: expectedMultiLine, -}, 'should correctly format named imports across multiple lines'); +}, 'should correctly format named multi-line imports'); + +rejects(async () => { + await import(`${fixtureBase}/long-multi-line.mjs`); +}, { + name: 'SyntaxError', + message: expectedLongMultiLine, +}, 'should correctly format named very long multi-line imports'); rejects(async () => { await import(`${fixtureBase}/json-hack.mjs`); diff --git a/test/fixtures/es-modules/package-cjs-named-error/fail.cjs b/test/fixtures/es-modules/package-cjs-named-error/fail.cjs index cab82d3eb60d60..95bf062a357e21 100644 --- a/test/fixtures/es-modules/package-cjs-named-error/fail.cjs +++ b/test/fixtures/es-modules/package-cjs-named-error/fail.cjs @@ -1,4 +1,14 @@ module.exports = { comeOn: 'fhqwhgads', everybody: 'to the limit', + one: 1, + two: 2, + three: 3, + four: 4, + five: 5, + six: 6, + seven: 7, + eight: 8, + nine: 9, + ten: 10, }; diff --git a/test/fixtures/es-modules/package-cjs-named-error/long-multi-line.mjs b/test/fixtures/es-modules/package-cjs-named-error/long-multi-line.mjs new file mode 100644 index 00000000000000..cf4c017d58d390 --- /dev/null +++ b/test/fixtures/es-modules/package-cjs-named-error/long-multi-line.mjs @@ -0,0 +1,14 @@ +import { + comeOn, + // everybody, + one, + two, + three, + four, + five, + six, + seven, + eight, + nine, + ten, +} from "./fail.cjs"; From ae0a8ee9221d4308dda92aa0f5b1a9c04a81ea53 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 16 Oct 2020 14:51:14 +0200 Subject: [PATCH 08/11] Fail if import example cannot be extracted --- lib/internal/modules/esm/module_job.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 44c76c89fce2ab..f8e59e30bf5d5d 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -90,7 +90,7 @@ function extractExample(file, lineNumber) { return `\n\nimport ${defaultImport} from '${node.node.source.value}';\n` + `const {${destructuringAssignment}} = ${defaultImport};\n`; } while (node === undefined || node.node.loc.start.line <= lineNumber); - return ''; + assert.fail('Could not find erroneous import statement'); } /* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of From 4d11c44098f6840d8e0b3cf62091d4e6a0c33dfc Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Fri, 29 Jan 2021 23:59:09 +0100 Subject: [PATCH 09/11] fixup! Add long multiline test with comment --- test/es-module/test-esm-cjs-named-error.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/es-module/test-esm-cjs-named-error.mjs b/test/es-module/test-esm-cjs-named-error.mjs index e70faf425c9048..f35603ec8d8804 100644 --- a/test/es-module/test-esm-cjs-named-error.mjs +++ b/test/es-module/test-esm-cjs-named-error.mjs @@ -14,7 +14,7 @@ const errTemplate = (specifier, name, namedImports, defaultName) => const expectedMultiLine = errTemplate('./fail.cjs', 'comeOn', '{ comeOn, everybody }'); -const expectedLongMultiLine = errTemplate('./fail.cjs', 'comeOn', +const expectedLongMultiLine = errTemplate('./fail.cjs', 'one', '{\n' + ' comeOn,\n' + ' one,\n' + From 182245f3fea12e6cc19c2632e5ffc0c45155676f Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Sat, 30 Jan 2021 00:03:24 +0100 Subject: [PATCH 10/11] Satisfy eslint --- lib/internal/modules/esm/module_job.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index f8e59e30bf5d5d..f32680303ac628 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -59,9 +59,9 @@ function extractExample(file, lineNumber) { node.node.specifiers, (specifier) => specifier.type === 'ImportDefaultSpecifier' ); - const defaultImport = defaultSpecifier - ? defaultSpecifier.local.name - : 'pkg'; + const defaultImport = defaultSpecifier ? + defaultSpecifier.local.name : + 'pkg'; const joinString = ', '; let totalLength = 0; @@ -72,9 +72,9 @@ function extractExample(file, lineNumber) { ), (specifier) => { const statement = - specifier.local.name === specifier.imported.name - ? `${specifier.imported.name}` - : `${specifier.imported.name}: ${specifier.local.name}`; + specifier.local.name === specifier.imported.name ? + `${specifier.imported.name}` : + `${specifier.imported.name}: ${specifier.local.name}`; totalLength += statement.length + joinString.length; return statement; }); From 533560f781a0110d9f391dd6e7b37f01847c9443 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Sat, 30 Jan 2021 00:07:00 +0100 Subject: [PATCH 11/11] Revert "module: improve named cjs import errors" This reverts commit 22f700cbc72ebcdb58531ab754f61672a384348a. --- lib/internal/modules/esm/module_job.js | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index f32680303ac628..7efd7ef5e70286 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -156,11 +156,8 @@ class ModuleJob { } jobsInGraph.add(moduleJob); const dependencyJobs = await moduleJob.linked; - return PromiseAll( - new SafeArrayIterator( - ArrayPrototypeMap(dependencyJobs, addJobsToDependencyGraph) - ) - ); + return PromiseAll(new SafeArrayIterator( + ArrayPrototypeMap(dependencyJobs, addJobsToDependencyGraph))); }; await addJobsToDependencyGraph(this); @@ -174,19 +171,15 @@ class ModuleJob { } } catch (e) { decorateErrorStack(e); - if ( - StringPrototypeIncludes(e.message, ' does not provide an export named') - ) { + if (StringPrototypeIncludes(e.message, + ' does not provide an export named')) { const splitStack = StringPrototypeSplit(e.stack, '\n'); const parentFileUrl = splitStack[0]; const { 1: childSpecifier, 2: name } = StringPrototypeMatch( e.message, - /module '(.*)' does not provide an export named '(.+)'/ - ); - const childFileURL = await this.loader.resolve( - childSpecifier, - parentFileUrl - ); + /module '(.*)' does not provide an export named '(.+)'/); + const childFileURL = + await this.loader.resolve(childSpecifier, parentFileUrl); const format = await this.loader.getFormat(childFileURL); if (format === 'commonjs') { const [, fileUrl, lineNumber] = StringPrototypeMatch( @@ -197,8 +190,7 @@ class ModuleJob { fileURLToPath(fileUrl), Number(lineNumber) ); - e.message = - `Named export '${name}' not found. The requested module` + + e.message = `Named export '${name}' not found. The requested module` + ` '${childSpecifier}' is a CommonJS module, which may not support` + ' all module.exports as named exports.\nCommonJS modules can ' + 'always be imported via the default export, for example using:' +