oak-cli/template/scripts/watchServer.js

245 lines
6.9 KiB
JavaScript

/* eslint-disable @typescript-eslint/no-require-imports */
require('module-alias/register');
const chokidar = require('chokidar');
const { join } = require('path');
const ts = require('typescript');
const path = require('path');
const _ = require('lodash');
const { startup } = require('@xuchangzju/oak-cli/lib/server/start');
const projectPath = join(__dirname, '..');
const dayjs = require('dayjs');
const enableTrace = !!process.env.ENABLE_TRACE;
//polyfill console.log 添加时间
const polyfill = (trace) => {
const getTime = () => {
return dayjs().format('YYYY-MM-DD HH:mm:ss.SSS');
};
const infoStart = '\x1B[36m[ Info';
const warnStart = '\x1B[33m[ Warn';
const errorStart = '\x1B[31m[ Error';
const clearColor = trace ? ']\x1B[0m\n' : ']\x1B[0m';
// 获取调用堆栈信息
const getCallerInfo = () => {
const originalFunc = Error.prepareStackTrace;
let callerInfo = null;
try {
const err = new Error();
let currentFile;
Error.prepareStackTrace = (err, stack) => {
return stack;
};
currentFile = err.stack.shift().getFileName();
while (err.stack.length) {
callerInfo = err.stack.shift();
if (currentFile !== callerInfo.getFileName()) {
break;
}
}
} catch (e) {
console.error(e);
}
Error.prepareStackTrace = originalFunc;
return callerInfo;
};
const getFileInfo = () => {
if (!trace) {
return '';
}
const callerInfo = getCallerInfo();
const fileInfo = callerInfo
? `${callerInfo.getFileName()}:${callerInfo.getLineNumber()}`
: '';
return fileInfo.trim();
};
// polyfill console.log 添加时间和文件位置
const oldLog = console.log;
console.log = function () {
oldLog(infoStart, getTime(), getFileInfo(), clearColor, ...arguments);
};
const oldWarn = console.warn;
console.warn = function () {
oldWarn(warnStart, getTime(), getFileInfo(), clearColor, ...arguments);
};
const oldError = console.error;
console.error = function () {
oldError(
errorStart,
getTime(),
getFileInfo(),
clearColor,
...arguments
);
};
};
polyfill(enableTrace);
let shutdown;
const restart = async () => {
if (shutdown) {
await shutdown();
}
// 清空lib以下目录的缓存
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function (key) {
if (key.includes('node_modules')) {
return;
}
if (key.includes('lib')) {
console.log('delete module cache:', key);
delete require.cache[key];
}
});
const pwd = process.cwd();
const simpleConnector = require('../lib/config/connector').default;
console.warn('----> Starting service......');
shutdown = await startup(pwd, simpleConnector);
};
const watchSourcePath = join(projectPath, 'src');
console.log('Watching for changes in', watchSourcePath);
// 查找配置文件
const configFileName = ts.findConfigFile(
/*searchPath*/ projectPath,
ts.sys.fileExists,
'tsconfig.build.json'
);
if (!configFileName) {
throw new Error("Could not find a valid 'tsconfig.build.json'.");
}
// 读取配置文件
const configFile = ts.readConfigFile(configFileName, ts.sys.readFile);
// 解析配置文件内容
const { options, projectReferences } = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configFileName)
);
const watcher = chokidar.watch(watchSourcePath, {
persistent: true,
// ignore render and i18n files
ignored: (file) => {
return (
file.endsWith('.tsx') ||
file.endsWith('.json') ||
file.endsWith('.xml') ||
file.includes('components') ||
file.includes('pages') ||
file.includes('hooks')
);
},
// awaitWriteFinish: true, // emit single event when chunked writes are completed
// atomic: true, // emit proper events when "atomic writes" (mv _tmp file) are used
interval: 100,
binaryInterval: 100,
cwd: projectPath,
depth: 99,
followSymlinks: true,
ignoreInitial: false,
ignorePermissionErrors: false,
usePolling: false,
alwaysStat: false,
});
let startWatching = false;
watcher.on('ready', () => {
console.warn('Initial scan complete. Ready for changes');
startWatching = true;
});
watcher.on('error', (error) => console.log(`Watcher error: ${error}`));
let isProcessing = false;
const onChangeDebounced = _.debounce(async (path, type) => {
if (isProcessing) {
console.log('Processing, please wait...');
return;
}
isProcessing = true;
try {
// 控制台清空
console.clear();
console.warn(`File ${path} has been ${type}d`);
const program = ts.createProgram({
rootNames: [path],
options,
projectReferences,
});
const sourceFile = program.getSourceFile(path);
// 是否有语法错误
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
if (diagnostics.length) {
const syntaxErrors = diagnostics.filter(
(diagnostic) =>
diagnostic.category === ts.DiagnosticCategory.Error
);
if (syntaxErrors.length) {
console.error(`Error in ${path}`);
syntaxErrors.forEach((diagnostic) => {
console.error(
`${ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n'
)}`
);
});
console.error(`文件存在语法错误,请检查修复后重试!`);
isProcessing = false;
return;
}
}
// 只输出单个文件
const emitResult = program.emit(sourceFile);
// 是否成功
const result = emitResult.emitSkipped;
if (result) {
console.error(`Emit failed for ${path}!`);
} else {
console.log(`Emit succeeded. reload service......`);
await restart();
}
} catch (e) {
console.clear();
console.error(e);
// 结束服务
// nodemon.emit('quit');
process.exit(1);
}
isProcessing = false;
}, 200);
watcher
.on('add', (path) => {
if (startWatching) {
onChangeDebounced(path, 'add');
}
})
.on('change', (path) => {
if (startWatching) {
onChangeDebounced(path, 'change');
}
})
.on('unlink', (path) => {
if (startWatching) {
onChangeDebounced(path, 'remove');
}
});
restart();