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


241

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

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

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

我也尝试使用JS.Class来获得更严格的OO模型,但是我放弃了,因为我无法弄清楚如何使提供的JavaScript文件与require()一起使用。我在这里想念什么吗?


4
谢谢大家为这个问题发布其他答案。显然,这是一个将迅速变化和发展的话题。
西蒙·凯夫

Answers:


169

如果您想编写一个既可以在客户端又可以在服务器端使用的模块,那么我有一篇简短的博客文章,介绍了一种快速简便的方法:为Node.js和浏览器编写,实质上是以下内容(this与相同window) :

(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的简单网络协议从另一台计算机上调用它。


优秀的。感谢您提供的信息,草兰。
西蒙·凯夫

2
真是很棒的草篮文章。我理解它,它起作用了,现在我又滚动了。太棒了!
Michael Dausmann

2
我在自己的项目中使用RequireJs,这将允许我在客户端和服务器上共享模块。我们将看看它是如何工作的。
kamranicus 2012年

5
@Caolan那个链接已经死了
Kamal Reddy

5
gemini链接已死。
borisdiakur

43

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

10
我喜欢这个答案比接受这个答案更好,因为它可以为像我这样的新手更好地解释。
Howie 2014年

在我的Express文件夹中,除了静态(公共)文件夹外,我还有一个名为“共享”的文件夹,也可以从客户端访问该文件夹,例如“ public”文件夹,如下所示:app.use(express.static('public')) ; app.use(express.static('shared')); 您的帖子扩展了我与客户端和服务器共享文件的想法。这正是我所需要的。谢谢!
合并

这个解决方案+ git subtree ==很棒。谢谢!
kevinmicke

@broesch在ES6中如何工作?我已经将其作为一个新问题问了,但存在一些ES6特定的问题,但是我很高兴在这里看到编辑!
Tedskovsky

15

检出使它能够在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)

这是最好的方法(适合我的需求)。这是我创建的一个工作示例:gist.github.com/drmikecrowe/4bf0938ea73bf704790f
Mike Crowe

13

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

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

我的游戏构建系统是一个简单的Bash脚本,该脚本通过C预处理程序运行文件,然后通过sed清除后面的垃圾cpp树叶,因此我可以使用所有常规的预处理程序,例如#include,#define,#ifdef等


2
我将JavaScript函数序列化为字符串从未发生过。谢谢你的提示。
西蒙·凯夫

13

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


有一个异步库
Jacek Pietal

11

也许这与问题并不完全一致,但我想我也同意。

我想使在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>

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


1
我喜欢这种方法,但是我发现我的静态文件移动了很多。我发现的一种解决方案是重新导出模块。例如,utilites.js用一行创建module.exports = require('./static/js/utilities');。这样一来,如果您随机整理内容,则只需更新一条路径。
Tom Makin 2014年

我喜欢这个主意。只是一条关于道路的便条纸,我花了一些时间才弄清楚。我utilities.jsshared项目下的文件夹中。使用require('/shared/utilities')给了我错误Cannot find module '/shared/utilities'。我必须使用类似的东西require('./../../shared/utilities')才能使其正常工作。因此,它总是从当前文件夹移到根目录然后再​​向下。
纽曼2015年

现在,我看到将共享模块放置在静态文件夹中的位置。谢谢(你的)信息!
合并

8

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

4

服务器可以简单地将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”对象并返回它,以便您可以将其分配到所需的位置,而不用污染全局名称空间。在此示例中,将其分配给window属性fooModule,该属性将包含file导出的代码foo.js


2
每次使用eval时,您都会杀死一个侏儒
Jacek Pietal,2015年

1
我会用window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule)
GingerPlusPlus

2

以前的解决方案都没有将CommonJS模块系统带到浏览器。

正如在其他的答案中提到,有喜欢的资产管理公司/打包解决方案Browserify堆垛机和有像RPC解决方案dnodenowjs

但是我找不到用于浏览器的CommonJS实现(包括require()函数和exports/ module.exports对象等)。所以我写了自己的书,后来才发现有人写的比我写的要好:https : //github.com/weepy/brequire。这就是所谓的Brequire(“浏览器要求”的缩写)。

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

2015年更新:我不再使用Brequire(几年来没有更新)。如果我只是在编写一个小的开放源代码模块,并且希望任何人都可以轻松使用,那么我将遵循类似于Caolan的答案的模式(上)-我写了一篇关于它的博客文章前。

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


1

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


1
该项目已经终止-您知道有什么好的替代品吗?groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
安德森·格林

我认识的唯一一个人是桥,是同一个人的,所以也被抛弃了。0.9版的socket.io还支持事件的回调-但是,它与now.js的共享代码完全不同,但是效果很好。
balupton

还有sharejs,似乎正在积极维护。sharejs.org
安德森·格林

1

如果您想使用类似Node.js的样式来编写浏览器,可以尝试dualify

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



1

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

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

解:

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

1
这次真是万分感谢。
Microsis

后续:假设我有两个文件在server.js和client.js之间共享:shared.jshelpers.js- shared.js使用from中的函数helpers.js,因此它需要const { helperFunc } = require('./helpers')在顶部,以便它在服务器端工作。问题出在客户端上,它抱怨require不是一个函数,但是如果我将require行if (typeof module === 'object') { ... }换成,服务器会说没有定义helperFunc()(在if语句之外)。有什么想法可以使其同时起作用吗?
Microsis

更新:我似乎已经通过将其放在以下位置来使它起作用shared.jshelperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;-不幸的是,每个导出函数都需要一行,但是希望这是一个好的解决方案?
Microsis

1

我编写了一个简单的模块,可以导入该模块(使用Node中的require或浏览器中的脚本标签),您可以使用该模块从客户端和服务器加载模块。

用法示例

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的当前目录中,请确保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服务器部署解决方案)。

如果一切顺利,将显示:

在此处输入图片说明


0

我写了这个,如果要将所有变量设置为全局范围,使用起来很简单:

(function(vars, global) {
    for (var i in vars) global[i] = vars[i];
})({
    abc: function() {
        ...
    },
    xyz: function() {
        ...
    }
}, typeof exports === "undefined" ? this : exports);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.