mm_machine
Version:
这是超级美眉框架机制构建辅助模块,用于快速构建一个机制,支持动态加载、热更新、模块管理等功能,并具有增强的错误处理和现代JavaScript特性支持。
574 lines (519 loc) • 13.2 kB
JavaScript
require('mm_logs');
const util = require('util');
const conf = require('mm_config');
const Mod = require('mm_hot_reload');
// 初始化模块管理器
$.mod = $.mod || new Mod();
/**
* 增强require函数,支持热更新
* @param {String} file 文件路径
* @param {Function} func 回调函数
* @returns {Object} 加载的模块
*/
$.require = function (file, func) {
return $.mod.load(file, func);
};
/**
* 增强JSON加载函数,支持热更新
* @param {String} file 文件路径
* @param {Function} func 回调函数
* @returns {Object} 解析的JSON对象
*/
$.loadJson = function (file, func) {
return $.mod.load(file, func);
};
/**
* 驱动基础类
* @class
*/
class Item {
/**
* 构造函数
* @param {String} dir 当前目录
* @param {String} dir_base 模块目录
* @constructor
*/
constructor(dir, dir_base) {
/**
* 当前路径
*/
this.dir = dir;
/**
* 默认文件
*/
this.default_file = './sys.json';
// 当前文件
this.filename = null;
/* 通用项 */
/**
* 配置参数
*/
this.config = {
/**
* 名称, 由中英文和下"_"组成, 用于卸载接口 例如: demo
*/
name: '',
/**
* 标题, 介绍作用
*/
title: '',
/**
* 描述, 用于描述该有什么用的
*/
description: '',
/**
* 文件路径, 当调用函数不存在时,会先从文件中加载
*/
func_file: './index.js',
/**
* 回调函数名 用于决定调用脚本的哪个函数
*/
func_name: '',
/**
* 排序
*/
sort: 10,
/**
* 状态, 0表示未启用, 1表示启用
*/
state: 1,
/**
* 显示, 0表示不显示, 1表示显示
*/
show: 0
};
/**
* 模块目录
*/
this.dir_base = dir_base;
/**
* 模式
* 1.生产模式,改变文件不会重新加载
* 2.热更新模式,改变配置文件会重新加载配置,不重新加载脚本
* 3.热重载模式,改变配置文件都会加载配置和脚本
* 4.热更新+重载模式,改变配置文件重新加载配置和脚本,执行完后重新加载脚本
* 5.重载模式,执行完后重新加载脚本,避免变量污染
*/
this.mode = 1;
// 加载完成
this.complete = false;
}
}
/**
* 设置配置
* @param {Object} config 配置对象
*/
Item.prototype.set_config = function (config) {
if (!config) return;
// 如果func_file改变,重置加载状态
if (config.func_file !== this.config.func_file) {
this.complete = false;
}
// 合并配置并应用配置处理
this.config = conf(Object.assign({}, this.config, config), this.filename);
};
/**
* 设置配置后钩子方法
*/
Item.prototype.set_config_after = function () {
// 空实现,供子类重写
};
/**
* 新建脚本文件
* @param {String} file 目标文件路径
*/
Item.prototype.new_script = function (file) {
const templateFile = `${this.dir_base}/script.js`;
if (templateFile.hasFile()) {
templateFile.copyFile(file);
}
};
/**
* 移除模块
* @param {Object|String} module 模块对象或路径
*/
Item.prototype.remove_module = function (module) {
if (!module) return;
try {
if (this.mode === 3 || this.mode === 4) {
// 移除模块和监听
$.mod.unload(module);
} else {
// 移除模块缓存
const filename = require.resolve(module);
if (require.cache[filename]) {
require.cache[filename] = null;
delete require.cache[filename];
}
}
} catch (err) {
$.log.error(`移除模块失败: ${err.message}`);
}
};
/**
* 卸载脚本
* @param {String} file 文件路径
*/
Item.prototype.unload_script = function (file) {
if (!file) {
const funcFile = this.config.func_file;
if (funcFile) {
file = funcFile.fullname(this.dir);
}
}
if (file) {
this.remove_module(file);
}
this.complete = false;
};
/**
* 重载脚本
*/
Item.prototype.reload_script = function () {
this.unload_script();
this.load_script();
};
/**
* 卸载钩子方法
*/
Item.prototype.unload = async function () {
// 空实现,供子类重写
};
/**
* 卸载之后处理
* @param {Boolean} remove 是否删除文件
*/
Item.prototype.unload_after = async function (remove) {
// 删除脚本
this.unload_script();
if (remove) {
this.remove_file();
}
};
/**
* 加载脚本
* @param {String} file 文件路径
* @param {String} name 函数名
* @returns {Object|null} 返回加载的模块对象
*/
Item.prototype.load_script = function (file, name = '') {
if (!file) {
const funcFile = this.config.func_file;
if (funcFile) {
file = funcFile.fullname(this.dir);
if (!file.hasFile()) {
this.new_script(file);
}
} else {
return null;
}
}
if (!name) {
name = this.config.func_name;
}
let cs;
try {
if (this.mode === 3 || this.mode === 4) {
cs = $.require(file, (loadedModule, changeType) => {
if (changeType === 'change' && loadedModule) {
if (name) {
if (loadedModule[name]) {
this.main = loadedModule[name];
}
} else if ($.push) {
$.push(this, loadedModule, true);
}
}
});
} else {
cs = require(file);
}
if (cs) {
if (name) {
if (cs[name]) {
this.main = cs[name];
}
} else if ($.push) {
$.push(this, cs, true);
}
}
return cs;
} catch (err) {
$.log.error(`加载脚本失败: ${err.message}`);
return null;
}
};
/**
* 新建配置文件
* @param {String} file 目标文件路径
*/
Item.prototype.new_config = function (file) {
const templateFile = `${this.dir_base}/config.tpl.json`;
templateFile.copyFile(file);
};
/**
* 加载配置文件
* @param {String} file 文件路径
* @param {String} name 配置项名称
* @return {Object|null} 配置对象
*/
Item.prototype.load_file = function (file, name) {
let config = null;
try {
const fullPath = file.fullname(this.dir);
let text = fullPath.loadText();
// 如果文件不存在,创建新的配置文件
if (!text) {
this.new_config(fullPath);
text = fullPath.loadText();
}
if (text) {
// 根据模式决定是否使用热更新加载
if (this.mode === 2 || this.mode === 3 || this.mode === 4) {
config = $.loadJson(fullPath, function (loadedConfig, changeType) {
if (changeType === 'change' && loadedConfig) {
try {
if (Array.isArray(loadedConfig)) {
// 在数组中查找匹配的配置项
const targetConfig = loadedConfig.find(item => item.name === this.config.name);
if (targetConfig) {
this.set_config(targetConfig);
this.set_config_after();
// 根据模式决定是否重新加载脚本
if (this.mode === 3 || this.mode === 4) {
this.reload_script();
}
}
} else {
this.set_config(loadedConfig);
this.set_config_after();
if (this.mode === 3 || this.mode === 4) {
this.reload_script();
}
}
} catch (err) {
$.log.error(`配置热更新失败: ${err.message}`);
}
}
}.bind(this));
} else {
config = fullPath.loadJson();
}
// 如果指定了名称且配置是数组,查找匹配项
if (name && Array.isArray(config)) {
config = config.find(item => item.name === name) || null;
}
}
// 更新目录和文件名引用
this.dir = fullPath.dirname();
this.filename = fullPath;
return config;
} catch (err) {
$.log.error(`加载配置文件失败: ${err.message}`);
return null;
}
};
/**
* 删除目录
*/
Item.prototype.del_dir = function () {
const funcFile = this.config.func_file;
if (funcFile && $.dir && $.dir.del) {
$.dir.del(this.dir);
}
};
/**
* 删除配置和脚本文件
* @returns {String|null} 错误消息,如果没有错误则返回null
*/
Item.prototype.remove_file = function () {
const name = this.config.name;
const file = this.filename;
let errorMessage = null;
try {
if (file && file.hasFile) {
if (file.hasFile()) {
const text = file.loadText();
if (text) {
const config = text.toJson();
if (Array.isArray(config)) {
// 在数组配置中查找并删除指定项
const index = config.findIndex(item => item.name === name);
if (index !== -1) {
this.del_dir();
config.splice(index, 1);
}
// 根据剩余配置决定保存或删除文件
if (config.length > 0) {
file.saveText(JSON.stringify(config, null, 4));
} else if (file.delFile) {
file.delFile();
}
} else {
// 单个配置直接删除目录
this.del_dir();
}
} else {
this.del_dir();
}
} else {
errorMessage = '配置文件不存在';
}
}
} catch (err) {
$.log.error(`删除文件失败: ${err.message}`);
errorMessage = `删除失败: ${err.message}`;
}
return errorMessage;
};
/**
* 载入配置
* @param {Object|String} configData 配置对象或配置路径
* @param {String} name 配置名称
*/
Item.prototype.load_config = function (configData, name) {
let config;
try {
if (configData) {
if (typeof configData === 'string') {
config = this.load_file(configData, name);
} else {
config = configData;
}
} else {
config = this.load_file(this.filename, name);
}
this.set_config(config);
this.set_config_after();
} catch (err) {
$.log.error(`载入配置失败: ${err.message}`);
}
};
/**
* 加载前处理
*/
Item.prototype.load_before = async function () {
try {
const module = this.load_script();
if (module) {
this.complete = true;
}
} catch (err) {
$.log.error(`加载前处理失败: ${err.message}`);
this.complete = false;
}
};
/**
* 加载处理
*/
Item.prototype.load = async function () {
// 空实现,供子类重写
};
/**
* 重载配置和脚本
*/
Item.prototype.reload = async function () {
await this.run('unload');
await this.run('load');
};
/**
* 保存配置
*/
Item.prototype.save = function () {
try {
if (!this.filename) return;
const fullPath = this.filename.fullname(this.dir);
const text = fullPath.loadText();
if (text && text.trim().startsWith('[')) {
// 数组格式配置
const configArray = text.toJson();
if (Array.isArray(configArray)) {
// 查找并更新现有配置项
const existingIndex = configArray.findIndex(item => item.name === this.config.name);
if (existingIndex !== -1) {
configArray[existingIndex] = this.config;
} else {
// 如果不存在,添加新配置项
configArray.push(this.config);
}
fullPath.saveText(JSON.stringify(configArray, null, 4));
return;
}
}
// 单对象格式配置,直接保存
fullPath.saveText(JSON.stringify(this.config, null, 4));
} catch (err) {
$.log.error(`保存配置失败: ${err.message}`);
}
};
/**
* 主要执行函数
* @param {*} params 参数集合
* @returns {Promise<any>} 执行结果
*/
Item.prototype.main = async function (...params) {
// 空实现,供子类重写
return null;
};
/**
* 运行主函数
* @param {*} params 参数集合
* @returns {Promise<any>} 执行结果
*/
Item.prototype.run = async function (...params) {
return await this.exec('main', ...params);
};
/**
* 调用函数(核心执行方法)
* @param {String} method 函数名
* @param {*} params 参数集合
* @returns {Promise<any>} 执行结果
*/
Item.prototype.exec = async function (method, ...params) {
if (!this[method]) {
return null;
}
let result;
try {
// 执行前置钩子
const beforeMethod = `${method}_before`;
if (this[beforeMethod]) {
try {
result = this[beforeMethod](...params);
if (util.types.isPromise(result)) {
result = await result;
}
} catch (err) {
$.log.error(`执行前置钩子 ${beforeMethod} 失败: ${err.message}`);
}
}
// 执行主方法
const methodResult = this[method](...params);
result = util.types.isPromise(methodResult) ? await methodResult : methodResult;
// 执行后置钩子
const afterMethod = `${method}_after`;
if (this[afterMethod]) {
try {
const afterResult = this[afterMethod](result, ...params);
if (util.types.isPromise(afterResult)) {
const resolvedAfterResult = await afterResult;
if (resolvedAfterResult !== undefined) {
result = resolvedAfterResult;
}
} else if (afterResult !== undefined) {
result = afterResult;
}
} catch (err) {
$.log.error(`执行后置钩子 ${afterMethod} 失败: ${err.message}`);
}
}
} catch (err) {
$.log.error(`执行方法 ${method} 失败: ${err.message}`);
return null;
}
return result;
};
/**
* @module 导出Item类
*/
module.exports = Item;