diff --git a/lib/server/watch.js b/lib/server/watch.js index 2c493e9..74c098b 100644 --- a/lib/server/watch.js +++ b/lib/server/watch.js @@ -5,7 +5,6 @@ const tslib_1 = require("tslib"); const chokidar_1 = tslib_1.__importDefault(require("chokidar")); const typescript_1 = tslib_1.__importDefault(require("typescript")); const path_1 = 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"); @@ -95,9 +94,156 @@ const getOverrideConfig = (config) => { ? overrideInner(config, defaultConfig) : defaultConfig; }; +/** + * 根据 alias 配置表将路径中的别名替换为真实路径 + * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module" + * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组 + * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径 + */ +function replaceAliasWithPath(path, aliasConfig) { + for (const [alias, targets] of Object.entries(aliasConfig)) { + // If alias ends with "*", handle it as a dynamic alias. + if (alias.endsWith('*')) { + // Create a regex pattern that matches paths starting with the alias, followed by any characters + const aliasPattern = new RegExp(`^${alias.replace(/\*$/, "")}(.*)`); // e.g., '@project/*' becomes '@project/(.*)' + const match = path.match(aliasPattern); + if (match) { + // Replace the alias with the target path, appending the matched part from the original path + const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array) + // Ensure that the target path ends with a slash if it's not already + const replacedPath = target.replace(/\/\*$/, "/") + match[1]; + return replacedPath; + } + } + else { + // Handle static alias without "*" by directly matching the path + if (path.startsWith(alias)) { + const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array) + // Replace the alias part with the target path + return path.replace(alias, target); + } + } + } + // If no alias matches, return the original path + return path; +} const watch = (projectPath, config) => { const realConfig = getOverrideConfig(config); const enableTrace = !!process.env.ENABLE_TRACE; + // 查找配置文件 + 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_1.default.dirname(configFileName)); + const aliasConfig = (0, lodash_1.cloneDeep)(options.paths) || {}; + // 输出原始配置 + // console.log("[DEBUG] Original alias config:", aliasConfig); + Object.keys(aliasConfig).forEach((key) => { + const value = aliasConfig[key]; + // 替换src + aliasConfig[key] = typeof value === "string" ? value.replace("src", "lib") : value.map((v) => v.replace("src", "lib")); + }); + // 输出真实的alias配置 + console.debug("[DEBUG] Running Alias config:", aliasConfig); + const createProgramAndSourceFile = (path, options, projectReferences) => { + 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 { program, sourceFile, diagnostics }; + } + } + return { program, sourceFile, diagnostics }; + }; + // 这个函数用于解决require的时候,如果文件不存在,会尝试编译ts文件 + // 测试:在src下新建一个ts文件,然后删除lib下的js文件,然后require这个文件,会发现会自动编译ts文件 + const polyfillLoader = () => { + const BuiltinModule = require("module"); + // 兼容一些模拟环境下的 module 构造函数 + const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule; + // 保存原始的 _resolveFilename 方法 + const oldResolveFilename = Module._resolveFilename; + // 重写 _resolveFilename 方法 + Module._resolveFilename = function (request, // 模块请求路径 + parent, // 调用方模块 + isMain, // 是否是主模块 + rFoptions // 解析选项 + ) { + let resolvedRequest = request; // 用于存储替换后的路径 + // 使用 replaceAliasWithPath 进行 alias 路径替换 + const replacedPath = replaceAliasWithPath(request, aliasConfig); + if (replacedPath !== request) { + console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); // 记录替换日志 + resolvedRequest = path_1.default.join(projectPath, replacedPath); // 更新解析路径 + } + let filename; // 用于存储最终解析的文件名 + try { + // 调用原始的 _resolveFilename 方法尝试解析文件 + filename = oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions); + } + catch (error) { + console.log(`[resolve] Failed to resolve: ${resolvedRequest}`); // 打印解析失败信息 + // 如果解析失败,尝试处理 .ts 源文件的动态编译 + if (error.code === "MODULE_NOT_FOUND") { + // 构造 .js 文件路径 + const jsPath = path_1.default.resolve(path_1.default.dirname(parent.filename), resolvedRequest + ".js"); + // 替换为对应的 .ts 文件路径 + const tsPath = jsPath + .replace(/\.js$/, ".ts") + .replace(path_1.default.join(projectPath, "lib"), // 替换 lib 为 src + path_1.default.join(projectPath, "src")); + // 检查对应的 .ts 文件是否存在 + if (fs_1.default.existsSync(tsPath)) { + console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`); + // 调用自定义函数进行 TypeScript 编译 + const { program, sourceFile, diagnostics } = createProgramAndSourceFile(tsPath, options, projectReferences); + // 如果存在编译错误,抛出异常 + if (diagnostics.length) { + console.error(`[resolve] Compilation failed for: ${tsPath}`); + throw error; + } + // 执行编译,并检查是否成功 + const emitResult = program.emit(sourceFile); + if (emitResult.emitSkipped) { + console.error(`[resolve] Emit skipped for: ${tsPath}`); + throw error; + } + console.log(`[resolve] Successfully compiled: ${tsPath}`); + // 将解析路径更新为对应的 .js 文件路径 + filename = jsPath; + } + else { + // 如果 .ts 文件不存在,打印错误并抛出异常 + console.error(`[resolve] ${tsPath} does not exist. Unable to resolve module.`); + throw error; + } + } + else { + // 如果不是 MODULE_NOT_FOUND 异常,直接抛出 + throw error; + } + } + // 返回最终解析的文件名 + return filename; + }; + }; + polyfillLoader(); //polyfill console.log 添加时间 const polyfillConsole = (trace) => { // 获取调用堆栈信息 @@ -141,6 +287,8 @@ const watch = (projectPath, config) => { }); }; realConfig.polyfill.console.enable && polyfillConsole(enableTrace); + // 这里注意要在require之前,因为require会触发编译 + const { startup } = require('./start'); return new Promise((resolve, reject) => { realConfig.lifecycle.onInit(); let shutdown; @@ -170,22 +318,13 @@ const watch = (projectPath, config) => { // eslint-disable-next-line @typescript-eslint/no-var-requires const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default; console.warn("----> Starting service......"); - shutdown = await (0, start_1.startup)(pwd, simpleConnector).then((shutdown) => { + shutdown = await startup(pwd, simpleConnector).then((shutdown) => { realConfig.lifecycle.onServerStart(); return shutdown; }); }; const watchSourcePath = path_1.default.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_1.default.dirname(configFileName)); const watcher = chokidar_1.default.watch(watchSourcePath, { persistent: true, ignored: (file) => file.endsWith(".tsx") || @@ -209,13 +348,14 @@ const watch = (projectPath, config) => { startWatching = true; }); watcher.on("error", (error) => console.log(`Watcher error: ${error}`)); - let isProcessing = false; + let processingQueue = []; const fileChangeHandler = async (path, type) => { // 判断一下是不是以下扩展名:ts if (!path.endsWith(".ts")) { // 如果是json文件,复制或者删除 if (path.endsWith(".json")) { - const targetPath = path.replace("src", "lib"); + // const targetPath = path.replace("src", "lib"); // 这里直接替换不对,应该是拿到项目目录,把项目目录/src 换成项目目录/lib + const targetPath = path.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib")); if (type === "remove") { fs_1.default.unlinkSync(targetPath); console.warn(`File ${targetPath} has been removed.`); @@ -245,8 +385,8 @@ const watch = (projectPath, config) => { const modulePath = path_1.default.resolve(path); // 将src替换为lib const libPath = modulePath - .replace("\\src\\", "\\lib\\") - .replace(".ts", ".js"); + .replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib")) + .replace(/\.ts$/, ".js"); let compileOnly = false; if (!require.cache[libPath]) { // 如果是删除的话,直接尝试删除lib下的文件 @@ -260,8 +400,11 @@ const watch = (projectPath, config) => { console.warn(`File ${libPath} has been removed.`); return; } - console.warn(`File ${libPath} is not in module cache, will compile only.`); - compileOnly = true; + // console.warn( + // `File ${libPath} is not in module cache, will compile only.` + // ); + // compileOnly = true; + // 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件 } else { // 如果是删除,则需要发出警告,文件正在被进程使用 @@ -270,24 +413,9 @@ const watch = (projectPath, config) => { return; } } - const program = typescript_1.default.createProgram({ - rootNames: [path], - options, - projectReferences, - }); - const sourceFile = program.getSourceFile(path); - // 是否有语法错误 - const diagnostics = typescript_1.default.getPreEmitDiagnostics(program, sourceFile); + const { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences); 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; - } + return; } // 只输出单个文件 realConfig.lifecycle.onBeforeCompile(); @@ -304,40 +432,47 @@ const watch = (projectPath, config) => { if (compileOnly) { return; } - await restart(); + // await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务 + if (processingQueue.length === 1) { + await restart(); + } + else { + console.log("Waiting for other operations to complete..."); + } } }; - const onChangeDebounced = (0, lodash_1.debounce)(async (path, type) => { - if (isProcessing) { + const onChangeDebounced = async (path, type) => { + if (processingQueue.includes(path)) { console.log("Processing, please wait..."); return; } try { - isProcessing = true; + processingQueue.push(path); await fileChangeHandler(path, type); } catch (e) { console.clear(); console.error(e); + process.exit(1); } finally { - isProcessing = false; + processingQueue = processingQueue.filter((p) => p !== path); } - }, 100); + }; watcher - .on("add", (path) => { + .on("add", async (path) => { if (startWatching) { - onChangeDebounced(path, "add"); + await onChangeDebounced(path, "add"); } }) - .on("change", (path) => { + .on("change", async (path) => { if (startWatching) { - onChangeDebounced(path, "change"); + await onChangeDebounced(path, "change"); } }) - .on("unlink", (path) => { + .on("unlink", async (path) => { if (startWatching) { - onChangeDebounced(path, "remove"); + await onChangeDebounced(path, "remove"); } }); const dispose = async () => { diff --git a/src/server/watch.ts b/src/server/watch.ts index 1e3357b..a6a52b2 100644 --- a/src/server/watch.ts +++ b/src/server/watch.ts @@ -1,10 +1,9 @@ import chokidar from "chokidar"; -import ts from "typescript"; +import ts, { CompilerOptions, ProjectReference } from "typescript"; import pathLib from "path"; -import { startup } from "./start"; import dayjs from "dayjs"; import fs from "fs"; -import { debounce } from "lodash"; +import { cloneDeep } from "lodash"; declare const require: NodeRequire; declare const process: NodeJS.Process; @@ -80,14 +79,14 @@ export type WatchConfig = { // 真实config不会出现?,所以这里新增一个type表示真实的config type DeepRequiredIfPresent = { [P in keyof T]-?: NonNullable extends (...args: any) => any // Check for function type - ? T[P] // Preserve the original function type - : NonNullable extends (infer U)[] - ? DeepRequiredIfPresent[] - : NonNullable extends object - ? DeepRequiredIfPresent> - : T[P] extends undefined | null - ? T[P] - : NonNullable; + ? T[P] // Preserve the original function type + : NonNullable extends (infer U)[] + ? DeepRequiredIfPresent[] + : NonNullable extends object + ? DeepRequiredIfPresent> + : T[P] extends undefined | null + ? T[P] + : NonNullable; }; export type RealWatchConfig = DeepRequiredIfPresent; @@ -116,8 +115,8 @@ const defaultConfig: RealWatchConfig = { level === "info" ? infoStart : level === "warn" - ? warnStart - : errorStart; + ? warnStart + : errorStart; return [ levelStart, getTime(), @@ -178,6 +177,43 @@ const getOverrideConfig = (config?: WatchConfig): RealWatchConfig => { : defaultConfig; }; +type AliasConfig = Record; + +/** + * 根据 alias 配置表将路径中的别名替换为真实路径 + * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module" + * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组 + * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径 + */ +function replaceAliasWithPath(path: string, aliasConfig: Record): string { + for (const [alias, targets] of Object.entries(aliasConfig)) { + // If alias ends with "*", handle it as a dynamic alias. + if (alias.endsWith('*')) { + // Create a regex pattern that matches paths starting with the alias, followed by any characters + const aliasPattern = new RegExp(`^${alias.replace(/\*$/, "")}(.*)`); // e.g., '@project/*' becomes '@project/(.*)' + + const match = path.match(aliasPattern); + if (match) { + // Replace the alias with the target path, appending the matched part from the original path + const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array) + // Ensure that the target path ends with a slash if it's not already + const replacedPath = target.replace(/\/\*$/, "/") + match[1]; + return replacedPath; + } + } else { + // Handle static alias without "*" by directly matching the path + if (path.startsWith(alias)) { + const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array) + // Replace the alias part with the target path + return path.replace(alias, target); + } + } + } + + // If no alias matches, return the original path + return path; +} + export const watch = ( projectPath: string, config?: WatchConfig @@ -185,6 +221,169 @@ export const watch = ( const realConfig = getOverrideConfig(config); const enableTrace = !!process.env.ENABLE_TRACE; + // 查找配置文件 + 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, + pathLib.dirname(configFileName) + ); + + const aliasConfig: AliasConfig = cloneDeep(options.paths) || {}; + + // 输出原始配置 + // console.log("[DEBUG] Original alias config:", aliasConfig); + + Object.keys(aliasConfig).forEach((key) => { + const value = aliasConfig[key]; + // 替换src + aliasConfig[key] = typeof value === "string" ? value.replace("src", "lib") : value.map((v) => v.replace("src", "lib")); + }); + + // 输出真实的alias配置 + console.debug("[DEBUG] Running Alias config:", aliasConfig); + + const createProgramAndSourceFile = (path: string, options: CompilerOptions, projectReferences: readonly ProjectReference[] | undefined) => { + 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 { program, sourceFile, diagnostics }; + } + } + + return { program, sourceFile, diagnostics }; + } + + // 这个函数用于解决require的时候,如果文件不存在,会尝试编译ts文件 + // 测试:在src下新建一个ts文件,然后删除lib下的js文件,然后require这个文件,会发现会自动编译ts文件 + const polyfillLoader = () => { + const BuiltinModule = require("module"); + + // 兼容一些模拟环境下的 module 构造函数 + const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule; + + // 保存原始的 _resolveFilename 方法 + const oldResolveFilename = Module._resolveFilename; + + // 重写 _resolveFilename 方法 + Module._resolveFilename = function ( + request: string, // 模块请求路径 + parent: typeof Module, // 调用方模块 + isMain: boolean, // 是否是主模块 + rFoptions: object | undefined // 解析选项 + ) { + let resolvedRequest = request; // 用于存储替换后的路径 + + // 使用 replaceAliasWithPath 进行 alias 路径替换 + const replacedPath = replaceAliasWithPath(request, aliasConfig); + if (replacedPath !== request) { + console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); // 记录替换日志 + resolvedRequest = pathLib.join(projectPath, replacedPath); // 更新解析路径 + } + + let filename; // 用于存储最终解析的文件名 + + try { + // 调用原始的 _resolveFilename 方法尝试解析文件 + filename = oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions); + } catch (error: any) { + console.log(`[resolve] Failed to resolve: ${resolvedRequest}`); // 打印解析失败信息 + + // 如果解析失败,尝试处理 .ts 源文件的动态编译 + if (error.code === "MODULE_NOT_FOUND") { + // 构造 .js 文件路径 + const jsPath = pathLib.resolve(pathLib.dirname(parent.filename), resolvedRequest + ".js"); + + // 替换为对应的 .ts 文件路径 + const tsPath = jsPath + .replace(/\.js$/, ".ts") + .replace( + pathLib.join(projectPath, "lib"), // 替换 lib 为 src + pathLib.join(projectPath, "src") + ); + + // 检查对应的 .ts 文件是否存在 + if (fs.existsSync(tsPath)) { + console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`); + + // 调用自定义函数进行 TypeScript 编译 + const { program, sourceFile, diagnostics } = createProgramAndSourceFile( + tsPath, + options, + projectReferences + ); + + // 如果存在编译错误,抛出异常 + if (diagnostics.length) { + console.error(`[resolve] Compilation failed for: ${tsPath}`); + throw error; + } + + // 执行编译,并检查是否成功 + const emitResult = program.emit(sourceFile); + + if (emitResult.emitSkipped) { + console.error(`[resolve] Emit skipped for: ${tsPath}`); + throw error; + } + + console.log(`[resolve] Successfully compiled: ${tsPath}`); + + // 将解析路径更新为对应的 .js 文件路径 + filename = jsPath; + } else { + // 如果 .ts 文件不存在,打印错误并抛出异常 + console.error(`[resolve] ${tsPath} does not exist. Unable to resolve module.`); + throw error; + } + } else { + // 如果不是 MODULE_NOT_FOUND 异常,直接抛出 + throw error; + } + } + + // 返回最终解析的文件名 + return filename; + }; + }; + + polyfillLoader(); + //polyfill console.log 添加时间 const polyfillConsole = (trace: boolean) => { // 获取调用堆栈信息 @@ -232,6 +431,11 @@ export const watch = ( realConfig.polyfill.console.enable && polyfillConsole(enableTrace); + // 这里注意要在require之前,因为require会触发编译 + const { startup } = require('./start') as { + startup: (pwd: string, connector: any) => Promise<() => Promise>; + } + return new Promise((resolve, reject) => { realConfig.lifecycle.onInit(); let shutdown: (() => Promise) | undefined; @@ -278,26 +482,7 @@ export const watch = ( 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, - pathLib.dirname(configFileName) - ); const watcher = chokidar.watch(watchSourcePath, { persistent: true, @@ -327,7 +512,7 @@ export const watch = ( watcher.on("error", (error) => console.log(`Watcher error: ${error}`)); - let isProcessing = false; + let processingQueue: string[] = []; const fileChangeHandler = async ( path: string, type: "add" | "remove" | "change" @@ -336,7 +521,8 @@ export const watch = ( if (!path.endsWith(".ts")) { // 如果是json文件,复制或者删除 if (path.endsWith(".json")) { - const targetPath = path.replace("src", "lib"); + // const targetPath = path.replace("src", "lib"); // 这里直接替换不对,应该是拿到项目目录,把项目目录/src 换成项目目录/lib + const targetPath = path.replace(pathLib.join(projectPath, "src"), pathLib.join(projectPath, "lib")); if (type === "remove") { fs.unlinkSync(targetPath); console.warn(`File ${targetPath} has been removed.`); @@ -375,8 +561,8 @@ export const watch = ( const modulePath = pathLib.resolve(path); // 将src替换为lib const libPath = modulePath - .replace("\\src\\", "\\lib\\") - .replace(".ts", ".js"); + .replace(pathLib.join(projectPath, "src"), pathLib.join(projectPath, "lib")) + .replace(/\.ts$/, ".js"); let compileOnly = false; if (!require.cache[libPath]) { // 如果是删除的话,直接尝试删除lib下的文件 @@ -389,10 +575,11 @@ export const watch = ( console.warn(`File ${libPath} has been removed.`); return; } - console.warn( - `File ${libPath} is not in module cache, will compile only.` - ); - compileOnly = true; + // console.warn( + // `File ${libPath} is not in module cache, will compile only.` + // ); + // compileOnly = true; + // 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件 } else { // 如果是删除,则需要发出警告,文件正在被进程使用 if (type === "remove") { @@ -400,34 +587,13 @@ export const watch = ( 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 { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences); + + if (diagnostics.length) { + return; } + // 只输出单个文件 realConfig.lifecycle.onBeforeCompile(); const emitResult = program.emit(sourceFile); @@ -438,51 +604,54 @@ export const watch = ( realConfig.lifecycle.onAfterCompile(); } else { console.log( - `Emit succeeded. ${ - compileOnly ? "" : "reload service......" + `Emit succeeded. ${compileOnly ? "" : "reload service......" }` ); realConfig.lifecycle.onAfterCompile(); if (compileOnly) { return; } - await restart(); + // await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务 + if (processingQueue.length === 1) { + await restart(); + } else { + console.log("Waiting for other operations to complete..."); + } } }; - const onChangeDebounced = debounce( + const onChangeDebounced = async (path: string, type: "add" | "remove" | "change") => { - if (isProcessing) { + if (processingQueue.includes(path)) { console.log("Processing, please wait..."); return; } try { - isProcessing = true; + processingQueue.push(path); await fileChangeHandler(path, type); } catch (e) { console.clear(); console.error(e); + process.exit(1); } finally { - isProcessing = false; + processingQueue = processingQueue.filter((p) => p !== path); } - }, - 100 - ); + } watcher - .on("add", (path) => { + .on("add", async (path) => { if (startWatching) { - onChangeDebounced(path, "add"); + await onChangeDebounced(path, "add"); } }) - .on("change", (path) => { + .on("change", async (path) => { if (startWatching) { - onChangeDebounced(path, "change"); + await onChangeDebounced(path, "change"); } }) - .on("unlink", (path) => { + .on("unlink", async (path) => { if (startWatching) { - onChangeDebounced(path, "remove"); + await onChangeDebounced(path, "remove"); } });