55 ArrayPrototypeMap,
66 ArrayPrototypePush,
77 FunctionPrototype,
8+ Number,
89 ObjectSetPrototypeOf,
910 PromiseAll,
1011 PromiseResolve,
@@ -14,20 +15,78 @@ const {
1415 SafeSet,
1516 StringPrototypeIncludes,
1617 StringPrototypeMatch,
17- StringPrototypeReplace,
1818 StringPrototypeSplit,
1919} = primordials ;
2020
2121const { ModuleWrap } = internalBinding ( 'module_wrap' ) ;
2222
2323const { decorateErrorStack } = require ( 'internal/util' ) ;
24+ const { fileURLToPath } = require ( 'url' ) ;
2425const assert = require ( 'internal/assert' ) ;
2526const resolvedPromise = PromiseResolve ( ) ;
2627
2728const noop = FunctionPrototype ;
2829
2930let hasPausedEntry = false ;
3031
32+ function extractExample ( file , lineNumber ) {
33+ const { readFileSync } = require ( 'fs' ) ;
34+ const { parse } = require ( 'internal/deps/acorn/acorn/dist/acorn' ) ;
35+ const { findNodeAt } = require ( 'internal/deps/acorn/acorn-walk/dist/walk' ) ;
36+
37+ const code = readFileSync ( file , { encoding : 'utf8' } ) ;
38+ const parsed = parse ( code , {
39+ sourceType : 'module' ,
40+ locations : true ,
41+ } ) ;
42+ let start = 0 ;
43+ let node ;
44+ do {
45+ node = findNodeAt ( parsed , start ) ;
46+ start = node . node . end + 1 ;
47+
48+ if ( node . node . loc . end . line < lineNumber ) {
49+ continue ;
50+ }
51+
52+ if ( node . node . type !== 'ImportDeclaration' ) {
53+ continue ;
54+ }
55+
56+ const defaultSpecifier = node . node . specifiers . find (
57+ ( specifier ) => specifier . type === 'ImportDefaultSpecifier'
58+ ) ;
59+ const defaultImport = defaultSpecifier
60+ ? defaultSpecifier . local . name
61+ : 'pkg' ;
62+
63+ const joinString = ', ' ;
64+ let totalLength = 0 ;
65+ const imports = node . node . specifiers
66+ . filter ( ( specifier ) => specifier . type === 'ImportSpecifier' )
67+ . map ( ( specifier ) => {
68+ const statement =
69+ specifier . local . name === specifier . imported . name
70+ ? `${ specifier . imported . name } `
71+ : `${ specifier . imported . name } : ${ specifier . local . name } ` ;
72+ totalLength += statement . length + joinString . length ;
73+ return statement ;
74+ } ) ;
75+
76+ const boilerplate = `const { } = ${ defaultImport } ;` ;
77+ const destructuringAssignment =
78+ totalLength > 80 - boilerplate . length
79+ ? `\n${ imports . map ( ( i ) => ` ${ i } ` ) . join ( ',\n' ) } \n`
80+ : ` ${ imports . join ( joinString ) } ` ;
81+
82+ return (
83+ `\n\nimport ${ defaultImport } from '${ node . node . source . value } ';\n` +
84+ `const {${ destructuringAssignment } } = ${ defaultImport } ;\n`
85+ ) ;
86+ } while ( node === undefined || node . node . loc . start . line <= lineNumber ) ;
87+ return '' ;
88+ }
89+
3190/* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of
3291 * its dependencies, over time. */
3392class ModuleJob {
@@ -91,8 +150,11 @@ class ModuleJob {
91150 }
92151 jobsInGraph . add ( moduleJob ) ;
93152 const dependencyJobs = await moduleJob . linked ;
94- return PromiseAll ( new SafeArrayIterator (
95- ArrayPrototypeMap ( dependencyJobs , addJobsToDependencyGraph ) ) ) ;
153+ return PromiseAll (
154+ new SafeArrayIterator (
155+ ArrayPrototypeMap ( dependencyJobs , addJobsToDependencyGraph )
156+ )
157+ ) ;
96158 } ;
97159 await addJobsToDependencyGraph ( this ) ;
98160
@@ -106,32 +168,35 @@ class ModuleJob {
106168 }
107169 } catch ( e ) {
108170 decorateErrorStack ( e ) ;
109- if ( StringPrototypeIncludes ( e . message ,
110- ' does not provide an export named' ) ) {
171+ if (
172+ StringPrototypeIncludes ( e . message , ' does not provide an export named' )
173+ ) {
111174 const splitStack = StringPrototypeSplit ( e . stack , '\n' ) ;
112175 const parentFileUrl = splitStack [ 0 ] ;
113176 const { 1 : childSpecifier , 2 : name } = StringPrototypeMatch (
114177 e . message ,
115- / m o d u l e ' ( .* ) ' d o e s n o t p r o v i d e a n e x p o r t n a m e d ' ( .+ ) ' / ) ;
116- const childFileURL =
117- await this . loader . resolve ( childSpecifier , parentFileUrl ) ;
178+ / m o d u l e ' ( .* ) ' d o e s n o t p r o v i d e a n e x p o r t n a m e d ' ( .+ ) ' /
179+ ) ;
180+ const childFileURL = await this . loader . resolve (
181+ childSpecifier ,
182+ parentFileUrl
183+ ) ;
118184 const format = await this . loader . getFormat ( childFileURL ) ;
119185 if ( format === 'commonjs' ) {
120- const importStatement = splitStack [ 1 ] ;
121- // TODO(@ctavan): The original error stack only provides the single
122- // line which causes the error. For multi-line import statements we
123- // cannot generate an equivalent object descructuring assignment by
124- // just parsing the error stack.
125- const oneLineNamedImports = StringPrototypeMatch ( importStatement , / { .* } / ) ;
126- const destructuringAssignment = oneLineNamedImports &&
127- StringPrototypeReplace ( oneLineNamedImports , / \s + a s \s + / g, ': ' ) ;
128- e . message = `Named export '${ name } ' not found. The requested module` +
186+ const [ , fileUrl , lineNumber ] = StringPrototypeMatch (
187+ parentFileUrl ,
188+ / ^ ( .* ) : ( [ 0 - 9 ] + ) $ /
189+ ) ;
190+ const example = extractExample (
191+ fileURLToPath ( fileUrl ) ,
192+ Number ( lineNumber )
193+ ) ;
194+ e . message =
195+ `Named export '${ name } ' not found. The requested module` +
129196 ` '${ childSpecifier } ' is a CommonJS module, which may not support` +
130197 ' all module.exports as named exports.\nCommonJS modules can ' +
131198 'always be imported via the default export, for example using:' +
132- `\n\nimport pkg from '${ childSpecifier } ';\n${
133- destructuringAssignment ?
134- `const ${ destructuringAssignment } = pkg;\n` : '' } `;
199+ example ;
135200 const newStack = StringPrototypeSplit ( e . stack , '\n' ) ;
136201 newStack [ 3 ] = `SyntaxError: ${ e . message } ` ;
137202 e . stack = ArrayPrototypeJoin ( newStack , '\n' ) ;
0 commit comments