Wie kann ich Code zwischen Node.js und dem Browser freigeben?

241

Ich erstelle eine kleine Anwendung mit einem JavaScript-Client (im Browser ausgeführt) und einem Node.js-Server, der über WebSocket kommuniziert.

Ich möchte Code zwischen dem Client und dem Server teilen. Ich habe gerade erst mit Node.js angefangen und meine Kenntnisse über modernes JavaScript sind, gelinde gesagt, etwas verrostet. Ich beschäftige mich also immer noch mit der CommonJS require () -Funktion. Wenn ich meine Pakete mit dem Objekt 'export' erstelle, kann ich nicht sehen, wie ich dieselben JavaScript-Dateien im Browser verwenden kann.

Ich möchte eine Reihe von Methoden und Klassen erstellen, die an beiden Enden verwendet werden, um das Codieren und Decodieren von Nachrichten und andere gespiegelte Aufgaben zu erleichtern. Die Node.js / CommonJS-Verpackungssysteme scheinen mich jedoch daran zu hindern, JavaScript-Dateien zu erstellen, die auf beiden Seiten verwendet werden können.

Ich habe auch versucht, mit JS.Class ein engeres OO-Modell zu erhalten, aber ich habe aufgegeben, weil ich nicht herausfinden konnte, wie die bereitgestellten JavaScript-Dateien mit require () funktionieren. Fehlt mir hier etwas?

Simon Cave
quelle
4
Vielen Dank an alle, die zusätzliche Antworten auf diese Frage veröffentlicht haben. Dies ist eindeutig ein Thema, das sich schnell ändern und weiterentwickeln wird.
Simon Cave

Antworten:

169

Wenn Sie ein Modul schreiben möchten, das sowohl clientseitig als auch serverseitig verwendet werden kann, habe ich einen kurzen Blogbeitrag über eine schnelle und einfache Methode: Schreiben für Node.js und den Browser , im Wesentlichen das Folgende (wo thisist dasselbe wie window) ::

(function(exports){

    // Your code goes here

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

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

Alternativ gibt es einige Projekte, die darauf abzielen, die Node.js-API auf der Clientseite zu implementieren, z. B. Maraks Zwillinge .

Möglicherweise interessiert Sie auch DNode , mit dem Sie eine JavaScript-Funktion verfügbar machen können , damit sie mithilfe eines einfachen JSON-basierten Netzwerkprotokolls von einem anderen Computer aus aufgerufen werden kann.

Caolan
quelle
Ausgezeichnet. Danke für die Info, Caolan.
Simon Cave
2
Wirklich toller Artikel Caolan. Ich habe es verstanden, es hat funktioniert, jetzt rolle ich wieder. Fantastisch!
Michael Dausmann
2
Ich verwende RequireJs in meinem eigenen Projekt, wodurch ich meine Module auf dem Client und dem Server freigeben kann. Wir werden sehen, wie es funktioniert.
Kamranicus
5
@Caolan, dass Link tot ist
Kamal Reddy
5
Der Zwillingslink ist tot.
Borisdiakur
43

Epeli hat hier eine nette Lösung http://epeli.github.com/piler/ , die sogar ohne die Bibliothek funktioniert. Fügen Sie diese einfach in eine Datei namens share.js ein

(function(exports){

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

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

Auf der Serverseite verwenden Sie einfach:

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

share.test();

Und auf der Clientseite laden Sie einfach die js-Datei und verwenden Sie sie dann

share.test();
broesch
quelle
10
Ich mag diese Antwort besser als die akzeptierte, weil sie für Neulinge wie mich besser erklärt wird.
Howie
In meinem Express-Ordner befindet sich neben dem statischen (öffentlichen) Ordner auch ein Ordner mit dem Namen "freigegeben", auf den auch der Client zugreifen kann, z. B. der Ordner "public": app.use (express.static ("public")) ;; app.use (express.static ('geteilt')); Und Ihr Beitrag erweitert meine Idee, Dateien mit dem Client und dem Server zu teilen. Genau das brauchte ich. Danke dir!
Kombinieren Sie
Diese Lösung + Git Teilbaum == genial. Vielen Dank!
Kevinmicke
@broesch Wie würde das in ES6 funktionieren? Ich habe dies als neue Frage gestellt , mit einigen ES6-spezifischen Problemen, aber ich würde mich genauso freuen, hier eine Bearbeitung zu sehen!
Tedskovsky
15

Überprüfen Sie den jQuery-Quellcode, mit dem dies funktioniert, im Node.js-Modulmuster, im AMD-Modulmuster und global im Browser:

(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)
wlingke
quelle
Dies ist die beste Methode (für das, was ich brauchte). Hier ist ein Arbeitsbeispiel, das ich erstellt habe: gist.github.com/drmikecrowe/4bf0938ea73bf704790f
Mike Crowe
13

Vergessen Sie nicht, dass die Zeichenfolgendarstellung einer JavaScript-Funktion den Quellcode für diese Funktion darstellt. Sie können Ihre Funktionen und Konstruktoren einfach gekapselt schreiben, damit sie toString () 'd sind und an den Client gesendet werden.

Eine andere Möglichkeit besteht darin, ein Build-System zu verwenden, den gemeinsamen Code in separaten Dateien abzulegen und diese dann sowohl in die Server- als auch in die Client-Skripte aufzunehmen. Ich verwende diesen Ansatz für ein einfaches Client / Server-Spiel über WebSockets, bei dem sowohl Server als auch Client im Wesentlichen dieselbe Spielschleife ausführen und der Client bei jedem Tick mit dem Server synchronisiert, um sicherzustellen, dass niemand betrügt.

Mein Build-System für das Spiel ist ein einfaches Bash- Skript, das die Dateien über den C-Präprozessor und dann über sed ausführt, um einige zurückgelassene Junk-CPP-Blätter zu bereinigen, sodass ich alle normalen Präprozessor-Inhalte wie #include, #define, #ifdef verwenden kann , etc.

Dagg Nabbit
quelle
2
Das Serialisieren von Javascript als Zeichenfolgen ist mir nie in den Sinn gekommen. Danke für den Tipp.
Simon Cave
13

Ich würde empfehlen, in den RequireJS-Adapter für Node.js zu schauen . Das Problem ist, dass das von Node.js standardmäßig verwendete CommonJS-Modulmuster nicht asynchron ist, wodurch das Laden im Webbrowser blockiert wird. RequireJS verwendet das AMD-Muster, das sowohl asynchron als auch mit Server und Client kompatibel ist, solange Sie den r.jsAdapter verwenden.

Heiser
quelle
Es gibt eine asynchrone Bibliothek
Jacek Pietal
11

Vielleicht stimmt das nicht ganz mit der Frage überein, aber ich dachte, ich würde das teilen.

Ich wollte ein paar einfache Funktionen des String-Dienstprogramms, die in String.prototype deklariert sind, sowohl für den Knoten als auch für den Browser verfügbar machen. Ich behalte diese Funktionen einfach in einer Datei namens utilities.js (in einem Unterordner) und kann sie sowohl über ein Skript-Tag in meinem Browser-Code als auch mithilfe von require (ohne die Erweiterung .js) in meinem Node.js-Skript referenzieren ::

my_node_script.js

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

my_browser_code.html

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

Ich hoffe, dies sind nützliche Informationen für andere als mich.

Markus Amalthea Magnuson
quelle
1
Ich mag diesen Ansatz, aber ich finde, dass meine statischen Dateien ziemlich viel verschoben werden. Eine Lösung, die ich gefunden habe, besteht darin, das Modul erneut zu exportieren. Erstellen Sie beispielsweise utilites.jsmit einer einzelnen Zeile module.exports = require('./static/js/utilities');. Auf diese Weise müssen Sie nur einen Pfad aktualisieren, wenn Sie Inhalte mischen.
Tom Makin
Ich mag diese Idee. Nur eine Notiz auf dem Weg, die ich eine Weile gebraucht habe, um herauszufinden. Mein utilities.jsbefindet sich im sharedOrdner unter dem Projekt. Die Verwendung require('/shared/utilities')gab mir den Fehler Cannot find module '/shared/utilities'. Ich muss so etwas verwenden require('./../../shared/utilities'), damit es funktioniert. Es geht also immer vom aktuellen Ordner und wandert nach oben zum Stamm und dann nach unten.
Newman
Jetzt sehe ich, wo das freigegebene Modul abgelegt werden soll - im statischen Ordner. Danke für die Information!
Kombinieren Sie den
9

Wenn Sie Modulbündler wie Webpack verwenden, um JavaScript-Dateien für die Verwendung in einem Browser zu bündeln, können Sie Ihr Node.js-Modul einfach für das in einem Browser ausgeführte Frontend wiederverwenden. Mit anderen Worten, Ihr Node.js-Modul kann von Node.js und dem Browser gemeinsam genutzt werden.

Sie haben beispielsweise den folgenden Code sum.js:

Normales Node.js-Modul: sum.js

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

module.exports = sum

Verwenden Sie das Modul in 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

Verwenden Sie es im Frontend wieder

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

Der Server kann einfach JavaScript-Quelldateien an den Client (Browser) senden. Der Trick besteht jedoch darin, dass der Client eine Mini-Exportumgebung bereitstellen muss, bevor er execden Code erstellen und als Modul speichern kann.

Eine einfache Möglichkeit, eine solche Umgebung zu erstellen, ist die Verwendung eines Verschlusses. Angenommen, Ihr Server stellt Quelldateien wie HTTP bereit http://example.com/js/foo.js. Der Browser kann die erforderlichen Dateien über eine XMLHttpRequest laden und den Code wie folgt laden:

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

Der Schlüssel ist, dass der Client den Fremdcode in eine anonyme Funktion einbinden kann, die sofort ausgeführt wird (ein Abschluss), wodurch das Objekt "exportiert" erstellt und zurückgegeben wird, sodass Sie es an der gewünschten Stelle zuweisen können, anstatt den globalen Namespace zu verschmutzen. In diesem Beispiel wird es dem Fensterattribut zugewiesen, das fooModuleden von der Datei exportierten Code enthält foo.js.

Maerics
quelle
2
Jedes Mal, wenn Sie eval verwenden, töten Sie einen Gnom
Jacek Pietal
1
Ich würde verwenden window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule).
GingerPlusPlus
2

Keine der vorherigen Lösungen bringt das CommonJS-Modulsystem in den Browser.

Wie in den anderen Antworten erwähnt, gibt es Asset Manager / Packager-Lösungen wie Browserify oder Piler und RPC-Lösungen wie dnode oder nowjs .

Ich konnte jedoch keine Implementierung von CommonJS für den Browser finden (einschließlich einer require()Funktion und exports/ oder von module.exportsObjekten usw.). Also schrieb ich mein eigenes, um später festzustellen, dass jemand anderes es besser geschrieben hatte als ich: https://github.com/weepy/brequire . Es heißt Brequire (kurz für Browser erforderlich).

Gemessen an der Popularität entsprechen die Vermögensverwalter den Anforderungen der meisten Entwickler. Wenn Sie jedoch eine Browser-Implementierung von CommonJS benötigen, ist Brequire wahrscheinlich genau das Richtige für Sie.

Update 2015: Ich verwende Brequire nicht mehr (es wurde seit einigen Jahren nicht mehr aktualisiert). Wenn ich nur ein kleines Open-Source-Modul schreibe und möchte, dass jeder es einfach verwenden kann, dann folge ich einem ähnlichen Muster wie Caolans Antwort (oben) - ich habe ein paar Jahre lang einen Blog-Beitrag darüber geschrieben vor.

Wenn ich jedoch Module für den privaten Gebrauch oder für eine Community schreibe, die auf CommonJS standardisiert ist (wie die Ampersand- Community), schreibe ich sie einfach im CommonJS-Format und verwende Browserify .

Peter Rust
quelle
1

now.js ist auch einen Blick wert. Sie können serverseitig von der Clientseite und clientseitige Funktionen von der Serverseite aus aufrufen

Balupton
quelle
1
Das Projekt wurde eingestellt - kennen Sie einen guten Ersatz dafür? groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
Anderson Green
Das einzige andere, das ich kenne, war Bridge und es war von denselben Leuten, also auch verlassen. Die Version 0.9 von socket.io unterstützt auch Rückrufe für Ereignisse - allerdings nicht wie der Freigabecode von now.js, aber es funktioniert gut genug.
Balupton
Es gibt auch sharejs, die aktiv gepflegt zu werden scheinen. sharejs.org
Anderson Green
1

Wenn Sie Ihren Browser im Node.js-ähnlichen Stil schreiben möchten, können Sie Dualify ausprobieren .

Es gibt keine Browser-Code-Kompilierung, sodass Sie Ihre Anwendung ohne Einschränkungen schreiben können.

farincz
quelle
1

Schreiben Sie Ihren Code als RequireJS- Module und Ihre Tests als Jasmine- Tests.

Auf diese Weise kann Code überall mit RequireJS geladen werden und die Tests werden im Browser mit jasmine-html und mit jasmine-node in Node.js ausgeführt, ohne dass der Code oder die Tests geändert werden müssen.

Hier ist ein funktionierendes Beispiel dafür.

Blacksonic
quelle
1

Anwendungsfall: Teilen Sie Ihre App-Konfiguration zwischen Node.js und dem Browser (dies ist nur eine Illustration, wahrscheinlich nicht der beste Ansatz, abhängig von Ihrer App).

Problem: Sie können weder window(existiert nicht in Node.js) noch global(existiert nicht im Browser) verwenden.

Lösung:

  • Datei config.js:

    var config = {
      foo: 'bar'
    };
    if (typeof module === 'object') module.exports = config;
  • Im Browser (index.html):

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

    Sie können jetzt die Entwicklungswerkzeuge öffnen und auf die globale Variable zugreifen config

  • In Node.js (app.js):

    const config = require('./config');
    console.log(config.foo); // Prints 'bar'
  • Mit Babel oder TypeScript:

    import config from './config';
    console.log(config.foo); // Prints 'bar'
tanguy_k
quelle
1
Danke dafür.
Microsis
Follow-up: Angenommen, ich habe zwei Dateien, die zwischen server.js und client.js gemeinsam genutzt werden: shared.jsund helpers.js- shared.jsverwendet Funktionen von helpers.js, sodass sie oben benötigt werden, damit sie const { helperFunc } = require('./helpers')serverseitig funktionieren. Das Problem liegt auf dem Client, es beschwert sich darüber, dass es requirekeine Funktion ist, aber wenn ich die erforderliche Zeile if (typeof module === 'object') { ... }einbinde, sagt der Server, dass helperFunc () nicht definiert ist (außerhalb der if-Anweisung). Irgendwelche Ideen, um es auf beiden zum Laufen zu bringen?
Microsis
Update: Ich habe es anscheinend zum Laufen gebracht, indem ich dies oben auf shared.js: helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;- Benötige ich leider eine Zeile für jede exportierte Funktion, aber hoffentlich ist es eine gute Lösung?
Microsis
1

Ich habe ein einfaches Modul geschrieben , das importiert werden kann (entweder mit require in Node oder mit Skript-Tags im Browser), mit dem Sie Module sowohl vom Client als auch vom Server laden können.

Anwendungsbeispiel

1. Modul definieren

Platzieren Sie Folgendes in einer Datei log2.jsin Ihrem statischen Ordner für Webdateien:

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;

So einfach ist das!

2. Verwenden des Moduls

Da es sich um einen bilateralen Modullader handelt, können wir ihn von beiden Seiten (Client und Server) laden. Daher können Sie Folgendes tun, müssen jedoch nicht beide gleichzeitig ausführen (geschweige denn in einer bestimmten Reihenfolge):

  • Im Knoten

In Node ist es einfach:

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

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

Dies sollte zurückkehren 2.

Wenn sich Ihre Datei nicht im aktuellen Verzeichnis von Node befindet, stellen Sie sicher, dass Sie loader.setRootden Pfad zu Ihrem statischen Ordner für Webdateien (oder wo immer sich Ihr Modul befindet) aufrufen .

  • Im Browser:

Definieren Sie zunächst die Webseite:

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

Stellen Sie sicher , dass Sie nicht die Datei direkt in Ihrem Browser öffnen; Da AJAX verwendet wird, sollten Sie sich stattdessen das Python 3- http.serverModul ansehen (oder was auch immer Ihre superschnelle, Befehlszeilen- und Ordner-Webserver-Bereitstellungslösung ist).

Wenn alles gut geht, erscheint Folgendes:

Geben Sie hier die Bildbeschreibung ein

Gustavo6046
quelle
0

Ich habe dies geschrieben, es ist einfach zu verwenden, wenn Sie alle Variablen auf den globalen Bereich setzen möchten:

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