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

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

我想在客户机和服务器之间共享代码。我才刚刚开始使用Node.js,至少可以说,我对现代JavaScript的知识有点生疏。因此,我仍然对CommonJS的require()函数感到困惑。如果我通过使用“export”对象创建我的包,那么我无法看到我如何在浏览器中使用相同的JavaScript文件。

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

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

87488 次浏览

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

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

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

服务器可以简单地将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导出的代码。

如果你想编写一个既可以在客户端也可以在服务器端使用的模块,我有一篇简短的博客文章,介绍了一个快速简单的方法:为Node.js和浏览器编写 .js,本质上如下所示(其中thiswindow相同):

(function(exports){


// Your code goes here


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


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

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

你可能还会对DNode感兴趣,它允许你公开一个JavaScript函数,这样就可以从另一台机器上使用一个简单的基于json的网络协议来调用它。

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

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

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();

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

正如在其他答案中提到的,有像Browserify堆垛机这样的资产管理器/打包器解决方案,还有像dnodenowjs这样的RPC解决方案。

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

从受欢迎程度来看,资产管理公司符合大多数开发商的需求。然而,如果你需要CommonJS的浏览器实现,Brequire可能会符合要求。

2015年更新:我不再使用Brequire(它在几年里没有更新)。如果我只是在编写一个小型的开源模块,并且我希望任何人都能轻松使用,那么我将遵循类似于Caolan的回答(上面)的模式——我在几年前写了一篇博文关于它。

然而,如果我是为私人使用或为一个在CommonJS上标准化的社区(如&社区)编写模块,那么我将用CommonJS格式编写它们并使用Browserify

也许这不完全符合你的问题,但我还是想分享一下。

我想在string上声明几个简单的string实用函数。原型,可用于节点和浏览器。我只是把这些函数保存在一个名为utilities.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>

我希望这些信息对别人有用,而不是对我。

如果你想用node .js风格编写浏览器,你可以尝试dualify

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

将代码编写为RequireJS模块,将测试编写为茉莉花测试。

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

这里是一个工作示例

在浏览器的Node.js模块模式、AMD模块模式和全局模式中检查jQuery源代码:

(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)

我写了这个,如果你想将所有变量设置为全局作用域,使用起来很简单:

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

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

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

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

过时的解决方案:

  • < p >文件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'
    

我写了一个简单的模块,它可以被导入(在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服务器部署解决方案)。

如果一切顺利,将会出现:

enter image description here

如果你使用诸如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