Como posso compartilhar código entre o Node.js e o navegador?

242

Estou criando um pequeno aplicativo com um cliente JavaScript (executado no navegador) e um servidor Node.js., comunicando-se usando o WebSocket.

Eu gostaria de compartilhar código entre o cliente e o servidor. Eu apenas comecei o Node.js e meu conhecimento do JavaScript moderno está um pouco enferrujado, para dizer o mínimo. Então, eu ainda estou tentando entender a função requireJS do CommonJS. Se estou criando meus pacotes usando o objeto 'export', não consigo ver como poderia usar os mesmos arquivos JavaScript no navegador.

Desejo criar um conjunto de métodos e classes usados ​​nas duas extremidades para facilitar a codificação e decodificação de mensagens e outras tarefas espelhadas. No entanto, os sistemas de empacotamento Node.js / CommonJS parecem me impedir de criar arquivos JavaScript que podem ser usados ​​nos dois lados.

Também tentei usar o JS.Class para obter um modelo OO mais rígido, mas desisti porque não conseguia descobrir como fazer com que os arquivos JavaScript fornecidos funcionassem com require (). Há algo que estou perdendo aqui?

Simon Cave
fonte
4
Obrigado a todos por postar respostas adicionais a esta pergunta. Este é claramente um tópico que mudará e evoluirá rapidamente.
Simon Cave

Respostas:

168

Se você deseja escrever um módulo que possa ser usado no lado do cliente e no servidor, tenho uma pequena postagem no blog sobre um método rápido e fácil: Escrever para o Node.js e o navegador , essencialmente o seguinte (onde thisé o mesmo window) :

(function(exports){

    // Your code goes here

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

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

Como alternativa, existem alguns projetos que visam implementar a API do Node.js. no lado do cliente, como os gemini de Marak .

Você também pode estar interessado no DNode , que permite expor uma função JavaScript para que possa ser chamada de outra máquina usando um protocolo de rede baseado em JSON simples.

Caolan
fonte
Excelente. Obrigado pela informação, Caolan.
Simon Cave
2
Artigo realmente ótimo Caolan. Eu entendi, funcionou, agora estou rolando novamente. Fantástico!
Michael Dausmann
2
Estou usando RequireJs em meu próprio projeto, o que me permitirá compartilhar meus módulos no cliente e no servidor. Vamos ver como isso funciona.
kamranicus
5
@Caolan que apontam está morto
Kamal Reddy
5
O link de gêmeos está morto.
22415 Borisdiakur
42

O Epeli tem uma boa solução aqui http://epeli.github.com/piler/ que até funciona sem a biblioteca, basta colocar isso em um arquivo chamado share.js

(function(exports){

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

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

No lado do servidor, basta usar:

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

share.test();

E no lado do cliente, basta carregar o arquivo js e usar

share.test();
broesch
fonte
10
Gosto mais desta resposta do que a aceita, porque é melhor explicada para iniciantes como eu.
Howie
Na minha pasta Express, além da pasta estática (pública), também tenho uma pasta chamada 'shared', que também pode ser acessada pelo cliente, como a pasta 'public', assim: app.use (express.static ('public')) ; app.use (express.static ('shared')); E sua postagem estende minha ideia de compartilhar arquivos com o cliente e o servidor. Isso é exatamente o que eu precisava. Obrigado!
Combine
Esta solução + git subárvore == impressionante. Obrigado!
precisa saber é o seguinte
@broesch Como isso funcionaria no ES6? Fiz isso como uma nova pergunta , com alguns problemas específicos do ES6, mas ficaria feliz em ver uma edição aqui!
Tedskovsky 14/01
15

Faça o check-out do código-fonte do jQuery que faz isso funcionar no padrão do módulo Node.js., AMD e global no navegador:

(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
fonte
Este é o melhor método (para o que eu precisava). Aqui está um exemplo de trabalho que eu criei: gist.github.com/drmikecrowe/4bf0938ea73bf704790f
Mike Crowe
13

Não esqueça que a representação em cadeia de uma função JavaScript representa o código-fonte dessa função. Você pode simplesmente escrever suas funções e construtores de maneira encapsulada para que eles possam ser toString () 'enviados para o cliente.

Outra maneira de fazer isso é usar um sistema de compilação, colocar o código comum em arquivos separados e incluí-los nos scripts do servidor e do cliente. Estou usando essa abordagem para um jogo simples de cliente / servidor via WebSockets, onde o servidor e o cliente executam essencialmente o mesmo ciclo de jogo e o cliente sincroniza com o servidor a cada tick para garantir que ninguém trapaceie.

Meu sistema de compilação para o jogo é um script Bash simples que executa os arquivos através do pré-processador C e, em seguida, através do sed para limpar algumas folhas indesejadas de cpp, para que eu possa usar todo o material pré-processador normal, como #include, #define, #ifdef etc.

Dagg Nabbit
fonte
2
Serializar funções javascript como strings nunca me ocorreu. Obrigado pela dica.
Simon Cave
13

Eu recomendaria olhando para o adaptador RequireJS para Node.js . O problema é que o padrão do módulo CommonJS que o Node.js usa por padrão não é assíncrono, o que bloqueia o carregamento no navegador da web. O RequireJS usa o padrão AMD, que é assíncrono e compatível com o servidor e o cliente, desde que você use o r.jsadaptador.

Husky
fonte
existe biblioteca assíncrona
Jacek Pietal
11

Talvez isso não esteja totalmente de acordo com a pergunta, mas pensei em compartilhar isso.

Eu queria disponibilizar algumas funções simples de utilitário de cadeia de caracteres, declaradas em String.prototype, para o nó e o navegador. Eu simplesmente mantenho essas funções em um arquivo chamado utilities.js (em uma subpasta) e posso referenciá-lo facilmente a partir de uma tag de script no código do meu navegador e usando o require (omitindo a extensão .js) no meu script Node.js. :

my_node_script.js

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

my_browser_code.html

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

Espero que seja uma informação útil para alguém que não seja eu.

Markus Amalthea Magnuson
fonte
1
Eu gosto dessa abordagem, mas acho que meus arquivos estáticos são movidos bastante. Uma solução que encontrei é reexportar o módulo. Por exemplo, crie utilites.jscom uma única linha module.exports = require('./static/js/utilities');. Dessa forma, você só precisa atualizar um caminho se embaralhar as coisas.
Tom Makin
Eu gosto desta ideia. Apenas uma nota no caminho que me levou um tempo para descobrir. Meu utilities.jsestá na sharedpasta sob o projeto. Usando require('/shared/utilities')me deu o erro Cannot find module '/shared/utilities'. Eu tenho que usar algo assim require('./../../shared/utilities')para fazer funcionar. Portanto, ele sempre sai da pasta atual e sobe até a raiz e depois desce.
newman
Agora vejo onde colocar o módulo compartilhado - na pasta estática. Obrigado pela informação!
Combine
9

Se você usar os bundlers de módulo, como o webpack para agrupar arquivos JavaScript para uso em um navegador, basta reutilizar o módulo Node.js. para o frontend em execução em um navegador. Em outras palavras, seu módulo Node.js. pode ser compartilhado entre o Node.js. e o navegador.

Por exemplo, você tem o seguinte código sum.js:

Módulo Node.js normal: sum.js

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

module.exports = sum

Use o módulo no 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

Reutilize-o no frontend

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

O servidor pode simplesmente enviar arquivos de origem JavaScript para o cliente (navegador), mas o truque é que o cliente precisará fornecer um mini ambiente de "exportação" antes de poder exec codificar e armazená-lo como um módulo.

Uma maneira simples de criar esse ambiente é usar um fechamento. Por exemplo, digamos que seu servidor forneça arquivos de origem via HTTP, como http://example.com/js/foo.js. O navegador pode carregar os arquivos necessários por meio de um XMLHttpRequest e carregar o código da seguinte forma:

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

A chave é que o cliente pode agrupar o código externo em uma função anônima para ser executada imediatamente (um fechamento) que cria o objeto "exporta" e o retorna para que você possa atribuí-lo onde quiser, em vez de poluir o espaço para nome global. Neste exemplo, ele é atribuído ao atributo de janela fooModuleque conterá o código exportado pelo arquivo foo.js.

maerics
fonte
2
toda vez que você usar eval você matar um gnomo
Jacek Pietal
1
Eu usaria window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule).
GingerPlusPlus
2

Nenhuma das soluções anteriores traz o sistema do módulo CommonJS para o navegador.

Conforme mencionado nas outras respostas, existem soluções de gerenciamento / empacotador de ativos, como Browserify ou Piler, e soluções de RPC, como dnode ou nowjs .

Mas não consegui encontrar uma implementação do CommonJS para o navegador (incluindo um require() função e exports/ module.exportsobjetos etc.). Então escrevi o meu, apenas para descobrir depois que alguém o havia escrito melhor do que eu: https://github.com/weepy/brequire . Chama-se Brequire (abreviação de Browser exigem).

A julgar pela popularidade, os gerentes de ativos atendem às necessidades da maioria dos desenvolvedores. No entanto, se você precisar de uma implementação de navegador do CommonJS, o Brequire provavelmente será adequado.

Atualização de 2015: não uso mais o Brequire (ele não é atualizado há alguns anos). Se estou escrevendo um pequeno módulo de código aberto e quero que alguém possa usá-lo facilmente, seguirei um padrão semelhante à resposta de Caolan (acima) - escrevi um post sobre isso alguns anos atrás.

No entanto, se estou escrevendo módulos para uso privado ou para uma comunidade padronizada no CommonJS (como a comunidade Ampersand ), escreverei no formato CommonJS e utilizarei o Browserify .

Peter Rust
fonte
1

now.js também vale a pena dar uma olhada. Ele permite que você chame as funções do lado do servidor e do lado do servidor.

Balupton
fonte
1
O projeto foi descontinuado - você conhece algum bom substituto para ele? groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
Anderson Green
o único outro que conheço era ponte e era pelas mesmas pessoas, também abandonado. A versão 0.9 do socket.io também suporta retornos de chamada para eventos - no entanto, nada como o código de compartilhamento do now.js, mas funciona bem o suficiente.
balupton
Há também sharejs, que parece ser mantido ativamente. sharejs.org
Anderson Green
1

Se você deseja escrever seu navegador no estilo Node.js., você pode tentar dualizar .

Não há compilação de código do navegador, portanto você pode escrever seu aplicativo sem limitações.

farincz
fonte
1

Escreva seu código como módulos RequireJS e seus testes como testes Jasmine .

Dessa forma, o código pode ser carregado em qualquer lugar com o RequireJS e os testes podem ser executados no navegador com jasmine-html e jasmine-node no Node.js sem a necessidade de modificar o código ou os testes.

Aqui está um exemplo de trabalho para isso.

Blacksonic
fonte
1

Caso de uso: compartilhe a configuração do seu aplicativo entre o Node.js e o navegador (esta é apenas uma ilustração, provavelmente não é a melhor abordagem, dependendo do seu aplicativo).

Problema: você não pode usar window(não existe no Node.js) nem global(não existe no navegador).

Solução:

  • Arquivo config.js:

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

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

    Agora você pode abrir as ferramentas de desenvolvimento e acessar a variável global config

  • No Node.js (app.js):

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

    import config from './config';
    console.log(config.foo); // Prints 'bar'
tanguy_k
fonte
1
Obrigado por isso.
Microsis 17/09/19
Acompanhamento: Digamos que eu tenho dois arquivos que são compartilhados entre server.js e client.js: shared.jse helpers.js- shared.jsusa funções de helpers.js, portanto, precisa const { helperFunc } = require('./helpers')na parte superior, para que funcione no lado do servidor. O problema está no cliente, queixa-se de requirenão ser uma função, mas se eu envolver a linha de solicitação if (typeof module === 'object') { ... }, o servidor diz que helperFunc () não está definido (fora da instrução if). Alguma idéia para fazê-lo funcionar em ambos?
Microsis 17/09/19
Atualização: parece que o fiz funcionar colocando isso no topo de shared.js: helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;- Infelizmente, será necessária uma linha para cada função exportada, mas espero que seja uma boa solução?
Microsis 17/09/19
1

Eu escrevi um módulo simples , que pode ser importado (usando require no Nó ou tags de script no navegador), que você pode usar para carregar módulos do cliente e do servidor.

Exemplo de uso

1. Definindo o módulo

Coloque o seguinte em um arquivo log2.js, dentro da pasta estática de arquivos da 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;

Simples assim!

2. Usando o módulo

Por ser um carregador de módulo bilateral , podemos carregá-lo de ambos os lados (cliente e servidor). Portanto, você pode fazer o seguinte, mas não precisa fazer as duas coisas ao mesmo tempo (muito menos em uma ordem específica):

  • No nó

No Node, é simples:

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

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

Isso deve retornar 2.

Se o seu arquivo não estiver no diretório atual do Node, lembre-se de chamar loader.setRooto caminho para a pasta estática de arquivos da Web (ou onde quer que esteja o seu módulo).

  • No navegador:

Primeiro, defina a página da 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>

Certifique-se de não abrir o arquivo diretamente no seu navegador; Como ele usa o AJAX, sugiro que você dê uma olhada no http.servermódulo do Python 3 (ou seja, qual for a sua solução de implantação de servidor da Web super rápida, de linha de comando e pasta).

Se tudo correr bem, isso aparecerá:

insira a descrição da imagem aqui

Gustavo6046
fonte
0

Eu escrevi isso, é simples de usar se você deseja definir todas as variáveis ​​para o escopo global:

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