Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"devDependencies": {
"@eggjs/bin": "workspace:*",
"@eggjs/tsconfig": "workspace:*",
"@oxc-node/core": "catalog:",
"@types/content-type": "catalog:",
"@types/js-yaml": "catalog:",
"@types/koa-compose": "catalog:",
Expand Down
15 changes: 13 additions & 2 deletions packages/utils/src/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ try {
// If import.meta is not available, it's likely CJS
isESM = false;
}
// Remember the auto-detected module format. `setSnapshotModuleLoader` flips
// `isESM` to false while a snapshot loader is active; clearing the loader must
// restore this value so consumers running later in the same realm are not left
// stuck in CJS mode. This matters under vitest `isolate: false`, where module
// state persists across test files.
const detectedIsESM = isESM;
const nodeMajorVersion = parseInt(process.versions.node.split('.', 1)[0], 10);
const supportImportMetaResolve = nodeMajorVersion >= 18;

Expand Down Expand Up @@ -415,10 +421,15 @@ let _snapshotModuleLoader: SnapshotModuleLoader | undefined;
*
* Also sets `isESM = false` because the snapshot bundle is CJS and
* esbuild's `import.meta` polyfill causes incorrect ESM detection.
*
* Pass `undefined` to clear the loader and restore the auto-detected `isESM`
* value. Always clear it once snapshot mode is no longer needed (e.g. in test
* teardown) so the module-level state does not leak into other files when
* vitest runs with `isolate: false`.
*/
export function setSnapshotModuleLoader(loader: SnapshotModuleLoader): void {
export function setSnapshotModuleLoader(loader: SnapshotModuleLoader | undefined): void {
_snapshotModuleLoader = loader;
isESM = false;
isESM = loader ? false : detectedIsESM;
}

export type { BundleModuleLoader } from '@eggjs/typings';
Expand Down
18 changes: 6 additions & 12 deletions packages/utils/test/snapshot-import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,12 @@ import { getFilepath } from './helper.ts';

describe('test/snapshot-import.test.ts', () => {
describe('setSnapshotModuleLoader', () => {
// We need to capture and restore isESM since setSnapshotModuleLoader mutates it.
// Use dynamic import to read the current value.
afterEach(async () => {
// Reset the snapshot loader by setting it to a no-op then clearing via
// module internals. Since there's no public "unset" API, we re-import
// and the module-level _snapshotModuleLoader remains set — but tests
// are isolated enough that this is fine. We'll use a different approach:
// just call setSnapshotModuleLoader with a passthrough that calls the
// real import, but that changes isESM. Instead, we accept that these
// tests run with the loader set and each test overrides it.
// Reset by overwriting with undefined via the setter trick:
// Actually we can't unset. Let's just re-import fresh for isolation.
// setSnapshotModuleLoader mutates module-level state (_snapshotModuleLoader
// and isESM). Clear it after each test, otherwise the state leaks into every
// other test file sharing this realm when vitest runs with `isolate: false`,
// breaking their module resolution ("Can not find plugin ...").
afterEach(() => {
setSnapshotModuleLoader(undefined);
});

it('should intercept importModule with registered loader', async () => {
Expand Down
7 changes: 6 additions & 1 deletion plugins/mock/src/app/extend/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ export default abstract class ApplicationUnittest extends Application {
const res = new http.ServerResponse(req);

if (options.reuseCtxStorage !== false) {
if (this.currentContext && !this.currentContext[REUSED_CTX]) {
// Only reuse the active async-local context when it actually belongs to
// this app. The context storage can be shared across app instances (e.g.
// multiple `mm.app()` apps in one realm, or vitest `isolate: false` where
// a previous app's context still lingers); reusing a foreign app's
// context would bind helpers/services to the wrong app config.
if (this.currentContext && this.currentContext.app === this && !this.currentContext[REUSED_CTX]) {
mockRequest(this.currentContext.request.req);
this.currentContext[REUSED_CTX] = true;
return this.currentContext as MockContext;
Expand Down
2 changes: 1 addition & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ catalog:
'@eggjs/scripts': ^4.0.0
'@fengmk2/ps-tree': ^2.0.1
'@oclif/core': ^4.2.0
'@oxc-node/core': ^0.0.35
'@oxc-node/core': ^0.1.0
'@swc-node/register': ^1.11.1
'@swc/core': ^1.15.1
'@types/accepts': ^1.3.7
Expand Down
10 changes: 6 additions & 4 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ const config: UserWorkspaceConfig = defineConfig({
env: {
// disable tegg plugins by default on unittest, make test speed up
DISABLE_TEGG_PLUGINS: 'true',
// TODO: aop plugin required this flag, otherwise there will be a SyntaxError: Invalid or unexpected token
NODE_OPTIONS: '--import=tsx/esm',
// FIXME: TypeError: Cannot read properties of undefined (reading 'mode')
// NODE_OPTIONS: '--import=@oxc-node/core/register',
// Transpile runtime `import()` of .ts files (egg loader resolving
// fixtures/plugins/app code, and the workspace `src` exports under
// node_modules) with oxc-node — noticeably faster than tsx and it handles
// decorators correctly. Requires @oxc-node/core >= 0.1.0, which fixes the
// earlier "Cannot read properties of undefined (reading 'mode')" crash.
NODE_OPTIONS: '--import=@oxc-node/core/register',
Comment on lines +40 to +44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all places mutating NODE_OPTIONS or forcing tsx/esm.
rg -n -C3 "NODE_OPTIONS|tsx/esm|`@oxc-node/core/register`|--import="

# Focus on setup-style files that can override runtime behavior.
fd -i "setup.ts" | xargs -r rg -n -C3 "NODE_OPTIONS|tsx/esm|--import="

Repository: eggjs/egg

Length of output: 16432


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== vitest.config.ts (relevant section) ==="
nl -ba vitest.config.ts | sed -n '1,120p'

echo "=== tegg/core/vitest/test/setup.ts ==="
nl -ba tegg/core/vitest/test/setup.ts | sed -n '1,120p'

Repository: eggjs/egg

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== vitest.config.ts (relevant section) ==="
nl -ba vitest.config.ts | sed -n '1,120p'

echo "=== tegg/core/vitest/test/setup.ts ==="
nl -ba tegg/core/vitest/test/setup.ts | sed -n '1,120p'

echo "=== vitest config setupFiles (search) ==="
rg -n "setupFiles|setupFiles\\.ts|tegg/core/vitest/test/setup\\.ts|test/setup\\.ts" vitest.config.ts tegg -S

Repository: eggjs/egg

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== vitest.config.ts (relevant section) ==="
nl -ba vitest.config.ts | sed -n '1,140p'

echo "=== tegg/core/vitest/test/setup.ts ==="
nl -ba tegg/core/vitest/test/setup.ts | sed -n '1,120p'

echo "=== vitest config setupFiles search ==="
rg -n "setupFiles|setupFiles\\.ts|tegg/core/vitest/test/setup\\.ts|test/setup\\.ts" vitest.config.ts tegg -S

Repository: eggjs/egg

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== vitest.config.ts (relevant section) ==="
nl -ba vitest.config.ts | sed -n '1,160p'

echo "=== tegg/core/vitest/test/setup.ts ==="
nl -ba tegg/core/vitest/test/setup.ts | sed -n '1,120p'

echo "=== vitest config setupFiles search ==="
rg -n "setupFiles|tegg/core/vitest/test/setup\.ts|test/setup\.ts" vitest.config.ts tegg -S

Repository: eggjs/egg

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== vitest.config.ts lines 1-90 ==="
python3 - <<'PY'
import itertools
path="vitest.config.ts"
with open(path,'r',encoding='utf-8') as f:
    for i,line in zip(range(1,91), f):
        print(f"{i:4d}  {line.rstrip()}")
PY

echo "=== tegg/core/vitest/test/setup.ts lines 1-80 ==="
python3 - <<'PY'
path="tegg/core/vitest/test/setup.ts"
with open(path,'r',encoding='utf-8') as f:
    for i,line in zip(range(1,81), f):
        print(f"{i:4d}  {line.rstrip()}")
PY

echo "=== targeted searches ==="
rg -n "tsx/esm|`@oxc-node/core/register`|process\.env\.NODE_OPTIONS|NODE_OPTIONS" vitest.config.ts tegg/core/vitest/test -S || true
rg -n "tsx/esm" tegg -S || true
rg -n "process\.env\.NODE_OPTIONS\s*=" tegg -S || true
rg -n "process\.env\.NODE_OPTIONS.*tsx/esm" tegg -S || true

Repository: eggjs/egg

Length of output: 3643


Avoid re-injecting tsx/esm into NODE_OPTIONS when @oxc-node/core/register is already set.

vitest.config.ts sets NODE_OPTIONS: '--import=@oxc-node/core/register', but tegg/core/vitest/test/setup.ts still appends --import=tsx/esm whenever tsx/esm is missing—so the migration ends up enabling both hooks.

Suggested follow-up adjustment
-const nodeOptions = process.env.NODE_OPTIONS ?? '';
-if (!nodeOptions.includes('tsx/esm')) {
-  process.env.NODE_OPTIONS = `${nodeOptions} --import=tsx/esm`.trim();
-}
+const nodeOptions = process.env.NODE_OPTIONS ?? '';
+if (
+  !nodeOptions.includes('`@oxc-node/core/register`') &&
+  !nodeOptions.includes('tsx/esm')
+) {
+  process.env.NODE_OPTIONS = `${nodeOptions} --import=`@oxc-node/core/register``.trim();
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vitest.config.ts` at line 44, The setup currently appends "--import=tsx/esm"
unconditionally when missing, causing both hooks to be enabled even though
vitest.config.ts already sets NODE_OPTIONS to
'--import=`@oxc-node/core/register`'; update the logic that appends the import
(the code that checks for and adds '--import=tsx/esm') to first inspect
NODE_OPTIONS (or the existing import string) for the substring
'`@oxc-node/core/register`' and skip adding '--import=tsx/esm' if that register
import is present; if neither import exists, add '--import=tsx/esm' as before
and ensure you merge into NODE_OPTIONS using the same separator and quoting
rules.

},
// poolOptions: {
// forks: {
Expand Down
93 changes: 93 additions & 0 deletions wiki/concepts/vitest-isolate-false-state-leaks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: Vitest isolate:false state leaks
type: concept
summary: Why the root vitest config (pool:threads + isolate:false) exposes cross-file/cross-project state leaks, the concrete leaks found, and how they were fixed.
source_files:
- vitest.config.ts
- packages/utils/src/import.ts
- packages/utils/test/snapshot-import.test.ts
- plugins/mock/src/app/extend/application.ts
- plugins/mock/src/lib/mock_agent.ts
- plugins/multipart/test/file-mode.test.ts
updated_at: 2026-06-08
status: active
---

## Context

The root `vitest.config.ts` runs the whole monorepo with `pool: 'threads'` and
`isolate: false`. Under this mode every test **file** in a worker shares one
Node realm: the module registry, `globalThis`, module-level `let` bindings, the
undici global dispatcher, `process` env/listeners and timers are all shared
across files (and across `projects`, since projects share the worker pool).

This is much faster, but any module that caches process- or realm-global state
without resetting it leaks that state into later files. Because vitest schedules
files across threads, _which_ test loses is order/timing dependent — so failures
are **nondeterministic** and move from run to run. That nondeterminism is the
signature of this class of bug, not flaky tests per se.

## How to reproduce / triage (CI-faithful)

- Use Node 22 or 24 (CI matrix). Node 26 introduces unrelated undici/deprecation
failures that are NOT isolate bugs — do not diagnose on Node 26.
- Install with utoo (`ut install --from pnpm`), not a bare `pnpm install`. utoo
hoists workspace packages (e.g. `egg`) to the root `node_modules`; tests like
`cluster/options` and `mock/format_options` resolve the framework via
`getFrameworkPath('egg')` from a fixture `baseDir` and only pass with that
hoisting. A non-utoo install causes phantom "egg is not found" /
"Cannot find module" failures that masquerade as isolate bugs.
- Clear `dist/` first (see AGENTS.md "Local CI" / duplicate-proto note).
- Triage rule: run a failing file **alone**. Passes alone but fails in the full
run ⇒ genuine cross-file leak. Fails alone too ⇒ a real bug or an
environmental dependency (MySQL/redis/DNS), not isolation.

## Root causes found

1. **Module-resolution poisoning via `@eggjs/utils` import.ts** (cross-project).
`setSnapshotModuleLoader()` set a module-level `_snapshotModuleLoader` and
flipped the module-level `isESM` to `false`, with no way to unset it.
`snapshot-import.test.ts` had a no-op `afterEach`, so after it ran, every
later file in the worker resolved modules in CJS + snapshot mode and failed
with `Can not find plugin @eggjs/<x>` / `Cannot find module
'@eggjs/<x>/package.json'`. This single leak caused most of the cross-project

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix broken list paragraph indentation at Line 53.

'@eggjs/<x>/package.json' is split onto a new unindented line, which breaks the flow of the numbered item in rendered markdown.

Proposed fix
-   with `Can not find plugin `@eggjs/`<x>` / `Cannot find module
-'`@eggjs/`<x>/package.json'`. This single leak caused most of the cross-project
+   with `Can not find plugin `@eggjs/`<x>` / `Cannot find module '`@eggjs/`<x>/package.json'`.
+   This single leak caused most of the cross-project
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'@eggjs/<x>/package.json'`. This single leak caused most of the cross-project
with `Can not find plugin `@eggjs/`<x>` / `Cannot find module '`@eggjs/`<x>/package.json'`.
This single leak caused most of the cross-project
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wiki/concepts/vitest-isolate-false-state-leaks.md` at line 53, The markdown
list item at line 53 in vitest-isolate-false-state-leaks.md has broken
formatting where the text `'`@eggjs/`<x>/package.json'` appears on a new
unindented line, disrupting the numbered list flow. Move this backtick-enclosed
text back onto the same line as the preceding text "This single leak caused most
of the cross-project" to restore proper paragraph continuity within the list
item, ensuring consistent indentation and markdown rendering.

failures (ajv-plugin, typebox-validate, view-nunjucks, standalone, …).
**Fix:** `setSnapshotModuleLoader(undefined)` now clears the loader and
restores the auto-detected `isESM`; the test clears it in `afterEach`.
(`setBundleModuleLoader` and the tegg/core loader tests already reset their
`globalThis.__EGG_BUNDLE_MODULE_LOADER__` in `afterEach`, so they were fine.)

2. **Wrong-app context reuse in `@eggjs/mock` `mockContext()`.**
`mockContext()` reused `this.currentContext` without checking the context
belongs to `this` app. With a shared/lingering async-local context (multiple
`mm.app()` apps in one realm, or `isolate:false` carry-over), `app2.mockContext()`
returned app1's context, binding helpers/services to the wrong app config.
This produced e.g. `security/surl` "custom white protocol" → `''` (app2's
helper read app1's whitelist) and `security/csrf` 401s.
**Fix:** only reuse when `this.currentContext.app === this`.

3. **Potential undici global dispatcher carry-over in `@eggjs/mock`
`mock_agent.ts`.** Dispatcher state is stored on `globalThis`
(`__globalDispatcher`, `__mockAgent`) and `__globalDispatcher` is captured
once and never cleared. Mitigated in practice by the global
`afterEach(mock.restore)` in `setup_vitest.ts`; noted as a latent risk.

## Not isolate bugs (do not chase as such)

- `orm-plugin` (`Table 'test.apps' doesn't exist`) needs MySQL; `redis` needs a
redis server; `security/ssrf` (`IllegalAddressError: illegal address`) needs
DNS/network. These pass in CI where the services exist; they fail in a bare
local run regardless of isolation.
- `multipart/file-mode` is **load-sensitive and flakes even with `isolate:true`**
(fails ~1/4 when run alongside the other multipart files). Deep tracing showed
the uploaded file is reported (200 + path) but `existsSync` is already false
right after the save `pipeline()` resolves under load — a race in the multipart
file-save path, not a state leak. It passes 100% as a single file. Treat as a
pre-existing flaky test to fix in the multipart save path or test, separately
from the isolate:false work.

## Result

Full Node-22 suite under `isolate:false`: 15 failing files → 3, of which 2 are
environmental (MySQL/DNS, green in CI) and 1 (`multipart/file-mode`) is a
pre-existing load flake independent of isolation.
1 change: 1 addition & 0 deletions wiki/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Read this file before exploring raw sources.
## Concepts

- [Repository Map](./concepts/repository-map.md) - High-level map of the main repository areas and where to look first.
- [Vitest isolate:false state leaks](./concepts/vitest-isolate-false-state-leaks.md) - Why pool:threads + isolate:false exposes cross-file/cross-project state leaks, the concrete leaks (import.ts snapshot loader, mock mockContext), and how to triage them.

## Workflows

Expand Down
6 changes: 6 additions & 0 deletions wiki/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Dates use the workspace-local Asia/Shanghai calendar date.

## [2026-06-08] concept | vitest isolate:false state leaks diagnosed and fixed

- sources touched: `packages/utils/src/import.ts`, `packages/utils/test/snapshot-import.test.ts`, `plugins/mock/src/app/extend/application.ts`
- pages updated: `wiki/index.md`, `wiki/log.md`, `wiki/concepts/vitest-isolate-false-state-leaks.md`
- note: Under root `pool:threads` + `isolate:false`, two realm-global leaks caused nondeterministic cross-file/cross-project failures. (1) `setSnapshotModuleLoader` left module-level `_snapshotModuleLoader`/`isESM=false` set (no-op test teardown), poisoning module resolution for later files (`Can not find plugin …`). (2) `mock.mockContext()` reused `currentContext` from a different app, binding helpers to the wrong app config (surl/csrf failures). Fixed both at the source. Full Node-22 suite: 15 → 3 failing files (remaining 2 environmental MySQL/DNS; `multipart/file-mode` is a pre-existing load flake that also fails under `isolate:true`). Reproduce on Node 22/24 with a utoo install — not Node 26 / bare pnpm.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the exact symbol name for clarity at Line 9.

mock.mockContext() reads like a namespace typo; elsewhere the API is referenced as mockContext().

Proposed fix
-- note: Under root `pool:threads` + `isolate:false`, two realm-global leaks caused nondeterministic cross-file/cross-project failures. (1) `setSnapshotModuleLoader` left module-level `_snapshotModuleLoader`/`isESM=false` set (no-op test teardown), poisoning module resolution for later files (`Can not find plugin …`). (2) `mock.mockContext()` reused `currentContext` from a different app, binding helpers to the wrong app config (surl/csrf failures). Fixed both at the source. Full Node-22 suite: 15 → 3 failing files (remaining 2 environmental MySQL/DNS; `multipart/file-mode` is a pre-existing load flake that also fails under `isolate:true`). Reproduce on Node 22/24 with a utoo install — not Node 26 / bare pnpm.
+- note: Under root `pool:threads` + `isolate:false`, two realm-global leaks caused nondeterministic cross-file/cross-project failures. (1) `setSnapshotModuleLoader` left module-level `_snapshotModuleLoader`/`isESM=false` set (no-op test teardown), poisoning module resolution for later files (`Can not find plugin …`). (2) `mockContext()` reused `currentContext` from a different app, binding helpers to the wrong app config (surl/csrf failures). Fixed both at the source. Full Node-22 suite: 15 → 3 failing files (remaining 2 environmental MySQL/DNS; `multipart/file-mode` is a pre-existing load flake that also fails under `isolate:true`). Reproduce on Node 22/24 with a utoo install — not Node 26 / bare pnpm.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- note: Under root `pool:threads` + `isolate:false`, two realm-global leaks caused nondeterministic cross-file/cross-project failures. (1) `setSnapshotModuleLoader` left module-level `_snapshotModuleLoader`/`isESM=false` set (no-op test teardown), poisoning module resolution for later files (`Can not find plugin …`). (2) `mock.mockContext()` reused `currentContext` from a different app, binding helpers to the wrong app config (surl/csrf failures). Fixed both at the source. Full Node-22 suite: 15 → 3 failing files (remaining 2 environmental MySQL/DNS; `multipart/file-mode` is a pre-existing load flake that also fails under `isolate:true`). Reproduce on Node 22/24 with a utoo install — not Node 26 / bare pnpm.
- note: Under root `pool:threads` + `isolate:false`, two realm-global leaks caused nondeterministic cross-file/cross-project failures. (1) `setSnapshotModuleLoader` left module-level `_snapshotModuleLoader`/`isESM=false` set (no-op test teardown), poisoning module resolution for later files (`Can not find plugin …`). (2) `mockContext()` reused `currentContext` from a different app, binding helpers to the wrong app config (surl/csrf failures). Fixed both at the source. Full Node-22 suite: 15 → 3 failing files (remaining 2 environmental MySQL/DNS; `multipart/file-mode` is a pre-existing load flake that also fails under `isolate:true`). Reproduce on Node 22/24 with a utoo install — not Node 26 / bare pnpm.
🧰 Tools
🪛 LanguageTool

[style] ~9-~9: Unless you want to emphasize “not”, use “Cannot” which is more common.
Context: ...ning module resolution for later files (Can not find plugin …). (2) `mock.mockContext(...

(CAN_NOT_PREMIUM)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wiki/log.md` at line 9, The log note uses the wrong symbol name
"mock.mockContext()" which is inconsistent with the rest of the API; update the
text to use the exact symbol "mockContext()" and verify other mentions in this
block (e.g. `setSnapshotModuleLoader`, `_snapshotModuleLoader`, `isESM`) are
spelled exactly as in code so the documentation matches the real API names.


## [2026-05-10] package | extract shared LoaderFS package

- sources touched: `packages/loader-fs/src/index.ts`, `packages/loader-fs/package.json`, `packages/core/src/index.ts`, `packages/core/src/loader/file_loader.ts`, `packages/core/src/loader/egg_loader.ts`
Expand Down
Loading