From 61705bf9e14431446b49f5f49fea06a16aebffbf Mon Sep 17 00:00:00 2001
From: pqcqaq <905739777@qq.com>
Date: Mon, 18 Aug 2025 16:21:14 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=85=A8=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BA=86server=20watch=EF=BC=8C?=
=?UTF-8?q?=E9=80=9A=E8=BF=87=E7=BC=96=E8=AF=91=E9=93=BE=E4=B8=8E=E4=BA=8B?=
=?UTF-8?q?=E4=BB=B6=E6=9C=BA=E5=88=B6=E8=A7=A3=E5=86=B3=E5=A4=9A=E6=96=87?=
=?UTF-8?q?=E4=BB=B6=E4=BF=AE=E6=94=B9=E7=9A=84=E6=83=85=E5=86=B5=EF=BC=8C?=
=?UTF-8?q?=E5=B9=B6=E4=B8=94=E6=94=AF=E6=8C=81=E4=BA=86=E5=9C=A8i18n?=
=?UTF-8?q?=E6=96=87=E4=BB=B6=E6=9B=B4=E6=96=B0=E7=9A=84=E6=83=85=E5=86=B5?=
=?UTF-8?q?=E4=B8=8B=EF=BC=8C=E8=87=AA=E5=8A=A8=E5=90=8C=E6=AD=A5=E5=88=B0?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=EF=BC=88=E5=88=A9=E7=94=A8=E4=BA=86?=
=?UTF-8?q?routine=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
lib/build.js | 2 +-
lib/clean.js | 2 +-
lib/create.js | 7 +-
lib/createConfig.js | 3 +-
lib/file-handle.d.ts | 2 -
lib/file-handle.js | 21 +-
lib/makeDependency.js | 2 +-
lib/makeDomain.js | 2 +-
lib/makeLocale.js | 2 +-
lib/makeRouter.js | 2 +-
lib/rename.js | 5 +-
lib/run.js | 2 +-
lib/server/initialize.d.ts | 1 -
lib/server/initialize.js | 3 +-
lib/server/start.d.ts | 1 -
lib/server/start.js | 7 +-
lib/server/watch.d.ts | 39 +-
lib/server/watch.js | 742 ++++++++++++++++++----------
lib/template.js | 23 +-
lib/utils.js | 19 +-
src/server/watch.ts | 965 ++++++++++++++++++++++++-------------
21 files changed, 1194 insertions(+), 658 deletions(-)
diff --git a/lib/build.js b/lib/build.js
index 6fee0ef..2db5ec8 100644
--- a/lib/build.js
+++ b/lib/build.js
@@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = build;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@@ -184,4 +185,3 @@ async function build(cmd) {
(0, tip_style_1.Error)(`${(0, tip_style_1.error)(`target could only be web or mp(wechatMp) or rn(native)`)}`);
}
}
-exports.default = build;
diff --git a/lib/clean.js b/lib/clean.js
index b6b057e..5e0e07f 100644
--- a/lib/clean.js
+++ b/lib/clean.js
@@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = run;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@@ -48,4 +49,3 @@ async function run(options) {
process.exit(-1);
}
}
-exports.default = run;
diff --git a/lib/create.js b/lib/create.js
index 71ea8dd..b30d4c1 100644
--- a/lib/create.js
+++ b/lib/create.js
@@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.update = exports.create = void 0;
+exports.create = create;
+exports.update = update;
const tslib_1 = require("tslib");
const ts = tslib_1.__importStar(require("typescript"));
const fs_1 = require("fs");
@@ -242,7 +243,7 @@ async function create(dirName, cmd) {
}
// 获取package.json内容
const packageJson = (0, template_1.packageJsonContent)({
- name: DEFAULT_PROJECT_NAME,
+ name: DEFAULT_PROJECT_NAME, // 后面再统一rename
version,
description,
cliName: config_1.CLI_NAME,
@@ -265,7 +266,6 @@ async function create(dirName, cmd) {
(0, tip_style_1.Error)((0, tip_style_1.error)(err));
}
}
-exports.create = create;
async function update(dirName, subDirName, cmd) {
const isDev = cmd.dev ? true : false;
try {
@@ -292,4 +292,3 @@ async function update(dirName, subDirName, cmd) {
console.error((0, tip_style_1.error)(err.message));
}
}
-exports.update = update;
diff --git a/lib/createConfig.js b/lib/createConfig.js
index 71233f6..4c8e467 100644
--- a/lib/createConfig.js
+++ b/lib/createConfig.js
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.CreateCompilerConfig = void 0;
+exports.CreateCompilerConfig = CreateCompilerConfig;
/**
* 创建一个oak编译器配置
* @param raw 原始配置
@@ -11,7 +11,6 @@ function CreateCompilerConfig(raw) {
// 在这里可以做配置的预处理
return raw;
}
-exports.CreateCompilerConfig = CreateCompilerConfig;
/**
* 将compiler.js中的模块导出使用以下形式:
* module.exports = CreateComilerConfig({})
diff --git a/lib/file-handle.d.ts b/lib/file-handle.d.ts
index 906763d..8c3c1bf 100644
--- a/lib/file-handle.d.ts
+++ b/lib/file-handle.d.ts
@@ -1,5 +1,3 @@
-///
-///
import { PathLike } from 'fs';
import { checkFileExistsAndCreateType } from './enum';
/**
diff --git a/lib/file-handle.js b/lib/file-handle.js
index 14f0c2f..3e5b805 100644
--- a/lib/file-handle.js
+++ b/lib/file-handle.js
@@ -1,6 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.checkFileExistsAndCreate = exports.checkFileExists = exports.copyFolder = exports.readFile = exports.writeFile = exports.deleteFolderRecursive = exports.parseJsonFile = exports.parseJsonFiles = exports.readDirGetFile = exports.readDirPath = void 0;
+exports.readDirPath = readDirPath;
+exports.readDirGetFile = readDirGetFile;
+exports.parseJsonFiles = parseJsonFiles;
+exports.parseJsonFile = parseJsonFile;
+exports.deleteFolderRecursive = deleteFolderRecursive;
+exports.writeFile = writeFile;
+exports.readFile = readFile;
+exports.copyFolder = copyFolder;
+exports.checkFileExists = checkFileExists;
+exports.checkFileExistsAndCreate = checkFileExistsAndCreate;
const fs_1 = require("fs");
const path_1 = require("path");
const enum_1 = require("./enum");
@@ -25,7 +34,6 @@ function readDirPath(entry) {
}
return pathList;
}
-exports.readDirPath = readDirPath;
/**
* @name 读取指定目录的文件(不进行深度遍历,只获取根目录)
* @export
@@ -36,7 +44,6 @@ function readDirGetFile(entry) {
const dirInfo = (0, fs_1.readdirSync)(entry);
return dirInfo;
}
-exports.readDirGetFile = readDirGetFile;
/**
* @name 解析json文件(数组)
* @export
@@ -51,7 +58,6 @@ function parseJsonFiles(arr) {
}
return result;
}
-exports.parseJsonFiles = parseJsonFiles;
/**
* @name 解析单个文件json
* @export
@@ -67,7 +73,6 @@ function parseJsonFile(file) {
return;
}
}
-exports.parseJsonFile = parseJsonFile;
/**
* @name 删除文件夹
* @export
@@ -97,7 +102,6 @@ function deleteFolderRecursive(entry) {
// console.log("文件夹不存在");
}
}
-exports.deleteFolderRecursive = deleteFolderRecursive;
;
function writeFile(path, data) {
try {
@@ -108,7 +112,6 @@ function writeFile(path, data) {
(0, tip_style_1.Error)((0, tip_style_1.error)('文件写入失败'));
}
}
-exports.writeFile = writeFile;
function readFile(path, options) {
try {
const data = (0, fs_1.readFileSync)(path, options);
@@ -119,7 +122,6 @@ function readFile(path, options) {
(0, tip_style_1.Error)((0, tip_style_1.error)('文件读取失败'));
}
}
-exports.readFile = readFile;
/**
* @name 拷贝文件夹
* @export
@@ -181,7 +183,6 @@ function copyFolder(currentDir, targetDir, overwrite = false) {
throw new global.Error(`需要copy的文件夹不存在: ${currentDir}`);
}
}
-exports.copyFolder = copyFolder;
/**
* @name 检测文件/文件夹是否存在
* @export
@@ -191,7 +192,6 @@ exports.copyFolder = copyFolder;
function checkFileExists(path) {
return (0, fs_1.existsSync)(path);
}
-exports.checkFileExists = checkFileExists;
/**
* @name 检测文件/文件夹是否存在,不存在则创建
* @export
@@ -217,4 +217,3 @@ function checkFileExistsAndCreate(path, data, type = enum_1.checkFileExistsAndCr
throw new global.Error(`${path} already exists!`);
}
}
-exports.checkFileExistsAndCreate = checkFileExistsAndCreate;
diff --git a/lib/makeDependency.js b/lib/makeDependency.js
index 271c321..0812d9a 100644
--- a/lib/makeDependency.js
+++ b/lib/makeDependency.js
@@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@@ -21,4 +22,3 @@ async function make(cmd) {
process.exit(-1);
}
}
-exports.default = make;
diff --git a/lib/makeDomain.js b/lib/makeDomain.js
index c5c89e4..eb140b2 100644
--- a/lib/makeDomain.js
+++ b/lib/makeDomain.js
@@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@@ -20,4 +21,3 @@ async function make() {
process.exit(-1);
}
}
-exports.default = make;
diff --git a/lib/makeLocale.js b/lib/makeLocale.js
index 7da9a71..f27f7fa 100644
--- a/lib/makeLocale.js
+++ b/lib/makeLocale.js
@@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const localeBuilder_1 = tslib_1.__importDefault(require("oak-domain/lib/compiler/localeBuilder"));
@@ -18,4 +19,3 @@ async function make(cmd) {
process.exit(-1);
}
}
-exports.default = make;
diff --git a/lib/makeRouter.js b/lib/makeRouter.js
index 61eda2b..d3a5196 100644
--- a/lib/makeRouter.js
+++ b/lib/makeRouter.js
@@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@@ -30,4 +31,3 @@ async function make(cmd, watch) {
process.exit(-1);
}
}
-exports.default = make;
diff --git a/lib/rename.js b/lib/rename.js
index 62b30e3..71af610 100644
--- a/lib/rename.js
+++ b/lib/rename.js
@@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.rename = exports.renameProject = void 0;
+exports.renameProject = renameProject;
+exports.rename = rename;
const path_1 = require("path");
const fs_1 = require("fs");
const editTemplate_1 = require("@react-native-community/cli/build/commands/init/editTemplate");
@@ -44,7 +45,6 @@ async function renameProject(dir, name, title, placeholderName, placeholderTitle
process.chdir(cwd);
(0, tip_style_1.Success)(`${(0, tip_style_1.success)(`Change project name to ${(0, tip_style_1.primary)(name)}, project title to ${(0, tip_style_1.primary)(title)}`)}`);
}
-exports.renameProject = renameProject;
async function rename(cmd) {
const { oldName, newName, oldTitle, newTitle } = cmd;
if (!oldName) {
@@ -65,4 +65,3 @@ async function rename(cmd) {
}
renameProject(process.cwd(), newName, newTitle, oldName, oldTitle);
}
-exports.rename = rename;
diff --git a/lib/run.js b/lib/run.js
index 4d431e1..bfb8be2 100644
--- a/lib/run.js
+++ b/lib/run.js
@@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = run;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@@ -59,4 +60,3 @@ async function run(options) {
process.exit(-1);
}
}
-exports.default = run;
diff --git a/lib/server/initialize.d.ts b/lib/server/initialize.d.ts
index 964ab37..b13884d 100644
--- a/lib/server/initialize.d.ts
+++ b/lib/server/initialize.d.ts
@@ -1,4 +1,3 @@
-///
import { EntityDict } from 'oak-domain/lib/types';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
diff --git a/lib/server/initialize.js b/lib/server/initialize.js
index 90f692b..acdfa85 100644
--- a/lib/server/initialize.js
+++ b/lib/server/initialize.js
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.initialize = void 0;
+exports.initialize = initialize;
///
const oak_backend_base_1 = require("oak-backend-base");
async function initialize(path) {
@@ -10,4 +10,3 @@ async function initialize(path) {
await appLoader.unmount();
console.log('data initialized');
}
-exports.initialize = initialize;
diff --git a/lib/server/start.d.ts b/lib/server/start.d.ts
index eb29256..ab1c9ef 100644
--- a/lib/server/start.d.ts
+++ b/lib/server/start.d.ts
@@ -1,4 +1,3 @@
-///
import './polyfill';
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
import { Connector, EntityDict } from 'oak-domain/lib/types';
diff --git a/lib/server/start.js b/lib/server/start.js
index 2302949..b55f511 100644
--- a/lib/server/start.js
+++ b/lib/server/start.js
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.startup = void 0;
+exports.startup = startup;
const tslib_1 = require("tslib");
///
require("./polyfill");
@@ -64,7 +64,7 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
}
: serverConfiguration.cors
? {
- origin: serverConfiguration.cors.origin,
+ origin: serverConfiguration.cors.origin, //socket.io配置cors origin是支持数组和字符串
allowedHeaders: [
...corsHeaders.concat(connector.getCorsHeader()),
...(serverConfiguration.cors.headers || []),
@@ -146,7 +146,7 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
if (!ui?.disable) {
(0, admin_ui_1.instrument)(io, {
auth: {
- type: "basic",
+ type: "basic", // 使用基本认证,生产建议关闭或换成自定义 auth
username: ui?.username || "admin",
password: bcryptjs_1.default.hashSync(passwordForAdminUI, 10), // 必须使用 bcrypt 加密之后的密码
},
@@ -368,4 +368,3 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
};
return shutdown;
}
-exports.startup = startup;
diff --git a/lib/server/watch.d.ts b/lib/server/watch.d.ts
index 45b0b46..632ca93 100644
--- a/lib/server/watch.d.ts
+++ b/lib/server/watch.d.ts
@@ -1,4 +1,3 @@
-///
export type LogFormatterProp = {
level: "info" | "warn" | "error";
caller: NodeJS.CallSite | null;
@@ -6,6 +5,7 @@ export type LogFormatterProp = {
};
export type LogFormatter = (props: LogFormatterProp) => any[];
export type WatchConfig = {
+ autoUpdateI18n?: boolean;
/**
* 是否启用polyfill
*/
@@ -36,34 +36,59 @@ export type WatchConfig = {
* 初始化时调用
* @returns void
*/
- onInit?: () => void;
+ onInit?: (config: RealWatchConfig) => void;
/**
* 服务启动时调用
* @returns void
*/
- onServerStart?: () => void;
+ onServerStart?: (config: RealWatchConfig) => void;
/**
* 编译前调用
* @returns void
*/
- onBeforeCompile?: () => void;
+ onBeforeCompile?: (config: RealWatchConfig) => void;
/**
* 编译后调用
* @returns void
*/
- onAfterCompile?: () => void;
+ onAfterCompile?: (config: RealWatchConfig) => void;
/**
* 服务关闭时调用
* @returns void
*/
- onServerShutdown?: () => void;
+ onServerShutdown?: (config: RealWatchConfig) => void;
/**
* 销毁监视器时调用
* @returns void
*/
- onDispose?: () => void;
+ onDispose?: (config: RealWatchConfig) => void;
};
};
+export type FileChangeType = "add" | "remove" | "change";
+export type FileChangeEvent = {
+ path: string;
+ type: FileChangeType;
+ timestamp: number;
+};
+export type CompileTask = {
+ id: string;
+ filePath: string;
+ changeType: FileChangeType;
+ timestamp: number;
+};
+export type CompileResult = {
+ taskId: string;
+ success: boolean;
+ filePath: string;
+ error?: string;
+};
+export type EventType = "file-changed" | "compile-task-added" | "compile-task-completed" | "compile-batch-started" | "compile-batch-completed" | "server-restart-needed" | "server-restarted";
+export type EventHandler = (data: T) => void | Promise;
+export type EventEmitter = {
+ on: (event: EventType, handler: EventHandler) => void;
+ emit: (event: EventType, data: T) => Promise;
+ off: (event: EventType, handler: EventHandler) => void;
+};
type DeepRequiredIfPresent = {
[P in keyof T]-?: NonNullable extends (...args: any) => any ? T[P] : NonNullable extends (infer U)[] ? DeepRequiredIfPresent[] : NonNullable extends object ? DeepRequiredIfPresent> : T[P] extends undefined | null ? T[P] : NonNullable;
};
diff --git a/lib/server/watch.js b/lib/server/watch.js
index f8d9d0a..62883cb 100644
--- a/lib/server/watch.js
+++ b/lib/server/watch.js
@@ -8,7 +8,105 @@ 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 createEventEmitter = () => {
+ const listeners = new Map();
+ return {
+ on: (event, handler) => {
+ if (!listeners.has(event)) {
+ listeners.set(event, new Set());
+ }
+ listeners.get(event).add(handler);
+ },
+ emit: async (event, data) => {
+ const handlers = listeners.get(event);
+ if (handlers) {
+ const promises = Array.from(handlers).map(handler => Promise.resolve(handler(data)));
+ await Promise.all(promises);
+ }
+ },
+ off: (event, handler) => {
+ const handlers = listeners.get(event);
+ if (handlers) {
+ handlers.delete(handler);
+ }
+ }
+ };
+};
+// 创建编译任务队列
+const createCompileQueue = (eventEmitter) => {
+ const queue = [];
+ let isProcessing = false;
+ let processingCount = 0;
+ let taskProcessor = async (task) => ({
+ taskId: task.id,
+ success: true,
+ filePath: task.filePath
+ });
+ const addTask = (task) => {
+ // 检查是否有相同文件的任务已存在,如果有则更新时间戳
+ const existingIndex = queue.findIndex(t => t.filePath === task.filePath);
+ if (existingIndex !== -1) {
+ queue[existingIndex] = { ...queue[existingIndex], ...task };
+ }
+ else {
+ queue.push(task);
+ }
+ eventEmitter.emit("compile-task-added", task);
+ processQueue();
+ };
+ const processQueue = async () => {
+ if (isProcessing || queue.length === 0) {
+ return;
+ }
+ isProcessing = true;
+ processingCount = queue.length;
+ await eventEmitter.emit("compile-batch-started", { count: processingCount });
+ const tasksToProcess = [...queue];
+ queue.length = 0; // 清空队列
+ const results = [];
+ for (const task of tasksToProcess) {
+ try {
+ const result = await taskProcessor(task);
+ results.push(result);
+ await eventEmitter.emit("compile-task-completed", result);
+ }
+ catch (error) {
+ const result = {
+ taskId: task.id,
+ success: false,
+ filePath: task.filePath,
+ error: error instanceof Error ? error.message : String(error)
+ };
+ results.push(result);
+ await eventEmitter.emit("compile-task-completed", result);
+ }
+ }
+ isProcessing = false;
+ processingCount = 0;
+ await eventEmitter.emit("compile-batch-completed", { results });
+ // 检查是否需要重启服务器
+ const hasSuccessfulCompiles = results.some(r => r.success);
+ if (hasSuccessfulCompiles) {
+ await eventEmitter.emit("server-restart-needed", { results });
+ }
+ // 如果在处理过程中又有新任务加入,继续处理
+ if (queue.length > 0) {
+ processQueue();
+ }
+ };
+ return {
+ addTask,
+ getQueueLength: () => queue.length,
+ isProcessing: () => isProcessing,
+ getProcessingCount: () => processingCount,
+ setTaskProcessor: (processor) => {
+ taskProcessor = processor;
+ }
+ };
+};
const defaultConfig = {
+ autoUpdateI18n: true,
polyfill: {
console: {
enable: true,
@@ -44,22 +142,22 @@ const defaultConfig = {
},
},
lifecycle: {
- onInit: () => {
+ onInit: (config) => {
console.log("----> Watcher initialized.");
},
- onServerStart: () => {
+ onServerStart: (config) => {
console.log("----> Server started.");
},
- onBeforeCompile: () => {
+ onBeforeCompile: (config) => {
console.log("----> Compiling......");
},
- onAfterCompile: () => {
+ onAfterCompile: (config) => {
console.log("----> Compile completed.");
},
- onServerShutdown: () => {
+ onServerShutdown: (config) => {
console.log("----> Server shutdown.");
},
- onDispose: () => {
+ onDispose: (config) => {
console.log("----> Watcher disposed.");
},
},
@@ -94,13 +192,265 @@ const getOverrideConfig = (config) => {
? overrideInner(config, defaultConfig)
: defaultConfig;
};
+// 当前运行目录
+const pwd = process.cwd();
+// 创建服务器管理器
+const createServerManager = (projectPath, eventEmitter, config) => {
+ let shutdown;
+ let isRestarting = false;
+ const restart = async () => {
+ if (isRestarting) {
+ return;
+ }
+ isRestarting = true;
+ if (shutdown) {
+ console.log("----> Shutting down service......");
+ await shutdown().then(() => config.lifecycle.onServerShutdown(config));
+ shutdown = undefined;
+ }
+ 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.`);
+ // 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;
+ });
+ isRestarting = false;
+ await eventEmitter.emit("server-restarted", {});
+ };
+ const dispose = async () => {
+ if (shutdown) {
+ await shutdown();
+ }
+ };
+ return {
+ restart,
+ dispose,
+ isRestarting: () => isRestarting
+ };
+};
+// 创建编译器
+const createCompiler = (projectPath, options, projectReferences, aliasConfig, config) => {
+ const createProgramAndSourceFile = (path) => {
+ 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 };
+ };
+ const compileTask = async (task) => {
+ const { filePath, changeType } = task;
+ // 判断文件类型
+ if (!filePath.endsWith(".ts")) {
+ // 处理非TypeScript文件 (如JSON文件)
+ if (filePath.endsWith(".json")) {
+ const targetPath = filePath.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"));
+ try {
+ if (changeType === "remove") {
+ if (fs_1.default.existsSync(targetPath)) {
+ fs_1.default.unlinkSync(targetPath);
+ }
+ console.warn(`File ${targetPath} has been removed.`);
+ }
+ else if (changeType === "add" || changeType === "change") {
+ // 确保目录存在
+ const dir = path_1.default.dirname(targetPath);
+ if (!fs_1.default.existsSync(dir)) {
+ fs_1.default.mkdirSync(dir, { recursive: true });
+ }
+ if (changeType === "change" && fs_1.default.existsSync(targetPath)) {
+ fs_1.default.unlinkSync(targetPath);
+ }
+ fs_1.default.copyFileSync(filePath, targetPath, fs_1.default.constants.COPYFILE_FICLONE);
+ console.warn(`File ${filePath} has been copied to ${targetPath}.`);
+ }
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ }
+ catch (error) {
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: error instanceof Error ? error.message : String(error)
+ };
+ }
+ }
+ else {
+ console.warn(`File ${filePath} is not [ts,json] file, skipped.`);
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ }
+ }
+ // 处理TypeScript文件
+ console.clear();
+ console.warn(`File ${filePath} has been ${changeType}d`);
+ const modulePath = path_1.default.resolve(filePath);
+ const libPath = modulePath
+ .replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"))
+ .replace(/\.ts$/, ".js");
+ if (changeType === "remove") {
+ try {
+ if (fs_1.default.existsSync(libPath)) {
+ fs_1.default.unlinkSync(libPath);
+ }
+ console.warn(`File ${libPath} has been removed.`);
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ }
+ catch (error) {
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: `Error removing file: ${error instanceof Error ? error.message : String(error)}`
+ };
+ }
+ }
+ // 编译TypeScript文件
+ const { program, sourceFile, diagnostics } = createProgramAndSourceFile(filePath);
+ if (diagnostics.length) {
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: "TypeScript compilation error"
+ };
+ }
+ // 只输出单个文件
+ config.lifecycle.onBeforeCompile(config);
+ const emitResult = program.emit(sourceFile);
+ if (emitResult.emitSkipped) {
+ console.error(`Emit failed for ${filePath}!`);
+ config.lifecycle.onAfterCompile(config);
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: "TypeScript emit failed"
+ };
+ }
+ else {
+ console.log(`Emit succeeded for ${filePath}.`);
+ config.lifecycle.onAfterCompile(config);
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ }
+ };
+ return {
+ compileTask
+ };
+};
+// 创建文件监视器
+const createFileWatcher = (projectPath, eventEmitter) => {
+ const watchSourcePath = path_1.default.join(projectPath, "src");
+ let startWatching = false;
+ console.log("Watching for changes in", watchSourcePath);
+ 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,
+ });
+ const handleFileChange = async (path, type) => {
+ if (!startWatching) {
+ return;
+ }
+ const event = {
+ path,
+ type,
+ timestamp: Date.now()
+ };
+ await eventEmitter.emit("file-changed", event);
+ };
+ watcher.on("ready", () => {
+ console.warn("Initial scan complete. Ready for changes");
+ startWatching = true;
+ });
+ watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
+ watcher
+ .on("add", async (path) => {
+ await handleFileChange(path, "add");
+ })
+ .on("change", async (path) => {
+ await handleFileChange(path, "change");
+ })
+ .on("unlink", async (path) => {
+ await handleFileChange(path, "remove");
+ });
+ const dispose = async () => {
+ await watcher.close();
+ };
+ return {
+ dispose
+ };
+};
+// 生成唯一任务ID
+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,则返回原始路径
*/
-function replaceAliasWithPath(path, aliasConfig) {
+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('*')) {
@@ -126,7 +476,7 @@ function replaceAliasWithPath(path, aliasConfig) {
}
// If no alias matches, return the original path
return path;
-}
+};
const watch = (projectPath, config) => {
const realConfig = getOverrideConfig(config);
const enableTrace = !!process.env.ENABLE_TRACE;
@@ -149,30 +499,7 @@ const watch = (projectPath, config) => {
});
// 输出真实的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文件
+ // 初始化polyfill
const polyfillLoader = () => {
const BuiltinModule = require("module");
// 模拟环境下的 module 构造函数
@@ -186,7 +513,13 @@ const watch = (projectPath, config) => {
// 检查并编译 .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);
+ const program = typescript_1.default.createProgram({
+ rootNames: [tsPath],
+ options,
+ projectReferences,
+ });
+ const sourceFile = program.getSourceFile(tsPath);
+ const diagnostics = typescript_1.default.getPreEmitDiagnostics(program, sourceFile);
if (diagnostics.length) {
console.error(`[resolve] Compilation failed for: ${tsPath}`);
throw new Error("TypeScript compilation error");
@@ -247,8 +580,6 @@ const watch = (projectPath, config) => {
}
};
};
- polyfillLoader();
- //polyfill console.log 添加时间
const polyfillConsole = (trace) => {
// 获取调用堆栈信息
const getCallerInfo = () => {
@@ -282,7 +613,7 @@ const watch = (projectPath, config) => {
const levelStr = level;
const oldFunc = console[levelStr];
console[levelStr] = function (...args) {
- oldFunc(...(defaultConfig.polyfill?.console?.formatter({
+ oldFunc(...(realConfig.polyfill?.console?.formatter({
level: levelStr,
caller: getCallerInfo(),
args,
@@ -290,247 +621,122 @@ const watch = (projectPath, config) => {
};
});
};
+ // 初始化polyfill
+ polyfillLoader();
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")}`);
+ // 初始编译检查
+ const initialCompile = () => {
+ 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 = typescript_1.default.createProgram({
+ rootNames: [tsFile],
+ options,
+ projectReferences,
});
- console.error(`文件存在语法错误,请检查修复后重试!`);
- process.exit(1);
+ const diagnostics = typescript_1.default.getPreEmitDiagnostics(program);
+ 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();
+ if (emitResult.emitSkipped) {
+ console.error(`Emit failed for ${tsFile}!`);
+ process.exit(1);
+ }
+ console.log(`Emit succeeded. ${tsFile} has been compiled.`);
}
- // 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/configuration/index.ts",
- "src/aspects/index.ts",
- "src/checkers/index.ts",
- "src/triggers/index.ts",
- "src/timers/index.ts",
- "src/routines/start.ts",
- "src/watchers/index.ts",
- "src/endpoints/index.ts",
- "src/data/index.ts",
- "src/ports/index.ts",
- ];
- compileFiles.forEach(tryCompile);
- }
+ };
+ // 所有要编译的目录
+ // 其他涉及到的目录会在运行的时候自动编译,这里主要处理的是动态require的文件
+ const compileFiles = [
+ "src/configuration/index.ts",
+ "src/aspects/index.ts",
+ "src/checkers/index.ts",
+ "src/triggers/index.ts",
+ "src/timers/index.ts",
+ "src/routines/start.ts",
+ "src/watchers/index.ts",
+ "src/endpoints/index.ts",
+ "src/data/index.ts",
+ "src/ports/index.ts",
+ ];
+ compileFiles.forEach(tryCompile);
+ }
+ };
return new Promise((resolve, reject) => {
- realConfig.lifecycle.onInit();
- let shutdown;
- const restart = async () => {
- if (shutdown) {
- console.log("----> Shutting down service......");
- await shutdown().then(realConfig.lifecycle.onServerShutdown);
- // reset shutdown
- shutdown = undefined;
- }
- 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++;
+ // 创建事件系统
+ const eventEmitter = createEventEmitter();
+ // 创建各个组件
+ const compileQueue = createCompileQueue(eventEmitter);
+ const compiler = createCompiler(projectPath, options, projectReferences, aliasConfig, realConfig);
+ const serverManager = createServerManager(projectPath, eventEmitter, realConfig);
+ const fileWatcher = createFileWatcher(projectPath, eventEmitter);
+ // 设置编译器处理器
+ compileQueue.setTaskProcessor(compiler.compileTask);
+ // 设置事件监听器
+ eventEmitter.on("file-changed", (event) => {
+ const task = {
+ id: generateTaskId(),
+ filePath: event.path,
+ changeType: event.type,
+ timestamp: event.timestamp
+ };
+ compileQueue.addTask(task);
+ });
+ eventEmitter.on("compile-batch-started", (data) => {
+ console.log(`----> Starting compilation batch (${data.count} files)...`);
+ });
+ eventEmitter.on("compile-batch-completed", (data) => {
+ const successCount = data.results.filter(r => r.success).length;
+ console.log(`----> Compilation batch completed: ${successCount}/${data.results.length} successful`);
+ });
+ // 编译成功之后,若设置的同步i18n,则触发i18n更新
+ if (realConfig.autoUpdateI18n) {
+ const projectI18nPath = path_1.default.join("src", "data", "i18n.ts");
+ eventEmitter.on("compile-task-completed", async (result) => {
+ if (result.filePath == projectI18nPath && result.success) {
+ console.log("-------------start upgrade i18n.-------------");
+ // 这里是copy:upgradeI18n的
+ const { checkAndUpdateI18n } = require('oak-backend-base/lib/routines/i18n.js');
+ const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default;
+ // 这里注意要在require之前,因为require会触发编译
+ const { startup } = require('./start');
+ startup(pwd, simpleConnector, true, true, checkAndUpdateI18n).then(() => {
+ console.log("------------upgrade i18n success.------------");
+ }).catch((err) => {
+ console.error("------------upgrade i18n failed!------------", err);
+ });
}
});
- 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(path_1.default.join(projectPath, "lib/config/connector")).default;
- console.warn("----> Starting service......");
- 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 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 processingQueue = [];
- const fileChangeHandler = async (path, type) => {
- // 判断一下是不是以下扩展名:ts
- if (!path.endsWith(".ts")) {
- // 如果是json文件,复制或者删除
- if (path.endsWith(".json")) {
- // 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.`);
- }
- 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 = path_1.default.resolve(path);
- // 将src替换为lib
- const libPath = modulePath
- .replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "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;
- // 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件
- }
- else {
- // 如果是删除,则需要发出警告,文件正在被进程使用
- if (type === "remove") {
- console.error(`File ${libPath} is being used, skiped.`);
- return;
- }
- }
- const { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences);
- if (diagnostics.length) {
- return;
- }
- // 只输出单个文件
- realConfig.lifecycle.onBeforeCompile();
- const emitResult = program.emit(sourceFile);
- // 是否成功
- const result = emitResult.emitSkipped;
- if (result) {
- console.error(`Emit failed for ${path}!`);
- realConfig.lifecycle.onAfterCompile();
- }
- else {
- console.log(`Emit succeeded. ${compileOnly ? "" : "reload service......"}`);
- realConfig.lifecycle.onAfterCompile();
- if (compileOnly) {
- return;
- }
- // await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务
- if (processingQueue.length === 1) {
- await restart();
- }
- else {
- console.log("Waiting for other operations to complete...");
- }
- }
- };
- const onChangeDebounced = async (path, type) => {
- if (processingQueue.includes(path)) {
- console.log("Processing, please wait...");
- return;
- }
- try {
- processingQueue.push(path);
- await fileChangeHandler(path, type);
- }
- catch (e) {
- console.clear();
- console.error(e);
- process.exit(1);
- }
- finally {
- processingQueue = processingQueue.filter((p) => p !== path);
- }
- };
- watcher
- .on("add", async (path) => {
- if (startWatching) {
- await onChangeDebounced(path, "add");
- }
- })
- .on("change", async (path) => {
- if (startWatching) {
- await onChangeDebounced(path, "change");
- }
- })
- .on("unlink", async (path) => {
- if (startWatching) {
- await onChangeDebounced(path, "remove");
+ }
+ eventEmitter.on("server-restart-needed", async () => {
+ if (!serverManager.isRestarting()) {
+ console.log("----> Restarting server...");
+ await serverManager.restart();
}
});
- const dispose = async () => {
- if (shutdown) {
- await shutdown();
- }
- await watcher.close();
- realConfig.lifecycle.onDispose();
- };
- restart()
+ // 初始化
+ realConfig.lifecycle.onInit(realConfig);
+ // 执行初始编译
+ initialCompile();
+ // 启动服务器
+ serverManager.restart()
.then(() => {
+ const dispose = async () => {
+ await fileWatcher.dispose();
+ await serverManager.dispose();
+ realConfig.lifecycle.onDispose(realConfig);
+ };
resolve(dispose);
})
.catch(reject);
diff --git a/lib/template.js b/lib/template.js
index 23a61b4..e1e16cc 100644
--- a/lib/template.js
+++ b/lib/template.js
@@ -1,6 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.updateCompilerJsContent = exports.oakConfigContentWithWeb = exports.oakConfigContentWithWeChatMp = exports.appJsonContentWithWeChatMp = exports.projectConfigContentWithWeChatMp = exports.tsConfigWebJsonContent = exports.tsConfigMpJsonContent = exports.tsConfigPathsJsonContent = exports.tsConfigBuildJsonContent = exports.tsConfigJsonContent = exports.packageJsonContent = void 0;
+exports.packageJsonContent = packageJsonContent;
+exports.tsConfigJsonContent = tsConfigJsonContent;
+exports.tsConfigBuildJsonContent = tsConfigBuildJsonContent;
+exports.tsConfigPathsJsonContent = tsConfigPathsJsonContent;
+exports.tsConfigMpJsonContent = tsConfigMpJsonContent;
+exports.tsConfigWebJsonContent = tsConfigWebJsonContent;
+exports.projectConfigContentWithWeChatMp = projectConfigContentWithWeChatMp;
+exports.appJsonContentWithWeChatMp = appJsonContentWithWeChatMp;
+exports.oakConfigContentWithWeChatMp = oakConfigContentWithWeChatMp;
+exports.oakConfigContentWithWeb = oakConfigContentWithWeb;
+exports.updateCompilerJsContent = updateCompilerJsContent;
const tslib_1 = require("tslib");
const child_process_1 = require("child_process");
const fs_1 = require("fs");
@@ -300,7 +310,6 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
}
`;
}
-exports.packageJsonContent = packageJsonContent;
function tsConfigJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
@@ -352,7 +361,6 @@ function tsConfigJsonContent() {
]
}`;
}
-exports.tsConfigJsonContent = tsConfigJsonContent;
function tsConfigBuildJsonContent() {
return `{
"extends": "./tsconfig.build.paths.json",
@@ -392,7 +400,6 @@ function tsConfigBuildJsonContent() {
]
}`;
}
-exports.tsConfigBuildJsonContent = tsConfigBuildJsonContent;
function tsConfigPathsJsonContent(deps) {
const paths = {
"@project/*": [
@@ -421,7 +428,6 @@ function tsConfigPathsJsonContent(deps) {
}
}, null, '\t');
}
-exports.tsConfigPathsJsonContent = tsConfigPathsJsonContent;
function tsConfigMpJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
@@ -469,7 +475,6 @@ function tsConfigMpJsonContent() {
]
}`;
}
-exports.tsConfigMpJsonContent = tsConfigMpJsonContent;
function tsConfigWebJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
@@ -519,7 +524,6 @@ function tsConfigWebJsonContent() {
]
}`;
}
-exports.tsConfigWebJsonContent = tsConfigWebJsonContent;
function projectConfigContentWithWeChatMp(oakConfigName, projectname, miniVersion) {
return `{
"description": "项目配置文件",
@@ -595,7 +599,6 @@ function projectConfigContentWithWeChatMp(oakConfigName, projectname, miniVersio
}
}`;
}
-exports.projectConfigContentWithWeChatMp = projectConfigContentWithWeChatMp;
function appJsonContentWithWeChatMp(isDev) {
const pages = [
'@project/pages/store/list/index',
@@ -619,21 +622,18 @@ function appJsonContentWithWeChatMp(isDev) {
"sitemapLocation": "sitemap.json"
}`;
}
-exports.appJsonContentWithWeChatMp = appJsonContentWithWeChatMp;
function oakConfigContentWithWeChatMp() {
return `{
"theme": {
}
}`;
}
-exports.oakConfigContentWithWeChatMp = oakConfigContentWithWeChatMp;
function oakConfigContentWithWeb() {
return `{
"theme": {
}
}`;
}
-exports.oakConfigContentWithWeb = oakConfigContentWithWeb;
function updateCompilerJsContent(directory, deps) {
const compilerJsPath = (0, path_1.join)(directory, 'configuration', 'compiler.js');
(0, assert_1.default)((0, fs_1.existsSync)(compilerJsPath));
@@ -692,4 +692,3 @@ function updateCompilerJsContent(directory, deps) {
(0, fs_1.writeFileSync)(compilerJsPath, code);
}
}
-exports.updateCompilerJsContent = updateCompilerJsContent;
diff --git a/lib/utils.js b/lib/utils.js
index 389c73a..3694c23 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,6 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.checkNodeVersion = exports.randomString = exports.deWeight = exports.formatJsonByFile = exports.union = exports.intersect = exports.difference = exports.getStr = exports.findJson = void 0;
+exports.findJson = findJson;
+exports.getStr = getStr;
+exports.difference = difference;
+exports.intersect = intersect;
+exports.union = union;
+exports.formatJsonByFile = formatJsonByFile;
+exports.deWeight = deWeight;
+exports.randomString = randomString;
+exports.checkNodeVersion = checkNodeVersion;
const tslib_1 = require("tslib");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
/**
@@ -20,7 +28,6 @@ function findJson(pathArr) {
}
return result;
}
-exports.findJson = findJson;
/**
* @name 已知前后文取中间文本
* @export
@@ -34,7 +41,6 @@ function getStr(str, start, end) {
let res = str.match(reg);
return res ? res[1] : null;
}
-exports.getStr = getStr;
/**
* @name 差集
* @export
@@ -46,7 +52,6 @@ exports.getStr = getStr;
function difference(current, target) {
return new Set([...target].filter(x => !current.has(x)));
}
-exports.difference = difference;
/**
* @name 获取交集
* @export
@@ -58,7 +63,6 @@ exports.difference = difference;
function intersect(current, target) {
return new Set([...target].filter(x => current.has(x)));
}
-exports.intersect = intersect;
/**
* @name 获取并集
* @export
@@ -70,7 +74,6 @@ exports.intersect = intersect;
function union(current, target) {
return new Set([...current, ...target]);
}
-exports.union = union;
/**
* @name 格式化json
* @export
@@ -81,7 +84,6 @@ exports.union = union;
function formatJsonByFile(data) {
return JSON.stringify(data, null, 2);
}
-exports.formatJsonByFile = formatJsonByFile;
/**
* @name 数组对象去重
* @export
@@ -98,7 +100,6 @@ function deWeight(arr, type) {
}
return new Set([...map.values()]);
}
-exports.deWeight = deWeight;
/**
* @name 随机字符串
* @export
@@ -114,7 +115,6 @@ function randomString(length) {
}
return result;
}
-exports.randomString = randomString;
/**
* @name 检查当前nodejs运行时版本
* @export
@@ -135,4 +135,3 @@ function checkNodeVersion() {
process.exit(-1);
}
}
-exports.checkNodeVersion = checkNodeVersion;
diff --git a/src/server/watch.ts b/src/server/watch.ts
index bec0a12..964058a 100644
--- a/src/server/watch.ts
+++ b/src/server/watch.ts
@@ -4,6 +4,16 @@ import pathLib from "path";
import dayjs from "dayjs";
import fs from "fs";
import { cloneDeep } from "lodash";
+import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
+
+/*
+ * 工作流程
+ 文件变更检测 → 生成文件变更事件
+ 事件处理 → 创建编译任务并加入队列
+ 批量编译 → 处理队列中的所有任务
+ 编译完成 → 触发服务器重启事件
+ 服务器重启 → 清理缓存并重新启动服务
+ */
declare const require: NodeRequire;
declare const process: NodeJS.Process;
@@ -17,6 +27,7 @@ export type LogFormatterProp = {
export type LogFormatter = (props: LogFormatterProp) => any[];
export type WatchConfig = {
+ autoUpdateI18n?: boolean;
/**
* 是否启用polyfill
*/
@@ -47,32 +58,109 @@ export type WatchConfig = {
* 初始化时调用
* @returns void
*/
- onInit?: () => void;
+ onInit?: (config: RealWatchConfig) => void;
/**
* 服务启动时调用
* @returns void
*/
- onServerStart?: () => void;
+ onServerStart?: (config: RealWatchConfig) => void;
/**
* 编译前调用
* @returns void
*/
- onBeforeCompile?: () => void;
+ onBeforeCompile?: (config: RealWatchConfig) => void;
/**
* 编译后调用
* @returns void
*/
- onAfterCompile?: () => void;
+ onAfterCompile?: (config: RealWatchConfig) => void;
/**
* 服务关闭时调用
* @returns void
*/
- onServerShutdown?: () => void;
+ onServerShutdown?: (config: RealWatchConfig) => void;
/**
* 销毁监视器时调用
* @returns void
*/
- onDispose?: () => void;
+ onDispose?: (config: RealWatchConfig) => void;
+ };
+};
+
+// 文件变更类型
+export type FileChangeType = "add" | "remove" | "change";
+
+// 文件变更事件
+export type FileChangeEvent = {
+ path: string;
+ type: FileChangeType;
+ timestamp: number;
+};
+
+// 编译任务
+export type CompileTask = {
+ id: string;
+ filePath: string;
+ changeType: FileChangeType;
+ timestamp: number;
+};
+
+// 编译结果
+export type CompileResult = {
+ taskId: string;
+ success: boolean;
+ filePath: string;
+ error?: string;
+};
+
+// 事件类型
+export type EventType =
+ | "file-changed"
+ | "compile-task-added"
+ | "compile-task-completed"
+ | "compile-batch-started"
+ | "compile-batch-completed"
+ | "server-restart-needed"
+ | "server-restarted";
+
+// 事件处理器
+export type EventHandler = (data: T) => void | Promise;
+
+// 简单的事件系统
+export type EventEmitter = {
+ on: (event: EventType, handler: EventHandler) => void;
+ emit: (event: EventType, data: T) => Promise;
+ off: (event: EventType, handler: EventHandler) => void;
+};
+
+// 创建事件发射器
+const createEventEmitter = (): EventEmitter => {
+ const listeners = new Map>();
+
+ return {
+ on: (event: EventType, handler: EventHandler) => {
+ if (!listeners.has(event)) {
+ listeners.set(event, new Set());
+ }
+ listeners.get(event)!.add(handler);
+ },
+
+ emit: async (event: EventType, data: T) => {
+ const handlers = listeners.get(event);
+ if (handlers) {
+ const promises = Array.from(handlers).map(handler =>
+ Promise.resolve(handler(data))
+ );
+ await Promise.all(promises);
+ }
+ },
+
+ off: (event: EventType, handler: EventHandler) => {
+ const handlers = listeners.get(event);
+ if (handlers) {
+ handlers.delete(handler);
+ }
+ }
};
};
@@ -89,14 +177,99 @@ type DeepRequiredIfPresent = {
: NonNullable;
};
+// 创建编译任务队列
+const createCompileQueue = (eventEmitter: EventEmitter) => {
+ const queue: CompileTask[] = [];
+ let isProcessing = false;
+ let processingCount = 0;
+ let taskProcessor: (task: CompileTask) => Promise = async (task) => ({
+ taskId: task.id,
+ success: true,
+ filePath: task.filePath
+ });
+
+ const addTask = (task: CompileTask) => {
+ // 检查是否有相同文件的任务已存在,如果有则更新时间戳
+ const existingIndex = queue.findIndex(t => t.filePath === task.filePath);
+ if (existingIndex !== -1) {
+ queue[existingIndex] = { ...queue[existingIndex], ...task };
+ } else {
+ queue.push(task);
+ }
+ eventEmitter.emit("compile-task-added", task);
+ processQueue();
+ };
+
+ const processQueue = async () => {
+ if (isProcessing || queue.length === 0) {
+ return;
+ }
+
+ isProcessing = true;
+ processingCount = queue.length;
+ await eventEmitter.emit("compile-batch-started", { count: processingCount });
+
+ const tasksToProcess = [...queue];
+ queue.length = 0; // 清空队列
+
+ const results: CompileResult[] = [];
+
+ for (const task of tasksToProcess) {
+ try {
+ const result = await taskProcessor(task);
+ results.push(result);
+ await eventEmitter.emit("compile-task-completed", result);
+ } catch (error) {
+ const result: CompileResult = {
+ taskId: task.id,
+ success: false,
+ filePath: task.filePath,
+ error: error instanceof Error ? error.message : String(error)
+ };
+ results.push(result);
+ await eventEmitter.emit("compile-task-completed", result);
+ }
+ }
+
+ isProcessing = false;
+ processingCount = 0;
+ await eventEmitter.emit("compile-batch-completed", { results });
+
+ // 检查是否需要重启服务器
+ const hasSuccessfulCompiles = results.some(r => r.success);
+ if (hasSuccessfulCompiles) {
+ await eventEmitter.emit("server-restart-needed", { results });
+ }
+
+ // 如果在处理过程中又有新任务加入,继续处理
+ if (queue.length > 0) {
+ processQueue();
+ }
+ };
+
+ return {
+ addTask,
+ getQueueLength: () => queue.length,
+ isProcessing: () => isProcessing,
+ getProcessingCount: () => processingCount,
+ setTaskProcessor: (processor: (task: CompileTask) => Promise) => {
+ taskProcessor = processor;
+ }
+ };
+};
+
export type RealWatchConfig = DeepRequiredIfPresent;
+type AliasConfig = Record;
+type ModuleType = import('module');
+
const defaultConfig: RealWatchConfig = {
+ autoUpdateI18n: true,
polyfill: {
console: {
enable: true,
trace: true,
- formatter: ({ level, caller, args }) => {
+ formatter: ({ level, caller, args }: LogFormatterProp) => {
const getFileInfo = () => {
if (!caller) {
return "";
@@ -128,22 +301,22 @@ const defaultConfig: RealWatchConfig = {
},
},
lifecycle: {
- onInit: () => {
+ onInit: (config: RealWatchConfig) => {
console.log("----> Watcher initialized.");
},
- onServerStart: () => {
+ onServerStart: (config: RealWatchConfig) => {
console.log("----> Server started.");
},
- onBeforeCompile: () => {
+ onBeforeCompile: (config: RealWatchConfig) => {
console.log("----> Compiling......");
},
- onAfterCompile: () => {
+ onAfterCompile: (config: RealWatchConfig) => {
console.log("----> Compile completed.");
},
- onServerShutdown: () => {
+ onServerShutdown: (config: RealWatchConfig) => {
console.log("----> Server shutdown.");
},
- onDispose: () => {
+ onDispose: (config: RealWatchConfig) => {
console.log("----> Watcher disposed.");
},
},
@@ -177,8 +350,327 @@ const getOverrideConfig = (config?: WatchConfig): RealWatchConfig => {
: defaultConfig;
};
-type AliasConfig = Record;
-type ModuleType = import('module')
+// 当前运行目录
+const pwd = process.cwd();
+
+// 创建服务器管理器
+const createServerManager = (
+ projectPath: string,
+ eventEmitter: EventEmitter,
+ config: RealWatchConfig
+) => {
+ let shutdown: (() => Promise) | undefined;
+ let isRestarting = false;
+
+ const restart = async () => {
+ if (isRestarting) {
+ return;
+ }
+
+ isRestarting = true;
+
+ if (shutdown) {
+ console.log("----> Shutting down service......");
+ await shutdown().then(() => config.lifecycle.onServerShutdown(config));
+ shutdown = undefined;
+ }
+
+ 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.`
+ );
+
+
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const simpleConnector = require(pathLib.join(
+ projectPath,
+ "lib/config/connector"
+ )).default;
+
+ console.warn("----> Starting service......");
+
+ // 这里注意要在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;
+ });
+
+ isRestarting = false;
+ await eventEmitter.emit("server-restarted", {});
+ };
+
+ const dispose = async () => {
+ if (shutdown) {
+ await shutdown();
+ }
+ };
+
+ return {
+ restart,
+ dispose,
+ isRestarting: () => isRestarting
+ };
+};
+
+// 创建编译器
+const createCompiler = (
+ projectPath: string,
+ options: CompilerOptions,
+ projectReferences: readonly ProjectReference[] | undefined,
+ aliasConfig: Record,
+ config: RealWatchConfig
+) => {
+ const createProgramAndSourceFile = (path: string) => {
+ 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 };
+ };
+
+ const compileTask = async (task: CompileTask): Promise => {
+ const { filePath, changeType } = task;
+
+ // 判断文件类型
+ if (!filePath.endsWith(".ts")) {
+ // 处理非TypeScript文件 (如JSON文件)
+ if (filePath.endsWith(".json")) {
+ const targetPath = filePath.replace(
+ pathLib.join(projectPath, "src"),
+ pathLib.join(projectPath, "lib")
+ );
+
+ try {
+ if (changeType === "remove") {
+ if (fs.existsSync(targetPath)) {
+ fs.unlinkSync(targetPath);
+ }
+ console.warn(`File ${targetPath} has been removed.`);
+ } else if (changeType === "add" || changeType === "change") {
+ // 确保目录存在
+ const dir = pathLib.dirname(targetPath);
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+
+ if (changeType === "change" && fs.existsSync(targetPath)) {
+ fs.unlinkSync(targetPath);
+ }
+ fs.copyFileSync(filePath, targetPath, fs.constants.COPYFILE_FICLONE);
+ console.warn(`File ${filePath} has been copied to ${targetPath}.`);
+ }
+
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ } catch (error) {
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: error instanceof Error ? error.message : String(error)
+ };
+ }
+ } else {
+ console.warn(`File ${filePath} is not [ts,json] file, skipped.`);
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ }
+ }
+
+ // 处理TypeScript文件
+ console.clear();
+ console.warn(`File ${filePath} has been ${changeType}d`);
+
+ const modulePath = pathLib.resolve(filePath);
+ const libPath = modulePath
+ .replace(pathLib.join(projectPath, "src"), pathLib.join(projectPath, "lib"))
+ .replace(/\.ts$/, ".js");
+
+ if (changeType === "remove") {
+ try {
+ if (fs.existsSync(libPath)) {
+ fs.unlinkSync(libPath);
+ }
+ console.warn(`File ${libPath} has been removed.`);
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ } catch (error) {
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: `Error removing file: ${error instanceof Error ? error.message : String(error)}`
+ };
+ }
+ }
+
+ // 编译TypeScript文件
+ const { program, sourceFile, diagnostics } = createProgramAndSourceFile(filePath);
+
+ if (diagnostics.length) {
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: "TypeScript compilation error"
+ };
+ }
+
+ // 只输出单个文件
+ config.lifecycle.onBeforeCompile(config);
+ const emitResult = program.emit(sourceFile);
+
+ if (emitResult.emitSkipped) {
+ console.error(`Emit failed for ${filePath}!`);
+ config.lifecycle.onAfterCompile(config);
+ return {
+ taskId: task.id,
+ success: false,
+ filePath,
+ error: "TypeScript emit failed"
+ };
+ } else {
+ console.log(`Emit succeeded for ${filePath}.`);
+ config.lifecycle.onAfterCompile(config);
+ return {
+ taskId: task.id,
+ success: true,
+ filePath
+ };
+ }
+ };
+
+ return {
+ compileTask
+ };
+};
+
+// 创建文件监视器
+const createFileWatcher = (
+ projectPath: string,
+ eventEmitter: EventEmitter
+) => {
+ const watchSourcePath = pathLib.join(projectPath, "src");
+ let startWatching = false;
+
+ console.log("Watching for changes in", watchSourcePath);
+
+ 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,
+ });
+
+ const handleFileChange = async (path: string, type: FileChangeType) => {
+ if (!startWatching) {
+ return;
+ }
+
+ const event: FileChangeEvent = {
+ path,
+ type,
+ timestamp: Date.now()
+ };
+
+ await eventEmitter.emit("file-changed", event);
+ };
+
+ watcher.on("ready", () => {
+ console.warn("Initial scan complete. Ready for changes");
+ startWatching = true;
+ });
+
+ watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
+
+ watcher
+ .on("add", async (path) => {
+ await handleFileChange(path, "add");
+ })
+ .on("change", async (path) => {
+ await handleFileChange(path, "change");
+ })
+ .on("unlink", async (path) => {
+ await handleFileChange(path, "remove");
+ });
+
+ const dispose = async () => {
+ await watcher.close();
+ };
+
+ return {
+ dispose
+ };
+};
+
+// 生成唯一任务ID
+const generateTaskId = (): string => {
+ return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+};
/**
* 根据 alias 配置表将路径中的别名替换为真实路径
@@ -186,7 +678,7 @@ type ModuleType = import('module')
* @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组
* @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径
*/
-function replaceAliasWithPath(path: string, aliasConfig: Record): string {
+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('*')) {
@@ -213,7 +705,7 @@ function replaceAliasWithPath(path: string, aliasConfig: Record {
const value = aliasConfig[key];
// 替换src
- aliasConfig[key] = typeof value === "string" ? value.replace("src", "lib") : value.map((v) => v.replace("src", "lib"));
+ 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);
- 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文件
+ // 初始化polyfill
const polyfillLoader = () => {
const BuiltinModule = require("module");
// 模拟环境下的 module 构造函数
@@ -311,11 +769,13 @@ export const watch = (
// 检查并编译 .ts 文件
if (fs.existsSync(tsPath)) {
console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`);
- const { program, sourceFile, diagnostics } = createProgramAndSourceFile(
- tsPath,
+ const program = ts.createProgram({
+ rootNames: [tsPath],
options,
- projectReferences
- );
+ projectReferences,
+ });
+ const sourceFile = program.getSourceFile(tsPath);
+ const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
if (diagnostics.length) {
console.error(`[resolve] Compilation failed for: ${tsPath}`);
@@ -396,9 +856,6 @@ export const watch = (
};
};
- polyfillLoader();
-
- //polyfill console.log 添加时间
const polyfillConsole = (trace: boolean) => {
// 获取调用堆栈信息
const getCallerInfo = (): NodeJS.CallSite | null => {
@@ -433,7 +890,7 @@ export const watch = (
const oldFunc = console[levelStr];
console[levelStr] = function (...args) {
oldFunc(
- ...(defaultConfig.polyfill?.console?.formatter({
+ ...(realConfig.polyfill?.console?.formatter({
level: levelStr,
caller: getCallerInfo(),
args,
@@ -443,293 +900,153 @@ export const watch = (
});
};
+ // 初始化polyfill
+ polyfillLoader();
realConfig.polyfill.console.enable && polyfillConsole(enableTrace);
- // 这里注意要在require之前,因为require会触发编译
- const { startup } = require('./start') as {
- 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"
- )}`
- );
+ // 初始编译检查
+ const initialCompile = () => {
+ 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 = ts.createProgram({
+ rootNames: [tsFile],
+ options,
+ projectReferences,
});
- console.error(`文件存在语法错误,请检查修复后重试!`);
- process.exit(1);
+ const diagnostics = ts.getPreEmitDiagnostics(program);
+ 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();
+
+ if (emitResult.emitSkipped) {
+ console.error(`Emit failed for ${tsFile}!`);
+ process.exit(1);
+ }
+
+ console.log(`Emit succeeded. ${tsFile} has been compiled.`);
}
- // 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/configuration/index.ts",
+ "src/aspects/index.ts",
+ "src/checkers/index.ts",
+ "src/triggers/index.ts",
+ "src/timers/index.ts",
+ "src/routines/start.ts",
+ "src/watchers/index.ts",
+ "src/endpoints/index.ts",
+ "src/data/index.ts",
+ "src/ports/index.ts",
+ ];
+ compileFiles.forEach(tryCompile);
}
- // 所有要编译的目录
- // 其他涉及到的目录会在运行的时候自动编译,这里主要处理的是动态require的文件
- const compileFiles = [
- "src/configuration/index.ts",
- "src/aspects/index.ts",
- "src/checkers/index.ts",
- "src/triggers/index.ts",
- "src/timers/index.ts",
- "src/routines/start.ts",
- "src/watchers/index.ts",
- "src/endpoints/index.ts",
- "src/data/index.ts",
- "src/ports/index.ts",
- ];
- compileFiles.forEach(tryCompile);
- }
+ };
return new Promise((resolve, reject) => {
- realConfig.lifecycle.onInit();
- let shutdown: (() => Promise) | undefined;
+ // 创建事件系统
+ const eventEmitter = createEventEmitter();
- const restart = async () => {
- if (shutdown) {
- console.log("----> Shutting down service......");
- await shutdown().then(realConfig.lifecycle.onServerShutdown);
- // reset shutdown
- shutdown = undefined;
- }
- 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(pathLib.join(
- projectPath,
- "lib/config/connector"
- )).default;
- console.warn("----> Starting service......");
- shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
- realConfig.lifecycle.onServerStart();
- return shutdown;
- });
- };
+ // 创建各个组件
+ const compileQueue = createCompileQueue(eventEmitter);
+ const compiler = createCompiler(projectPath, options, projectReferences, aliasConfig, realConfig);
+ const serverManager = createServerManager(projectPath, eventEmitter, realConfig);
+ const fileWatcher = createFileWatcher(projectPath, eventEmitter);
- const watchSourcePath = pathLib.join(projectPath, "src");
+ // 设置编译器处理器
+ compileQueue.setTaskProcessor(compiler.compileTask);
- console.log("Watching for changes in", watchSourcePath);
-
-
-
- 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,
+ // 设置事件监听器
+ eventEmitter.on("file-changed", (event: FileChangeEvent) => {
+ const task: CompileTask = {
+ id: generateTaskId(),
+ filePath: event.path,
+ changeType: event.type,
+ timestamp: event.timestamp
+ };
+ compileQueue.addTask(task);
});
- let startWatching = false;
-
- watcher.on("ready", () => {
- console.warn("Initial scan complete. Ready for changes");
- startWatching = true;
+ eventEmitter.on("compile-batch-started", (data: { count: number }) => {
+ console.log(`----> Starting compilation batch (${data.count} files)...`);
});
- watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
+ eventEmitter.on("compile-batch-completed", (data: { results: CompileResult[] }) => {
+ const successCount = data.results.filter(r => r.success).length;
+ console.log(`----> Compilation batch completed: ${successCount}/${data.results.length} successful`);
+ });
- let processingQueue: string[] = [];
- 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"); // 这里直接替换不对,应该是拿到项目目录,把项目目录/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.`);
- } 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}.`
- );
+ // 编译成功之后,若设置的同步i18n,则触发i18n更新
+ if (realConfig.autoUpdateI18n) {
+ const projectI18nPath = pathLib.join("src", "data", "i18n.ts");
+ eventEmitter.on("compile-task-completed", async (result: CompileResult) => {
+ if (result.filePath == projectI18nPath && result.success) {
+ console.log("-------------start upgrade i18n.-------------")
+ // 这里是copy:upgradeI18n的
+ const { checkAndUpdateI18n } = require('oak-backend-base/lib/routines/i18n.js');
+ const simpleConnector = require(pathLib.join(
+ projectPath,
+ "lib/config/connector"
+ )).default;
+ // 这里注意要在require之前,因为require会触发编译
+ const { startup } = require('./start') as {
+ startup: (
+ pwd: string,
+ connector: any,
+ omitWatchers?: boolean,
+ omitTimers?: boolean,
+ routine?: (context: AsyncContext) => Promise
+ ) => Promise<() => Promise>;
}
- } 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 = pathLib.resolve(path);
- // 将src替换为lib
- const libPath = modulePath
- .replace(pathLib.join(projectPath, "src"), pathLib.join(projectPath, "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;
- // 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件
- } else {
- // 如果是删除,则需要发出警告,文件正在被进程使用
- if (type === "remove") {
- console.error(`File ${libPath} is being used, skiped.`);
- return;
- }
- }
- const { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences);
-
- if (diagnostics.length) {
- return;
- }
-
- // 只输出单个文件
- realConfig.lifecycle.onBeforeCompile();
- const emitResult = program.emit(sourceFile);
- // 是否成功
- const result = emitResult.emitSkipped;
- if (result) {
- console.error(`Emit failed for ${path}!`);
- realConfig.lifecycle.onAfterCompile();
- } else {
- console.log(
- `Emit succeeded. ${compileOnly ? "" : "reload service......"
- }`
- );
- realConfig.lifecycle.onAfterCompile();
- if (compileOnly) {
- return;
- }
- // await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务
- if (processingQueue.length === 1) {
- await restart();
- } else {
- console.log("Waiting for other operations to complete...");
- }
- }
- };
-
- const onChangeDebounced =
- async (path: string, type: "add" | "remove" | "change") => {
- if (processingQueue.includes(path)) {
- console.log("Processing, please wait...");
- return;
- }
- try {
- processingQueue.push(path);
- await fileChangeHandler(path, type);
- } catch (e) {
- console.clear();
- console.error(e);
- process.exit(1);
- } finally {
- processingQueue = processingQueue.filter((p) => p !== path);
- }
- }
-
- watcher
- .on("add", async (path) => {
- if (startWatching) {
- await onChangeDebounced(path, "add");
- }
- })
- .on("change", async (path) => {
- if (startWatching) {
- await onChangeDebounced(path, "change");
- }
- })
- .on("unlink", async (path) => {
- if (startWatching) {
- await onChangeDebounced(path, "remove");
+ startup(pwd, simpleConnector, true, true, checkAndUpdateI18n).then(() => {
+ console.log("------------upgrade i18n success.------------")
+ }).catch((err) => {
+ console.error("------------upgrade i18n failed!------------", err)
+ })
}
});
+ }
- const dispose = async () => {
- if (shutdown) {
- await shutdown();
+ eventEmitter.on("server-restart-needed", async () => {
+ if (!serverManager.isRestarting()) {
+ console.log("----> Restarting server...");
+ await serverManager.restart();
}
- await watcher.close();
- realConfig.lifecycle.onDispose();
- };
+ });
- restart()
+ // 初始化
+ realConfig.lifecycle.onInit(realConfig);
+
+ // 执行初始编译
+ initialCompile();
+
+ // 启动服务器
+ serverManager.restart()
.then(() => {
+ const dispose = async () => {
+ await fileWatcher.dispose();
+ await serverManager.dispose();
+ realConfig.lifecycle.onDispose(realConfig);
+ };
resolve(dispose);
})
.catch(reject);