From 25846b6e09d94ae2576f3629af77c16b8bb78102 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Sun, 17 Nov 2024 21:12:38 +0800 Subject: [PATCH] feat(boot): support loading ESM artifacts BREAKING CHANGE: `loadClassesFromFiles` now returns a `Promise` see: https://github.com/loopbackio/loopback-next/issues/10744 Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- .../src/__tests__/fixtures/cjs.artifact.cjs | 1 + .../src/__tests__/fixtures/esm.artifact.mjs | 1 + .../unit/booters/booter-utils.unit.ts | 40 ++++++++++++++++--- .../boot/src/booters/base-artifact.booter.ts | 5 ++- packages/boot/src/booters/booter-utils.ts | 6 +-- 5 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 packages/boot/src/__tests__/fixtures/cjs.artifact.cjs create mode 100644 packages/boot/src/__tests__/fixtures/esm.artifact.mjs diff --git a/packages/boot/src/__tests__/fixtures/cjs.artifact.cjs b/packages/boot/src/__tests__/fixtures/cjs.artifact.cjs new file mode 100644 index 000000000000..3dccd4045522 --- /dev/null +++ b/packages/boot/src/__tests__/fixtures/cjs.artifact.cjs @@ -0,0 +1 @@ +export class DummyCJSClass {} diff --git a/packages/boot/src/__tests__/fixtures/esm.artifact.mjs b/packages/boot/src/__tests__/fixtures/esm.artifact.mjs new file mode 100644 index 000000000000..8660cab0d69e --- /dev/null +++ b/packages/boot/src/__tests__/fixtures/esm.artifact.mjs @@ -0,0 +1 @@ +export class DummyESMClass {} diff --git a/packages/boot/src/__tests__/unit/booters/booter-utils.unit.ts b/packages/boot/src/__tests__/unit/booters/booter-utils.unit.ts index 769ebe638e81..554c499c85bc 100644 --- a/packages/boot/src/__tests__/unit/booters/booter-utils.unit.ts +++ b/packages/boot/src/__tests__/unit/booters/booter-utils.unit.ts @@ -66,7 +66,37 @@ describe('booter-utils unit tests', () => { const files = [resolve(sandbox.path, 'multiple.artifact.js')]; const NUM_CLASSES = 2; // Number of classes in above file - const classes = loadClassesFromFiles(files, sandbox.path); + const classes = await loadClassesFromFiles(files, sandbox.path); + expect(classes).to.have.lengthOf(NUM_CLASSES); + expect(classes[0]).to.be.a.Function(); + expect(classes[1]).to.be.a.Function(); + }); + + it('loads classes from CJS files', async () => { + const artifactFilename = 'multiple.artifact.cjs'; + await sandbox.copyFile( + resolve(__dirname, '../../fixtures/multiple.artifact.cjs'), + ); + const NUM_CLASSES = 2; + const classes = await loadClassesFromFiles( + [resolve(sandbox.path, artifactFilename)], + sandbox.path, + ); + expect(classes).to.have.lengthOf(NUM_CLASSES); + expect(classes[0]).to.be.a.Function(); + expect(classes[1]).to.be.a.Function(); + }); + + it('loads classes from ESM files', async () => { + const artifactFilename = 'multiple.artifact.mjs'; + await sandbox.copyFile( + resolve(__dirname, '../../fixtures/multiple.artifact.mjs'), + ); + const NUM_CLASSES = 2; + const classes = await loadClassesFromFiles( + [resolve(sandbox.path, artifactFilename)], + sandbox.path, + ); expect(classes).to.have.lengthOf(NUM_CLASSES); expect(classes[0]).to.be.a.Function(); expect(classes[1]).to.be.a.Function(); @@ -78,16 +108,16 @@ describe('booter-utils unit tests', () => { ); const files = [resolve(sandbox.path, 'empty.artifact.js')]; - const classes = loadClassesFromFiles(files, sandbox.path); + const classes = await loadClassesFromFiles(files, sandbox.path); expect(classes).to.be.an.Array(); expect(classes).to.be.empty(); }); it('throws an error given a non-existent file', async () => { const files = [resolve(sandbox.path, 'fake.artifact.js')]; - expect(() => loadClassesFromFiles(files, sandbox.path)).to.throw( - /Cannot find module/, - ); + await expect( + loadClassesFromFiles(files, sandbox.path), + ).to.be.rejectedWith(/Cannot find module/); }); }); }); diff --git a/packages/boot/src/booters/base-artifact.booter.ts b/packages/boot/src/booters/base-artifact.booter.ts index 50150a50bbd0..5653dcdf31fd 100644 --- a/packages/boot/src/booters/base-artifact.booter.ts +++ b/packages/boot/src/booters/base-artifact.booter.ts @@ -137,6 +137,9 @@ export class BaseArtifactBooter implements Booter { * and then process the artifact classes as appropriate. */ async load() { - this.classes = loadClassesFromFiles(this.discovered, this.projectRoot); + this.classes = await loadClassesFromFiles( + this.discovered, + this.projectRoot, + ); } } diff --git a/packages/boot/src/booters/booter-utils.ts b/packages/boot/src/booters/booter-utils.ts index 52fa5f58bff2..a4bd6d3d2fc8 100644 --- a/packages/boot/src/booters/booter-utils.ts +++ b/packages/boot/src/booters/booter-utils.ts @@ -46,14 +46,14 @@ export function isClass(target: any): target is Constructor { * @param projectRootDir - The project root directory * @returns An array of Class constructors from a file */ -export function loadClassesFromFiles( +export async function loadClassesFromFiles( files: string[], projectRootDir: string, -): Constructor<{}>[] { +): Promise[]> { const classes: Constructor<{}>[] = []; for (const file of files) { debug('Loading artifact file %j', path.relative(projectRootDir, file)); - const moduleObj = require(file); + const moduleObj = await import(file); for (const k in moduleObj) { const exported = moduleObj[k]; if (isClass(exported)) {