Skip to content

Commit

Permalink
feat: support file returning async functions when loading it (#272)
Browse files Browse the repository at this point in the history
Use Case:

dynamically load mysql connection configuration from a remote config
center to keep it secure.

```ts
// config.default.ts

export default async function() {
  return {
    mysql: {
      ...(await nacos.get('mysqlConfig'))
    }
  };
}
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Enhanced asynchronous file loading capabilities, including support for
both CommonJS and ES modules.
- Introduction of new modules that return promises with configuration
data.

- **Bug Fixes**
	- Improved handling of promises in the file loading process.

- **Tests**
- Added multiple test cases to verify the functionality of loading
various types of modules, including asynchronous functions and
promise-based exports.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
AntiMoron authored Dec 18, 2024
1 parent cf7203e commit cb48d0e
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/loader/egg_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import fs from 'node:fs';
import path from 'node:path';
import assert from 'node:assert';
import { debuglog, inspect } from 'node:util';
import { isAsyncFunction, isClass, isGeneratorFunction, isObject } from 'is-type-of';
import { homedir } from 'node-homedir';
import { isAsyncFunction, isClass, isGeneratorFunction, isObject, isPromise } from 'is-type-of';
import type { Logger } from 'egg-logger';
import { getParamNames, readJSONSync, readJSON } from 'utility';
import { extend } from 'extend2';
Expand Down Expand Up @@ -1447,6 +1447,9 @@ export class EggLoader {
let mod = await this.requireFile(fullpath);
if (typeof mod === 'function' && !isClass(mod)) {
mod = mod(...inject);
if (isPromise(mod)) {
mod = await mod;
}
}
return mod;
}
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/load_file/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";

module.exports = async () => {
return { clients: "Test Config" };
};
5 changes: 5 additions & 0 deletions test/fixtures/load_file/es-module-default-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
exports.__esModule = true;
exports["default"] = async function () {
return { clients: "Test Config" };
};
3 changes: 3 additions & 0 deletions test/fixtures/load_file/es-module-default-null.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use strict";
exports.__esModule = true;
exports["default"] = null;
5 changes: 5 additions & 0 deletions test/fixtures/load_file/es-module-default-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
exports.__esModule = true;
exports["default"] = function () {
return Promise.resolve({ clients: "Test Config" });
};
5 changes: 5 additions & 0 deletions test/fixtures/load_file/es-module-default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
exports.__esModule = true;
exports["default"] = {
fn() {}
};
3 changes: 3 additions & 0 deletions test/fixtures/load_file/promise_function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function () {
return Promise.resolve({ clients: "Test Config" });
};
4 changes: 4 additions & 0 deletions test/fixtures/loadfile-esm/es-module-default-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use strict";
export default async function () {
return { clients: "Test Config" };
};
4 changes: 4 additions & 0 deletions test/fixtures/loadfile-esm/es-module-default-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use strict";
export default function () {
return Promise.resolve({ clients: "Test Config" });
};
5 changes: 5 additions & 0 deletions test/fixtures/loadfile/es-module-default-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
exports.__esModule = true;
exports["default"] = async function () {
return { clients: "Test Config" };
};
5 changes: 5 additions & 0 deletions test/fixtures/loadfile/es-module-default-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
exports.__esModule = true;
exports["default"] = function () {
return Promise.resolve({ clients: "Test Config" });
};
36 changes: 36 additions & 0 deletions test/loader/load_file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,40 @@ describe('test/loader/load_file.test.ts', () => {
}
assert.equal(result, '---\nmap:\n a: 1\n b: 2');
});

it('should load cjs module file which returns function returning a promise', async () => {
app = createApp('load_file');
const result = (await app.loader.loadFile(getFilepath('load_file/promise_function.js')));
assert.deepEqual(result, { clients: 'Test Config' });
});

it('should load cjs module file which returns async function', async () => {
app = createApp('load_file');
const result = (await app.loader.loadFile(getFilepath('load_file/async.js')));
assert.deepEqual(result, { clients: 'Test Config' });
});

it('should load compiled es module file', async () => {
app = createApp('load_file');
const result = (await app.loader.loadFile(getFilepath('load_file/es-module-default.js')));
assert(result.fn);
});

it('should load compiled es module file which default = null', async () => {
app = createApp('load_file');
const result = (await app.loader.loadFile(getFilepath('load_file/es-module-default-null.js')));
assert.equal(result, null);
});

it('should load compiled es module file which default = function returning a promise', async () => {
app = createApp('load_file');
const result = (await app.loader.loadFile(getFilepath('load_file/es-module-default-promise.js')));
assert.deepEqual(result, { clients: 'Test Config' });
});

it('should load compiled es module file which default = async function', async () => {
app = createApp('load_file');
const result = (await app.loader.loadFile(getFilepath('load_file/es-module-default-async.js')));
assert.deepEqual(result, { clients: 'Test Config' });
});
});
20 changes: 20 additions & 0 deletions test/utils/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ describe('test/utils/index.test.ts', () => {
assert.equal(result, null);
});

it('should load es module with default = async function', async () => {
const result = await utils.loadFile(path.join(baseDir, 'es-module-default-async.js'));
assert(typeof result().then === 'function');
});

it('should load es module with default = function returning a promise', async () => {
const result = await utils.loadFile(path.join(baseDir, 'es-module-default-promise.js'));
assert(typeof result().then === 'function');
});

it('should load no js file', async () => {
let result = (await utils.loadFile(path.join(baseDir, 'no-js.yml'))).toString();
if (process.platform === 'win32') {
Expand Down Expand Up @@ -110,6 +120,16 @@ describe('test/utils/index.test.ts', () => {
assert.equal(result, null);
});

it('should load es module with default = async function', async () => {
const result = await utils.loadFile(path.join(baseDir, 'es-module-default-async.js'));
assert(typeof result().then === 'function');
});

it('should load es module with default = function returning a promise', async () => {
const result = await utils.loadFile(path.join(baseDir, 'es-module-default-promise.js'));
assert(typeof result().then === 'function');
});

it('should load no js file', async () => {
let result = (await utils.loadFile(path.join(baseDir, 'no-js.yml'))).toString();
if (process.platform === 'win32') {
Expand Down

0 comments on commit cb48d0e

Please sign in to comment.