@@ -126,6 +126,58 @@ const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => {
126126 }
127127} ;
128128
129+ /**
130+ * If `error` is a SyntaxError from V8 for a missing named export on a CJS module, rewrite its message to the friendlier
131+ * "Named export '...' not found..." form. Must be called after `decorateErrorStack(error)` so that the arrow (source
132+ * context with the import statement text) has been prepended to `error.stack`.
133+ * @param {Error } error
134+ * @param {ModuleWrap } module The parent module that triggered the instantiation.
135+ * @param {boolean[] } commonJsDeps Per-request array indicating whether each dependency is a CJS module, aligned with
136+ * `module.getModuleRequests()`.
137+ */
138+ const handleCJSNamedExportError = ( error , module , commonJsDeps ) => {
139+ // TODO(@bcoe): Add source map support to exception that occurs as result
140+ // of missing named export. This is currently not possible because
141+ // stack trace originates in module_job, not the file itself. A hidden
142+ // symbol with filename could be set in node_errors.cc to facilitate this.
143+ if ( ! getSourceMapsSupport ( ) . enabled &&
144+ StringPrototypeIncludes ( error . message ,
145+ ' does not provide an export named' ) ) {
146+ const splitStack = StringPrototypeSplit ( error . stack , '\n' , 2 ) ;
147+ const { 1 : childSpecifier , 2 : name } = RegExpPrototypeExec (
148+ / 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 ' ( .+ ) ' / ,
149+ error . message ) ;
150+ const moduleRequests = module . getModuleRequests ( ) ;
151+ let isCommonJS = false ;
152+ for ( let i = 0 ; i < moduleRequests . length ; ++ i ) {
153+ if ( moduleRequests [ i ] . specifier === childSpecifier ) {
154+ isCommonJS = commonJsDeps [ i ] ;
155+ break ;
156+ }
157+ }
158+ if ( isCommonJS ) {
159+ const importStatement = splitStack [ 1 ] ;
160+ // TODO(@ctavan): The original error stack only provides the single
161+ // line which causes the error. For multi-line import statements we
162+ // cannot generate an equivalent object destructuring assignment by
163+ // just parsing the error stack.
164+ const oneLineNamedImports = RegExpPrototypeExec ( / { .* } / , importStatement ) ;
165+ const destructuringAssignment = oneLineNamedImports &&
166+ RegExpPrototypeSymbolReplace ( / \s + a s \s + / g, oneLineNamedImports , ': ' ) ;
167+ error . message = `Named export '${ name } ' not found. The requested module` +
168+ ` '${ childSpecifier } ' is a CommonJS module, which may not support` +
169+ ' all module.exports as named exports.\nCommonJS modules can ' +
170+ 'always be imported via the default export, for example using:' +
171+ `\n\nimport pkg from '${ childSpecifier } ';\n${
172+ destructuringAssignment ?
173+ `const ${ destructuringAssignment } = pkg;\n` : '' } `;
174+ const newStack = StringPrototypeSplit ( error . stack , '\n' ) ;
175+ newStack [ 3 ] = `SyntaxError: ${ error . message } ` ;
176+ error . stack = ArrayPrototypeJoin ( newStack , '\n' ) ;
177+ }
178+ }
179+ } ;
180+
129181class ModuleJobBase {
130182 constructor ( loader , url , importAttributes , phase , isMain , inspectBrk ) {
131183 assert ( typeof phase === 'number' ) ;
@@ -325,50 +377,10 @@ class ModuleJob extends ModuleJobBase {
325377 } else {
326378 this . module . instantiate ( ) ;
327379 }
328- } catch ( e ) {
329- decorateErrorStack ( e ) ;
330- // TODO(@bcoe): Add source map support to exception that occurs as result
331- // of missing named export. This is currently not possible because
332- // stack trace originates in module_job, not the file itself. A hidden
333- // symbol with filename could be set in node_errors.cc to facilitate this.
334- if ( ! getSourceMapsSupport ( ) . enabled &&
335- StringPrototypeIncludes ( e . message ,
336- ' does not provide an export named' ) ) {
337- const splitStack = StringPrototypeSplit ( e . stack , '\n' , 2 ) ;
338- const { 1 : childSpecifier , 2 : name } = RegExpPrototypeExec (
339- / 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 ' ( .+ ) ' / ,
340- e . message ) ;
341- const moduleRequests = this . module . getModuleRequests ( ) ;
342- let isCommonJS = false ;
343- for ( let i = 0 ; i < moduleRequests . length ; ++ i ) {
344- if ( moduleRequests [ i ] . specifier === childSpecifier ) {
345- isCommonJS = this . commonJsDeps [ i ] ;
346- break ;
347- }
348- }
349-
350- if ( isCommonJS ) {
351- const importStatement = splitStack [ 1 ] ;
352- // TODO(@ctavan): The original error stack only provides the single
353- // line which causes the error. For multi-line import statements we
354- // cannot generate an equivalent object destructuring assignment by
355- // just parsing the error stack.
356- const oneLineNamedImports = RegExpPrototypeExec ( / { .* } / , importStatement ) ;
357- const destructuringAssignment = oneLineNamedImports &&
358- RegExpPrototypeSymbolReplace ( / \s + a s \s + / g, oneLineNamedImports , ': ' ) ;
359- e . message = `Named export '${ name } ' not found. The requested module` +
360- ` '${ childSpecifier } ' is a CommonJS module, which may not support` +
361- ' all module.exports as named exports.\nCommonJS modules can ' +
362- 'always be imported via the default export, for example using:' +
363- `\n\nimport pkg from '${ childSpecifier } ';\n${
364- destructuringAssignment ?
365- `const ${ destructuringAssignment } = pkg;\n` : '' } `;
366- const newStack = StringPrototypeSplit ( e . stack , '\n' ) ;
367- newStack [ 3 ] = `SyntaxError: ${ e . message } ` ;
368- e . stack = ArrayPrototypeJoin ( newStack , '\n' ) ;
369- }
370- }
371- throw e ;
380+ } catch ( error ) {
381+ decorateErrorStack ( error ) ;
382+ handleCJSNamedExportError ( error , this . module , this . commonJsDeps ) ;
383+ throw error ;
372384 }
373385
374386 for ( const dependencyJob of jobsInGraph ) {
@@ -510,7 +522,13 @@ class ModuleJobSync extends ModuleJobBase {
510522 }
511523 if ( status < kInstantiated ) {
512524 // Fresh module: instantiate it now (links were already resolved synchronously in constructor)
513- this . module . instantiate ( ) ;
525+ try {
526+ this . module . instantiate ( ) ;
527+ } catch ( error ) {
528+ decorateErrorStack ( error ) ;
529+ handleCJSNamedExportError ( error , this . module , this . commonJsDeps ) ;
530+ throw error ;
531+ }
514532 }
515533 // `status === kInstantiated`: either just instantiated above, or previously instantiated
516534 // but evaluation was deferred (e.g. TLA detected by a prior `runSync()` call)
0 commit comments