oak-cli/template/scripts/watchServer.js

310 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 { resolve } = require('path');
const _ = require('lodash');
const { startup } = require('@xuchangzju/oak-cli/lib/server/start');
const projectPath = join(__dirname, '..');
const dayjs = require('dayjs');
const fs = require('fs');
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();
Error.prepareStackTrace = (err, stack) => {
return stack;
};
const 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.startsWith(projectPath)) {
return;
} else if (key.includes('lib') && !key.includes('node_modules')) {
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.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 fileChangeHandler = async (path, type) => {
// 判断一下是不是以下扩展名ts,tsx
if (!path.endsWith('.ts')) {
// 如果是json或者xml文件复制或者删除
if (path.endsWith('.json') || path.endsWith('.xml')) {
const targetPath = path.replace('src', 'lib');
if (type === 'remove') {
fs.unlinkSync(targetPath);
console.warn(`File ${targetPath} has been removed.`);
} else if (type === 'add') {
fs.copyFileSync(
path,
targetPath,
fs.constants.COPYFILE_FICLONE
);
console.warn(`File ${path} has been created at ${targetPath}.`);
} else if (type === 'change') {
// 强制覆盖文件
if (fs.existsSync(targetPath)) {
fs.unlinkSync(targetPath);
}
fs.copyFileSync(
path,
targetPath,
fs.constants.COPYFILE_FICLONE
);
console.warn(`File ${path} has been copied to ${targetPath}.`);
}
} else {
console.warn(`File ${path} is not [ts,json,xml] file, skiped.`);
}
return;
}
// 控制台清空
console.clear();
console.warn(`File ${path} has been ${type}d`);
// 先判断一下这个文件在不在require.cache里面
const modulePath = resolve(path);
// 将src替换为lib
const libPath = modulePath
.replace('\\src\\', '\\lib\\')
.replace('.ts', '.js');
let compileOnly = false;
if (!require.cache[libPath]) {
// 如果是删除的话直接尝试删除lib下的文件
if (type === 'remove') {
try {
fs.unlinkSync(libPath);
} catch (e) {
console.error(`Error in delete ${libPath}`, e);
}
console.warn(`File ${libPath} has been removed.`);
return;
}
console.warn(
`File ${libPath} is not in module cache, will compile only.`
);
compileOnly = true;
} else {
// 如果是删除,则需要发出警告,文件正在被进程使用
if (type === 'remove') {
console.error(`File ${libPath} is being used, skiped.`);
return;
}
}
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(`文件存在语法错误,请检查修复后重试!`);
return;
}
}
// 只输出单个文件
const emitResult = program.emit(sourceFile);
// 是否成功
const result = emitResult.emitSkipped;
if (result) {
console.error(`Emit failed for ${path}!`);
} else {
console.log(
`Emit succeeded. ${compileOnly ? '' : 'reload service......'}`
);
if (compileOnly) {
return;
}
await restart();
}
};
const onChangeDebounced = _.debounce(async (path, type) => {
if (isProcessing) {
console.log('Processing, please wait...');
return;
}
try {
isProcessing = true;
await fileChangeHandler(path, type);
} catch (e) {
console.clear();
console.error(e);
} finally {
isProcessing = false;
}
}, 100);
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();