Merge branch 'release'

This commit is contained in:
Xu Chang 2024-12-05 12:08:56 +08:00
commit 0e2808962b
10 changed files with 595 additions and 280 deletions

View File

@ -4,4 +4,4 @@ import { Connector, EntityDict } from 'oak-domain/lib/types';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
export declare function startup<ED extends EntityDict & BaseEntityDict, FrontCxt extends SyncContext<ED>, Cxt extends BackendRuntimeContext<ED>>(path: string, connector: Connector<ED, FrontCxt>, omitWatchers?: boolean, omitTimers?: boolean, routine?: (context: AsyncContext<ED>) => Promise<void>): Promise<(() => void) | undefined>;
export declare function startup<ED extends EntityDict & BaseEntityDict, FrontCxt extends SyncContext<ED>, Cxt extends BackendRuntimeContext<ED>>(path: string, connector: Connector<ED, FrontCxt>, omitWatchers?: boolean, omitTimers?: boolean, routine?: (context: AsyncContext<ED>) => Promise<void>): Promise<(() => Promise<void>) | undefined>;

View File

@ -235,8 +235,8 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
process.exit(0);
});
const shutdown = async () => {
httpServer.close();
koa.removeAllListeners();
await httpServer.close();
await koa.removeAllListeners();
await appLoader.unmount();
};
return shutdown;

1
lib/server/watch.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare const watch: (projectPath: string) => void;

261
lib/server/watch.js Normal file
View File

@ -0,0 +1,261 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.watch = void 0;
const tslib_1 = require("tslib");
const chokidar_1 = tslib_1.__importDefault(require("chokidar"));
const path_1 = require("path");
const typescript_1 = tslib_1.__importDefault(require("typescript"));
const path_2 = tslib_1.__importDefault(require("path"));
const start_1 = require("./start");
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
const fs_1 = tslib_1.__importDefault(require("fs"));
const lodash_1 = require("lodash");
const watch = (projectPath) => {
const enableTrace = !!process.env.ENABLE_TRACE;
//polyfill console.log 添加时间
const polyfill = (trace) => {
const getTime = () => (0, dayjs_1.default)().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) => stack;
const stack = err.stack; // Type assertion here
const currentFile = stack[0].getFileName();
for (let i = 1; i < stack.length; i++) {
// Start from index 1
const callSite = stack[i];
if (currentFile !== callSite.getFileName()) {
callerInfo = callSite;
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 (...args) {
oldLog(infoStart, getTime(), getFileInfo(), clearColor, ...args);
};
const oldWarn = console.warn;
console.warn = function (...args) {
oldWarn(warnStart, getTime(), getFileInfo(), clearColor, ...args);
};
const oldError = console.error;
console.error = function (...args) {
oldError(errorStart, getTime(), getFileInfo(), clearColor, ...args);
};
};
polyfill(enableTrace);
let shutdown;
const restart = async () => {
if (shutdown) {
console.log("----> Shutting down service......");
await shutdown();
}
console.warn("----> Clearing require cache of project......");
let deleteCount = 0;
// 清空lib以下目录的缓存
Object.keys(require.cache).forEach((key) => {
// 如果不是项目目录下的文件,不删除
if (!key.startsWith(projectPath)) {
return;
}
else if (key.includes("lib") && !key.includes("node_modules")) {
delete require.cache[key];
deleteCount++;
}
});
console.warn(`----> ${deleteCount} modules has been removed from require.cache.`);
const pwd = process.cwd();
// eslint-disable-next-line @typescript-eslint/no-var-requires
const simpleConnector = require((0, path_1.join)(projectPath, 'lib/config/connector')).default;
console.warn("----> Starting service......");
shutdown = await (0, start_1.startup)(pwd, simpleConnector);
};
const watchSourcePath = (0, path_1.join)(projectPath, "src");
console.log("Watching for changes in", watchSourcePath);
// 查找配置文件
const configFileName = typescript_1.default.findConfigFile(projectPath, typescript_1.default.sys.fileExists, "tsconfig.build.json");
if (!configFileName) {
throw new Error("Could not find a valid 'tsconfig.build.json'.");
}
// 读取配置文件
const configFile = typescript_1.default.readConfigFile(configFileName, typescript_1.default.sys.readFile);
// 解析配置文件内容
const { options, projectReferences } = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_2.default.dirname(configFileName));
const watcher = chokidar_1.default.watch(watchSourcePath, {
persistent: true,
ignored: (file) => file.endsWith(".tsx") ||
file.endsWith(".xml") ||
file.includes("components") ||
file.includes("pages") ||
file.includes("hooks"),
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
if (!path.endsWith(".ts")) {
// 如果是json文件复制或者删除
if (path.endsWith(".json")) {
const targetPath = path.replace("src", "lib");
if (type === "remove") {
fs_1.default.unlinkSync(targetPath);
console.warn(`File ${targetPath} has been removed.`);
}
else if (type === "add") {
fs_1.default.copyFileSync(path, targetPath, fs_1.default.constants.COPYFILE_FICLONE);
console.warn(`File ${path} has been created at ${targetPath}.`);
}
else if (type === "change") {
// 强制覆盖文件
if (fs_1.default.existsSync(targetPath)) {
fs_1.default.unlinkSync(targetPath);
}
fs_1.default.copyFileSync(path, targetPath, fs_1.default.constants.COPYFILE_FICLONE);
console.warn(`File ${path} has been copied to ${targetPath}.`);
}
}
else {
console.warn(`File ${path} is not [ts,json] file, skiped.`);
}
return;
}
// 控制台清空
console.clear();
console.warn(`File ${path} has been ${type}d`);
// 先判断一下这个文件在不在require.cache里面
const modulePath = (0, path_1.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_1.default.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 = typescript_1.default.createProgram({
rootNames: [path],
options,
projectReferences,
});
const sourceFile = program.getSourceFile(path);
// 是否有语法错误
const diagnostics = typescript_1.default.getPreEmitDiagnostics(program, sourceFile);
if (diagnostics.length) {
const syntaxErrors = diagnostics.filter((diagnostic) => diagnostic.category === typescript_1.default.DiagnosticCategory.Error);
if (syntaxErrors.length) {
console.error(`Error in ${path}`);
syntaxErrors.forEach((diagnostic) => {
console.error(`${typescript_1.default.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 = (0, lodash_1.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();
};
exports.watch = watch;

View File

@ -303,7 +303,10 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
"readable-stream": "3.6.2"
},
"_moduleAliases": {
"@project": "./lib"
"@project": "./lib",
"@oak-general-business": "./node_modules/oak-general-business/lib",
"@oak-frontend-base": "./node_modules/oak-frontend-base/lib",
"@oak-app-domain": "./lib/oak-app-domain"
}
}
`;

View File

@ -1,6 +1,6 @@
{
"name": "@xuchangzju/oak-cli",
"version": "4.0.16",
"version": "4.0.17",
"description": "client for oak framework",
"main": "lib/index.js",
"scripts": {
@ -32,6 +32,7 @@
"@types/cross-spawn": "^6.0.2",
"@types/inquirer": "^9.0.3",
"@types/koa-router": "^7.4.4",
"@types/lodash": "^4.17.13",
"@types/node": "^20.6.0",
"@types/shelljs": "^0.8.8",
"@types/uuid": "^8.3.4",
@ -99,7 +100,7 @@
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.5.3",
"node-watch": "^0.7.4",
"oak-backend-base": "^4.1.10",
"oak-backend-base": "^4.1.11",
"oak-domain": "^5.1.11",
"oak-frontend-base": "^5.3.20",
"parse-asn1": "5.1.6",

View File

@ -38,7 +38,7 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
omitWatchers?: boolean,
omitTimers?: boolean,
routine?: (context: AsyncContext<ED>) => Promise<void>,
): Promise<(() => void )| undefined> {
): Promise<(() => Promise<void> )| undefined> {
const serverConfiguration: ServerConfiguration = require(join(
path,
'lib',
@ -307,8 +307,8 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
});
const shutdown = async () => {
httpServer.close();
koa.removeAllListeners();
await httpServer.close();
await koa.removeAllListeners();
await appLoader.unmount();
}

315
src/server/watch.ts Normal file
View File

@ -0,0 +1,315 @@
import chokidar from "chokidar";
import { join, resolve } from "path";
import ts from "typescript";
import path from "path";
import { startup } from "./start";
import dayjs from "dayjs";
import fs from "fs";
import { debounce } from "lodash";
declare const require: NodeRequire;
declare const process: NodeJS.Process;
export const watch = (projectPath: string) => {
const enableTrace = !!process.env.ENABLE_TRACE;
//polyfill console.log 添加时间
const polyfill = (trace: boolean) => {
const getTime = () => 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 = (): NodeJS.CallSite | null => {
const originalFunc = Error.prepareStackTrace;
let callerInfo: NodeJS.CallSite | null = null;
try {
const err = new Error();
Error.prepareStackTrace = (err, stack) => stack;
const stack = err.stack as unknown as NodeJS.CallSite[]; // Type assertion here
const currentFile = stack[0].getFileName();
for (let i = 1; i < stack.length; i++) {
// Start from index 1
const callSite = stack[i];
if (currentFile !== callSite.getFileName()) {
callerInfo = callSite;
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 (...args) {
oldLog(infoStart, getTime(), getFileInfo(), clearColor, ...args);
};
const oldWarn = console.warn;
console.warn = function (...args) {
oldWarn(warnStart, getTime(), getFileInfo(), clearColor, ...args);
};
const oldError = console.error;
console.error = function (...args) {
oldError(errorStart, getTime(), getFileInfo(), clearColor, ...args);
};
};
polyfill(enableTrace);
let shutdown: (() => Promise<void>) | undefined;
const restart = async () => {
if (shutdown) {
console.log("----> Shutting down service......");
await shutdown();
}
console.warn("----> Clearing require cache of project......");
let deleteCount = 0;
// 清空lib以下目录的缓存
Object.keys(require.cache).forEach((key) => {
// 如果不是项目目录下的文件,不删除
if (!key.startsWith(projectPath)) {
return;
} else if (key.includes("lib") && !key.includes("node_modules")) {
delete require.cache[key];
deleteCount++;
}
});
console.warn(`----> ${deleteCount} modules has been removed from require.cache.`);
const pwd = process.cwd();
// eslint-disable-next-line @typescript-eslint/no-var-requires
const simpleConnector = require(join(projectPath, '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(
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,
ignored: (file: string) =>
file.endsWith(".tsx") ||
file.endsWith(".xml") ||
file.includes("components") ||
file.includes("pages") ||
file.includes("hooks"),
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: string,
type: "add" | "remove" | "change"
) => {
// 判断一下是不是以下扩展名ts
if (!path.endsWith(".ts")) {
// 如果是json文件复制或者删除
if (path.endsWith(".json")) {
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] 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: string, type: "add" | "remove" | "change") => {
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();
};

View File

@ -67,7 +67,7 @@ export function packageJsonContent({
}
const serverInitScript = isDev ? "cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node scripts/initServer.js" : "cross-env OAK_PLATFORM=server node scripts/initServer.js";
const serverStartScript = isDev ? "cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node scripts/startServer.js" : "cross-env OAK_PLATFORM=server node scripts/startServer.js";
// const serverStartScript = isDev ? "cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node scripts/startServer.js" : "cross-env OAK_PLATFORM=server node scripts/startServer.js";
const serverStartWatchScript = isDev ? "cross-env ENABLE_TRACE=true cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node --stack-size=65500 scripts/watchServer.js" : "cross-env OAK_PLATFORM=server node --stack-size=65500 scripts/watchServer.js";
return `{
"name": "${name}",
@ -105,8 +105,7 @@ export function packageJsonContent({
"run:ios": "oak-cli run -p ios",
"run:android": "oak-cli run -p android",
"server:init": "${serverInitScript}",
"server:start": "${serverStartScript}",
"server:start:watch": "${serverStartWatchScript}",
"server:start": "${serverStartWatchScript}",
"postinstall": "npm run make:dep"
},
"keywords": [],

View File

@ -1,271 +1,6 @@
/* 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 { watch } = require('@xuchangzju/oak-cli/lib/server/watch');
const pwd = process.cwd();
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`);
// 先判断一下这个文件在不在require.cache里面
const modulePath = resolve(path);
// 将src替换为lib
const libPath = modulePath
.replace('\\src\\', '\\lib\\')
.replace('.ts', '.js');
let compileOnly = false;
if (!require.cache[libPath]) {
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.`);
isProcessing = false;
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(`文件存在语法错误,请检查修复后重试!`);
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. ${compileOnly ? '' : 'reload service......'}`
);
if (compileOnly) {
isProcessing = false;
return;
}
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();
watch(pwd);