Node.jsとブラウザ間でコードを共有するにはどうすればよいですか?


242

JavaScriptクライアント(ブラウザーで実行)とNode.jsサーバーを備え、WebSocketを使用して通信する小さなアプリケーションを作成しています。

クライアントとサーバー間でコードを共有したいと思います。私はNode.jsから始めたばかりで、現代のJavaScriptに関する私の知識は控えめに言っても少し錆びています。したがって、私はCommonJSのrequire()関数についてまだ頭を抱えています。'export'オブジェクトを使用してパッケージを作成している場合、ブラウザーで同じJavaScriptファイルを使用する方法がわかりません。

メッセージのエンコードとデコード、およびその他のミラーリングされたタスクを容易にするために、両端で使用される一連のメソッドとクラスを作成したいと思います。ただし、Node.js / CommonJSパッケージングシステムでは、両側で使用できるJavaScriptファイルを作成できないようです。

さらに、JS.Classを使用してより緊密なOOモデルを取得しようとしましたが、提供されたJavaScriptファイルをrequire()で動作させる方法を理解できなかったため、あきらめました。ここに欠けているものはありますか?


4
この質問に対する追加の回答を投稿してくださった皆さん、ありがとうございました。これは明らかに急速に変化し進化するトピックです。
Simon Cave

回答:


168

あなたは、クライアント側とサーバー側の両方を使用することができますモジュールを書きたい場合は、私が迅速かつ簡単な方法で、短いブログ記事を持っている:Node.jsのとブラウザのために書く、(ここで、基本的に以下thisと同じですwindow) :

(function(exports){

    // Your code goes here

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

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

あるいは、Marakのgeminiなど、クライアント側にNode.js APIを実装することを目的としたプロジェクトがいくつかあります。

JavaScript関数を公開して、単純なJSONベースのネットワークプロトコルを使用して別のマシンから呼び出せるようにするDNodeにも興味があるかもしれません。


優れた。情報をありがとう、Caolan。
Simon Cave

2
本当に素晴らしい記事Caolan。私はそれを理解しました、それはうまくいきました、今私は再び転がっています。素晴らしい!
Michael Dausmann、2011

2
私は自分のプロジェクトでRequireJsを使用しています。これにより、クライアントとサーバーでモジュールを共有できます。それがどのように機能するかを見ていきます。
kamranicus

5
@Caolanそのリンクは死んでいます
Kamal Reddy

5
ジェミニのリンクは死んでいます。
borisdiakur 2015

42

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
この答えは、私のような初心者にはわかりやすいので、受け入れられた答えよりも好きです。
ハウィー2014

私のExpressフォルダーには、静的(パブリック)フォルダーのほかに、「共有」という名前のフォルダーもあり、「public」フォルダーのようにクライアントからもアクセスできます:app.use(express.static( 'public')) ; app.use(express.static( 'shared')); そして、あなたの投稿はクライアントとサーバーでファイルを共有するという私の考えを拡張します。これはまさに私が必要としたものです。ありがとうございました!
2017年

このソリューション+ 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()で送信してクライアントに送信することができます。

これを行う別の方法は、ビルドシステムを使用して、共通コードを個別のファイルに入れ、サーバーとクライアントの両方のスクリプトに含めることです。私はWebSocketを介した単純なクライアント/サーバーゲームにそのアプローチを使用しています。サーバーとクライアントの両方が基本的に同じゲームループを実行し、クライアントがティックごとにサーバーと同期して、だまされないようにします。

ゲームのビルドシステムは、Cプリプロセッサを介してファイルを実行し、次にsedを介して背後にあるジャンクcppリーフをクリーンアップする単純なBashスクリプトです。そのため、#include、#define、#ifdefなどの通常のプリプロセッサをすべて使用できます。 、など


2
文字列としてjavascript関数をシリアル化することは、私には決して起こらなかった。先端をありがとう。
Simon Cave

13

Node.js用RequireJSアダプターを調べることをお勧めします。問題は、Node.jsがデフォルトで使用するCommonJSモジュールパターンが非同期ではなく、Webブラウザーでのロードがブロックされることです。アダプタを使用している限り、RequireJSはAMDパターンを使用します。これは非同期であり、サーバーとクライアントの両方と互換性がありますr.js


非同期ライブラリがあります
Jacek Pietal

11

多分これは質問と完全に一致していませんが、私はこれを共有したいと思いました。

String.prototypeで宣言されているいくつかの単純な文字列ユーティリティ関数を、ノードとブラウザの両方で使用できるようにしたいと考えました。これらの関数は、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>

これが私以外の人の役に立つ情報であることを願っています。


1
私はこのアプローチが好きですが、静的ファイルがかなり移動することがわかりました。私が見つけた1つの解決策は、モジュールを再エクスポートすることです。たとえばutilites.js、1行で作成しmodule.exports = require('./static/js/utilities');ます。この方法では、ものをシャッフルする場合に1つのパスを更新するだけで済みます。
Tom Makin

私はこのアイデアが好きです。私が理解するのにしばらくかかった道のちょうどメモ。私utilities.jssharedプロジェクトのフォルダにあります。使用require('/shared/utilities')するとエラーが発生しましたCannot find module '/shared/utilities'。これrequire('./../../shared/utilities')を機能させるには、このようなものを使用する必要があります。そのため、常に現在のフォルダーからルートに移動し、次にルートに移動します。
ニューマン2015年

これで、共有モジュールを配置する場所が静的フォルダーに表示されます。情報をありがとう!
結合

9

次のような使用モジュールbundlers使用する場合は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);
  }
});

重要なのは、クライアントが外部コードを匿名関数にラップしてすぐに実行される(クロージャー)ことで、「エクスポート」オブジェクトが作成されて返されるため、グローバル名前空間を汚染するのではなく、好きな場所に割り当てることができるということです。この例ではfooModule、ファイルによってエクスポートされたコードを含むwindow属性に割り当てられていますfoo.js


2
evalを使用する
たびに

1
使用しますwindow.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule)
GingerPlusPlus 2016

2

以前のソリューションでは、CommonJSモジュールシステムをブラウザーに提供していません。

他の回答で述べたように、などの資産管理/パッケージャのソリューションがあるBrowserifyまたはパイラーはなどRPCのソリューションがあるdnodeまたはnowjsが

しかし、ブラウザ用のCommonJSの実装(require()関数やexports/ module.exportsオブジェクトなどを含む)を見つけることができませんでした。それで私は自分で書いたのですが、後で誰かが私よりも上手に書いたことを発見しました:https : //github.com/weepy/brequire。これは、Brequire(ブラウザが必要とする)と呼ばれます。

人気から判断すると、アセットマネージャーはほとんどの開発者のニーズに適合します。ただし、CommonJSのブラウザー実装が必要な場合は、Brequireがおそらくその要件に適合します。

2015年の更新: Brequireを使用しなくなりました(数年で更新されていません)。小さなオープンソースモジュールを作成しているだけで、誰でも簡単に使用できるようにしたい場合は、Caolanの回答(上記)に似たパターンに従います-これについて2 、3年ブログ投稿しました前。

私は(のような私的使用のためか、CommonJSに標準化されているコミュニティのためのモジュールを書いている場合は、アンパサンドのコミュニティ)、その後、私はちょうどCommonJS形式でそれらを書いて使用しますBrowserifyを


1

now.jsも一見の価値があります。クライアント側からサーバー側を呼び出し、サーバー側からクライアント側の関数を呼び出すことができます。


1
プロジェクトは中止されました-あなたはそれの良い代替品を知っていますか?groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
アンダーソングリーン

私が知っている唯一の他のものは橋であり、それは同じ人々によるものだったので、放棄されました。socket.ioのバージョン0.9は、イベントのコールバックもサポートしていますが、now.jsの共有コードとは異なりますが、十分に機能します。
balupton 2013年

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の間で共有される2つのファイルがあるとします。shared.jsそしてhelpers.js- shared.jsがからの関数を使用する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.モジュールの使用

これは双方向モジュールローダーなので、両側(クライアントとサーバー)からロードできます。したがって、次のことを実行できますが、両方を同時に実行する必要はありません(特定の順序で単独で実行する必要はありません)。

  • ノード内

ノードでは、それは簡単です:

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

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

これは戻るはず2です。

ファイルがNodeの現在のディレクトリにない場合は、必ずloader.setRoot静的Webファイルフォルダーへのパス(またはモジュールがどこにあるか)を使用して呼び出してください。

  • ブラウザで:

まず、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を使用しているため、http.server代わりにPython 3のモジュール(または、超高速、コマンドライン、フォルダーWebサーバーデプロイメントソリューション)を確認することをお勧めします。

すべてがうまくいけば、これは表示されます:

ここに画像の説明を入力してください


0

これは私が書いたもので、すべての変数をグローバルスコープに設定する場合は簡単に使用できます。

(function(vars, global) {
    for (var i in vars) global[i] = vars[i];
})({
    abc: function() {
        ...
    },
    xyz: function() {
        ...
    }
}, typeof exports === "undefined" ? this : exports);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.