可以卸载、删除或取消 require Node.js 模块吗?

4
我们正在开发一款Electron应用程序,允许用户提供自己的“模块”来运行。我们正在寻找一种方法来引用这些模块,但是如果需要的话,又可以删除或终止这些模块。
我们查阅了一些教程,似乎讨论了这个话题,但我们无法完全终止这些模块。我们尝试使用模块内的计时器来探索这个问题,并观察到即使在删除模块引用之后,计时器仍然在运行。
参考链接:https://repl.it/repls/QuerulousSorrowfulQuery index.js
// Load module
let Mod = require('./mod.js'); 

// Call the module function (which starts a setInterval)
Mod();

// Delete the module after 3 seconds
setTimeout(function () {
  Mod = null;
  delete Mod;
  console.log('Deleted!')
}, 3000);

./mod.js

function Mod() {
  setInterval(function () {
    console.log('Mod log');
  }, 1000);
}

module.exports = Mod;

期望输出

Mod log
Mod log
Deleted!

实际输出

Mod log
Mod log
Deleted!
Mod log 
...
(continues to log 'Mod log' indefinitely)

也许我们过于考虑了,也许这些模块不会占用太多内存,但我们加载的模块会有非常高的工作量,因此有能力随时停止它们似乎很重要。 使用真实用例进行编辑 以下是我们目前使用此技术的方式。两个问题是以正确的方式加载模块和在完成后卸载模块。 renderer.js(在浏览器上下文中运行,具有访问document等的权限)
const webview = document.getElementById('webview'); // A webview object essentially gives us control over a webpage similar to how one can control an iframe in a regular browser.
const url = 'https://ourserver.com/module.js';
let mod;
request({
  method: 'get',
  url: url,
}, function (err, httpResponse, body) {
  if (!err) {
    mod = requireFromString(body, url); // Module is loaded
    mod(webview); // Module is run
    // ...
    // Some time later, the module needs to be 'unloaded'. 
    // We are currently 'unloading' it by dereferencing the 'mod' variable, but as mentioned above, this doesn't really work. So we would like to have a way to wipe the module and timers and etc and free up any memory or resources it was using!
    mod = null;
    delete mod;
  } 
})

function requireFromString(src, filename) {
  var Module = module.constructor;
  var m = new Module();
  m._compile(src, filename);
  return m.exports;
}

https://ourserver.com/module.js

// This code module will only have access to node modules that are packaged with our app but that is OK for now!
let _ = require('lodash'); 
let obj = {
  key: 'value'
}
async function main(webview) {
  console.log(_.get(obj, 'key')) // prints 'value'
  webview.loadURL('https://google.com') // loads Google in the web browser
}

module.exports = main;

如果有人不熟悉 Electron 的话,renderer.js 具有访问 'webview' 元素的权限,这些元素与 iframe 几乎完全相同。因此将其传递给 'module.js' 将允许模块访问并操作网页,例如更改 URL,在该网页上点击按钮等。


这个回答解决了你的问题吗?如何在node.js中的“require”之后删除模块? - Ahmet Zeybek
是的,我看到了那个问题并尝试了几乎所有的答案。然而,即使将这个问题中的示例应用于这些答案,计时器仍然记录着东西!你有其他的建议吗? - T Mack
这是一个基于Web的应用程序吗?我的意思是说,这个模块会在浏览器端运行吗?顺便说一下,您不能使用delete运算符删除函数,它只能用于删除对象属性。 - Ahmet Zeybek
你尝试过先将 Mod 赋值给某个本地变量,然后在超时内清除/设置该变量为 null 吗?或者你可以使用 clearInterval - Dananjaya Ariyasena
@AhmetZeybek,这是在一个 Electron 应用程序上的,我应该提到了我的错误。 - T Mack
@Dananjaya Ariyasena 这是个好主意,但不可行,因为可能有很多计时器,而计时器更多地被用作概念证明,即代码并没有完全卸载。 - T Mack
1个回答

5

没有办法杀死一个模块并停止或关闭它正在使用的任何资源。这不是node.js的功能。这样的模块可能有定时器,打开文件,打开套接字,运行服务器等等...另外,node.js没有提供一种“卸载”曾经加载的代码的方法。

您可以从模块缓存中删除模块,但这不会影响现有的、已经加载的代码或其资源。

我所知道的唯一可靠的方法是在作为子进程加载的单独的node.js应用程序中加载用户的模块,然后您可以退出该进程或杀死该进程,然后操作系统将回收它使用的任何资源并从内存中卸载所有内容。这种子进程方案还具有用户的代码与主服务器代码更加隔离的优点。您甚至可以通过在虚拟机中运行这个进程来进一步隔离它。


@TMack - 我在询问用户代码需要做什么,因为它需要访问您的应用程序中的哪些数据以及它试图实现什么结果。当您将事物移动到另一个进程时,如何执行取决于您需要向其通信以及它需要回传什么信息,以及该通信是持续进行还是只是一次性的。 - jfriend00
它们将类似于插件的形式运作。该应用程序是一个浏览器,还具有一些自动化功能,因此用户可以编写自己的脚本来自动执行诸如单击网站按钮之类的操作。最终,他们需要能够运行多个这样的脚本,并且可以随时启动和停止脚本。总的来说,我们希望这些脚本的上下文与常规节点模块非常相似,因为它可以访问大多数节点功能并能够“require”包。我提到这一点是因为我们遇到了一个问题,即vm中无法使用require。 - T Mack
@TMack - 那仍然没有解释它与您的主服务器有何种程度的通信。您能给我一些他们所做的示例吗?您说点击按钮-但是如何点击?用户代码是否在您的服务器上运行?还是被插入到您的网页中并在浏览器中运行?我现在真的很困惑。如果这些插件正在持续运行并可能参与许多服务器事件,那么在将插件移出进程时需要更多的工作,但可以完成。 - jfriend00
@TMack - 现在我明白这是一个电子应用程序,而且你正在尝试允许此代码修改webView,但我对这种架构的理解还不足以进一步建议如何在进程外执行此操作。 - jfriend00
@TMack - 运行子进程的简单示例在这里:https://nodejs.org/api/child_process.html。 - jfriend00
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接