Come posso condividere il codice tra Node.js e il browser?


242

Sto creando una piccola applicazione con un client JavaScript (eseguito nel browser) e un server Node.js, comunicando tramite WebSocket.

Vorrei condividere il codice tra il client e il server. Ho appena iniziato con Node.js e la mia conoscenza del JavaScript moderno è un po 'arrugginita, per non dire altro. Quindi mi sto ancora concentrando sulla funzione richiesta () di CommonJS. Se sto creando i miei pacchetti usando l'oggetto 'export', allora non riesco a vedere come potrei usare gli stessi file JavaScript nel browser.

Voglio creare una serie di metodi e classi che vengono utilizzati su entrambe le estremità per facilitare la codifica e la decodifica dei messaggi e altre attività con mirroring. Tuttavia, i sistemi di packaging Node.js / CommonJS sembrano impedirmi di creare file JavaScript che possono essere utilizzati su entrambi i lati.

Ho anche provato a utilizzare JS.Class per ottenere un modello OO più stretto, ma ho rinunciato perché non riuscivo a capire come ottenere i file JavaScript forniti con cui lavorare (request). C'è qualcosa che mi manca qui?


4
Grazie a tutti per aver pubblicato ulteriori risposte a questa domanda. Questo è chiaramente un argomento che cambierà rapidamente e si evolverà.
Simon Cave,

Risposte:


168

Se vuoi scrivere un modulo che può essere usato sia sul lato client che sul lato server, ho un breve post sul blog su un metodo rapido e semplice: scrivere per Node.js e il browser , essenzialmente il seguente (dove thisè uguale a window) :

(function(exports){

    // Your code goes here

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

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

In alternativa, ci sono alcuni progetti che mirano a implementare l'API Node.js sul lato client, come quella di Marak gemini .

Potresti anche essere interessato a DNode , che ti consente di esporre una funzione JavaScript in modo che possa essere chiamata da un altro computer utilizzando un semplice protocollo di rete basato su JSON.


Eccellente. Grazie per le informazioni, Caolan.
Simon Cave,

2
Davvero ottimo articolo Caolan. L'ho capito, ha funzionato, ora sto di nuovo rotolando. Fantastico!
Michael Dausmann,

2
Sto usando RequireJs nel mio progetto, che mi permetterà di condividere i miei moduli sul client e sul server. Vedremo come funziona.
kamranicus,

5
@Caolan quel link è morto
Kamal Reddy,

5
Il collegamento Gemelli è morto.
Borisdiakur,

42

Epeli ha una buona soluzione qui http://epeli.github.com/piler/ che funziona anche senza la libreria, basta inserirlo in un file chiamato share.js

(function(exports){

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

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

Sul lato server basta usare:

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

share.test();

E sul lato client basta caricare il file js e quindi utilizzare

share.test();

10
Mi piace questa risposta meglio di quella accettata perché è meglio spiegata per i neofiti come me.
Howie,

Nella mia cartella Express oltre alla cartella statica (pubblica), ho anche una cartella denominata 'condivisa' che è anche accessibile dal client come la cartella 'pubblica' come quella: app.use (express.static ('public')) ; app.use (express.static ( 'condiviso')); E il tuo post estende la mia idea di condividere file con il client e il server. Questo è esattamente ciò di cui avevo bisogno. Grazie!
Combina il

Questa soluzione + git subtree == eccezionale. Grazie!
Kevinevke

@broesch Come funzionerebbe in ES6? L'ho fatto come una nuova domanda , con alcuni problemi specifici di ES6, ma sarei altrettanto felice di vedere una modifica qui!
Tedskovsky,

15

Verifica il codice sorgente jQuery che consente di farlo funzionare nel modello del modulo Node.js, nel modello del modulo AMD e globale nel 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)

Questo è il metodo migliore (per quello che mi serviva). Ecco un esempio funzionante che ho creato: gist.github.com/drmikecrowe/4bf0938ea73bf704790f
Mike Crowe,

13

Non dimenticare che la rappresentazione in formato stringa di una funzione JavaScript rappresenta il codice sorgente per quella funzione. Potresti semplicemente scrivere le tue funzioni e costruttori in un modo incapsulato in modo che possano essere toString () 'd e inviati al client.

Un altro modo per farlo è utilizzare un sistema di compilazione, inserire il codice comune in file separati e quindi includerli negli script del server e del client. Sto usando questo approccio per un semplice gioco client / server tramite WebSocket in cui il server e il client eseguono essenzialmente lo stesso loop di gioco e il client si sincronizza con il server ogni tick per assicurarsi che nessuno stia tradendo.

Il mio sistema di compilazione per il gioco è un semplice script di Bash che esegue i file attraverso il preprocessore C e poi attraverso sed per ripulire alcuni fogli di cpp spazzatura, quindi posso usare tutte le normali cose del preprocessore come #include, #define, #ifdef , eccetera.


2
Serializzare le funzioni javascript come stringhe non mi è mai venuto in mente. Grazie per il consiglio.
Simon Cave,

13

Vorrei raccomandare esaminando l'all'adattatore RequireJS per Node.js . Il problema è che il modello di modulo CommonJS utilizzato da Node.js per impostazione predefinita non è asincrono, il che blocca il caricamento nel browser Web. RequireJS utilizza il modello AMD, che è sia asincrono che compatibile con server e client, purché si utilizzi l' r.jsadattatore.


c'è una biblioteca asincrona
Jacek Pietal,

11

Forse questo non è del tutto in linea con la domanda, ma ho pensato di condividerlo.

Volevo rendere disponibili un paio di semplici funzioni di utilità stringa, dichiarate su String.prototype, sia per il nodo che per il browser. Semplicemente mantengo queste funzioni in un file chiamato utilities.js (in una sottocartella) e posso facilmente fare riferimento sia da un tag di script nel mio codice del browser, sia usando Require (omettendo l'estensione .js) nel mio script Node.js :

my_node_script.js

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

my_browser_code.html

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

Spero che si tratti di informazioni utili a qualcuno diverso da me.


1
Mi piace questo approccio ma trovo che i miei file statici vengano spostati parecchio. Una soluzione che ho trovato è riesportare il modulo. Ad esempio, crea utilites.jscon una sola riga module.exports = require('./static/js/utilities');. In questo modo è necessario aggiornare un solo percorso se si mescolano elementi in giro.
Tom Makin,

Mi piace questa idea. Solo una nota sul percorso che mi ha impiegato un po 'di tempo per capire. Il mio utilities.jsè nella sharedcartella sotto il progetto. Usando require('/shared/utilities')mi ha dato l'errore Cannot find module '/shared/utilities'. Devo usare qualcosa di simile require('./../../shared/utilities')per farlo funzionare. Quindi, passa sempre dalla cartella corrente e viaggia verso l'alto, poi verso il basso.
Newman,

Ora vedo dove posizionare il modulo condiviso - nella cartella statica. Grazie per le informazioni!
Combina il

9

Se usi bundle di moduli come webpack per raggruppare i file JavaScript da utilizzare in un browser, puoi semplicemente riutilizzare il modulo Node.js per il frontend in esecuzione in un browser. In altre parole, il modulo Node.js può essere condiviso tra Node.js e il browser.

Ad esempio, hai il seguente codice sum.js:

Modulo Node.js normale: sum.js

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

module.exports = sum

Utilizzare il modulo 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

Riutilizzalo nel frontend

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

4

Il server può semplicemente inviare file sorgente JavaScript al client (browser) ma il trucco è che il client dovrà fornire un mini ambiente "export" prima di poter exec il codice e archiviarlo come modulo.

Un modo semplice per creare un tale ambiente è utilizzare una chiusura. Ad esempio, supponiamo che il tuo server fornisca i file di origine tramite HTTP come http://example.com/js/foo.js. Il browser può caricare i file richiesti tramite XMLHttpRequest e caricare il codice in questo modo:

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

La chiave è che il client può racchiudere il codice esterno in una funzione anonima da eseguire immediatamente (una chiusura) che crea l'oggetto "export" e lo restituisce in modo da poterlo assegnare dove preferisci, piuttosto che inquinare lo spazio dei nomi globale. In questo esempio, è assegnato all'attributo window fooModuleche conterrà il codice esportato dal file foo.js.


2
ogni volta che usi eval uccidi uno gnomo
Jacek Pietal l'

1
Vorrei usare window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule).
GingerPlusPlus

2

Nessuna delle soluzioni precedenti porta il sistema del modulo CommonJS nel browser.

Come menzionato nelle altre risposte, esistono soluzioni di gestione patrimoniale / packager come Browserify o Piler e ci sono soluzioni RPC come dnode o nowjs .

Ma non sono riuscito a trovare un'implementazione di CommonJS per il browser (inclusa una require()funzione e exports/ module.exportsoggetti, ecc.). Così ho scritto il mio, solo per scoprire in seguito che qualcun altro lo aveva scritto meglio di me: https://github.com/weepy/brequire . Si chiama Brequire (abbreviazione di Browser richiede).

A giudicare dalla popolarità, i gestori patrimoniali soddisfano le esigenze della maggior parte degli sviluppatori. Tuttavia, se hai bisogno di un'implementazione del browser di CommonJS, Brequire probabilmente si adatta al conto.

Aggiornamento 2015: non utilizzo più Brequire (non è stato aggiornato da alcuni anni). Se sto solo scrivendo un piccolo modulo open source e voglio che chiunque sia in grado di usarlo facilmente, seguirò uno schema simile alla risposta di Caolan (sopra) - Ho scritto un post sul blog a riguardo un paio di anni fa.

Tuttavia, se sto scrivendo moduli per uso privato o per una comunità standardizzata su CommonJS (come la comunità Ampersand ), li scriverò semplicemente in formato CommonJS e userò Browserify .


1

Anche now.js merita una visita . Consente di chiamare le funzioni lato server dal lato client e lato client dal lato server


1
Il progetto è stato sospeso - conosci qualche buon sostituto per questo? groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
Anderson Green

l'unico altro che conosco era il bridge ed era delle stesse persone, quindi anche abbandonato. La versione 0.9 di socket.io supporta anche i callback per gli eventi - comunque niente come il codice di condivisione di now.js, ma funziona abbastanza bene.
Balupton,

C'è anche sharejs, che sembra essere attivamente mantenuto. sharejs.org
Anderson Green

1

Se vuoi scrivere il tuo browser in stile simile a Node.js puoi provare dualify .

Non esiste una compilazione del codice del browser, quindi è possibile scrivere l'applicazione senza limitazioni.


1

Scrivi il tuo codice come moduli RequireJS e i tuoi test come test Jasmine .

In questo modo il codice può essere caricato ovunque con RequireJS e i test possono essere eseguiti nel browser con jasmine-html e con jasmine-node in Node.js senza la necessità di modificare il codice o i test.

Ecco un esempio funzionante per questo.


1

Caso d'uso: condividi la configurazione della tua app tra Node.js e il browser (questa è solo un'illustrazione, probabilmente non l'approccio migliore a seconda della tua app).

Problema: non è possibile utilizzare window(non esiste in Node.js) néglobal (non esiste nel browser).

Soluzione:

  • File config.js:

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

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

    Ora puoi aprire gli strumenti di sviluppo e accedere alla variabile globale config

  • In Node.js (app.js):

    const config = require('./config');
    console.log(config.foo); // Prints 'bar'
  • Con Babele o TypeScript:

    import config from './config';
    console.log(config.foo); // Prints 'bar'

1
Grazie per questo.
Microsis,

Seguito: supponiamo che io abbia due file condivisi tra server.js e client.js: shared.jse helpers.js-shared.js usa le funzioni da helpers.js, quindi ha bisogno const { helperFunc } = require('./helpers')in alto, perché funzioni sul lato server. Il problema è sul client, si lamenta di requirenon essere una funzione, ma se avvolgo la riga richiesta if (typeof module === 'object') { ... }, il server dice che helperFunc () non è definito (al di fuori dell'istruzione if). Qualche idea per farlo funzionare su entrambi?
Microsis,

Aggiornamento: mi sembra che abbia funzionato posizionandolo in cima a shared.js: helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;- Avrà bisogno di una linea per ogni funzione esportata purtroppo ma speriamo che sia una buona soluzione?
Microsis,

1

Ho scritto un modulo semplice , che può essere importato (utilizzando il comando Require in Node o i tag di script nel browser), che è possibile utilizzare per caricare i moduli sia dal client che dal server.

Esempio di utilizzo

1. Definizione del modulo

Inserire quanto segue in un file log2.js, nella cartella dei file Web statici:

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;

Semplice come quella!

2. Utilizzo del modulo

Poiché è un caricatore di moduli bilaterale , possiamo caricarlo da entrambi i lati (client e server). Pertanto, è possibile effettuare le seguenti operazioni, ma non è necessario eseguire entrambe le operazioni contemporaneamente (figuriamoci in un ordine particolare):

  • Nel nodo

In Node, è semplice:

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

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

Questo dovrebbe tornare 2.

Se il file non si trova nella directory corrente del nodo, assicurarsi di chiamare loader.setRootcon il percorso della cartella dei file Web statici (o ovunque si trovi il modulo).

  • Nel browser:

Innanzitutto, definire la pagina 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>

Assicurati di non aprire il file direttamente nel tuo browser; dal momento che utilizza AJAX, ti suggerisco di dare un'occhiata al http.servermodulo di Python 3 (o qualunque sia la tua soluzione di distribuzione di server Web superveloce, da riga di comando, cartella).

Se tutto va bene, questo apparirà:

inserisci qui la descrizione dell'immagine


0

Ho scritto questo, è semplice da usare se si desidera impostare tutte le variabili nell'ambito globale:

(function(vars, global) {
    for (var i in vars) global[i] = vars[i];
})({
    abc: function() {
        ...
    },
    xyz: function() {
        ...
    }
}, typeof exports === "undefined" ? this : exports);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.