@eggjs/core 6.3.0-beta.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/commonjs/base_context_class.d.ts +3 -3
  2. package/dist/commonjs/base_context_class.js +1 -1
  3. package/dist/commonjs/egg.d.ts +17 -10
  4. package/dist/commonjs/egg.js +30 -4
  5. package/dist/commonjs/index.d.ts +2 -0
  6. package/dist/commonjs/index.js +2 -1
  7. package/dist/commonjs/lifecycle.d.ts +5 -2
  8. package/dist/commonjs/lifecycle.js +35 -15
  9. package/dist/commonjs/loader/context_loader.d.ts +3 -3
  10. package/dist/commonjs/loader/context_loader.js +1 -1
  11. package/dist/commonjs/loader/egg_loader.d.ts +4 -40
  12. package/dist/commonjs/loader/egg_loader.js +56 -39
  13. package/dist/commonjs/loader/file_loader.d.ts +5 -1
  14. package/dist/commonjs/loader/file_loader.js +18 -5
  15. package/dist/commonjs/singleton.d.ts +29 -0
  16. package/dist/commonjs/singleton.js +125 -0
  17. package/dist/commonjs/types.d.ts +53 -0
  18. package/dist/commonjs/types.js +3 -0
  19. package/dist/commonjs/utils/index.js +10 -5
  20. package/dist/commonjs/utils/sequencify.js +1 -1
  21. package/dist/commonjs/utils/timing.js +1 -1
  22. package/dist/esm/base_context_class.d.ts +3 -3
  23. package/dist/esm/base_context_class.js +1 -1
  24. package/dist/esm/egg.d.ts +17 -10
  25. package/dist/esm/egg.js +30 -4
  26. package/dist/esm/index.d.ts +2 -0
  27. package/dist/esm/index.js +2 -1
  28. package/dist/esm/lifecycle.d.ts +5 -2
  29. package/dist/esm/lifecycle.js +35 -15
  30. package/dist/esm/loader/context_loader.d.ts +3 -3
  31. package/dist/esm/loader/context_loader.js +1 -1
  32. package/dist/esm/loader/egg_loader.d.ts +4 -40
  33. package/dist/esm/loader/egg_loader.js +60 -43
  34. package/dist/esm/loader/file_loader.d.ts +5 -1
  35. package/dist/esm/loader/file_loader.js +17 -4
  36. package/dist/esm/singleton.d.ts +29 -0
  37. package/dist/esm/singleton.js +118 -0
  38. package/dist/esm/types.d.ts +53 -0
  39. package/dist/esm/types.js +2 -0
  40. package/dist/esm/utils/index.js +10 -5
  41. package/dist/esm/utils/sequencify.js +1 -1
  42. package/dist/esm/utils/timing.js +1 -1
  43. package/dist/package.json +1 -1
  44. package/package.json +16 -15
  45. package/src/base_context_class.ts +3 -3
  46. package/src/egg.ts +40 -12
  47. package/src/index.ts +2 -0
  48. package/src/lifecycle.ts +40 -15
  49. package/src/loader/context_loader.ts +4 -4
  50. package/src/loader/egg_loader.ts +76 -90
  51. package/src/loader/file_loader.ts +16 -4
  52. package/src/singleton.ts +149 -0
  53. package/src/types.ts +56 -0
  54. package/src/utils/index.ts +9 -4
  55. package/src/utils/sequencify.ts +1 -1
  56. package/src/utils/timing.ts +1 -1
package/src/lifecycle.ts CHANGED
@@ -62,13 +62,15 @@ export interface LifecycleOptions {
62
62
  logger: EggConsoleLogger;
63
63
  }
64
64
 
65
+ export type FunWithFullPath = Fun & { fullPath?: string };
66
+
65
67
  export class Lifecycle extends EventEmitter {
66
68
  #init: boolean;
67
69
  #readyObject: ReadyObject;
68
70
  #bootHooks: (BootImplClass | ILifecycleBoot)[];
69
71
  #boots: ILifecycleBoot[];
70
72
  #isClosed: boolean;
71
- #closeFunctionSet: Set<Fun>;
73
+ #closeFunctionSet: Set<FunWithFullPath>;
72
74
  loadReady: Ready;
73
75
  bootReady: Ready;
74
76
  options: LifecycleOptions;
@@ -96,10 +98,10 @@ export class Lifecycle extends EventEmitter {
96
98
  this.#initReady();
97
99
  this
98
100
  .on('ready_stat', data => {
99
- this.logger.info('[egg:core:ready_stat] end ready task %s, remain %j', data.id, data.remain);
101
+ this.logger.info('[@eggjs/core/lifecycle:ready_stat] end ready task %s, remain %j', data.id, data.remain);
100
102
  })
101
103
  .on('ready_timeout', id => {
102
- this.logger.warn('[egg:core:ready_timeout] %s seconds later %s was still unable to finish.', this.readyTimeout / 1000, id);
104
+ this.logger.warn('[@eggjs/core/lifecycle:ready_timeout] %s seconds later %s was still unable to finish.', this.readyTimeout / 1000, id);
103
105
  });
104
106
 
105
107
  this.ready(err => {
@@ -149,11 +151,12 @@ export class Lifecycle extends EventEmitter {
149
151
  this.#bootHooks.push(bootHootOrBootClass);
150
152
  }
151
153
 
152
- addFunctionAsBootHook<T = EggCore>(hook: (app: T) => void) {
154
+ addFunctionAsBootHook<T = EggCore>(hook: (app: T) => void, fullPath?: string) {
153
155
  assert(this.#init === false, 'do not add hook when lifecycle has been initialized');
154
156
  // app.js is exported as a function
155
157
  // call this function in configDidLoad
156
- this.#bootHooks.push(class Boot implements ILifecycleBoot {
158
+ class Boot implements ILifecycleBoot {
159
+ static fullPath?: string;
157
160
  app: T;
158
161
  constructor(app: T) {
159
162
  this.app = app;
@@ -161,25 +164,34 @@ export class Lifecycle extends EventEmitter {
161
164
  configDidLoad() {
162
165
  hook(this.app);
163
166
  }
164
- });
167
+ }
168
+ Boot.fullPath = fullPath;
169
+ this.#bootHooks.push(Boot);
165
170
  }
166
171
 
167
172
  /**
168
173
  * init boots and trigger config did config
169
174
  */
170
175
  init() {
176
+ debug('%s init lifecycle', this.app.type);
171
177
  assert(this.#init === false, 'lifecycle have been init');
172
178
  this.#init = true;
173
179
  this.#boots = this.#bootHooks.map(BootHootOrBootClass => {
180
+ let instance = BootHootOrBootClass as ILifecycleBoot;
174
181
  if (isClass(BootHootOrBootClass)) {
175
- return new BootHootOrBootClass(this.app);
182
+ instance = new BootHootOrBootClass(this.app);
183
+ if (!instance.fullPath && 'fullPath' in BootHootOrBootClass) {
184
+ instance.fullPath = BootHootOrBootClass.fullPath as string;
185
+ }
176
186
  }
177
- return BootHootOrBootClass;
187
+ debug('[init] add boot instance: %o', instance.fullPath);
188
+ return instance;
178
189
  });
179
190
  }
180
191
 
181
192
  registerBeforeStart(scope: Fun, name: string) {
182
- debug('add registerBeforeStart, name: %o', name);
193
+ debug('%s add registerBeforeStart, name: %o',
194
+ this.options.app.type, name);
183
195
  this.#registerReadyCallback({
184
196
  scope,
185
197
  ready: this.loadReady,
@@ -188,17 +200,24 @@ export class Lifecycle extends EventEmitter {
188
200
  });
189
201
  }
190
202
 
191
- registerBeforeClose(fn: Fun) {
203
+ registerBeforeClose(fn: FunWithFullPath, fullPath?: string) {
192
204
  assert(typeof fn === 'function', 'argument should be function');
193
205
  assert(this.#isClosed === false, 'app has been closed');
206
+ if (fullPath) {
207
+ fn.fullPath = fullPath;
208
+ }
194
209
  this.#closeFunctionSet.add(fn);
210
+ debug('%s register beforeClose at %o, count: %d',
211
+ this.app.type, fullPath, this.#closeFunctionSet.size);
195
212
  }
196
213
 
197
214
  async close() {
198
215
  // close in reverse order: first created, last closed
199
216
  const closeFns = Array.from(this.#closeFunctionSet);
200
- debug('close %d beforeClose functions', closeFns.length);
217
+ debug('%s start trigger %d beforeClose functions',
218
+ this.app.type, closeFns.length);
201
219
  for (const fn of closeFns.reverse()) {
220
+ debug('%s trigger beforeClose at %o', this.app.type, fn.fullPath);
202
221
  await utils.callFn(fn);
203
222
  this.#closeFunctionSet.delete(fn);
204
223
  }
@@ -207,13 +226,14 @@ export class Lifecycle extends EventEmitter {
207
226
  this.removeAllListeners();
208
227
  this.app.removeAllListeners();
209
228
  this.#isClosed = true;
210
- debug('%s closed', this.options.app.type);
229
+ debug('%s closed', this.app.type);
211
230
  }
212
231
 
213
232
  triggerConfigWillLoad() {
214
233
  debug('trigger configWillLoad start');
215
234
  for (const boot of this.#boots) {
216
235
  if (typeof boot.configWillLoad === 'function') {
236
+ debug('trigger configWillLoad at %o', boot.fullPath);
217
237
  boot.configWillLoad();
218
238
  }
219
239
  }
@@ -225,12 +245,13 @@ export class Lifecycle extends EventEmitter {
225
245
  debug('trigger configDidLoad start');
226
246
  for (const boot of this.#boots) {
227
247
  if (typeof boot.configDidLoad === 'function') {
248
+ debug('trigger configDidLoad at %o', boot.fullPath);
228
249
  boot.configDidLoad();
229
250
  }
230
251
  // function boot hook register after configDidLoad trigger
231
252
  if (typeof boot.beforeClose === 'function') {
232
253
  const beforeClose = boot.beforeClose.bind(boot);
233
- this.registerBeforeClose(beforeClose);
254
+ this.registerBeforeClose(beforeClose, boot.fullPath);
234
255
  }
235
256
  }
236
257
  debug('trigger configDidLoad end');
@@ -276,10 +297,12 @@ export class Lifecycle extends EventEmitter {
276
297
  return (async () => {
277
298
  for (const boot of this.#boots) {
278
299
  if (typeof boot.didReady === 'function') {
300
+ debug('trigger didReady at %o', boot.fullPath);
279
301
  try {
280
302
  await boot.didReady(err);
281
- } catch (e) {
282
- this.emit('error', e);
303
+ } catch (err) {
304
+ debug('trigger didReady error at %o, error: %s', boot.fullPath, err);
305
+ this.emit('error', err);
283
306
  }
284
307
  }
285
308
  }
@@ -294,9 +317,11 @@ export class Lifecycle extends EventEmitter {
294
317
  if (typeof boot.serverDidReady !== 'function') {
295
318
  continue;
296
319
  }
320
+ debug('trigger serverDidReady at %o', boot.fullPath);
297
321
  try {
298
322
  await boot.serverDidReady();
299
323
  } catch (err) {
324
+ debug('trigger serverDidReady error at %o, error: %s', boot.fullPath, err);
300
325
  this.emit('error', err);
301
326
  }
302
327
  }
@@ -1,18 +1,18 @@
1
1
  import assert from 'node:assert';
2
2
  import { isClass, isPrimitive } from 'is-type-of';
3
3
  import { FileLoader, EXPORTS, type FileLoaderOptions } from './file_loader.js';
4
- import type { ContextDelegation } from '../egg.js';
4
+ import type { Context } from '../egg.js';
5
5
 
6
6
  const CLASS_LOADER = Symbol('classLoader');
7
7
 
8
8
  export interface ClassLoaderOptions {
9
- ctx: ContextDelegation;
9
+ ctx: Context;
10
10
  properties: any;
11
11
  }
12
12
 
13
13
  export class ClassLoader {
14
14
  readonly _cache = new Map();
15
- _ctx: ContextDelegation;
15
+ _ctx: Context;
16
16
 
17
17
  constructor(options: ClassLoaderOptions) {
18
18
  assert(options.ctx, 'options.ctx is required');
@@ -98,7 +98,7 @@ export class ContextLoader extends FileLoader {
98
98
  }
99
99
  }
100
100
 
101
- function getInstance(values: any, ctx: ContextDelegation) {
101
+ function getInstance(values: any, ctx: Context) {
102
102
  // it's a directory when it has no exports
103
103
  // then use ClassLoader
104
104
  const Class = values[EXPORTS] ? values : null;
@@ -5,66 +5,35 @@ import { debuglog, inspect } from 'node:util';
5
5
  import { homedir } from 'node-homedir';
6
6
  import { isAsyncFunction, isClass, isGeneratorFunction, isObject, isPromise } from 'is-type-of';
7
7
  import type { Logger } from 'egg-logger';
8
- import { getParamNames, readJSONSync, readJSON } from 'utility';
8
+ import {
9
+ getParamNames, readJSONSync, readJSON, exists,
10
+ } from 'utility';
9
11
  import { extend } from 'extend2';
10
- import { Request, Response, Context, Application } from '@eggjs/koa';
12
+ import { Request, Response, Application, Context as KoaContext } from '@eggjs/koa';
13
+ import { register as tsconfigPathsRegister } from 'tsconfig-paths';
14
+ import { isESM, isSupportTypeScript } from '@eggjs/utils';
11
15
  import { pathMatching, type PathMatchingOptions } from 'egg-path-matching';
12
16
  import { now, diff } from 'performance-ms';
13
- import { FULLPATH, FileLoader, FileLoaderOptions } from './file_loader.js';
17
+ import { CaseStyle, FULLPATH, FileLoader, FileLoaderOptions } from './file_loader.js';
14
18
  import { ContextLoader, ContextLoaderOptions } from './context_loader.js';
15
19
  import utils, { Fun } from '../utils/index.js';
16
20
  import sequencify from '../utils/sequencify.js';
17
21
  import { Timing } from '../utils/timing.js';
18
- import type { ContextDelegation, EggCore, MiddlewareFunc } from '../egg.js';
19
- import { BaseContextClass } from '../base_context_class.js';
22
+ import type {
23
+ Context, EggCore, MiddlewareFunc,
24
+ } from '../egg.js';
25
+ import type { BaseContextClass } from '../base_context_class.js';
26
+ import type { EggAppConfig, EggAppInfo, EggPluginInfo } from '../types.js';
20
27
 
21
28
  const debug = debuglog('@eggjs/core/loader/egg_loader');
22
29
 
23
30
  const originalPrototypes: Record<string, any> = {
24
31
  request: Request.prototype,
25
32
  response: Response.prototype,
26
- context: Context.prototype,
33
+ context: KoaContext.prototype,
27
34
  application: Application.prototype,
28
35
  };
29
36
 
30
- export interface EggAppInfo {
31
- /** package.json */
32
- pkg: Record<string, any>;
33
- /** the application name from package.json */
34
- name: string;
35
- /** current directory of application */
36
- baseDir: string;
37
- /** equals to serverEnv */
38
- env: string;
39
- /** equals to serverScope */
40
- scope: string;
41
- /** home directory of the OS */
42
- HOME: string;
43
- /** baseDir when local and unittest, HOME when other environment */
44
- root: string;
45
- }
46
-
47
- export interface EggPluginInfo {
48
- /** the plugin name, it can be used in `dep` */
49
- name: string;
50
- /** the package name of plugin */
51
- package?: string;
52
- version?: string;
53
- /** whether enabled */
54
- enable: boolean;
55
- implicitEnable?: boolean;
56
- /** the directory of the plugin package */
57
- path?: string;
58
- /** the dependent plugins, you can use the plugin name */
59
- dependencies: string[];
60
- /** the optional dependent plugins. */
61
- optionalDependencies: string[];
62
- dependents?: string[];
63
- /** specify the serverEnv that only enable the plugin in it */
64
- env: string[];
65
- /** the file plugin config in. */
66
- from: string;
67
- }
68
37
 
69
38
  export interface EggLoaderOptions {
70
39
  /** server env */
@@ -100,7 +69,6 @@ export class EggLoader {
100
69
  readonly appInfo: EggAppInfo;
101
70
  dirs?: EggDirInfo[];
102
71
 
103
-
104
72
  /**
105
73
  * @class
106
74
  * @param {Object} options - options
@@ -130,12 +98,11 @@ export class EggLoader {
130
98
  if (process.env.EGG_TYPESCRIPT === 'true' || (this.pkg.egg && this.pkg.egg.typescript)) {
131
99
  // skip require tsconfig-paths if tsconfig.json not exists
132
100
  const tsConfigFile = path.join(this.options.baseDir, 'tsconfig.json');
133
- // FIXME: support esm
134
- if (fs.existsSync(tsConfigFile) && typeof require === 'function') {
135
- // eslint-disable-next-line @typescript-eslint/no-var-requires
136
- require('tsconfig-paths').register({ cwd: this.options.baseDir });
101
+ if (fs.existsSync(tsConfigFile)) {
102
+ tsconfigPathsRegister({ cwd: this.options.baseDir } as any);
137
103
  } else {
138
- this.logger.info('[@eggjs/core:egg_loader] skip register "tsconfig-paths" because tsconfig.json not exists at %s',
104
+ this.logger.info(
105
+ '[@eggjs/core/egg_loader] skip register "tsconfig-paths" because tsconfig.json not exists at %s',
139
106
  tsConfigFile);
140
107
  }
141
108
  }
@@ -634,19 +601,19 @@ export class EggLoader {
634
601
  plugin.version = pkg.version;
635
602
  }
636
603
  // support commonjs and esm dist files
637
- plugin.path = this.#formatPluginPathFromPackageJSON(plugin.path!, pkg);
604
+ plugin.path = await this.#formatPluginPathFromPackageJSON(plugin.path!, pkg);
638
605
  }
639
606
 
640
607
  const logger = this.options.logger;
641
608
  if (!config) {
642
- logger.warn(`[@eggjs/core:egg_loader] pkg.eggPlugin is missing in ${pluginPackage}`);
609
+ logger.warn(`[@eggjs/core/egg_loader] pkg.eggPlugin is missing in ${pluginPackage}`);
643
610
  return;
644
611
  }
645
612
 
646
613
  if (config.name && config.strict !== false && config.name !== plugin.name) {
647
614
  // pluginName is configured in config/plugin.js
648
615
  // pluginConfigName is pkg.eggPlugin.name
649
- logger.warn(`[@eggjs/core:egg_loader] pluginName(${plugin.name}) is different from pluginConfigName(${config.name})`);
616
+ logger.warn(`[@eggjs/core/egg_loader] pluginName(${plugin.name}) is different from pluginConfigName(${config.name})`);
650
617
  }
651
618
 
652
619
  // dep compatible
@@ -781,33 +748,43 @@ export class EggLoader {
781
748
  const pluginPkgFile = utils.resolvePath(`${name}/package.json`, { paths: [ ...this.lookupDirs ] });
782
749
  return path.dirname(pluginPkgFile);
783
750
  } catch (err) {
784
- debug('[resolvePluginPath] error: %o', err);
751
+ debug('[resolvePluginPath] error: %o, plugin info: %o', err, plugin);
785
752
  throw new Error(`Can not find plugin ${name} in "${[ ...this.lookupDirs ].join(', ')}"`, {
786
753
  cause: err,
787
754
  });
788
755
  }
789
756
  }
790
757
 
791
- #formatPluginPathFromPackageJSON(pluginPath: string, pluginPkg: {
758
+ async #formatPluginPathFromPackageJSON(pluginPath: string, pluginPkg: {
792
759
  eggPlugin?: {
793
760
  exports?: {
794
761
  import?: string;
795
762
  require?: string;
763
+ typescript?: string;
796
764
  };
797
765
  };
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);
766
+ }): Promise<string> {
767
+ let realPluginPath = pluginPath;
768
+ const exports = pluginPkg.eggPlugin?.exports;
769
+ if (exports) {
770
+ if (isESM) {
771
+ if (exports.import) {
772
+ realPluginPath = path.join(pluginPath, exports.import);
803
773
  }
804
774
  } else {
805
- if (pluginPkg.eggPlugin.exports.import) {
806
- pluginPath = path.join(pluginPath, pluginPkg.eggPlugin.exports.import);
775
+ if (exports.require) {
776
+ realPluginPath = path.join(pluginPath, exports.require);
777
+ }
778
+ }
779
+ if (exports.typescript && isSupportTypeScript()) {
780
+ if (!(await exists(realPluginPath))) {
781
+ // if require/import path not exists, use typescript path for development stage
782
+ realPluginPath = path.join(pluginPath, exports.typescript);
783
+ debug('[formatPluginPathFromPackageJSON] use typescript path %o', realPluginPath);
807
784
  }
808
785
  }
809
786
  }
810
- return pluginPath;
787
+ return realPluginPath;
811
788
  }
812
789
 
813
790
  #extendPlugins(targets: Record<string, EggPluginInfo>, plugins: Record<string, EggPluginInfo>) {
@@ -843,7 +820,7 @@ export class EggLoader {
843
820
 
844
821
  /** start Config loader */
845
822
  configMeta: Record<string, any>;
846
- config: Record<string, any>;
823
+ config: EggAppConfig;
847
824
 
848
825
  /**
849
826
  * Load config/config.js
@@ -857,7 +834,10 @@ export class EggLoader {
857
834
  this.timing.start('Load Config');
858
835
  this.configMeta = {};
859
836
 
860
- const target: Record<string, any> = {};
837
+ const target: EggAppConfig = {
838
+ middleware: [],
839
+ coreMiddleware: [],
840
+ };
861
841
 
862
842
  // Load Application config first
863
843
  const appConfig = await this.#preloadAppConfig();
@@ -1166,8 +1146,10 @@ export class EggLoader {
1166
1146
  async #loadBootHook(fileName: string) {
1167
1147
  this.timing.start(`Load ${fileName}.js`);
1168
1148
  for (const unit of this.getLoadUnits()) {
1169
- const bootFilePath = this.resolveModule(path.join(unit.path, fileName));
1149
+ const bootFile = path.join(unit.path, fileName);
1150
+ const bootFilePath = this.resolveModule(bootFile);
1170
1151
  if (!bootFilePath) {
1152
+ // debug('[loadBootHook] %o not found', bootFile);
1171
1153
  continue;
1172
1154
  }
1173
1155
  const bootHook = await this.requireFile(bootFilePath);
@@ -1175,12 +1157,14 @@ export class EggLoader {
1175
1157
  bootHook.prototype.fullPath = bootFilePath;
1176
1158
  // if is boot class, add to lifecycle
1177
1159
  this.lifecycle.addBootHook(bootHook);
1160
+ debug('[loadBootHook] add BootHookClass from %o', bootFilePath);
1178
1161
  } else if (typeof bootHook === 'function') {
1179
1162
  // if is boot function, wrap to class
1180
1163
  // for compatibility
1181
- this.lifecycle.addFunctionAsBootHook(bootHook);
1164
+ this.lifecycle.addFunctionAsBootHook(bootHook, bootFilePath);
1165
+ debug('[loadBootHook] add bootHookFunction from %o', bootFilePath);
1182
1166
  } else {
1183
- this.options.logger.warn('[@eggjs/core:egg_loader] %s must exports a boot class', bootFilePath);
1167
+ this.options.logger.warn('[@eggjs/core/egg_loader] %s must exports a boot class', bootFilePath);
1184
1168
  }
1185
1169
  }
1186
1170
  // init boots
@@ -1202,7 +1186,7 @@ export class EggLoader {
1202
1186
  const servicePaths = this.getLoadUnits().map(unit => path.join(unit.path, 'app/service'));
1203
1187
  options = {
1204
1188
  call: true,
1205
- caseStyle: 'lower',
1189
+ caseStyle: CaseStyle.lower,
1206
1190
  fieldClass: 'serviceClasses',
1207
1191
  directory: servicePaths,
1208
1192
  ...options,
@@ -1242,7 +1226,7 @@ export class EggLoader {
1242
1226
  opt = {
1243
1227
  call: false,
1244
1228
  override: true,
1245
- caseStyle: 'lower',
1229
+ caseStyle: CaseStyle.lower,
1246
1230
  directory: middlewarePaths,
1247
1231
  ...opt,
1248
1232
  };
@@ -1291,13 +1275,13 @@ export class EggLoader {
1291
1275
  }
1292
1276
  app.use(mw);
1293
1277
  debug('[loadMiddleware] Use middleware: %s with options: %j', name, options);
1294
- this.options.logger.info('[@eggjs/core:egg_loader] Use middleware: %s', name);
1278
+ this.options.logger.info('[@eggjs/core/egg_loader] Use middleware: %s', name);
1295
1279
  } else {
1296
- this.options.logger.info('[@eggjs/core:egg_loader] Disable middleware: %s', name);
1280
+ this.options.logger.info('[@eggjs/core/egg_loader] Disable middleware: %s', name);
1297
1281
  }
1298
1282
  }
1299
1283
 
1300
- this.options.logger.info('[@eggjs/core:egg_loader] Loaded middleware from %j', middlewarePaths);
1284
+ this.options.logger.info('[@eggjs/core/egg_loader] Loaded middleware from %j', middlewarePaths);
1301
1285
  this.timing.end('Load Middleware');
1302
1286
 
1303
1287
  // add router middleware, make sure router is the last middleware
@@ -1317,7 +1301,7 @@ export class EggLoader {
1317
1301
  this.timing.start('Load Controller');
1318
1302
  const controllerBase = path.join(this.options.baseDir, 'app/controller');
1319
1303
  opt = {
1320
- caseStyle: 'lower',
1304
+ caseStyle: CaseStyle.lower,
1321
1305
  directory: controllerBase,
1322
1306
  initializer: (obj, opt) => {
1323
1307
  // return class if it exports a function
@@ -1355,7 +1339,7 @@ export class EggLoader {
1355
1339
  };
1356
1340
  await this.loadToApp(controllerBase, 'controller', opt as FileLoaderOptions);
1357
1341
  debug('[loadController] app.controller => %o', this.app.controller);
1358
- this.options.logger.info('[@eggjs/core:egg_loader] Controller loaded: %s', controllerBase);
1342
+ this.options.logger.info('[@eggjs/core/egg_loader] Controller loaded: %s', controllerBase);
1359
1343
  this.timing.end('Load Controller');
1360
1344
  }
1361
1345
  /** end Controller loader */
@@ -1397,7 +1381,7 @@ export class EggLoader {
1397
1381
  case 'ctx': {
1398
1382
  assert(!(property in this.app.context), `customLoader should not override ctx.${property}`);
1399
1383
  const options = {
1400
- caseStyle: 'lower',
1384
+ caseStyle: CaseStyle.lower,
1401
1385
  fieldClass: `${property}Classes`,
1402
1386
  ...loaderConfig,
1403
1387
  directory,
@@ -1408,7 +1392,7 @@ export class EggLoader {
1408
1392
  case 'app': {
1409
1393
  assert(!(property in this.app), `customLoader should not override app.${property}`);
1410
1394
  const options = {
1411
- caseStyle: 'lower',
1395
+ caseStyle: CaseStyle.lower,
1412
1396
  initializer: (Clazz: unknown) => {
1413
1397
  return isClass(Clazz) ? new Clazz(this.app) : Clazz;
1414
1398
  },
@@ -1527,10 +1511,11 @@ export class EggLoader {
1527
1511
  * @param {Object} options - see {@link FileLoader}
1528
1512
  * @since 1.0.0
1529
1513
  */
1530
- async loadToApp(directory: string | string[], property: string | symbol, options?: FileLoaderOptions) {
1514
+ async loadToApp(directory: string | string[], property: string | symbol,
1515
+ options?: Omit<FileLoaderOptions, 'inject' | 'target'>) {
1531
1516
  const target = {};
1532
1517
  Reflect.set(this.app, property, target);
1533
- options = {
1518
+ const loadOptions: FileLoaderOptions = {
1534
1519
  ...options,
1535
1520
  directory: options?.directory ?? directory,
1536
1521
  target,
@@ -1539,7 +1524,7 @@ export class EggLoader {
1539
1524
 
1540
1525
  const timingKey = `Load "${String(property)}" to Application`;
1541
1526
  this.timing.start(timingKey);
1542
- await new FileLoader(options).load();
1527
+ await new FileLoader(loadOptions).load();
1543
1528
  this.timing.end(timingKey);
1544
1529
  }
1545
1530
 
@@ -1550,8 +1535,9 @@ export class EggLoader {
1550
1535
  * @param {Object} options - see {@link ContextLoader}
1551
1536
  * @since 1.0.0
1552
1537
  */
1553
- async loadToContext(directory: string | string[], property: string | symbol, options?: ContextLoaderOptions) {
1554
- options = {
1538
+ async loadToContext(directory: string | string[], property: string | symbol,
1539
+ options?: Omit<ContextLoaderOptions, 'inject' | 'property'>) {
1540
+ const loadOptions: ContextLoaderOptions = {
1555
1541
  ...options,
1556
1542
  directory: options?.directory || directory,
1557
1543
  property,
@@ -1560,7 +1546,7 @@ export class EggLoader {
1560
1546
 
1561
1547
  const timingKey = `Load "${String(property)}" to Context`;
1562
1548
  this.timing.start(timingKey);
1563
- await new ContextLoader(options).load();
1549
+ await new ContextLoader(loadOptions).load();
1564
1550
  this.timing.end(timingKey);
1565
1551
  }
1566
1552
 
@@ -1595,13 +1581,13 @@ export class EggLoader {
1595
1581
  let fullPath;
1596
1582
  try {
1597
1583
  fullPath = utils.resolvePath(filepath);
1598
- } catch (e) {
1599
- return undefined;
1600
- }
1601
-
1602
- if (process.env.EGG_TYPESCRIPT !== 'true' && fullPath.endsWith('.ts')) {
1584
+ } catch (err: any) {
1585
+ // debug('[resolveModule] Module %o resolve error: %s', filepath, err.stack);
1603
1586
  return undefined;
1604
1587
  }
1588
+ // if (process.env.EGG_TYPESCRIPT !== 'true' && fullPath.endsWith('.ts')) {
1589
+ // return undefined;
1590
+ // }
1605
1591
  return fullPath;
1606
1592
  }
1607
1593
  }
@@ -1687,7 +1673,7 @@ function wrapControllerClass(Controller: typeof BaseContextClass, fullPath: stri
1687
1673
  }
1688
1674
 
1689
1675
  function controllerMethodToMiddleware(Controller: typeof BaseContextClass, key: string) {
1690
- return function classControllerMiddleware(this: ContextDelegation, ...args: any[]) {
1676
+ return function classControllerMiddleware(this: Context, ...args: any[]) {
1691
1677
  const controller: any = new Controller(this);
1692
1678
  if (!this.app.config.controller?.supportParams) {
1693
1679
  args = [ this ];
@@ -1723,7 +1709,7 @@ function wrapObject(obj: Record<string, any>, fullPath: string, prefix?: string)
1723
1709
  }
1724
1710
 
1725
1711
  function objectFunctionToMiddleware(func: Fun) {
1726
- async function objectControllerMiddleware(this: ContextDelegation, ...args: any[]) {
1712
+ async function objectControllerMiddleware(this: Context, ...args: any[]) {
1727
1713
  if (!this.app.config.controller?.supportParams) {
1728
1714
  args = [ this ];
1729
1715
  }
@@ -6,12 +6,17 @@ import globby from 'globby';
6
6
  import { isClass, isGeneratorFunction, isAsyncFunction, isPrimitive } from 'is-type-of';
7
7
  import utils, { Fun } from '../utils/index.js';
8
8
 
9
- const debug = debuglog('@eggjs/core:file_loader');
9
+ const debug = debuglog('@eggjs/core/file_loader');
10
10
 
11
11
  export const FULLPATH = Symbol('EGG_LOADER_ITEM_FULLPATH');
12
12
  export const EXPORTS = Symbol('EGG_LOADER_ITEM_EXPORTS');
13
13
 
14
- export type CaseStyle = 'camel' | 'lower' | 'upper';
14
+ export enum CaseStyle {
15
+ camel = 'camel',
16
+ lower = 'lower',
17
+ upper = 'upper',
18
+ }
19
+
15
20
  export type CaseStyleFunction = (filepath: string) => string[];
16
21
  export type FileLoaderInitializer = (exports: unknown, options: { path: string; pathName: string }) => unknown;
17
22
  export type FileLoaderFilter = (exports: unknown) => boolean;
@@ -79,7 +84,7 @@ export class FileLoader {
79
84
  assert(options.directory, 'options.directory is required');
80
85
  assert(options.target, 'options.target is required');
81
86
  this.options = {
82
- caseStyle: 'camel',
87
+ caseStyle: CaseStyle.camel,
83
88
  call: true,
84
89
  override: false,
85
90
  ...options,
@@ -88,7 +93,7 @@ export class FileLoader {
88
93
  // compatible old options _lowercaseFirst_
89
94
  if (this.options.lowercaseFirst === true) {
90
95
  utils.deprecated('lowercaseFirst is deprecated, use caseStyle instead');
91
- this.options.caseStyle = 'lower';
96
+ this.options.caseStyle = CaseStyle.lower;
92
97
  }
93
98
  }
94
99
 
@@ -185,6 +190,13 @@ export class FileLoader {
185
190
  for (const filepath of filepaths) {
186
191
  const fullpath = path.join(directory, filepath);
187
192
  if (!fs.statSync(fullpath).isFile()) continue;
193
+ if (filepath.endsWith('.js')) {
194
+ const filepathTs = filepath.replace(/\.js$/, '.ts');
195
+ if (filepaths.includes(filepathTs)) {
196
+ debug('[parse] ignore %s, because %s exists', fullpath, filepathTs);
197
+ continue;
198
+ }
199
+ }
188
200
  // get properties
189
201
  // app/service/foo/bar.js => [ 'foo', 'bar' ]
190
202
  const properties = getProperties(filepath, this.options.caseStyle);