feat: 完全重构并优化了server watch,通过编译链与事件机制解决多文件修改的情况,并且支持了在i18n文件更新的情况下,自动同步到数据库(利用了routine)
This commit is contained in:
parent
b3f56fb7b8
commit
61705bf9e1
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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({})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { PathLike } from 'fs';
|
||||
import { checkFileExistsAndCreateType } from './enum';
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference path="../../src/typings/polyfill.d.ts" />
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.initialize = void 0;
|
||||
exports.initialize = initialize;
|
||||
/// <reference path="../typings/polyfill.d.ts" />
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference path="../../src/typings/polyfill.d.ts" />
|
||||
import './polyfill';
|
||||
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
||||
import { Connector, EntityDict } from 'oak-domain/lib/types';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.startup = void 0;
|
||||
exports.startup = startup;
|
||||
const tslib_1 = require("tslib");
|
||||
/// <reference path="../typings/polyfill.d.ts" />
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="node" />
|
||||
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<T = any> = (data: T) => void | Promise<void>;
|
||||
export type EventEmitter = {
|
||||
on: <T>(event: EventType, handler: EventHandler<T>) => void;
|
||||
emit: <T>(event: EventType, data: T) => Promise<void>;
|
||||
off: <T>(event: EventType, handler: EventHandler<T>) => void;
|
||||
};
|
||||
type DeepRequiredIfPresent<T> = {
|
||||
[P in keyof T]-?: NonNullable<T[P]> extends (...args: any) => any ? T[P] : NonNullable<T[P]> extends (infer U)[] ? DeepRequiredIfPresent<U>[] : NonNullable<T[P]> extends object ? DeepRequiredIfPresent<NonNullable<T[P]>> : T[P] extends undefined | null ? T[P] : NonNullable<T[P]>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
19
lib/utils.js
19
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;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue