如何在 Node.js 和浏览器之间共享代码?

Posted

技术标签:

【中文标题】如何在 Node.js 和浏览器之间共享代码?【英文标题】:How can I share code between Node.js and the browser? 【发布时间】:2011-03-14 14:33:08 【问题描述】:

我正在创建一个带有 javascript 客户端(在浏览器中运行)和 Node.js 服务器的小型应用程序,使用 WebSocket 进行通信。

我想在客户端和服务器之间共享代码。我才刚刚开始使用 Node.js,至少可以说,我对现代 JavaScript 的了解有点生疏。所以我仍然对 CommonJS 的 require() 函数有所了解。如果我使用“导出”对象创建我的包,那么我看不到如何在浏览器中使用相同的 JavaScript 文件。

我想创建一组用于两端的方法和类,以方便编码和解码消息以及其他镜像任务。但是,Node.js/CommonJS 打包系统似乎阻止我创建可以在双方使用的 JavaScript 文件。

我也尝试使用 JS.Class 来获得更紧密的 OO 模型,但我放弃了,因为我不知道如何让提供的 JavaScript 文件与 require() 一起工作。我在这里有什么遗漏吗?

【问题讨论】:

感谢大家发布对此问题的其他答案。这显然是一个将迅速变化和发展的话题。 【参考方案1】:

如果你想编写一个可以在客户端和服务器端使用的模块,我有一篇关于快速简便方法的简短博文:Writing for Node.js and the browser,基本上如下(其中thiswindow 相同):

(function(exports)

    // Your code goes here

   exports.test = function()
        return 'hello world'
    ;

)(typeof exports === 'undefined'? this['mymodule']=: exports);

另外还有一些项目旨在在客户端实现 Node.js API,例如 Marak 的 gemini

您可能还对DNode 感兴趣,它允许您公开一个 JavaScript 函数,以便可以使用基于 JSON 的简单网络协议从另一台机器调用它。

【讨论】:

优秀。谢谢你的信息,曹兰。 草兰的文章真的很棒。我明白了,它奏效了,现在我又开始了。太棒了! 我在自己的项目中使用RequireJs,这将允许我在客户端和服务器上共享我的模块。我们会看看结果如何。 当现有网站有一个全局计数器window.exports = 123; 时会发生什么?我不确定针对 typeof .. 'undefined' 进行测试是否是最好的方法,对阳性案例进行测试会更好吗? gemini 链接已失效。【参考方案2】:

Epeli 在这里有一个很好的解决方案 http://epeli.github.com/piler/ 甚至可以在没有库的情况下工作,只需将它放在一个名为 share.js 的文件中

(function(exports)

  exports.test = function()
       return 'This is a function from shared module';
  ;

(typeof exports === 'undefined' ? this.share =  : exports));

在服务器端只需使用:

var share = require('./share.js');

share.test();

在客户端只需加载js文件然后使用

share.test();

【讨论】:

我比接受的答案更喜欢这个答案,因为它更适合像我这样的新手解释。 在我的 Express 文件夹中,除了静态(公共)文件夹之外,我还有一个名为“shared”的文件夹,也可以从客户端访问,就像这样的“公共”文件夹:app.use(express .static('public')); app.use(express.static('shared'));您的帖子扩展了我与客户端和服务器共享文件的想法。这正是我所需要的。谢谢! 这个解决方案 + git subtree == 真棒。谢谢! @broesch 这在 ES6 中如何工作?我以new question 的身份提出了这个问题,遇到了一些特定于 ES6 的问题,但我很高兴在这里看到编辑!【参考方案3】:

查看 jQuery 源代码,使其在 Node.js 模块模式、AMD 模块模式和浏览器中的全局模式下工作:

(function(window)
    var jQuery = 'blah';

    if (typeof module === "object" && module && typeof module.exports === "object") 

        // Expose jQuery as module.exports in loaders that implement the Node
        // module pattern (including browserify). Do not create the global, since
        // the user will be storing it themselves locally, and globals are frowned
        // upon in the Node module world.
        module.exports = jQuery;
    
    else 
        // Otherwise expose jQuery to the global object as usual
        window.jQuery = window.$ = jQuery;

        // Register as a named AMD module, since jQuery can be concatenated with other
        // files that may use define, but not via a proper concatenation script that
        // understands anonymous AMD modules. A named AMD is safest and most robust
        // way to register. Lowercase jquery is used because AMD module names are
        // derived from file names, and jQuery is normally delivered in a lowercase
        // file name. Do this after creating the global so that if an AMD module wants
        // to call noConflict to hide this version of jQuery, it will work.
        if (typeof define === "function" && define.amd) 
            define("jquery", [], function ()  return jQuery; );
        
    
)(this)

【讨论】:

这是最好的方法(对于我需要的)。这是我创建的一个工作示例:gist.github.com/drmikecrowe/4bf0938ea73bf704790f【参考方案4】:

不要忘记,JavaScript 函数的字符串表示代表该函数的源代码。您可以简单地以封装的方式编写函数和构造函数,以便它们可以被 toString() 处理并发送到客户端。

另一种方法是使用构建系统,将公共代码放在单独的文件中,然后将它们包含在服务器和客户端脚本中。我正在通过 WebSockets 将这种方法用于简单的客户端/服务器游戏,其中服务器和客户端都运行基本相同的游戏循环,并且客户端在每个滴答声中都与服务器同步,以确保没有人作弊。

我的游戏构建系统是一个简单的Bash 脚本,它通过 C 预处理器运行文件,然后通过 sed 清理一些垃圾 cpp 留下的东西,所以我可以使用所有正常的预处理器东西,比如 #include, #define、#ifdef 等。

【讨论】:

我从未想过将 javascript 函数序列化为字符串。感谢您的提示。【参考方案5】:

我建议您查看RequireJS adapter for Node.js。问题是 Node.js 默认使用的 CommonJS 模块模式不是异步的,它会阻止在 Web 浏览器中加载。只要你使用r.js 适配器,RequireJS 使用 AMD 模式,它既异步又兼容服务器和客户端。

【讨论】:

有异步库【参考方案6】:

也许这不完全符合问题,但我想我会分享这个。

我想制作几个简单的字符串实用函数,在 String.prototype 上声明,可供节点和浏览器使用。我只是将这些函数保存在一个名为 utility.js 的文件中(在一个子文件夹中),并且可以轻松地从我的浏览器代码中的脚本标记以及在我的 Node.js 脚本中使用 require(省略 .js 扩展名)来引用它:

my_node_script.js

var utilities = require('./static/js/utilities')

my_browser_code.html

<script src="/static/js/utilities.js"></script>

我希望这对我以外的人有用。

【讨论】:

我喜欢这种方法,但我发现我的静态文件经常被移动。我发现的一种解决方案是重新导出模块。例如,使用单行 module.exports = require('./static/js/utilities'); 创建 utilites.js。这样,如果您随机播放内容,您只需要更新一个路径。 我喜欢这个主意。只是关于路径的注释,我花了一段时间才弄清楚。我的utilities.js 位于项目下的shared 文件夹中。使用require('/shared/utilities') 给了我错误Cannot find module '/shared/utilities'。我必须使用类似require('./../../shared/utilities') 的东西才能使其工作。所以,它总是从当前文件夹开始,向上移动到根目录,然后向下移动。 现在我看到了共享模块的放置位置 - 在静态文件夹中。感谢您的信息! 为什么这不是一个更广泛共享的答案?这非常简单,在没有其他建议的情况下对我有用,即使在现代浏览器中也是如此!【参考方案7】:

如果您使用诸如 webpack 之类的模块捆绑器来捆绑 JavaScript 文件以在浏览器中使用,您可以简单地将 Node.js 模块重用于在浏览器中运行的前端。换句话说,您的 Node.js 模块可以在 Node.js 和浏览器之间共享。

例如,你有如下代码 sum.js:

普通 Node.js 模块:sum.js

const sum = (a, b) => 
    return a + b


module.exports = sum

在 Node.js 中使用模块

const sum = require('path-to-sum.js')
console.log('Sum of 2 and 5: ', sum(2, 5)) // Sum of 2 and 5:  7

在前端重用它

import sum from 'path-to-sum.js'
console.log('Sum of 2 and 5: ', sum(2, 5)) // Sum of 2 and 5:  7

【讨论】:

【参考方案8】:

服务器可以简单地将 JavaScript 源文件发送到客户端(浏览器),但诀窍是客户端必须提供一个小型“导出”环境,然后才能exec 代码并将其存储为模块。

制作这种环境的一种简单方法是使用闭包。例如,假设您的服务器通过 HTTP 提供源文件,例如 http://example.com/js/foo.js。浏览器可以通过 XMLHttpRequest 加载所需的文件并像这样加载代码:

ajaxRequest(
  method: 'GET',
  url: 'http://example.com/js/foo.js',
  onSuccess: function(xhr) 
    var pre = '(function()var exports=;'
      , post = ';return exports;)()';
    window.fooModule = eval(pre + xhr.responseText + post);
  
);

关键是客户端可以将外部代码包装到一个匿名函数中以立即运行(闭包),该函数创建“exports”对象并返回它,以便您可以将其分配到您想要的位置,而不是污染全局命名空间。在此示例中,它被分配给窗口属性fooModule,其中将包含文件foo.js 导出的代码。

【讨论】:

每次使用 eval 都会杀死一个 gnome 我会使用window.fooModule = ; (new Function('exports', xhr.responseText))(window.fooModule)【参考方案9】:

之前的解决方案都没有将 CommonJS 模块系统引入浏览器。

正如其他答案中提到的,有资产管理器/打包器解决方案,如 Browserify 或 Piler,还有 RPC 解决方案,如 dnode 或 nowjs。

但我找不到用于浏览器的 CommonJS 实现(包括 require() 函数和 exports / module.exports 对象等)。所以我自己写了,后来发现有人写得比我好:https://github.com/weepy/brequire。它被称为 Brequire(Browser require 的缩写)。

从受欢迎程度来看,资产管理器符合大多数开发人员的需求。但是,如果您需要 CommonJS 的浏览器实现,Brequire 可能适合。

2015 年更新:我不再使用 Brequire(它已经几年没有更新了)。如果我只是在编写一个小型的开源模块,并且我希望任何人都能够轻松使用,那么我将遵循类似于 Caolan 的答案(上图)的模式——我写了几年 a blog post以前。

但是,如果我正在为私人使用或为 CommonJS 标准化的社区(如 Ampersand 社区)编写模块,那么我将仅以 CommonJS 格式编写它们并使用 Browserify。

【讨论】:

【参考方案10】:

用例:在 Node.js 和浏览器之间共享您的应用配置(这只是一个说明,可能不是最佳方法,具体取决于您的应用)。

问题:您不能使用window(Node.js 中不存在)或global(浏览器中不存在)。

编辑:现在我们可以感谢 globalThis 和 Node.js >= 12。

过时的解决方案:

文件 config.js:

  var config = 
    foo: 'bar'
  ;
  if (typeof module === 'object') module.exports = config;

在浏览器中(index.html):

  <script src="config.js"></script>
  <script src="myApp.js"></script>

您现在可以打开开发工具并访问全局变量config

在 Node.js (app.js) 中:

  const config = require('./config');
  console.log(config.foo); // Prints 'bar'

使用 Babel 或 TypeScript:

  import config from './config';
  console.log(config.foo); // Prints 'bar'

【讨论】:

后续:假设我有两个文件在 server.js 和 client.js 之间共享:shared.jshelpers.js -- shared.js 使用来自 helpers.js 的函数,所以它需要const helperFunc = require('./helpers') 在顶部,它可以在服务器端工作。问题出在客户端,它抱怨require 不是一个函数,但是如果我将require 行包装在if (typeof module === 'object') ... 中,服务器会说没有定义helperFunc()(在if 语句之外)。有什么想法让它在两者上都起作用吗? 更新:我似乎已经把它放在shared.js 的顶部:helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc; -- 不幸的是,每个导出的函数都需要一行,但希望这是一个好的解决方案? 使用 Babel 或 Typescript(使用 vite.js)给出未捕获的 SyntaxError: The requested module '/src/uti/index.js?t=1616864600284' does not provide an export named 'default' @987654339 @ 将在 Node 中出错【参考方案11】:

now.js 也值得一看。它允许您从客户端调用服务器端,并从服务器端调用客户端函数

【讨论】:

这个项目已经停止了——你知道有什么好的替代品吗? groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ 我知道的唯一另外一个是bridge,它是由同一个人制作的,所以也被遗弃了。 0.9 版的 socket.io 也支持事件回调——虽然没有什么比 now.js 的共享代码,但它运行得很好。 还有sharejs,似乎在积极维护。 sharejs.org【参考方案12】:

如果你想用类似 Node.js 的风格编写浏览器,你可以试试dualify。

没有浏览器代码编译,因此您可以不受限制地编写您的应用程序。

【讨论】:

链接已失效。该链接太死了,以至于 DuckDuckGo 和 Google 搜索引擎在搜索“dualifyjs”时找不到任何东西。 DDG searchGoogle search。浏览所有 8 页的 Google 搜索结果中的“dualify”,似乎没有提到 JavaScript 库。我找到了一个GitHub repo with the name "Dualify",但它完全是空的。我找到了一个GitHub repo that just cloned this question and its answers 这是 8 年的唯一答案,Javascript 世界发生了许多变化。例如 nowdays 节点支持 ecma 脚本模块 (.mjs) (nodejs.org/api/esm.html)【参考方案13】:

将您的代码编写为RequireJS 模块,将您的测试编写为Jasmine 测试。

这样,代码可以在任何地方使用 RequireJS 加载,测试可以在浏览器中使用 jasmine-html 和 Node.js 中的 jasmine-node 运行,而无需修改代码或测试。

这里是a working example。

【讨论】:

【参考方案14】:

我写了a simple module,它可以被导入(在Node中使用require,或者在浏览器中使用脚本标签),你可以使用它来从客户端和服务器加载模块。

示例用法

1。定义模块

将以下内容放入您的静态 Web 文件夹中的文件 log2.js

let exports = ;

exports.log2 = function(x) 
    if ( (typeof stdlib) !== 'undefined' )
        return stdlib.math.log(x) / stdlib.math.log(2);

    return Math.log(x) / Math.log(2);
;

return exports;

就这么简单!

2。使用模块

由于它是一个双向模块加载器,我们可以从两侧(客户端和服务器)加载它。因此,您可以执行以下操作,但不需要同时执行这两项操作(更不用说按特定顺序):

在节点中

在 Node 中,这很简单:

var loader = require('./mloader.js');
loader.setRoot('./web');

var logModule = loader.importModuleSync('log2.js');
console.log(logModule.log2(4));

这应该返回2

如果您的文件不在 Node 的当前目录中,请确保调用 loader.setRoot 并使用您的静态 Web 文件文件夹(或您的模块所在的任何位置)的路径。

在浏览器中:

首先,定义网页:

<html>
    <header>
        <meta charset="utf-8" />
        <title>Module Loader Availability Test</title>

        <script src="mloader.js"></script>
    </header>

    <body>
        <h1>Result</h1>
        <p id="result"><span style="color: #000088">Testing...</span></p>

        <script>
            let mod = loader.importModuleSync('./log2.js', 'log2');

            if ( mod.log2(8) === 3 && loader.importModuleSync('./log2.js', 'log2') === mod )
                document.getElementById('result').innerHTML = "Your browser supports bilateral modules!";

            else
                document.getElementById('result').innerHTML = "Your browser doesn't support bilateral modules.";
        </script>
    </body>
</html>

确保您不要直接在浏览器中打开文件;由于它使用 AJAX,我建议您改为查看 Python 3 的 http.server 模块(或任何您的超快速、命令行、文件夹 Web 服务器部署解决方案)。

如果一切顺利,就会出现这个:

【讨论】:

【参考方案15】:

这是我写的,如果你想将所有变量设置为全局范围,使用起来很简单:

(function(vars, global) 
    for (var i in vars) global[i] = vars[i];
)(
    abc: function() 
        ...
    ,
    xyz: function() 
        ...
    
, typeof exports === "undefined" ? this : exports);

【讨论】:

以上是关于如何在 Node.js 和浏览器之间共享代码?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 AngularJS 客户端和 Node.js 服务器之间重用代码 [关闭]

使Node.js中的http.request适用于浏览器

如何在 node.js 中的模块之间共享连接池?

如何在没有浏览器的 node.js 中使用 FormData?

如何使用 node.js、Express 和 knox 将文件从浏览器上传到 Amazon S3? [关闭]

在 node.js 中的客户端和服务器 js 文件之间共享 mysql 连接