Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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 packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './singleton.ts';
export * from './loader/egg_loader.ts';
export * from './loader/file_loader.ts';
export * from './loader/context_loader.ts';
export * from './loader/manifest.ts';
export * from './utils/sequencify.ts';
export * from './utils/timing.ts';
export type * from './types.ts';
48 changes: 48 additions & 0 deletions packages/core/src/loader/egg_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { sequencify } from '../utils/sequencify.ts';
import { Timing } from '../utils/timing.ts';
import { type ContextLoaderOptions, ContextLoader } from './context_loader.ts';
import { type FileLoaderOptions, CaseStyle, FULLPATH, FileLoader } from './file_loader.ts';
import { ManifestStore, type ManifestTegg, type StartupManifest } from './manifest.ts';

const debug = debuglog('egg/core/loader/egg_loader');

Expand Down Expand Up @@ -67,6 +68,14 @@ export class EggLoader {
readonly appInfo: EggAppInfo;
readonly outDir?: string;
dirs?: EggDirInfo[];
/** Pre-computed startup manifest for skipping file I/O */
readonly manifest: ManifestStore | null;
/** Collected resolveModule results for manifest generation */
readonly resolveCacheCollector: Record<string, string | null> = {};
/** Collected file discovery results for manifest generation */
readonly fileDiscoveryCollector: Record<string, string[]> = {};
/** Collected tegg manifest data (populated by tegg plugin) */
teggManifestCollector?: ManifestTegg;

/**
* @class
Expand Down Expand Up @@ -154,6 +163,12 @@ export class EggLoader {
* @since 1.0.0
*/
this.appInfo = this.getAppInfo();

// Load pre-computed startup manifest if available
this.manifest = ManifestStore.load(this.options.baseDir, this.serverEnv, this.serverScope);
if (this.manifest) {
debug('startup manifest loaded, will skip redundant file I/O');
}
}

get app(): EggCore {
Expand Down Expand Up @@ -1627,6 +1642,8 @@ export class EggLoader {
directory: options?.directory ?? directory,
target,
inject: this.app,
manifest: this.manifest,
fileDiscoveryCollector: this.fileDiscoveryCollector,
};

const timingKey = `Load "${String(property)}" to Application`;
Expand All @@ -1652,6 +1669,8 @@ export class EggLoader {
directory: options?.directory || directory,
property,
inject: this.app,
manifest: this.manifest,
fileDiscoveryCollector: this.fileDiscoveryCollector,
};

const timingKey = `Load "${String(property)}" to Context`;
Expand Down Expand Up @@ -1688,6 +1707,15 @@ export class EggLoader {
}

resolveModule(filepath: string): string | undefined {
// Check manifest cache first
if (this.manifest) {
const cached = this.manifest.getResolveCache(filepath);
if (cached !== undefined) {
debug('[resolveModule:manifest] %o => %o', filepath, cached);
return cached ?? undefined;
}
}

let fullPath: string | undefined;
try {
fullPath = utils.resolvePath(filepath);
Expand All @@ -1697,6 +1725,10 @@ export class EggLoader {
if (!fullPath) {
fullPath = this.#resolveFromOutDir(filepath);
}

// Collect for manifest generation
this.resolveCacheCollector[filepath] = fullPath ?? null;

return fullPath;
}

Expand Down Expand Up @@ -1734,6 +1766,22 @@ export class EggLoader {
}
}
}

/**
* Generate startup manifest from collected data.
* Should be called after all loading phases complete.
*/
generateManifest(tegg?: ManifestTegg): StartupManifest {
return ManifestStore.generate({
baseDir: this.options.baseDir,
serverEnv: this.serverEnv,
serverScope: this.serverScope,
typescriptEnabled: isSupportTypeScript(),
tegg,
resolveCache: this.resolveCacheCollector,
fileDiscovery: this.fileDiscoveryCollector,
});
}
}

// convert dep to dependencies for compatibility
Expand Down
18 changes: 16 additions & 2 deletions packages/core/src/loader/file_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import globby from 'globby';
import { isClass, isGeneratorFunction, isAsyncFunction, isPrimitive } from 'is-type-of';

import utils, { type Fun } from '../utils/index.ts';
import type { ManifestStore } from './manifest.ts';

const debug = debuglog('egg/core/file_loader');

Expand Down Expand Up @@ -49,6 +50,10 @@ export interface FileLoaderOptions {
/** set property's case when converting a filepath to property list. */
caseStyle?: CaseStyle | CaseStyleFunction;
lowercaseFirst?: boolean;
/** Pre-computed startup manifest for skipping globby scans */
manifest?: ManifestStore | null;
/** Collector for file discovery results during manifest generation */
fileDiscoveryCollector?: Record<string, string[]>;
}

export interface FileLoaderParseItem {
Expand Down Expand Up @@ -193,8 +198,17 @@ export class FileLoader {
const items: FileLoaderParseItem[] = [];
debug('[parse] parsing directories: %j', directories);
for (const directory of directories) {
const filepaths = globby.sync(files, { cwd: directory });
debug('[parse] globby files: %o, cwd: %o => %o', files, directory, filepaths);
const cachedFiles = this.options.manifest?.getFileDiscovery(directory);
const filepaths = cachedFiles ?? globby.sync(files, { cwd: directory });
if (cachedFiles) {
debug('[parse:manifest] using cached files for %o, count: %d', directory, cachedFiles.length);
} else {
debug('[parse] globby files: %o, cwd: %o => %o', files, directory, filepaths);
// Collect for manifest generation
if (this.options.fileDiscoveryCollector) {
this.options.fileDiscoveryCollector[directory] = filepaths;
}
}
for (const filepath of filepaths) {
const fullpath = path.join(directory, filepath);
if (!fs.statSync(fullpath).isFile()) continue;
Expand Down
Loading
Loading