Как я могу совместно использовать код между Node.js и браузером?

Я создаю небольшое приложение с клиентом JavaScript (выполняется в браузере) и сервером Node.js, обмениваясь сообщениями с помощью WebSocket.

Я хотел бы поделиться кодом между клиентом и сервером. Я только что начал с Node.js, и мои знания современного JavaScript немного реже, мягко говоря. Таким образом, я все еще получаю свою голову вокруг функции CommonJS require(). Если я создаю свои пакеты с помощью объекта "export", то я не вижу, как я могу использовать одни и те же файлы JavaScript в браузере.

Я хочу создать набор методов и классов, которые используются на обоих концах для облегчения кодирования и декодирования сообщений и других зеркальных задач. Однако системы упаковки Node.js/CommonJS, по-видимому, не позволяют мне создавать файлы JavaScript, которые могут использоваться с обеих сторон.

Я также попытался использовать JS.Class, чтобы получить более жесткую OO-модель, но я отказался, потому что не мог понять, как получить предоставленные файлы JavaScript для работы с require(). Здесь что-то не хватает?

Ответ 1

Если вы хотите написать модуль, который может использоваться как на стороне клиента, так и на стороне сервера, у меня есть короткое сообщение в блоге по быстрому и простому методу: Написание для Node.js и браузера, в основном следующее (где this совпадает с window):

(function(exports){

    // Your code goes here

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

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

В качестве альтернативы есть проекты, направленные на реализацию API Node.js на стороне клиента, например Marak gemini.

Вы также можете быть заинтересованы в DNode, который позволяет вам выставить функцию JavaScript, чтобы ее можно было вызывать с другой машины, используя простой сетевой протокол на основе JSON.

Ответ 2

У 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();

Ответ 3

Проверьте исходный код jQuery, который делает эту работу в шаблоне модуля Node.js, шаблоне модуля AMD и глобальном в браузере:

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

Ответ 4

Не забывайте, что строковое представление функции JavaScript представляет собой исходный код для этой функции. Вы можете просто написать свои функции и конструкторы инкапсулированным способом, чтобы они могли быть toString() 'd и отправлены клиенту.

Другой способ сделать это - использовать систему сборки, поместить общий код в отдельные файлы, а затем включить их как на сервер, так и на клиентские скрипты. Я использую этот подход для простой клиент-серверной игры через WebSockets, где сервер и клиент выполняют по существу один и тот же игровой цикл, и клиент синхронизируется с сервером на каждый тик, чтобы убедиться, что никто не обманывает.

Моя система сборки для игры - это простой Bash script, который запускает файлы через препроцессор C, а затем через sed чтобы очистить некоторые нежелательные cpp файлы, я могу использовать все обычные файлы препроцессора, такие как #include, #define, #ifdef и т.д.

Ответ 5

Я бы рекомендовал изучить адаптер RequireJS для Node.js. Проблема заключается в том, что шаблон модуля Node.js для модели CommonJS по умолчанию не является асинхронным, что блокирует загрузку в веб-браузере. RequireJS использует шаблон AMD, который является одновременно асинхронным и совместимым с сервером и клиентом, если вы используете адаптер r.js.

Ответ 6

Возможно, это не совсем соответствует этому вопросу, но я думал, что поделюсь этим.

Я хотел сделать пару простых функций командной строки, объявленных на String.prototype, доступных как для node, так и для браузера. Я просто сохраняю эти функции в файле с именем utilities.js(во вложенной папке) и легко ссылаюсь на него как из script -tag в моем коде браузера, так и с помощью require (исключая расширение .js) в моем Node.js script:

my_node_script.js

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

my_browser_code.html

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

Надеюсь, это полезная информация для кого-то, кроме меня.

Ответ 7

Сервер может просто отправлять исходные файлы 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);
  }
});

Ключ заключается в том, что клиент может обернуть внешний код в анонимную функцию, которая будет запущена немедленно (закрытие), которая создает объект "export" и возвращает его, чтобы вы могли назначить его там, где хотите, а не загрязнять глобальное пространство имен. В этом примере ему присваивается атрибут window fooModule, который будет содержать код, экспортируемый файлом foo.js.

Ответ 8

Ни одно из предыдущих решений не приносит системе модуля CommonJS браузер.

Как уже упоминалось в других ответах, существуют решения для управляющих активами/упаковщика, такие как Browserify или Piler, и есть RPC-решения, такие как dnode или nowjs.

Но я не смог найти реализацию CommonJS для браузера (включая функцию require() и exports/module.exports и т.д.). Поэтому я написал свое собственное, чтобы потом узнать, что кто-то написал его лучше, чем я: https://github.com/weepy/brequire. Он называется Brequire (сокращение требуется для браузера).

Судя по популярности, управляющие активами отвечают потребностям большинства разработчиков. Однако, если вам нужна реализация браузера CommonJS, Brequire, вероятно, будет соответствовать счету.

2015 Обновление: Я больше не использую Brequire (он не обновлялся через несколько лет). Если я просто напишу небольшой модуль с открытым исходным кодом, и я хочу, чтобы кто-нибудь мог легко его использовать, тогда я буду следовать шаблону, подобному ответу Caolan (выше) - я написал сообщение в блоге об этом пару лет назад.

Однако, если я пишу модули для частного использования или для сообщества, стандартизованного в CommonJS (например, сообщество Ampersand) то я просто напишу их в формате CommonJS и использую Browserify.

Ответ 9

now.js тоже стоит посмотреть. Он позволяет вызывать серверную сторону с клиентской стороны, а клиентские функции - с серверной стороны

Ответ 10

Если вы хотите написать свой браузер в стиле Node.js-like, вы можете попробовать dualify.

Нет компиляции кода браузера, поэтому вы можете писать приложение без ограничений.

Ответ 11

Напишите ваш код как RequireJS модули и ваши тесты как Jasmine.

Этот способ можно загружать везде с помощью RequireJS, и тесты запускаются в браузере с помощью jasmine-html и jasmine-node в Node.js без необходимости изменения кода или тестов.

Вот рабочий пример для этого.

Ответ 12

Я написал это, его просто использовать, если вы хотите установить все переменные в глобальную область:

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

Ответ 13

Случай использования: поделитесь конфигурацией своего приложения между 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'
    

Ответ 14

Я написал простой модуль, который можно импортировать (либо использовать в узле, либо в теги сценария в браузере), который можно использовать для загрузки модулей как с клиента, так и с сервера.

Пример использования

1. Определение модуля

Поместите следующее в файл log2.js внутри папки статических веб файлов:

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.

Если ваш файл не находится в текущем каталоге узла, обязательно вызовите loader.setRoot с помощью пути к папке статических веб файлов (или где бы вы ни находились).

  • В браузере:

Сначала определите веб-страницу:

<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 (или, что бы вы ни http.server сверхбыстрой, командной строки, решение для развертывания веб-сервера папки).

Если все будет хорошо, появится следующее:

enter image description here

Ответ 15

Если вы используете утилиты модулей использования, такие как 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