Skip to content
Merged
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
3 changes: 1 addition & 2 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ jobs:
- name: cnpmcore
node-version: 24
command: |
npm install
npm run lint -- --quiet
npm install --legacy-peer-deps
npm run typecheck
npm run build
npm run prepublishOnly
Comment on lines 47 to 53
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

The cnpmcore e2e command no longer runs npm run lint and also switches install behavior to npm install --legacy-peer-deps. This reduces CI signal (lint regressions won’t be caught here) and the change isn’t described in the PR summary. Please either re-add the lint step (after install) or add an explicit note in the PR description explaining why lint must be skipped and why --legacy-peer-deps is required for this job.

Copilot uses AI. Check for mistakes.
Expand Down
15 changes: 15 additions & 0 deletions packages/cluster/src/master.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'node:fs';
import module from 'node:module';
import net from 'node:net';
import os from 'node:os';
import path from 'node:path';
Expand Down Expand Up @@ -57,6 +58,20 @@ export class Master extends ReadyEventEmitter {

async #start(options?: ClusterOptions) {
this.options = await parseOptions(options);

// Enable compile cache — env vars propagate to forked workers automatically.
// Duplicated from ManifestStore.enableCompileCache (@eggjs/core is not a dependency).
if (!process.env.NODE_COMPILE_CACHE && !process.env.NODE_DISABLE_COMPILE_CACHE) {
const cacheDir = path.join(this.options.baseDir, '.egg', 'compile-cache');
process.env.NODE_COMPILE_CACHE = cacheDir;
process.env.NODE_COMPILE_CACHE_PORTABLE = '1';
try {
module.enableCompileCache?.(cacheDir);
} catch {
/* non-fatal */
}
}

this.workerManager = new WorkerManager();
this.messenger = new Messenger(this, this.workerManager);
this.isProduction = isProduction(this.options);
Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/loader/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import module from 'node:module';
import path from 'node:path';
import { debuglog } from 'node:util';

Expand Down Expand Up @@ -240,6 +241,48 @@ export class ManifestStore {
} catch (err: any) {
if (err.code !== 'ENOENT') throw err;
}
ManifestStore.cleanCompileCache(baseDir);
}

// --- Compile Cache ---

/**
* Enable Node.js module compile cache for the current process.
* Sets NODE_COMPILE_CACHE and NODE_COMPILE_CACHE_PORTABLE env vars
* so forked child processes also inherit compile cache.
*/
static enableCompileCache(baseDir: string): void {
if (process.env.NODE_COMPILE_CACHE || process.env.NODE_DISABLE_COMPILE_CACHE) return;
const cacheDir = path.join(baseDir, '.egg', 'compile-cache');
process.env.NODE_COMPILE_CACHE = cacheDir;
process.env.NODE_COMPILE_CACHE_PORTABLE = '1';
try {
const result = module.enableCompileCache?.(cacheDir);
debug('compile cache enabled: %o', result);
} catch (err) {
debug('compile cache enable failed: %o', err);
}
}

/**
* Flush accumulated compile cache entries to disk.
*/
static flushCompileCache(): void {
try {
module.flushCompileCache?.();
debug('compile cache flushed');
} catch (err) {
debug('compile cache flush failed: %o', err);
}
}

/**
* Remove the compile cache directory.
*/
static cleanCompileCache(baseDir: string): void {
const compileCacheDir = path.join(baseDir, '.egg', 'compile-cache');
fs.rmSync(compileCacheDir, { recursive: true, force: true });
debug('compile cache removed: %s', compileCacheDir);
}

// --- Path Utilities ---
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/fixtures/compile-cache-target/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { compiled: true };
89 changes: 89 additions & 0 deletions packages/core/test/loader/manifest.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import module from 'node:module';
import path from 'node:path';

import mm from 'mm';
Expand Down Expand Up @@ -550,4 +551,92 @@ describe('ManifestStore', () => {
}
});
});

describe('compile cache', () => {
const savedCompileCache = process.env.NODE_COMPILE_CACHE;
const savedPortable = process.env.NODE_COMPILE_CACHE_PORTABLE;
const savedDisable = process.env.NODE_DISABLE_COMPILE_CACHE;

afterEach(() => {
for (const [key, saved] of [
['NODE_COMPILE_CACHE', savedCompileCache],
['NODE_COMPILE_CACHE_PORTABLE', savedPortable],
['NODE_DISABLE_COMPILE_CACHE', savedDisable],
] as const) {
if (saved !== undefined) {
process.env[key] = saved;
} else {
delete process.env[key];
}
}
});

it('should enable compile cache, set env vars, and generate cache files', () => {
const baseDir = setupBaseDir();
try {
ManifestStore.enableCompileCache(baseDir);
const expectedDir = path.join(baseDir, '.egg', 'compile-cache');
assert.equal(process.env.NODE_COMPILE_CACHE, expectedDir);
assert.equal(process.env.NODE_COMPILE_CACHE_PORTABLE, '1');

// Verify compile cache is active
const cacheDir = module.getCompileCacheDir?.();
assert.ok(cacheDir, 'compile cache dir should be set');

// Load a fixture module guaranteed not to be pre-cached
require('../fixtures/compile-cache-target/index.cjs');

// Flush and verify cache files are generated
ManifestStore.flushCompileCache();
assert.ok(fs.existsSync(cacheDir), 'compile cache directory should exist');
const entries = fs.readdirSync(cacheDir);
assert.ok(entries.length > 0, 'compile cache should contain cache files');
} finally {
fs.rmSync(baseDir, { recursive: true, force: true });
}
});

it('should flush compile cache without error', () => {
assert.doesNotThrow(() => {
ManifestStore.flushCompileCache();
});
});

it('should clean compile cache directory', () => {
const baseDir = setupBaseDir();
try {
const cacheDir = path.join(baseDir, '.egg', 'compile-cache');
fs.mkdirSync(cacheDir, { recursive: true });
fs.writeFileSync(path.join(cacheDir, 'test.bin'), 'cached data');
assert.ok(fs.existsSync(cacheDir));

ManifestStore.cleanCompileCache(baseDir);
assert.ok(!fs.existsSync(cacheDir), 'compile cache directory should be removed');
} finally {
fs.rmSync(baseDir, { recursive: true, force: true });
}
});

it('should not throw when cleaning non-existent compile cache', () => {
assert.doesNotThrow(() => {
ManifestStore.cleanCompileCache(tmpDir);
});
});

it('clean() should also remove compile cache directory', async () => {
const baseDir = setupBaseDir();
try {
await generateAndWrite(baseDir);
const cacheDir = path.join(baseDir, '.egg', 'compile-cache');
fs.mkdirSync(cacheDir, { recursive: true });
fs.writeFileSync(path.join(cacheDir, 'test.bin'), 'data');

ManifestStore.clean(baseDir);
assert.ok(!fs.existsSync(path.join(baseDir, '.egg', 'manifest.json')), 'manifest.json should be removed');
assert.ok(!fs.existsSync(cacheDir), 'compile cache directory should be removed');
} finally {
fs.rmSync(baseDir, { recursive: true, force: true });
}
});
});
});
3 changes: 3 additions & 0 deletions packages/egg/src/lib/egg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export class EggApplicationCore extends EggCore {
this.dumpConfig();
this.dumpTiming();
this.dumpManifest();
ManifestStore.flushCompileCache();
this.coreLogger.info('[egg] dump config after ready, %sms', Date.now() - dumpStartTime);
}),
);
Expand Down Expand Up @@ -218,6 +219,7 @@ export class EggApplicationCore extends EggCore {
await this.agent?.close();
}

ManifestStore.flushCompileCache();
for (const logger of this.loggers.values()) {
logger.close();
}
Expand Down Expand Up @@ -558,6 +560,7 @@ export class EggApplicationCore extends EggCore {
return;
}
const manifest = this.loader.generateManifest();
ManifestStore.enableCompileCache(this.baseDir);
ManifestStore.write(this.baseDir, manifest).catch((err: Error) => {
this.coreLogger.warn('[egg] dumpManifest write error: %s', err.message);
});
Expand Down
2 changes: 2 additions & 0 deletions packages/egg/src/lib/start.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'node:path';

import { ManifestStore } from '@eggjs/core';
import { importModule } from '@eggjs/utils';
import { readJSON } from 'utility';

Expand Down Expand Up @@ -35,6 +36,7 @@ export interface SingleModeAgent extends Agent {
export async function startEgg(options: StartEggOptions = {}): Promise<SingleModeApplication> {
options.baseDir = options.baseDir ?? process.cwd();
options.mode = 'single';
ManifestStore.enableCompileCache(options.baseDir);

// get agent from options.framework and package.egg.framework
if (!options.framework) {
Expand Down
15 changes: 15 additions & 0 deletions tools/scripts/scripts/start-cluster.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const nodeModule = require('node:module');
const path = require('node:path');
const { debuglog } = require('node:util');

const { importModule } = require('@eggjs/utils');
Expand All @@ -8,6 +10,19 @@ async function main() {
debug('argv: %o', process.argv);
const options = JSON.parse(process.argv[2]);
debug('start cluster options: %o', options);

// Duplicated from ManifestStore.enableCompileCache (@eggjs/core is not a dependency)
if (!process.env.NODE_COMPILE_CACHE && !process.env.NODE_DISABLE_COMPILE_CACHE) {
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.

这个判断要抽到一个统一的util里去么?看起来好几处都有

const cacheDir = path.join(options.baseDir ?? process.cwd(), '.egg', 'compile-cache');
process.env.NODE_COMPILE_CACHE = cacheDir;
process.env.NODE_COMPILE_CACHE_PORTABLE = '1';
try {
nodeModule.enableCompileCache?.(cacheDir);
} catch {
/* non-fatal */
}
}

const exports = await importModule(options.framework);
let startCluster = exports.startCluster;
if (typeof startCluster !== 'function') {
Expand Down
15 changes: 15 additions & 0 deletions tools/scripts/scripts/start-cluster.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import module from 'node:module';
import path from 'node:path';
import { debuglog } from 'node:util';

import { importModule } from '@eggjs/utils';
Expand All @@ -8,6 +10,19 @@ async function main() {
debug('argv: %o', process.argv);
const options = JSON.parse(process.argv[2]);
debug('start cluster options: %o', options);

// Duplicated from ManifestStore.enableCompileCache (@eggjs/core is not a dependency)
if (!process.env.NODE_COMPILE_CACHE && !process.env.NODE_DISABLE_COMPILE_CACHE) {
const cacheDir = path.join(options.baseDir ?? process.cwd(), '.egg', 'compile-cache');
process.env.NODE_COMPILE_CACHE = cacheDir;
process.env.NODE_COMPILE_CACHE_PORTABLE = '1';
try {
module.enableCompileCache?.(cacheDir);
} catch {
/* non-fatal */
}
}

const { startCluster } = await importModule(options.framework);
await startCluster(options);
}
Expand Down
Loading