我正在创建一个带有 JavaScript 客户端(在浏览器中运行)和 Node.js 服务器的小型应用程序,使用 WebSocket 进行通信。
我想在客户端和服务器之间共享代码。我才刚刚开始使用 Node.js,至少可以说,我对现代 JavaScript 的了解有点生疏。所以我仍然对 CommonJS 的 require() 函数有所了解。如果我使用“导出”对象创建我的包,那么我看不到如何在浏览器中使用相同的 JavaScript 文件。
我想创建一组在两端使用的方法和类,以方便编码和解码消息以及其他镜像任务。然而,Node.js/CommonJS 打包系统似乎阻止我创建可以在双方使用的 JavaScript 文件。
我还尝试使用 JS.Class 来获得更紧密的 OO 模型,但我放弃了,因为我不知道如何让提供的 JavaScript 文件与 require() 一起工作。我在这里想念什么吗?
如果您想编写一个可以在客户端和服务器端使用的模块,我有一篇关于快速简便方法的简短博文:Writing for Node.js and the browser,基本上如下(其中 this
与 window
相同):
(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 的简单网络协议从另一台机器调用它。
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();
查看 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)
不要忘记 JavaScript 函数的字符串表示表示该函数的源代码。您可以简单地以封装的方式编写函数和构造函数,以便它们可以被 toString() 处理并发送到客户端。
另一种方法是使用构建系统,将公共代码放在单独的文件中,然后将它们包含在服务器和客户端脚本中。我正在通过 WebSockets 将这种方法用于简单的客户端/服务器游戏,其中服务器和客户端都运行基本相同的游戏循环,并且客户端在每个滴答声中都与服务器同步,以确保没有人作弊。
我的游戏构建系统是一个简单的 Bash 脚本,它通过 C 预处理器运行文件,然后通过 sed 清理一些垃圾 cpp 留下的东西,所以我可以使用所有正常的预处理器东西,如#include、#define、 #ifdef 等
我建议查看 RequireJS adapter for Node.js。问题是 Node.js 默认使用的 CommonJS 模块模式不是异步的,它会阻止在 Web 浏览器中加载。 RequireJS 使用 AMD 模式,它既异步又兼容服务器和客户端,只要您使用 r.js
适配器即可。
也许这并不完全符合问题,但我想我会分享这个。
我想制作几个简单的字符串实用函数,在 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')
的东西才能使其工作。所以,它总是从当前文件夹开始,向上移动到根目录,然后向下移动。
如果您使用诸如 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
服务器可以简单地将 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
导出的代码。
window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule)
。
之前的解决方案都没有将 CommonJS 模块系统引入浏览器。
正如其他答案中提到的,有像 Browserify 或 Piler 这样的资产管理器/打包器解决方案,还有像 dnode 或 nowjs 这样的 RPC 解决方案。
但我找不到浏览器的 CommonJS 实现(包括 require()
函数和 exports
/ module.exports
对象等)。所以我自己写了,后来发现有人写得比我好:https://github.com/weepy/brequire。它被称为 Brequire(浏览器要求的缩写)。
从受欢迎程度来看,资产管理公司符合大多数开发人员的需求。但是,如果您需要 CommonJS 的浏览器实现,Brequire 可能适合。
2015 年更新:我不再使用 Brequire(它已经几年没有更新了)。如果我只是编写一个小型的开源模块并且我希望任何人都能够轻松使用,那么我将遵循类似于 Caolan 的答案(上图)的模式——我写了 a blog post 关于它几年前。
但是,如果我正在为私人使用或为 CommonJS 标准化的社区(如 Ampersand 社区)编写模块,那么我将仅以 CommonJS 格式编写它们并使用 Browserify。
用例:在 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) 中: 您现在可以打开开发工具并访问全局变量 config
在 Node.js (app.js) 中: const config = require('./config');控制台.log(config.foo); // 打印'bar'
使用 Babel 或 TypeScript:从 './config' 导入配置;控制台.log(config.foo); // 打印'bar'
shared.js
和 helpers.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;
- 不幸的是,每个导出的函数都需要一行,但希望这是一个好的解决方案?
export default
will error in Node
now.js 也值得一看。它允许您从客户端调用服务器端,并从服务器端调用客户端函数
如果您想以类似 Node.js 的样式编写浏览器,您可以尝试 dualify。
没有浏览器代码编译,因此您可以不受限制地编写您的应用程序。
将您的代码编写为 RequireJS 模块,并将您的测试编写为 Jasmine 测试。
通过这种方式,可以使用 RequireJS 在任何地方加载代码,并且可以使用 jasmine-html 和 Node.js 中的 jasmine-node 在浏览器中运行测试,而无需修改代码或测试。
我写了 a simple module,它可以被导入(在 Node 中使用 require,或者在浏览器中使用 script 标签),您可以使用它来从客户端和服务器加载模块。
示例用法
1. 定义模块
将以下内容放在文件 log2.js
中,位于您的静态 Web 文件文件夹中:
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 的当前目录中,请确保使用静态 Web 文件文件夹(或模块所在的任何位置)的路径调用 loader.setRoot
。
在浏览器中:
首先,定义网页:
<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 服务器部署解决方案)。
如果一切顺利,将会出现:
https://i.stack.imgur.com/i9Wgi.png
我写了这个,如果你想将所有变量设置为全局范围,它很容易使用:
(function(vars, global) {
for (var i in vars) global[i] = vars[i];
})({
abc: function() {
...
},
xyz: function() {
...
}
}, typeof exports === "undefined" ? this : exports);
window.exports = 123;
时会发生什么?我不确定针对typeof .. 'undefined'
进行测试是否是最好的方法,对阳性情况进行测试会更好吗?