Nodejsコードホットアップデート実装
44707 ワード
実現原理:node file systemモジュールのwatchインタフェースを利用してフォルダのファイル変更イベントがトリガーされた後、require.cache内の対応するキャッシュを削除してvmモジュールを使用して新しくロードされたコード(基礎検査文法、後続はvm content内でテスト実行可能)をコンパイルした後、requireを使用してコードをロードし、新しいコードはrequire.cacheにキャッシュされます.require.cacheのキャッシュ・データ・コードのリカバリに失敗した場合は、次のようになります.
注意:この方法で管理されたホット・アップデートのコードは、グローバルなrequireを使用して単独でロードすることはできません.handleMap変数キャッシュの除去を統一的に管理したくない場合は、管理されたコードを使用するたびにrequireを使用してコードを取得します(requireはキャッシュ内からチェックしてからファイル・ロードを探します).
const fs = require("fs");
const path = require("path");
const vm = require("vm");
const handlerMap = {};
const cacheMap = {};
/**
*
* [@return](/user/return) {Promise.}
*/
const loadHandlers = async function () {
///
const files = await new Promise((resolve, reject) => {
fs.readdir(path.join(__dirname, 'hots'), function (err, files) {
if (err) {
reject(err);
} else {
resolve(files);
}
});
});
///
for (let f in files) {
if (/.*?\.js$/.test(files[f])) {
handlerMap[files[f]] = await loadHandler(path.join(__dirname, 'hots', files[f]));
}
}
///
watchHandlers();
};
/**
*
*/
const watchHandlers = function () {
console.log('watching ', path.join(__dirname, 'hots'));
fs.watch(path.join(__dirname, 'hots'), {recursive: true}, function (eventType, filename) {
if (/.*?\.js$/.test(filename)) {
///
if( cacheMap[require.resolve(path.join(__dirname, 'hots', filename))] )
delete cacheMap[require.resolve(path.join(__dirname, 'hots', filename))];
/// , , bug
cacheMap[require.resolve(path.join(__dirname, 'hots', filename))] = require.cache[require.resolve(path.join(__dirname, 'hots', filename))];
/// require.cache
require.cache[require.resolve(path.join(__dirname, 'hots', filename))] = null;
loadHandler(path.join(__dirname, 'hots', filename)).then(function (data) {
if (data) {
handlerMap[filename] = data;
} else {
delete handlerMap[filename];
}
console.log(" ", filename, " ", handlerMap);
}).catch(function (err) {
console.log(" : :", err, " :", handlerMap);
require.cache[require.resolve(path.join(__dirname, 'hots', filename))] = cacheMap[require.resolve(path.join(__dirname, 'hots', filename))];
cacheMap[require.resolve(path.join(__dirname, 'hots', filename))] = null;
});
}
});
};
/**
*
* [@param](/user/param) filename
* [@return](/user/return) {Promise.}
*/
const loadHandler = async function (filename) {
const exists = await new Promise(resolve => {
///
fs.access(filename, fs.constants.F_OK | fs.constants.R_OK, err => {
if (err) {
resolve(false);
} else {
resolve(true);
}
});
});
if (exists) {
return await new Promise((resolve, reject) => {
fs.readFile(filename, function (err, data) {
if (err) {
resolve(null);
} else {
try {
/// vm script
new vm.Script(data);
//const script = new vm.Script(data);
// const context = new vm.createContext({
// require: require,
// module: {}
// });
// script.runInContext(context);
} catch (e) {
/// ,
reject(e);
return;
}
///
resolve(require(filename));
}
});
});
} else {
///
return null;
}
};
loadHandlers().then(function () {
console.log("run...");
}).catch(function (e) {
console.error(e);
});
注意:この方法で管理されたホット・アップデートのコードは、グローバルなrequireを使用して単独でロードすることはできません.handleMap変数キャッシュの除去を統一的に管理したくない場合は、管理されたコードを使用するたびにrequireを使用してコードを取得します(requireはキャッシュ内からチェックしてからファイル・ロードを探します).