Skip to content

Commit df9efed

Browse files
authored
feat: support pkg.eggPlugin.exports property (#274)
e.g.: ```json "eggPlugin": { "name": "schedule", "exports": { "import": "./dist/esm", "require": "./dist/commonjs" } } ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a Node.js version badge to the README for improved visibility. - Introduced a new asynchronous method to check for file existence. - Added support for Node.js version 23 in CI configuration. - New module export structure for plugin settings. - **Bug Fixes** - Enhanced error handling for plugin loading, providing clearer error messages. - **Documentation** - Updated comments and logging for better clarity and structure. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 7811cce commit df9efed

File tree

13 files changed

+152
-56
lines changed

13 files changed

+152
-56
lines changed

.github/workflows/nodejs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ jobs:
1111
name: Node.js
1212
uses: node-modules/github-actions/.github/workflows/node-test.yml@master
1313
with:
14-
version: '18.19.0, 18, 20, 22'
14+
version: '18.19.0, 18, 20, 22, 23'
1515
secrets:
1616
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[![Test coverage][codecov-image]][codecov-url]
66
[![Known Vulnerabilities][snyk-image]][snyk-url]
77
[![npm download][download-image]][download-url]
8+
[![Node.js Version](https://img.shields.io/node/v/@eggjs/core.svg?style=flat)](https://nodejs.org/en/download/)
89

910
[npm-image]: https://img.shields.io/npm/v/@eggjs/core.svg?style=flat-square
1011
[npm-url]: https://npmjs.org/package/@eggjs/core

src/egg.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,12 @@ export class EggCore extends KoaApplication {
248248
* console.log('done');
249249
* });
250250
*/
251+
ready(): Promise<void>;
252+
ready(flagOrFunction: ReadyFunctionArg): void;
251253
ready(flagOrFunction?: ReadyFunctionArg) {
254+
if (flagOrFunction === undefined) {
255+
return this.lifecycle.ready();
256+
}
252257
return this.lifecycle.ready(flagOrFunction);
253258
}
254259

src/lifecycle.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,13 @@ export class Lifecycle extends EventEmitter {
109109
});
110110
}
111111

112-
ready(arg?: ReadyFunctionArg) {
113-
return this.#readyObject.ready(arg);
112+
ready(): Promise<void>;
113+
ready(flagOrFunction: ReadyFunctionArg): void;
114+
ready(flagOrFunction?: ReadyFunctionArg) {
115+
if (flagOrFunction === undefined) {
116+
return this.#readyObject.ready();
117+
}
118+
return this.#readyObject.ready(flagOrFunction);
114119
}
115120

116121
get app() {

src/loader/egg_loader.ts

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { debuglog, inspect } from 'node:util';
55
import { isAsyncFunction, isClass, isGeneratorFunction, isObject } from 'is-type-of';
66
import { homedir } from 'node-homedir';
77
import type { Logger } from 'egg-logger';
8-
import { getParamNames, readJSONSync } from 'utility';
8+
import { getParamNames, readJSONSync, readJSON } from 'utility';
99
import { extend } from 'extend2';
1010
import { Request, Response, Context, Application } from '@eggjs/koa';
1111
import { pathMatching, type PathMatchingOptions } from 'egg-path-matching';
@@ -462,7 +462,7 @@ export class EggLoader {
462462
plugin.path = this.getPluginPath(plugin);
463463

464464
// read plugin information from ${plugin.path}/package.json
465-
this.#mergePluginConfig(plugin);
465+
await this.#mergePluginConfig(plugin);
466466

467467
// disable the plugin that not match the serverEnv
468468
if (env && plugin.env.length > 0 && !plugin.env.includes(env)) {
@@ -538,7 +538,7 @@ export class EggLoader {
538538
for (const name in customPlugins) {
539539
this.#normalizePluginConfig(customPlugins, name, configPath);
540540
}
541-
debug('Loaded custom plugins: %j', Object.keys(customPlugins));
541+
debug('Loaded custom plugins: %o', customPlugins);
542542
}
543543
return customPlugins;
544544
}
@@ -623,16 +623,18 @@ export class EggLoader {
623623
// "strict": true, whether check plugin name, default to true.
624624
// }
625625
// }
626-
#mergePluginConfig(plugin: EggPluginInfo) {
626+
async #mergePluginConfig(plugin: EggPluginInfo) {
627627
let pkg;
628628
let config;
629629
const pluginPackage = path.join(plugin.path!, 'package.json');
630-
if (fs.existsSync(pluginPackage)) {
631-
pkg = readJSONSync(pluginPackage);
630+
if (await utils.existsPath(pluginPackage)) {
631+
pkg = await readJSON(pluginPackage);
632632
config = pkg.eggPlugin;
633633
if (pkg.version) {
634634
plugin.version = pkg.version;
635635
}
636+
// support commonjs and esm dist files
637+
plugin.path = this.#formatPluginPathFromPackageJSON(plugin.path!, pkg);
636638
}
637639

638640
const logger = this.options.logger;
@@ -712,9 +714,9 @@ export class EggLoader {
712714
}
713715

714716
// Following plugins will be enabled implicitly.
715-
// - configclient required by [hsfclient]
716-
// - eagleeye required by [hsfclient]
717-
// - diamond required by [hsfclient]
717+
// - configclient required by [rpcClient]
718+
// - monitor required by [rpcClient]
719+
// - diamond required by [rpcClient]
718720
if (implicitEnabledPlugins.length) {
719721
let message = implicitEnabledPlugins
720722
.map(name => ` - ${name} required by [${requireMap[name]}]`)
@@ -769,20 +771,43 @@ export class EggLoader {
769771

770772
#resolvePluginPath(plugin: EggPluginInfo) {
771773
const name = plugin.package || plugin.name;
772-
773774
try {
774775
// should find the plugin directory
775776
// pnpm will lift the node_modules to the sibling directory
776777
// 'node_modules/.pnpm/[email protected]/node_modules/yadan/node_modules',
777778
// 'node_modules/.pnpm/[email protected]/node_modules', <- this is the sibling directory
778779
// 'node_modules/.pnpm/[email protected]/node_modules/egg/node_modules',
779780
// 'node_modules/.pnpm/[email protected]/node_modules', <- this is the sibling directory
780-
const filePath = utils.resolvePath(`${name}/package.json`, { paths: [ ...this.lookupDirs ] });
781-
return path.dirname(filePath);
782-
} catch (err: any) {
781+
const pluginPkgFile = utils.resolvePath(`${name}/package.json`, { paths: [ ...this.lookupDirs ] });
782+
return path.dirname(pluginPkgFile);
783+
} catch (err) {
783784
debug('[resolvePluginPath] error: %o', err);
784-
throw new Error(`Can not find plugin ${name} in "${[ ...this.lookupDirs ].join(', ')}"`);
785+
throw new Error(`Can not find plugin ${name} in "${[ ...this.lookupDirs ].join(', ')}"`, {
786+
cause: err,
787+
});
788+
}
789+
}
790+
791+
#formatPluginPathFromPackageJSON(pluginPath: string, pluginPkg: {
792+
eggPlugin?: {
793+
exports?: {
794+
import?: string;
795+
require?: string;
796+
};
797+
};
798+
}) {
799+
if (pluginPkg.eggPlugin?.exports) {
800+
if (typeof require === 'function') {
801+
if (pluginPkg.eggPlugin.exports.require) {
802+
pluginPath = path.join(pluginPath, pluginPkg.eggPlugin.exports.require);
803+
}
804+
} else {
805+
if (pluginPkg.eggPlugin.exports.import) {
806+
pluginPath = path.join(pluginPath, pluginPkg.eggPlugin.exports.import);
807+
}
808+
}
785809
}
810+
return pluginPath;
786811
}
787812

788813
#extendPlugins(targets: Record<string, EggPluginInfo>, plugins: Record<string, EggPluginInfo>) {
@@ -1036,9 +1061,10 @@ export class EggLoader {
10361061
debug('loadExtend %s, filepaths: %j', name, filepaths);
10371062

10381063
const mergeRecord = new Map();
1039-
for (let filepath of filepaths) {
1040-
filepath = this.resolveModule(filepath)!;
1064+
for (const rawFilepath of filepaths) {
1065+
const filepath = this.resolveModule(rawFilepath)!;
10411066
if (!filepath) {
1067+
debug('loadExtend %o not found', rawFilepath);
10421068
continue;
10431069
}
10441070
if (filepath.endsWith('/index.js')) {

src/utils/index.ts

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { debuglog } from 'node:util';
22
import path from 'node:path';
33
import fs from 'node:fs';
4+
import { stat } from 'node:fs/promises';
45
import BuiltinModule from 'node:module';
56
import { importResolve, importModule } from '@eggjs/utils';
67

@@ -18,14 +19,61 @@ const extensions = (Module as any)._extensions;
1819
const extensionNames = Object.keys(extensions).concat([ '.cjs', '.mjs' ]);
1920
debug('Module extensions: %j', extensionNames);
2021

22+
function getCalleeFromStack(withLine?: boolean, stackIndex?: number) {
23+
stackIndex = stackIndex === undefined ? 2 : stackIndex;
24+
const limit = Error.stackTraceLimit;
25+
const prep = Error.prepareStackTrace;
26+
27+
Error.prepareStackTrace = prepareObjectStackTrace;
28+
Error.stackTraceLimit = 5;
29+
30+
// capture the stack
31+
const obj: any = {};
32+
Error.captureStackTrace(obj);
33+
let callSite = obj.stack[stackIndex];
34+
let fileName = '';
35+
if (callSite) {
36+
// egg-mock will create a proxy
37+
// https://github.com/eggjs/egg-mock/blob/master/lib/app.js#L174
38+
fileName = callSite.getFileName();
39+
/* istanbul ignore if */
40+
if (fileName && fileName.endsWith('egg-mock/lib/app.js')) {
41+
// TODO: add test
42+
callSite = obj.stack[stackIndex + 1];
43+
fileName = callSite.getFileName();
44+
}
45+
}
46+
47+
Error.prepareStackTrace = prep;
48+
Error.stackTraceLimit = limit;
49+
50+
if (!callSite || !fileName) return '<anonymous>';
51+
if (!withLine) return fileName;
52+
return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`;
53+
}
54+
2155
export default {
2256
deprecated(message: string) {
23-
console.warn('[@eggjs/core:deprecated] %s', message);
57+
if (debug.enabled) {
58+
console.trace('[@eggjs/core:deprecated] %s', message);
59+
} else {
60+
console.warn('[@eggjs/core:deprecated] %s', message);
61+
console.warn('[@eggjs/core:deprecated] set NODE_DEBUG=@eggjs/core:utils can show call stack');
62+
}
2463
},
2564

2665
extensions,
2766
extensionNames,
2867

68+
async existsPath(filepath: string) {
69+
try {
70+
await stat(filepath);
71+
return true;
72+
} catch {
73+
return false;
74+
}
75+
},
76+
2977
async loadFile(filepath: string) {
3078
try {
3179
// if not js module, just return content buffer
@@ -55,38 +103,7 @@ export default {
55103
return ctx ? fn.call(ctx, ...args) : fn(...args);
56104
},
57105

58-
getCalleeFromStack(withLine?: boolean, stackIndex?: number) {
59-
stackIndex = stackIndex === undefined ? 2 : stackIndex;
60-
const limit = Error.stackTraceLimit;
61-
const prep = Error.prepareStackTrace;
62-
63-
Error.prepareStackTrace = prepareObjectStackTrace;
64-
Error.stackTraceLimit = 5;
65-
66-
// capture the stack
67-
const obj: any = {};
68-
Error.captureStackTrace(obj);
69-
let callSite = obj.stack[stackIndex];
70-
let fileName = '';
71-
if (callSite) {
72-
// egg-mock will create a proxy
73-
// https://github.com/eggjs/egg-mock/blob/master/lib/app.js#L174
74-
fileName = callSite.getFileName();
75-
/* istanbul ignore if */
76-
if (fileName && fileName.endsWith('egg-mock/lib/app.js')) {
77-
// TODO: add test
78-
callSite = obj.stack[stackIndex + 1];
79-
fileName = callSite.getFileName();
80-
}
81-
}
82-
83-
Error.prepareStackTrace = prep;
84-
Error.stackTraceLimit = limit;
85-
86-
if (!callSite || !fileName) return '<anonymous>';
87-
if (!withLine) return fileName;
88-
return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`;
89-
},
106+
getCalleeFromStack,
90107

91108
getResolvedFilename(filepath: string, baseDir: string) {
92109
const reg = /[/\\]/g;

test/fixtures/plugin-egg-plugin/config/plugin.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
module.exports = {
42
a: {
53
enable: true,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
a: {
3+
enable: true,
4+
},
5+
};

test/fixtures/plugin-pkg-exports/node_modules/a/foo_dist/commonjs/config/config.default.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/plugin-pkg-exports/node_modules/a/foo_dist/esm/config/config.default.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)