88
99import { json , workspaces } from '@angular-devkit/core' ;
1010import * as path from 'path' ;
11+ import { URL , pathToFileURL } from 'url' ;
1112import { deserialize , serialize } from 'v8' ;
1213import { BuilderInfo } from '../src' ;
1314import { Schema as BuilderSchema } from '../src/builders-schema' ;
@@ -197,7 +198,8 @@ export class WorkspaceNodeModulesArchitectHost implements ArchitectHost<NodeModu
197198 }
198199
199200 async loadBuilder ( info : NodeModulesBuilderInfo ) : Promise < Builder > {
200- const builder = ( await import ( info . import ) ) . default ;
201+ const builder = await getBuilder ( info . import ) ;
202+
201203 if ( builder [ BuilderSymbol ] ) {
202204 return builder ;
203205 }
@@ -210,3 +212,47 @@ export class WorkspaceNodeModulesArchitectHost implements ArchitectHost<NodeModu
210212 throw new Error ( 'Builder is not a builder' ) ;
211213 }
212214}
215+
216+ /**
217+ * This uses a dynamic import to load a module which may be ESM.
218+ * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
219+ * will currently, unconditionally downlevel dynamic import into a require call.
220+ * require calls cannot load ESM code and will result in a runtime error. To workaround
221+ * this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
222+ * Once TypeScript provides support for keeping the dynamic import this workaround can
223+ * be dropped.
224+ *
225+ * @param modulePath The path of the module to load.
226+ * @returns A Promise that resolves to the dynamically imported module.
227+ */
228+ function loadEsmModule < T > ( modulePath : string | URL ) : Promise < T > {
229+ return new Function ( 'modulePath' , `return import(modulePath);` ) ( modulePath ) as Promise < T > ;
230+ }
231+
232+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233+ async function getBuilder ( builderPath : string ) : Promise < any > {
234+ switch ( path . extname ( builderPath ) ) {
235+ case '.mjs' :
236+ // Load the ESM configuration file using the TypeScript dynamic import workaround.
237+ // Once TypeScript provides support for keeping the dynamic import this workaround can be
238+ // changed to a direct dynamic import.
239+ return ( await loadEsmModule < { default : unknown } > ( pathToFileURL ( builderPath ) ) ) . default ;
240+ case '.cjs' :
241+ return require ( builderPath ) ;
242+ default :
243+ // The file could be either CommonJS or ESM.
244+ // CommonJS is tried first then ESM if loading fails.
245+ try {
246+ return require ( builderPath ) ;
247+ } catch ( e ) {
248+ if ( e . code === 'ERR_REQUIRE_ESM' ) {
249+ // Load the ESM configuration file using the TypeScript dynamic import workaround.
250+ // Once TypeScript provides support for keeping the dynamic import this workaround can be
251+ // changed to a direct dynamic import.
252+ return ( await loadEsmModule < { default : unknown } > ( pathToFileURL ( builderPath ) ) ) . default ;
253+ }
254+
255+ throw e ;
256+ }
257+ }
258+ }
0 commit comments