8
8
9
9
import { json , workspaces } from '@angular-devkit/core' ;
10
10
import * as path from 'path' ;
11
+ import { URL , pathToFileURL } from 'url' ;
11
12
import { deserialize , serialize } from 'v8' ;
12
13
import { BuilderInfo } from '../src' ;
13
14
import { Schema as BuilderSchema } from '../src/builders-schema' ;
@@ -197,7 +198,8 @@ export class WorkspaceNodeModulesArchitectHost implements ArchitectHost<NodeModu
197
198
}
198
199
199
200
async loadBuilder ( info : NodeModulesBuilderInfo ) : Promise < Builder > {
200
- const builder = ( await import ( info . import ) ) . default ;
201
+ const builder = await getBuilder ( info . import ) ;
202
+
201
203
if ( builder [ BuilderSymbol ] ) {
202
204
return builder ;
203
205
}
@@ -210,3 +212,47 @@ export class WorkspaceNodeModulesArchitectHost implements ArchitectHost<NodeModu
210
212
throw new Error ( 'Builder is not a builder' ) ;
211
213
}
212
214
}
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