node.js - 跨平台模块系统

标签 node.js module setter defineproperty

编辑:为了找到解决方案,我编辑了该帖子以更清楚地解释我想要完成的任务。

我正在尝试重新发明轮子,用最少的代码来创建跨平台异步模块加载系统

理想情况下,这应该适用于任何 ES5 运行时引擎,但主要目标是 Node.js 和浏览器。


我想要完成的是使用 setter 创建一个全局对象,其中设置的对象是模块内容。 Node.js 通过 module.exports = {} 完成此操作,我正在尝试复制此行为。

我遇到的问题很有趣,因为全局 setter 不会创建模块文件名和导出对象的 1:1 映射。


第一次尝试:

到目前为止,我已经尝试将 setter 绑定(bind)到特定于特定的函数调用。它总是求助于最后加载的模块。我认为通过将 setter 包装在闭包中,它会将 module 参数保留在调用堆栈中,但我错了 - 因为 setter 发生了变化。


改进的解决方案,但尚未完全实现:

我还尝试使用导出对象中定义的 name 属性来创建此映射,但事实证明效率低下且易于规避。 IE。通过导出与其用途不相符的名称,并且可能有意或无意地覆盖系统中的其他模块。


这里是一些示例代码:

let exporter = {}
global.exporter = exporter

const imports = function(module, callback) {
  return new (function(module, callback) {
    Object.defineProperty(exporter, 'exports', {
      enumerable: false,
      configurable: true,
      set: function(exportFile) {
        console.log('Setting export file:', exportFile.name, ':', module)
        callback(exportFile)
      },
    })

    console.log('loading module: ', module)
    require(module)
  })(module, callback)
}

在模块文件中使用setter:

exporter.exports = {
  name: 'File1',
}

使用新导入的示例代码。

function load(name) {
  imports(__dirname + '/modules/' + name, function(exportFile) {
    console.log('Module loaded: ', exportFile.name)
  })
}

load('1') // instant
load('2') // 2 second timeout
load('3') // 1 second timeout

输出:

loading module:  .../modules/1
Setting export file: File1 : .../modules/1
Module loaded:  File1
loading module:  .../modules/2
loading module:  .../modules/3
Setting export file: File3 : .../modules/3
Module loaded:  File3
Setting export file: File2 : .../modules/3
Module loaded:  File2


我感谢任何可以解决此上下文问题的帮助!

我也愿意接受任何其他建议来完成同样的任务,而不使用任何特定于 Node 的东西,因为我计划使这个跨平台兼容。

最佳答案

What I'm trying to accomplish is creating a global object with a setter, from which the object being set is the module contents. Node.js accomplishes this with module.exports = {} and I'm trying to replicate this behavior.

您的问题是您确实使用全局对象。由于模块是异步加载的,因此当模块执行时,全局对象可能处于不正确的状态。可能有一种方法可以在 require 之后重置全局对象。调用,以便您的特定示例工作正常,但有些情况它无法涵盖,并且您将在很长一段时间内与错误一起玩打地鼠游戏。

虽然module看起来像一个全局对象,它实际上是为每个模块重新创建的对象。 documentation对此有明确说明:

[Node.js] helps to provide some global-looking variables that are actually specific to the module, such as:

  • The module and exports objects that the implementor can use to export values from the module.
  • The convenience variables __filename and __dirname, containing the module's absolute filename and directory path.

为模块提供独立的对象进行修改将使代码总体变得更加简单。

在我上面引用的文档部分之上,您会发现:

Before a module's code is executed, Node.js will wrap it with a function wrapper that looks like the following:

(function(exports, require, module, __filename, __dirname) { 
// Module code actually lives in here 
});

您可以从中取出一个页面并进行如下包装:

(function (exporter, ...) {
// Module code here...
});

这是一个例子:

    const source = `
    exporter.exports = {
      SomeVar: "Some Value",
    };
    `;

    function wrapInFunction(source) {
      return `(function (exporter) { ${source} })`;
    }

    const exporter = {
      exports: {},
    };

    eval(wrapInFunction(source))(exporter);
    console.log(exporter);

使用eval的注意事项这里。您可能听说过“eval 是邪恶的”。就目前情况而言,这是事实。这句话是为了提醒人们const x = /* value from some user input */; eval('table.' + x );既不必要(因为您可以执行 table[x] ),又很危险,因为用户输入是原始评估的,并且您不信任用户输入运行任意代码。用户可以设置 x做坏事的人。使用eval在某些情况下仍然是有道理的,就像这里的情况一样。在浏览器中您可以避免 eval将源代码插入 script并聆听 load事件,但您在安全方面没有获得任何好处。这又是特定于平台的。如果您使用 Node.js,则可以使用 vm module ,但它附带了这样的免责声明:“vm 模块不是安全机制。请勿使用它来运行不受信任的代码。”并且它也是特定于平台的。

<小时/>

顺便说一句,您当前的代码不是跨平台的。您的代码取决于 require call,仅在某些平台上可用。 (值得注意的是,如果不加载其他模块,它就不会出现在浏览器上。)我怀疑您将其作为稍后开发的功能的占位符,但我想我还是会提到它,因为跨平台支持是您的目标之一。

关于node.js - 跨平台模块系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54760810/

相关文章:

node.js - Mongoose getter 和 setter 无法正常工作

javascript - Axios 错误 : . .. .data.pipe 不是函数

java - 将 Java 构造函数/方法标记为不供客户端使用的首选方法?

javascript - 在每个 Promise.then 函数之后执行一个通用方法

node.js - 找不到模块 "lodash"

Gradle 编译 : How to identify group and module from dependency?

go - Setter 不在模型中工作

c# - 具有 getter 和 setter 的值类型仍然是值类型吗?

module - 为什么在模块中声明常量时 omp 函数不起作用?

javascript - 何时定义模块以及何时仅使用 requireJS 请求文件