From a840f92822690f871af857673ea303b99322cb5b Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 17 Dec 2024 11:51:15 +0800 Subject: [PATCH] feat: support `pkg.eggPlugin.exports` property e.g.: ```json "eggPlugin": { "name": "schedule", "exports": { "import": "./dist/esm", "require": "./dist/commonjs" } } ``` --- README.md | 1 + src/loader/egg_loader.ts | 58 +++++++++++++++++++++--------- src/utils/index.ts | 77 +++++++++++++++++++++++----------------- 3 files changed, 88 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index cad214e4..ba9dfe59 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Test coverage][codecov-image]][codecov-url] [![Known Vulnerabilities][snyk-image]][snyk-url] [![npm download][download-image]][download-url] +[![Node.js Version](https://img.shields.io/node/v/@eggjs/core.svg?style=flat)](https://nodejs.org/en/download/) [npm-image]: https://img.shields.io/npm/v/@eggjs/core.svg?style=flat-square [npm-url]: https://npmjs.org/package/@eggjs/core diff --git a/src/loader/egg_loader.ts b/src/loader/egg_loader.ts index 7c38254c..4f2fe1c4 100644 --- a/src/loader/egg_loader.ts +++ b/src/loader/egg_loader.ts @@ -5,7 +5,7 @@ import { debuglog, inspect } from 'node:util'; import { isAsyncFunction, isClass, isGeneratorFunction, isObject } from 'is-type-of'; import { homedir } from 'node-homedir'; import type { Logger } from 'egg-logger'; -import { getParamNames, readJSONSync } from 'utility'; +import { getParamNames, readJSONSync, readJSON } from 'utility'; import { extend } from 'extend2'; import { Request, Response, Context, Application } from '@eggjs/koa'; import { pathMatching, type PathMatchingOptions } from 'egg-path-matching'; @@ -462,7 +462,7 @@ export class EggLoader { plugin.path = this.getPluginPath(plugin); // read plugin information from ${plugin.path}/package.json - this.#mergePluginConfig(plugin); + await this.#mergePluginConfig(plugin); // disable the plugin that not match the serverEnv if (env && plugin.env.length > 0 && !plugin.env.includes(env)) { @@ -538,7 +538,7 @@ export class EggLoader { for (const name in customPlugins) { this.#normalizePluginConfig(customPlugins, name, configPath); } - debug('Loaded custom plugins: %j', Object.keys(customPlugins)); + debug('Loaded custom plugins: %o', customPlugins); } return customPlugins; } @@ -623,16 +623,18 @@ export class EggLoader { // "strict": true, whether check plugin name, default to true. // } // } - #mergePluginConfig(plugin: EggPluginInfo) { + async #mergePluginConfig(plugin: EggPluginInfo) { let pkg; let config; const pluginPackage = path.join(plugin.path!, 'package.json'); - if (fs.existsSync(pluginPackage)) { - pkg = readJSONSync(pluginPackage); + if (await utils.existsPath(pluginPackage)) { + pkg = await readJSON(pluginPackage); config = pkg.eggPlugin; if (pkg.version) { plugin.version = pkg.version; } + // support commonjs and esm dist files + plugin.path = this.#formatPluginPathFromPackageJSON(plugin.path!, pkg); } const logger = this.options.logger; @@ -712,9 +714,9 @@ export class EggLoader { } // Following plugins will be enabled implicitly. - // - configclient required by [hsfclient] - // - eagleeye required by [hsfclient] - // - diamond required by [hsfclient] + // - configclient required by [rpcClient] + // - monitor required by [rpcClient] + // - diamond required by [rpcClient] if (implicitEnabledPlugins.length) { let message = implicitEnabledPlugins .map(name => ` - ${name} required by [${requireMap[name]}]`) @@ -769,7 +771,6 @@ export class EggLoader { #resolvePluginPath(plugin: EggPluginInfo) { const name = plugin.package || plugin.name; - try { // should find the plugin directory // pnpm will lift the node_modules to the sibling directory @@ -777,12 +778,36 @@ export class EggLoader { // 'node_modules/.pnpm/yadan@2.0.0/node_modules', <- this is the sibling directory // 'node_modules/.pnpm/egg@2.33.1/node_modules/egg/node_modules', // 'node_modules/.pnpm/egg@2.33.1/node_modules', <- this is the sibling directory - const filePath = utils.resolvePath(`${name}/package.json`, { paths: [ ...this.lookupDirs ] }); - return path.dirname(filePath); - } catch (err: any) { + const pluginPkgFile = utils.resolvePath(`${name}/package.json`, { paths: [ ...this.lookupDirs ] }); + return path.dirname(pluginPkgFile); + } catch (err) { debug('[resolvePluginPath] error: %o', err); - throw new Error(`Can not find plugin ${name} in "${[ ...this.lookupDirs ].join(', ')}"`); + throw new Error(`Can not find plugin ${name} in "${[ ...this.lookupDirs ].join(', ')}"`, { + cause: err, + }); + } + } + + #formatPluginPathFromPackageJSON(pluginPath: string, pluginPkg: { + eggPlugin?: { + exports?: { + import?: string; + require?: string; + }; + }; + }) { + if (pluginPkg.eggPlugin?.exports) { + if (typeof require === 'function') { + if (pluginPkg.eggPlugin.exports.require) { + pluginPath = path.join(pluginPath, pluginPkg.eggPlugin.exports.require); + } + } else { + if (pluginPkg.eggPlugin.exports.import) { + pluginPath = path.join(pluginPath, pluginPkg.eggPlugin.exports.import); + } + } } + return pluginPath; } #extendPlugins(targets: Record, plugins: Record) { @@ -1036,9 +1061,10 @@ export class EggLoader { debug('loadExtend %s, filepaths: %j', name, filepaths); const mergeRecord = new Map(); - for (let filepath of filepaths) { - filepath = this.resolveModule(filepath)!; + for (const rawFilepath of filepaths) { + const filepath = this.resolveModule(rawFilepath)!; if (!filepath) { + debug('loadExtend %o not found', rawFilepath); continue; } if (filepath.endsWith('/index.js')) { diff --git a/src/utils/index.ts b/src/utils/index.ts index 9ff70e50..950041ff 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ import { debuglog } from 'node:util'; import path from 'node:path'; import fs from 'node:fs'; +import { stat } from 'node:fs/promises'; import BuiltinModule from 'node:module'; import { importResolve, importModule } from '@eggjs/utils'; @@ -18,14 +19,57 @@ const extensions = (Module as any)._extensions; const extensionNames = Object.keys(extensions).concat([ '.cjs', '.mjs' ]); debug('Module extensions: %j', extensionNames); +function getCalleeFromStack(withLine?: boolean, stackIndex?: number) { + stackIndex = stackIndex === undefined ? 2 : stackIndex; + const limit = Error.stackTraceLimit; + const prep = Error.prepareStackTrace; + + Error.prepareStackTrace = prepareObjectStackTrace; + Error.stackTraceLimit = 5; + + // capture the stack + const obj: any = {}; + Error.captureStackTrace(obj); + let callSite = obj.stack[stackIndex]; + let fileName = ''; + if (callSite) { + // egg-mock will create a proxy + // https://github.com/eggjs/egg-mock/blob/master/lib/app.js#L174 + fileName = callSite.getFileName(); + /* istanbul ignore if */ + if (fileName && fileName.endsWith('egg-mock/lib/app.js')) { + // TODO: add test + callSite = obj.stack[stackIndex + 1]; + fileName = callSite.getFileName(); + } + } + + Error.prepareStackTrace = prep; + Error.stackTraceLimit = limit; + + if (!callSite || !fileName) return ''; + if (!withLine) return fileName; + return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`; +} + export default { deprecated(message: string) { + // console.trace('[@eggjs/core:deprecated] %s', message); console.warn('[@eggjs/core:deprecated] %s', message); }, extensions, extensionNames, + async existsPath(filepath: string) { + try { + await stat(filepath); + return true; + } catch { + return false; + } + }, + async loadFile(filepath: string) { try { // if not js module, just return content buffer @@ -55,38 +99,7 @@ export default { return ctx ? fn.call(ctx, ...args) : fn(...args); }, - getCalleeFromStack(withLine?: boolean, stackIndex?: number) { - stackIndex = stackIndex === undefined ? 2 : stackIndex; - const limit = Error.stackTraceLimit; - const prep = Error.prepareStackTrace; - - Error.prepareStackTrace = prepareObjectStackTrace; - Error.stackTraceLimit = 5; - - // capture the stack - const obj: any = {}; - Error.captureStackTrace(obj); - let callSite = obj.stack[stackIndex]; - let fileName = ''; - if (callSite) { - // egg-mock will create a proxy - // https://github.com/eggjs/egg-mock/blob/master/lib/app.js#L174 - fileName = callSite.getFileName(); - /* istanbul ignore if */ - if (fileName && fileName.endsWith('egg-mock/lib/app.js')) { - // TODO: add test - callSite = obj.stack[stackIndex + 1]; - fileName = callSite.getFileName(); - } - } - - Error.prepareStackTrace = prep; - Error.stackTraceLimit = limit; - - if (!callSite || !fileName) return ''; - if (!withLine) return fileName; - return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`; - }, + getCalleeFromStack, getResolvedFilename(filepath: string, baseDir: string) { const reg = /[/\\]/g;