use cordis@4

This commit is contained in:
undefined committed 2025-02-19 05:28:12 +08:00
1 parent 085b6c9b4c
commit 9264f340cb
18 files changed
+284 -168

No files matched your search

+74 -50
View File
@@ -2,13 +2,52 @@
/* eslint-disable consistent-return */
/* eslint-disable import/no-dynamic-require */
/* eslint-disable @typescript-eslint/no-shadow */
import { readFileSync } from 'fs';
import { relative, resolve, sep } from 'path';
import { codeFrameColumns } from '@babel/code-frame';
import { FSWatcher, watch } from 'chokidar';
import { debounce } from 'lodash';
import { Context, MainScope, Service } from '../context';
import { BuildFailure } from 'esbuild';
import { Context, Plugin, Service } from '../context';
import { Logger } from '../logger';
import { unwrapExports } from '../utils';
declare module '../context' {
interface Events {
'hmr/reload': (reloads: Map<Plugin, { filename: string, runtime: Plugin.Runtime<Context> }>) => void;
}
}
function isBuildFailure(e: any): e is BuildFailure {
return Array.isArray(e?.errors) && e.errors.every((error: any) => error.text);
}
export function handleError(ctx: Context, e: any) {
if (!isBuildFailure(e)) {
ctx.logger.warn(e);
return;
}
for (const error of e.errors) {
if (!error.location) {
ctx.logger.warn(error.text);
continue;
}
try {
const { file, line, column } = error.location;
const source = readFileSync(file, 'utf8');
const formatted = codeFrameColumns(source, {
start: { line, column },
}, {
highlightCode: true,
message: error.text,
});
ctx.logger.warn(`File: ${file}:${line}:${column}\n${formatted}`);
} catch (e) {
ctx.logger.warn(e);
}
}
}
function loadDependencies(filename: string, ignored: Set<string>) {
const dependencies = new Set<string>();
function traverse({ filename, children }: NodeJS.Module) {
@@ -22,14 +61,6 @@ function loadDependencies(filename: string, ignored: Set<string>) {
const logger = new Logger('watch');
function coerce(val: any) {
// resolve error when stack is undefined, e.g. axios error with status code 401
const { message, stack } = val instanceof Error && val.stack ? val : new Error(val as any);
const lines = stack.split('\n');
const index = lines.findIndex((line) => line.endsWith(message));
return lines.slice(index).join('\n');
}
export default class Watcher extends Service {
private root: string;
private watcher: FSWatcher;
@@ -39,7 +70,7 @@ export default class Watcher extends Service {
private stashed = new Set<string>();
constructor(public ctx: Context) {
super(ctx, 'watcher', true);
super(ctx, 'watcher');
this.externals = new Set(Object.keys(require.cache));
}
@@ -48,7 +79,6 @@ export default class Watcher extends Service {
const roots = [this.root];
if (process.env.WATCH_ROOT) roots.push(process.env.WATCH_ROOT);
this.watcher = watch(roots, {
...this.config,
ignored: (file) => [
'node_modules', '.git', 'logs', '.cache', '.yarn', 'tsconfig.tsbuildinfo',
].some((rule) => file.startsWith(`${rule}/`) || file.endsWith(`/${rule}`) || file.includes(`/${rule}/`)),
@@ -56,7 +86,7 @@ export default class Watcher extends Service {
logger.info(`Start watching changes in ${this.root}`);
// files independent from any plugins will trigger a full reload
const triggerLocalReload = debounce(() => this.triggerLocalReload(), 1000);
const triggerLocalReload = this.ctx.debounce(() => this.triggerLocalReload(), 1000);
this.watcher.on('change', (path) => {
if (path.includes(`${sep}.`)) return;
@@ -140,14 +170,13 @@ export default class Watcher extends Service {
}
private triggerLocalReload() {
const start = Date.now();
this.analyzeChanges();
/** plugins pending classification */
const pending = new Map<string, MainScope>();
const pending = new Map<string, Plugin.Runtime<Context>>();
/** plugins that should be reloaded */
const reloads = new Map<MainScope, string>();
const reloads = new Map<Plugin, { filename: string, runtime: Plugin.Runtime<Context> }>();
// we assume that plugin entry files are "atomic"
// that is, reloading them will not cause any other reloads
@@ -173,25 +202,13 @@ export default class Watcher extends Service {
for (const dep of dependencies) this.accepted.add(dep);
// prepare for reload
let isMarked = false;
const visited = new Set<MainScope>();
const queued = [runtime];
while (queued.length) {
const runtime = queued.shift();
if (visited.has(runtime)) continue;
visited.add(runtime);
if (reloads.has(runtime)) {
isMarked = true;
break;
}
for (const state of runtime.children) {
queued.push(state.runtime);
}
}
if (!isMarked) reloads.set(runtime, filename);
reloads.set(runtime.plugin, {
filename,
runtime,
});
}
const backup: Record<string, NodeJS.Module> = {};
const backup: Record<string, NodeJS.Module> = Object.create(null);
for (const filename of this.accepted) {
backup[filename] = require.cache[filename];
delete require.cache[filename];
@@ -204,54 +221,61 @@ export default class Watcher extends Service {
}
logger.debug('Will reload the following file(s): %o', this.accepted.keys());
const attempts = {};
const attempts: Record<string, any> = {};
try {
for (const [, filename] of reloads) {
for (const [, { filename }] of reloads) {
attempts[filename] = unwrapExports(require(filename));
}
} catch (err) {
logger.warn(err);
} catch (e) {
handleError(this.ctx, e);
return rollback();
}
const reload = (plugin: any, runtime?: Plugin.Runtime) => {
if (!runtime) return;
for (const oldFiber of runtime.scopes) {
oldFiber.parent.plugin(plugin, oldFiber.config);
}
};
try {
for (const [runtime, filename] of reloads) {
for (const [plugin, { filename, runtime }] of reloads) {
const path = relative(this.root, filename);
const states = runtime.children.slice();
try {
this.ctx.registry.delete(runtime.plugin);
this.ctx.registry.delete(plugin);
} catch (err) {
logger.warn(`failed to dispose plugin at %c\n${coerce(err)}`, path);
logger.warn('failed to dispose plugin at %c', path);
logger.warn(err);
}
try {
const plugin = attempts[filename];
for (const state of states) {
state.parent.plugin(plugin, state.config);
}
reload(attempts[filename], runtime);
logger.info('reload plugin at %c', path);
} catch (err) {
logger.warn(`failed to reload plugin at %c\n${coerce(err)}`, path);
logger.warn('failed to reload plugin at %c', path);
logger.warn(err);
throw err;
}
}
} catch {
// rollback require.cache and plugin states
rollback();
for (const [runtime, filename] of reloads) {
for (const [plugin, { filename, runtime }] of reloads) {
try {
this.ctx.registry.delete(attempts[filename]);
runtime.parent.plugin(runtime.plugin, runtime.config);
reload(plugin, runtime);
} catch (err) {
logger.warn(err);
}
}
logger.warn('Rolling back changes');
return;
}
// emit reload event on success
this.ctx.emit('hmr/reload', reloads);
// reset stashed files
this.stashed = new Set();
logger.success('Reload done in %d ms', Date.now() - start);
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ export class WorkerService extends Service {
coll = db.collection('schedule');
constructor(ctx: Context) {
super(ctx, 'worker', true);
super(ctx, 'worker');
this.consume();
}