express-wx
Version:
Express Router for building Wechat Offical Account Message Server easily, supporting loading request (message) handlers dynamically. 快速构建微信公众号消息后端,支持动态加载请求处理逻辑代码。
139 lines (138 loc) • 5.61 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateOnChangeCb = exports.loadRouter = exports.watchRecursively = exports.NocacheRequire = void 0;
const Fs = require("fs");
const util_1 = require("util");
const Path = require("path");
function NocacheRequire(path) {
path = Path.resolve(path);
delete require.cache[path];
return require(path);
}
exports.NocacheRequire = NocacheRequire;
let watchers = {};
async function watchRecursively(path, extraCallback) {
const callback = async (event, filename) => {
var _a;
filename = Path.resolve(filename);
let stat;
try {
stat = await (0, util_1.promisify)(Fs.stat)(filename);
}
catch (e) {
}
if (event == "rename" && !stat) {
let type = watchers[filename] ? "dirRemove" : "fileRemove";
(_a = watchers[filename]) === null || _a === void 0 ? void 0 : _a.close();
delete watchers[filename];
extraCallback(event, filename, type);
}
else if (event == "rename" && stat) {
if (stat.isDirectory()) {
watchDir(filename);
extraCallback(event, filename, "dirAdd");
}
else {
extraCallback(event, filename, "fileAdd");
}
}
else {
extraCallback(event, filename, "fileChange");
}
};
function watchDir(dirname = path) {
(async () => {
var _a;
(_a = watchers[dirname]) === null || _a === void 0 ? void 0 : _a.close();
watchers[dirname] = Fs.watch(dirname, (event, filename) => callback(event, Path.join(dirname, filename)))
.addListener('error', (err) => {
console.warn(err.message, dirname);
});
for await (let dir of await (0, util_1.promisify)(Fs.opendir)(dirname)) {
if (dir.isDirectory())
watchDir(Path.join(dirname, dir.name));
}
})().catch(reason => {
console.warn(reason.message);
});
}
let stat = await (0, util_1.promisify)(Fs.stat)(Path.resolve(path));
if (stat.isDirectory())
watchDir(); // 如果是目录,调用watchDir方法watch这个目录,其内部会自动递归watchDir所有子目录
else {
// 如果是文件,则只watch单个文件
let dirname = Path.dirname(Path.resolve(path));
Fs.watch(path, (event, filename) => callback(event, Path.join(dirname, filename)));
}
}
exports.watchRecursively = watchRecursively;
/**
* 加载动态路由。path可以是目录也可以是文件名,目录的话就会递归加载,文件名就是只加载一个、返回只有一个元素的对象。
*/
async function loadRouter(path) {
path = Path.resolve(path);
let stat;
try {
stat = await (0, util_1.promisify)(Fs.stat)(path);
if (stat.isDirectory()) {
let result = {};
let files = await (0, util_1.promisify)(Fs.readdir)(path);
for (let file of files) {
file = Path.join(path, file);
Object.assign(result, await loadRouter(file));
}
return result;
}
else {
if (Path.extname(path) !== ".js")
return {};
else {
let module = NocacheRequire(path);
module = module.default || module;
if (typeof module !== "function")
return {};
// 尝试从文件名中获得优先级数值
// 文件名中的优先级数值高于文件内priority属性定义的优先级数值,
// 因此用正则判断文件名上是否有优先级数值的定义,如果有必定覆盖priority属性。
let splited = Path.basename(path, ".js").split("_");
let lastPartStr = splited[splited.length - 1];
let lastPartNumber = lastPartStr.match(/^-?\d+(.\d+)?$/) ? Number(lastPartStr) : null;
if (lastPartNumber)
module.priority = lastPartNumber;
// 如果导入的对象上不存在nameForLog属性,则添加上文件名作为默认值,以备可能的日志记录中使用
module.nameForLog = module.nameForLog || Path.basename(path, ".js");
let obj = {};
obj[path] = module;
return obj;
}
}
}
catch (e) {
return {};
}
}
exports.loadRouter = loadRouter;
function generateOnChangeCb(wxRouter) {
return async function (event, filename, type) {
if (type === "dirAdd" || type === "fileAdd" || type === "fileChange") {
let newRouters = await loadRouter(filename);
wxRouter._handlerMap = Object.assign(wxRouter._handlerMap, newRouters);
}
else if (type === "fileRemove") {
let old = wxRouter._handlerMap;
delete old[filename];
wxRouter._handlerMap = old;
}
else if (type === "dirRemove") {
let old = wxRouter._handlerMap;
for (let key in old) {
if (Path.relative(filename, key).indexOf("..") !== 0) {
//key相对于filename的相对路径的开头不是..,即key是filename目录的子文件。
delete old[key];
}
}
wxRouter._handlerMap = old;
}
};
}
exports.generateOnChangeCb = generateOnChangeCb;