From 35b3ff3694ccf765f38f9b8ac3a6fa61dd001daa Mon Sep 17 00:00:00 2001 From: pqcqaq <905739777@qq.com> Date: Sat, 25 Jan 2025 13:43:10 +0800 Subject: [PATCH] =?UTF-8?q?watch=E4=B8=8D=E5=86=8D=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E6=8F=90=E5=89=8Dbuild?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/server/watch.js | 139 ++++++++++++++++--------- src/server/watch.ts | 245 +++++++++++++++++++++++++++----------------- 2 files changed, 244 insertions(+), 140 deletions(-) diff --git a/lib/server/watch.js b/lib/server/watch.js index 74c098b..c60e79b 100644 --- a/lib/server/watch.js +++ b/lib/server/watch.js @@ -175,72 +175,76 @@ const watch = (projectPath, config) => { // 测试:在src下新建一个ts文件,然后删除lib下的js文件,然后require这个文件,会发现会自动编译ts文件 const polyfillLoader = () => { const BuiltinModule = require("module"); - // 兼容一些模拟环境下的 module 构造函数 + // 模拟环境下的 module 构造函数 const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule; - // 保存原始的 _resolveFilename 方法 + // 保存原始 _resolveFilename 方法 const oldResolveFilename = Module._resolveFilename; + // 通用的路径检查与编译函数 + function resolveAndCompile(requestPath, parent, options, projectReferences) { + const jsPath = path_1.default.resolve(requestPath + ".js"); + const tsPath = jsPath.replace(/\.js$/, ".ts").replace(path_1.default.join(projectPath, "lib"), path_1.default.join(projectPath, "src")); + // 检查并编译 .ts 文件 + if (fs_1.default.existsSync(tsPath)) { + console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`); + const { program, sourceFile, diagnostics } = createProgramAndSourceFile(tsPath, options, projectReferences); + if (diagnostics.length) { + console.error(`[resolve] Compilation failed for: ${tsPath}`); + throw new Error("TypeScript compilation error"); + } + const emitResult = program.emit(sourceFile); + if (emitResult.emitSkipped) { + console.error(`[resolve] Emit skipped for: ${tsPath}`); + throw new Error("TypeScript emit skipped"); + } + console.log(`[resolve] Successfully compiled: ${tsPath}`); + return jsPath; + } + // 如果没有找到对应的 .ts 文件 + if (fs_1.default.existsSync(jsPath)) { + return jsPath; + } + throw new Error(`[resolve] Unable to find module: ${requestPath}`); + } + // 处理文件夹导入的情况 + function resolveDirectory(requestPath, parent, options, projectReferences) { + const indexJs = path_1.default.join(requestPath, "index.js"); + const indexTs = path_1.default.join(requestPath, "index.ts").replace(path_1.default.join(projectPath, "lib"), path_1.default.join(projectPath, "src")); + if (fs_1.default.existsSync(indexTs)) { + console.log(`[resolve] Found TypeScript index file: ${indexTs}, attempting to compile...`); + return resolveAndCompile(indexTs, parent, options, projectReferences); + } + if (fs_1.default.existsSync(indexJs)) { + return indexJs; + } + throw new Error(`[resolve] No index file found in directory: ${requestPath}`); + } // 重写 _resolveFilename 方法 Module._resolveFilename = function (request, // 模块请求路径 parent, // 调用方模块 isMain, // 是否是主模块 rFoptions // 解析选项 ) { - let resolvedRequest = request; // 用于存储替换后的路径 - // 使用 replaceAliasWithPath 进行 alias 路径替换 + let resolvedRequest = request; const replacedPath = replaceAliasWithPath(request, aliasConfig); if (replacedPath !== request) { - console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); // 记录替换日志 - resolvedRequest = path_1.default.join(projectPath, replacedPath); // 更新解析路径 + 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); + return 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; + const requestPath = path_1.default.resolve(path_1.default.dirname(parent.filename), resolvedRequest); + // 处理文件夹导入 + if (fs_1.default.existsSync(requestPath) && fs_1.default.statSync(requestPath).isDirectory()) { + return resolveDirectory(requestPath, parent, options, projectReferences); } + // 处理单文件导入 + return resolveAndCompile(requestPath, parent, options, projectReferences); } - else { - // 如果不是 MODULE_NOT_FOUND 异常,直接抛出 - throw error; - } + throw error; } - // 返回最终解析的文件名 - return filename; }; }; polyfillLoader(); @@ -289,6 +293,45 @@ const watch = (projectPath, config) => { realConfig.polyfill.console.enable && polyfillConsole(enableTrace); // 这里注意要在require之前,因为require会触发编译 const { startup } = require('./start'); + // 如果lib目录是空的,则直接编译所有的ts文件 + const serverConfigFile = path_1.default.join(projectPath, "lib/configuration/server.js"); + if (!fs_1.default.existsSync(serverConfigFile)) { + // 尝试编译src/configuration/server.ts + console.log(`[watch] Server configuration file not found, attempting to compile the project......`); + const tryCompile = (tsFile) => { + console.log(`[watch] Compiling: ${tsFile}`); + if (fs_1.default.existsSync(tsFile)) { + const { program, diagnostics } = createProgramAndSourceFile(tsFile, options, projectReferences); + if (diagnostics.length) { + console.error(`Error in ${tsFile}`); + diagnostics.forEach((diagnostic) => { + console.error(`${typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`); + }); + console.error(`文件存在语法错误,请检查修复后重试!`); + process.exit(1); + } + // const emitResult = program.emit(sourceFile); + // 编译所有的文件 + const emitResult = program.emit(); + if (emitResult.emitSkipped) { + console.error(`Emit failed for ${tsFile}!`); + process.exit(1); + } + console.log(`Emit succeeded. ${tsFile} has been compiled.`); + } + }; + // 所有要编译的目录 + // 其他涉及到的目录会在运行的时候自动编译,这里主要处理的是动态require的文件 + const compileFiles = [ + "src/aspects/index.ts", + "src/checkers/index.ts", + "src/triggers/index.ts", + "src/timers/index.ts", + "src/routines/start.ts", + "src/watchers/index.ts" + ]; + compileFiles.forEach(tryCompile); + } return new Promise((resolve, reject) => { realConfig.lifecycle.onInit(); let shutdown; diff --git a/src/server/watch.ts b/src/server/watch.ts index a6a52b2..8bc0d9a 100644 --- a/src/server/watch.ts +++ b/src/server/watch.ts @@ -178,6 +178,7 @@ const getOverrideConfig = (config?: WatchConfig): RealWatchConfig => { }; type AliasConfig = Record; +type ModuleType = import('module') /** * 根据 alias 配置表将路径中的别名替换为真实路径 @@ -186,32 +187,32 @@ type AliasConfig = Record; * @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); - } - } - } + 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/(.*)' - // If no alias matches, return the original path - return path; + 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 = ( @@ -293,92 +294,105 @@ export const watch = ( // 测试:在src下新建一个ts文件,然后删除lib下的js文件,然后require这个文件,会发现会自动编译ts文件 const polyfillLoader = () => { const BuiltinModule = require("module"); - - // 兼容一些模拟环境下的 module 构造函数 + // 模拟环境下的 module 构造函数 const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule; - // 保存原始的 _resolveFilename 方法 + // 保存原始 _resolveFilename 方法 const oldResolveFilename = Module._resolveFilename; + // 通用的路径检查与编译函数 + function resolveAndCompile(requestPath: string, parent: ModuleType, options: CompilerOptions, projectReferences: readonly ts.ProjectReference[] | undefined) { + const jsPath = pathLib.resolve(requestPath + ".js"); + const tsPath = jsPath.replace(/\.js$/, ".ts").replace( + pathLib.join(projectPath, "lib"), + pathLib.join(projectPath, "src") + ); + + // 检查并编译 .ts 文件 + if (fs.existsSync(tsPath)) { + console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`); + const { program, sourceFile, diagnostics } = createProgramAndSourceFile( + tsPath, + options, + projectReferences + ); + + if (diagnostics.length) { + console.error(`[resolve] Compilation failed for: ${tsPath}`); + throw new Error("TypeScript compilation error"); + } + + const emitResult = program.emit(sourceFile); + + if (emitResult.emitSkipped) { + console.error(`[resolve] Emit skipped for: ${tsPath}`); + throw new Error("TypeScript emit skipped"); + } + + console.log(`[resolve] Successfully compiled: ${tsPath}`); + return jsPath; + } + + // 如果没有找到对应的 .ts 文件 + if (fs.existsSync(jsPath)) { + return jsPath; + } + + throw new Error(`[resolve] Unable to find module: ${requestPath}`); + } + + // 处理文件夹导入的情况 + function resolveDirectory(requestPath: string, parent: ModuleType, options: CompilerOptions, projectReferences: readonly ts.ProjectReference[] | undefined) { + const indexJs = pathLib.join(requestPath, "index.js"); + const indexTs = pathLib.join(requestPath, "index.ts").replace( + pathLib.join(projectPath, "lib"), + pathLib.join(projectPath, "src") + ); + + if (fs.existsSync(indexTs)) { + console.log(`[resolve] Found TypeScript index file: ${indexTs}, attempting to compile...`); + return resolveAndCompile(indexTs, parent, options, projectReferences); + } + + if (fs.existsSync(indexJs)) { + return indexJs; + } + + throw new Error(`[resolve] No index file found in directory: ${requestPath}`); + } + // 重写 _resolveFilename 方法 Module._resolveFilename = function ( request: string, // 模块请求路径 - parent: typeof Module, // 调用方模块 + parent: ModuleType, // 调用方模块 isMain: boolean, // 是否是主模块 rFoptions: object | undefined // 解析选项 ) { - let resolvedRequest = request; // 用于存储替换后的路径 - - // 使用 replaceAliasWithPath 进行 alias 路径替换 + let resolvedRequest = request; const replacedPath = replaceAliasWithPath(request, aliasConfig); - if (replacedPath !== request) { - console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); // 记录替换日志 - resolvedRequest = pathLib.join(projectPath, replacedPath); // 更新解析路径 - } - let filename; // 用于存储最终解析的文件名 + if (replacedPath !== request) { + console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); + resolvedRequest = pathLib.join(projectPath, replacedPath); + } try { - // 调用原始的 _resolveFilename 方法尝试解析文件 - filename = oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions); + return 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"); + const requestPath = pathLib.resolve(pathLib.dirname(parent.filename), resolvedRequest); - // 替换为对应的 .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; + // 处理文件夹导入 + if (fs.existsSync(requestPath) && fs.statSync(requestPath).isDirectory()) { + return resolveDirectory(requestPath, parent, options, projectReferences); } - } else { - // 如果不是 MODULE_NOT_FOUND 异常,直接抛出 - throw error; - } - } - // 返回最终解析的文件名 - return filename; + // 处理单文件导入 + return resolveAndCompile(requestPath, parent, options, projectReferences); + } + + throw error; + } }; }; @@ -436,6 +450,53 @@ export const watch = ( startup: (pwd: string, connector: any) => Promise<() => Promise>; } + // 如果lib目录是空的,则直接编译所有的ts文件 + const serverConfigFile = pathLib.join(projectPath, "lib/configuration/server.js"); + if (!fs.existsSync(serverConfigFile)) { + // 尝试编译src/configuration/server.ts + console.log(`[watch] Server configuration file not found, attempting to compile the project......`); + const tryCompile = (tsFile: string) => { + console.log(`[watch] Compiling: ${tsFile}`); + if (fs.existsSync(tsFile)) { + const { program, diagnostics } = createProgramAndSourceFile(tsFile, options, projectReferences); + if (diagnostics.length) { + console.error(`Error in ${tsFile}`); + diagnostics.forEach((diagnostic) => { + console.error( + `${ts.flattenDiagnosticMessageText( + diagnostic.messageText, + "\n" + )}` + ); + }); + console.error(`文件存在语法错误,请检查修复后重试!`); + process.exit(1); + } + // const emitResult = program.emit(sourceFile); + // 编译所有的文件 + const emitResult = program.emit(); + + if (emitResult.emitSkipped) { + console.error(`Emit failed for ${tsFile}!`); + process.exit(1); + } + + console.log(`Emit succeeded. ${tsFile} has been compiled.`); + } + } + // 所有要编译的目录 + // 其他涉及到的目录会在运行的时候自动编译,这里主要处理的是动态require的文件 + const compileFiles = [ + "src/aspects/index.ts", + "src/checkers/index.ts", + "src/triggers/index.ts", + "src/timers/index.ts", + "src/routines/start.ts", + "src/watchers/index.ts" + ]; + compileFiles.forEach(tryCompile); + } + return new Promise((resolve, reject) => { realConfig.lifecycle.onInit(); let shutdown: (() => Promise) | undefined;