web-dev-qa-db-fra.com

Comment puis-je partager le code entre Node.js et le navigateur?

Je crée une petite application avec un client JavaScript (exécuté dans le navigateur) et un serveur Node.js, communiquant à l'aide de WebSocket.

Je souhaite partager le code entre le client et le serveur. Je viens juste de commencer avec Node.js et ma connaissance du JavaScript moderne est un peu rouillée, c'est le moins qu'on puisse dire. Je suis donc encore en train de comprendre la fonction require () de CommonJS. Si je crée mes packages à l'aide de l'objet "export", je ne vois pas comment utiliser les mêmes fichiers JavaScript dans le navigateur.

Je souhaite créer un ensemble de méthodes et de classes utilisées aux deux extrémités pour faciliter le codage et le décodage des messages, ainsi que d'autres tâches en miroir. Cependant, les systèmes de packaging Node.js/CommonJS semblent m'empêcher de créer des fichiers JavaScript pouvant être utilisés des deux côtés.

J'ai également essayé d'utiliser JS.Class pour obtenir un modèle OO plus étroit, mais j'ai abandonné car je ne savais pas comment utiliser les fichiers JavaScript fournis avec require (). Y a-t-il quelque chose qui me manque ici?

232
Simon Cave

Si vous voulez écrire un module utilisable à la fois côté client et côté serveur, j’ai un bref article de blog sur une méthode simple et rapide: Écriture pour Node.js et le navigateur, essentiellement le En suivant (où this est identique à window):

(function(exports){

    // Your code goes here

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

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

Il existe également des projets visant à implémenter l'API Node.js côté client, tels que Marak's gemini .

Vous pourriez également être intéressé par DNode , qui vous permet d’exposer une fonction JavaScript afin qu’elle puisse être appelée depuis une autre machine à l’aide d’un simple protocole réseau basé sur JSON.

163
Caolan

Epeli a une solution intéressante ici http://epeli.github.com/piler/ qui fonctionne même sans la bibliothèque, il suffit de le mettre dans un fichier nommé share.js

(function(exports){

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

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

Côté serveur, utilisez simplement:

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

share.test();

Et du côté client, chargez simplement le fichier js puis utilisez

share.test();
39
broesch

Vérifiez le code source jQuery qui permet de résoudre ce problème dans le modèle de module Node.js, le modèle de module AMD et le code global dans le navigateur:

(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)
14
wlingke

Je recommanderais de regarder dans l'adaptateur RequireJS pour Node.js . Le problème est que le modèle de module CommonJS utilisé par Node.js par défaut n’est pas asynchrone, ce qui bloque le chargement dans le navigateur Web. RequireJS utilise le modèle AMD, à la fois asynchrone et compatible avec le serveur et le client, à condition que vous utilisiez l'adaptateur r.js.

13
Husky

N'oubliez pas que la représentation sous forme de chaîne d'une fonction JavaScript représente le code source de cette fonction. Vous pouvez simplement écrire vos fonctions et vos constructeurs de manière encapsulée afin qu'ils puissent être toString () et envoyés au client.

Une autre méthode consiste à utiliser un système de génération, à placer le code commun dans des fichiers séparés, puis à les inclure dans les scripts du serveur et du client. J'utilise cette approche pour un jeu client/serveur simple via WebSockets où le serveur et le client exécutent essentiellement la même boucle de jeu et où le client se synchronise avec le serveur à chaque cran pour s'assurer que personne ne triche.

Mon système de construction pour le jeu est un simple Bash script qui exécute les fichiers via le préprocesseur C, puis avec sed pour nettoyer certaines feuilles inutiles, afin que je puisse utiliser tous les éléments normaux du préprocesseur tels que #include, # définir, # ifdef, etc.

13
Dagg Nabbit

Cela n’est peut-être pas tout à fait conforme à la question, mais j’ai pensé partager cela.

Je souhaitais mettre à disposition deux fonctions simples d’utilitaire de chaîne, déclarées sur String.prototype, à la fois pour le noeud et le navigateur. Je conserve simplement ces fonctions dans un fichier nommé utilities.js (dans un sous-dossier) et je peux facilement le référencer à partir d'un script-tag dans le code de mon navigateur et en utilisant require (en omettant l'extension .js) dans mon script Node.js :

mon_node_script.js

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

my_browser_code.html

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

J'espère que c'est une information utile pour quelqu'un d'autre que moi.

11

Le serveur peut simplement envoyer des fichiers sources JavaScript au client (navigateur), mais le truc est que le client devra fournir un mini-environnement "exports" avant de pouvoir exec le code et de le stocker sous forme de module.

Un moyen simple de créer un tel environnement consiste à utiliser une fermeture. Par exemple, supposons que votre serveur fournisse des fichiers source via HTTP tels que http://example.com/js/foo.js. Le navigateur peut charger les fichiers requis via XMLHttpRequest et charger le code de la manière suivante:

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 clé est que le client peut encapsuler le code étranger dans une fonction anonyme à exécuter immédiatement (une fermeture) qui crée l'objet "exports" et le renvoie afin que vous puissiez l'affecter où vous voulez, au lieu de polluer l'espace de noms global. Dans cet exemple, il est attribué à l'attribut de fenêtre fooModule qui contiendra le code exporté par le fichier foo.js.

4
maerics

Aucune des solutions précédentes n'apporte le système de module CommonJS au navigateur.

Comme mentionné dans les autres réponses, il existe des solutions de gestionnaire de stock/emballeur telles que Browserify ou Piler et il existe des solutions RPC telles que dnode ou nowjs . _.

Mais je n'ai pas pu trouver d'implémentation de CommonJS pour le navigateur (y compris une fonction require() et des objets exportsmodule.exports, etc.). Alors j’écris moi-même, pour découvrir par la suite que quelqu'un d’autre l’a écrit mieux que moi: https://github.com/weepy/brequire . Cela s'appelle Brequire (abréviation de Browser require).

À en juger par leur popularité, les gestionnaires d’actifs répondent aux besoins de la plupart des développeurs. Toutefois, si vous avez besoin d’une implémentation de CommonJS dans votre navigateur, Brequire sera probablement à la hauteur.

_/Mise à jour 2015: Je n'utilise plus Brequire (il n'a pas été mis à jour depuis quelques années). Si j'écris juste un petit module open-source et que je veux que tout le monde puisse l'utiliser facilement, je vais suivre un modèle similaire à la réponse de Caolan (ci-dessus) - J'ai écrit un article de blog } _ à ce sujet il y a quelques années.

Cependant, si j'écris des modules pour un usage privé ou pour une communauté standardisée sur CommonJS (comme la communauté Ampersand ), je les écris simplement au format CommonJS et utilise Browserify . _.

2
Peter Rust

Si vous utilisez des groupeurs de modules tels que webpack pour regrouper des fichiers JavaScript destinés à être utilisés dans un navigateur, vous pouvez simplement réutiliser votre module Node.js pour le client s’exécutant dans un navigateur. En d'autres termes, votre module Node.js peut être partagé entre Node.js et le navigateur. 

Par exemple, vous avez le code suivant sum.js:

Module Node.js normal: sum.js

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

module.exports = sum

Utilisez le module dans 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

Le réutiliser dans le frontend

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

Ecrivez votre code sous la forme RequireJS modules et vos tests sous la forme Jasmine tests.

De cette façon, le code peut être chargé partout avec RequireJS et les tests exécutés dans le navigateur avec jasmine-html et jasmine-node in Node.js sans qu'il soit nécessaire de modifier le code ou les tests.

Voici un exemple de travail pour cela.

1
Blacksonic

J'ai écrit un module simple , pouvant être importé (à l'aide de require in Node ou de balises de script dans le navigateur), que vous pouvez utiliser pour charger des modules à la fois depuis le client et le serveur.

Exemple d'utilisation

1. Définir le module

Placez les éléments suivants dans un fichier log2.js, dans votre dossier de fichiers Web statiques:

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;

Aussi simple que cela!

2. Utiliser le module

Puisqu'il s'agit d'un chargeur de modules bilatéral, nous pouvons le charger des deux côtés (client et serveur). Par conséquent, vous pouvez effectuer les opérations suivantes, mais vous n'avez pas besoin de faire les deux à la fois (et encore moins dans un ordre particulier):

  • Dans le noeud

Dans Node, c'est simple:

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

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

Cela devrait renvoyer 2.

Si votre fichier ne se trouve pas dans le répertoire actuel du nœud, veillez à appeler loader.setRoot avec le chemin d'accès à votre dossier de fichiers Web statiques (ou à l'emplacement de votre module).

  • Dans le navigateur:

Tout d’abord, définissez la page 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>

Assurez-vous que ne pas ouvrez le fichier directement dans votre navigateur. Puisqu'il utilise AJAX, je vous suggère de jeter un coup d'œil au module http.server de Python 3 (ou quelle que soit votre solution de déploiement de serveur Web à super commande, en ligne de commande, à dossier).

Si tout se passe bien, cela va apparaître:

 enter image description here

1
Gustavo6046

now.js mérite également le détour. Il vous permet d’appeler côté serveur depuis le côté client et les fonctions côté client depuis le côté serveur.

1
balupton

Si vous voulez écrire votre navigateur dans le style Node.js, vous pouvez essayer dualify .

Il n'y a pas de compilation de code de navigateur, vous pouvez donc écrire votre application sans limitation.

1
farincz

Cas d'utilisation: partagez la configuration de votre application entre Node.js et le navigateur (il s'agit simplement d'une illustration, ce n'est probablement pas la meilleure approche en fonction de votre application).

Problème: vous ne pouvez pas utiliser window (n'existe pas dans Node.js) ni global (n'existe pas dans le navigateur).

Solution:

  • Fichier config.js:

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

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

    Vous pouvez maintenant ouvrir les outils de développement et accéder à la variable globale config

  • Dans Node.js (app.js):

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

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

J'ai écrit ceci, il est simple à utiliser si vous souhaitez définir toutes les variables dans la portée globale:

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