UNPKG

mm_machine

Version:

这是超级美眉框架机制构建辅助模块,用于快速构建一个机制,支持动态加载、热更新、模块管理等功能,并具有增强的错误处理和现代JavaScript特性支持。

574 lines (519 loc) 13.2 kB
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;