diff --git a/lib/server/watch.js b/lib/server/watch.js index d93019a..8019e88 100644 --- a/lib/server/watch.js +++ b/lib/server/watch.js @@ -7,8 +7,8 @@ const typescript_1 = tslib_1.__importDefault(require("typescript")); const path_1 = tslib_1.__importDefault(require("path")); const dayjs_1 = tslib_1.__importDefault(require("dayjs")); const fs_1 = tslib_1.__importDefault(require("fs")); -const lodash_1 = require("lodash"); const polyfill_1 = require("./polyfill"); +const tsc_alias_1 = require("tsc-alias"); // 创建事件发射器 const createEventEmitter = () => { const listeners = new Map(); @@ -227,12 +227,19 @@ const createServerManager = (projectPath, eventEmitter, 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......"); - // 这里注意要在require之前,因为require会触发编译 - const { startup } = require('./start'); - shutdown = await startup(pwd, simpleConnector).then((shutdown) => { - config.lifecycle.onServerStart(config); - return shutdown; - }); + try { + // 这里注意要在require之前,因为require会触发编译 + const { startup } = require('./start'); + shutdown = await startup(pwd, simpleConnector).then((shutdown) => { + config.lifecycle.onServerStart(config); + return shutdown; + }); + } + catch (error) { + console.error("----> Failed to start service:", error); + isRestarting = false; + return; + } isRestarting = false; await eventEmitter.emit("server-restarted", {}); }; @@ -248,7 +255,7 @@ const createServerManager = (projectPath, eventEmitter, config) => { }; }; // 创建编译器 -const createCompiler = (projectPath, options, projectReferences, aliasConfig, config) => { +const createCompiler = async (projectPath, options, projectReferences, treatFile, config) => { const createProgramAndSourceFile = (path) => { const program = typescript_1.default.createProgram({ rootNames: [path], @@ -375,6 +382,8 @@ const createCompiler = (projectPath, options, projectReferences, aliasConfig, co else { console.log(`Emit succeeded for ${filePath}.`); config.lifecycle.onAfterCompile(config); + const jsFilePath = libPath; + treatFile(jsFilePath); return { taskId: task.id, success: true, @@ -445,40 +454,39 @@ const createFileWatcher = (projectPath, eventEmitter) => { const generateTaskId = () => { return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }; -/** - * 根据 alias 配置表将路径中的别名替换为真实路径 - * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module" - * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组 - * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径 - */ -const 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) => { +// /** +// * 根据 alias 配置表将路径中的别名替换为真实路径 +// * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module" +// * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组 +// * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径 +// */ +// const 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; +// }; +const watch = async (projectPath, config) => { const realConfig = getOverrideConfig(config); const enableTrace = !!process.env.ENABLE_TRACE; // 查找配置文件 @@ -486,20 +494,30 @@ const watch = (projectPath, config) => { 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")); + const runFile = await (0, tsc_alias_1.prepareSingleFileReplaceTscAliasPaths)({ + configFile: path_1.default.join(projectPath, "tsconfig.build.json"), + resolveFullPaths: true, }); + function treatFile(filePath) { + const fileContents = fs_1.default.readFileSync(filePath, 'utf8'); + const newContents = runFile({ fileContents, filePath }); + // do stuff with newContents + fs_1.default.writeFileSync(filePath, newContents, 'utf8'); + } + // // 读取配置文件 + 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: 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: string) => v.replace("src", "lib")); + // }); // 输出真实的alias配置 - console.debug("[DEBUG] Running Alias config:", aliasConfig); + // console.debug("[DEBUG] Running Alias config:", aliasConfig); // 初始化polyfill const polyfillLoader = () => { const BuiltinModule = require("module"); @@ -530,6 +548,9 @@ const watch = (projectPath, config) => { console.error(`[resolve] Emit skipped for: ${tsPath}`); throw new Error("TypeScript emit skipped"); } + else { + treatFile(jsPath); + } console.log(`[resolve] Successfully compiled: ${tsPath}`); return jsPath; } @@ -559,11 +580,11 @@ const watch = (projectPath, config) => { rFoptions // 解析选项 ) { 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); - } + // const replacedPath = replaceAliasWithPath(request, aliasConfig); + // if (replacedPath !== request) { + // console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); + // resolvedRequest = pathLib.join(projectPath, replacedPath); + // } try { return oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions); } @@ -631,14 +652,34 @@ const watch = (projectPath, config) => { "src/ports/index.ts", ]; compileFiles.forEach(tryCompile); + // 最后替换lib目录下所有的路径别名 + console.log(`[watch] Replacing path aliases in lib directory......`); + const libDir = path_1.default.join(projectPath, "lib"); + const walkDir = (dir) => { + const files = fs_1.default.readdirSync(dir); + files.forEach((file) => { + const fullPath = path_1.default.join(dir, file); + const stat = fs_1.default.statSync(fullPath); + if (stat.isDirectory()) { + walkDir(fullPath); + } + else if (stat.isFile() && fullPath.endsWith(".js")) { + const fileContents = fs_1.default.readFileSync(fullPath, 'utf8'); + const newContents = runFile({ fileContents, filePath: fullPath }); + fs_1.default.writeFileSync(fullPath, newContents, 'utf8'); + } + }); + }; + walkDir(libDir); + console.log(`[watch] Path alias replacement completed.`); } }; - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { // 创建事件系统 const eventEmitter = createEventEmitter(); // 创建各个组件 const compileQueue = createCompileQueue(eventEmitter); - const compiler = createCompiler(projectPath, options, projectReferences, aliasConfig, realConfig); + const compiler = await createCompiler(projectPath, options, projectReferences, treatFile, realConfig); const serverManager = createServerManager(projectPath, eventEmitter, realConfig); const fileWatcher = createFileWatcher(projectPath, eventEmitter); // 设置编译器处理器 diff --git a/package.json b/package.json index d435926..4ff5ca3 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ "stylelint-webpack-plugin": "^3.2.0", "tailwindcss": "^3.0.2", "terser-webpack-plugin": "^5.2.5", + "tsc-alias": "^1.8.16", "tslib": "^2.4.0", "ui-extract-webpack-plugin": "^1.0.0", "uuid": "^8.3.2", diff --git a/src/server/watch.ts b/src/server/watch.ts index abad55b..61ad4a0 100644 --- a/src/server/watch.ts +++ b/src/server/watch.ts @@ -3,9 +3,10 @@ import ts, { CompilerOptions, ProjectReference } from "typescript"; import pathLib from "path"; import dayjs from "dayjs"; import fs from "fs"; -import { cloneDeep } from "lodash"; +// import { cloneDeep } from "lodash"; import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore"; import { LogFormatter, LogFormatterProp, polyfillConsole } from "./polyfill"; +import { prepareSingleFileReplaceTscAliasPaths, SingleFileReplacer } from 'tsc-alias'; /* * 工作流程 @@ -397,15 +398,22 @@ const createServerManager = ( console.warn("----> Starting service......"); - // 这里注意要在require之前,因为require会触发编译 - const { startup } = require('./start') as { - startup: (pwd: string, connector: any) => Promise<() => Promise>; - } + try { + // 这里注意要在require之前,因为require会触发编译 + const { startup } = require('./start') as { + startup: (pwd: string, connector: any) => Promise<() => Promise>; + } - shutdown = await startup(pwd, simpleConnector).then((shutdown) => { - config.lifecycle.onServerStart(config); - return shutdown; - }); + shutdown = await startup(pwd, simpleConnector).then((shutdown) => { + config.lifecycle.onServerStart(config); + return shutdown; + }); + + } catch (error) { + console.error("----> Failed to start service:", error); + isRestarting = false; + return; + } isRestarting = false; await eventEmitter.emit("server-restarted", {}); @@ -425,13 +433,14 @@ const createServerManager = ( }; // 创建编译器 -const createCompiler = ( +const createCompiler = async ( projectPath: string, options: CompilerOptions, projectReferences: readonly ProjectReference[] | undefined, - aliasConfig: Record, + treatFile: (filePath: string) => void, config: RealWatchConfig ) => { + const createProgramAndSourceFile = (path: string) => { const program = ts.createProgram({ rootNames: [path], @@ -578,6 +587,8 @@ const createCompiler = ( } else { console.log(`Emit succeeded for ${filePath}.`); config.lifecycle.onAfterCompile(config); + const jsFilePath = libPath; + treatFile(jsFilePath); return { taskId: task.id, success: true, @@ -666,42 +677,42 @@ const generateTaskId = (): string => { return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }; -/** - * 根据 alias 配置表将路径中的别名替换为真实路径 - * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module" - * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组 - * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径 - */ -const 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/(.*)' +// /** +// * 根据 alias 配置表将路径中的别名替换为真实路径 +// * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module" +// * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组 +// * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径 +// */ +// const 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); - } - } - } +// 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; -}; +// // If no alias matches, return the original path +// return path; +// }; -export const watch = ( +export const watch = async ( projectPath: string, config?: WatchConfig ): Promise<() => Promise> => { @@ -719,29 +730,42 @@ export const watch = ( throw new Error("Could not find a valid 'tsconfig.build.json'."); } - // 读取配置文件 + const runFile: SingleFileReplacer = await prepareSingleFileReplaceTscAliasPaths({ + configFile: pathLib.join(projectPath, "tsconfig.build.json"), + resolveFullPaths: true, + }); + + function treatFile(filePath: string) { + console.log(`Processing file for alias replacement: ${filePath}`); + const fileContents = fs.readFileSync(filePath, 'utf8'); + const newContents = runFile({ fileContents, filePath }); + // do stuff with newContents + fs.writeFileSync(filePath, newContents, 'utf8'); + } + + // // 读取配置文件 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) || {}; + // const aliasConfig: AliasConfig = cloneDeep(options.paths) || {}; - // 输出原始配置 - // console.log("[DEBUG] Original alias config:", aliasConfig); + // // 输出原始配置 + // // 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: string) => v.replace("src", "lib")); - }); + // Object.keys(aliasConfig).forEach((key) => { + // const value = aliasConfig[key]; + // // 替换src + // aliasConfig[key] = typeof value === "string" ? value.replace("src", "lib") : value.map((v: string) => v.replace("src", "lib")); + // }); // 输出真实的alias配置 - console.debug("[DEBUG] Running Alias config:", aliasConfig); + // console.debug("[DEBUG] Running Alias config:", aliasConfig); // 初始化polyfill const polyfillLoader = () => { @@ -781,6 +805,8 @@ export const watch = ( if (emitResult.emitSkipped) { console.error(`[resolve] Emit skipped for: ${tsPath}`); throw new Error("TypeScript emit skipped"); + } else { + treatFile(jsPath) } console.log(`[resolve] Successfully compiled: ${tsPath}`); @@ -823,13 +849,12 @@ export const watch = ( rFoptions: object | undefined // 解析选项 ) { let resolvedRequest = request; - const replacedPath = replaceAliasWithPath(request, aliasConfig); - - if (replacedPath !== request) { - console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); - resolvedRequest = pathLib.join(projectPath, replacedPath); - } + // const replacedPath = replaceAliasWithPath(request, aliasConfig); + // if (replacedPath !== request) { + // console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`); + // resolvedRequest = pathLib.join(projectPath, replacedPath); + // } try { return oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions); } catch (error: any) { @@ -908,16 +933,36 @@ export const watch = ( "src/ports/index.ts", ]; compileFiles.forEach(tryCompile); + // 最后替换lib目录下所有的路径别名 + console.log(`[watch] Replacing path aliases in lib directory......`); + const libDir = pathLib.join(projectPath, "lib"); + const walkDir = (dir: string) => { + const files = fs.readdirSync(dir); + files.forEach((file) => { + const fullPath = pathLib.join(dir, file); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + walkDir(fullPath); + } + else if (stat.isFile() && fullPath.endsWith(".js")) { + const fileContents = fs.readFileSync(fullPath, 'utf8'); + const newContents = runFile({ fileContents, filePath: fullPath }); + fs.writeFileSync(fullPath, newContents, 'utf8'); + } + }); + }; + walkDir(libDir); + console.log(`[watch] Path alias replacement completed.`); } }; - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { // 创建事件系统 const eventEmitter = createEventEmitter(); // 创建各个组件 const compileQueue = createCompileQueue(eventEmitter); - const compiler = createCompiler(projectPath, options, projectReferences, aliasConfig, realConfig); + const compiler = await createCompiler(projectPath, options, projectReferences, treatFile, realConfig); const serverManager = createServerManager(projectPath, eventEmitter, realConfig); const fileWatcher = createFileWatcher(projectPath, eventEmitter);