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

242

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

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

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

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

Саймон Кейв
источник
4
Спасибо всем за размещение дополнительных ответов на этот вопрос. Это явно тема, которая будет быстро меняться и развиваться.
Саймон Кейв

Ответы:

168

Если вы хотите написать модуль, который можно использовать как на стороне клиента, так и на стороне сервера, у меня есть короткое сообщение в блоге о быстром и простом методе: написание для 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 на стороне клиента, например, Gemini Марака .

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

Caolan
источник
Превосходно. Спасибо за информацию, Caolan.
Саймон Кейв
2
Действительно отличная статья Caolan. Я понял это, это сработало, теперь я снова катаюсь. Фантастика!
Майкл Даусманн
2
Я использую RequireJ в своем собственном проекте, что позволит мне делиться моими модулями на клиенте и сервере. Посмотрим, как это получится.
kamranicus
5
@Caolan эта ссылка мертва
Камаль Редди
5
Ссылка на Близнецов мертва.
Борисдиакур
42

У 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();
broesch
источник
10
Мне нравится этот ответ лучше, чем принятый, потому что он лучше объясняется для таких новичков, как я.
Хоуи
В моей папке Express, кроме статической (публичной) папки, у меня также есть папка с именем «shared», которая также доступна из клиента, например, как «public»: app.use (express.static («public»)) ; app.use (express.static ( 'общий')); И ваш пост расширяет мою идею об обмене файлами с клиентом и сервером. Это именно то, что мне было нужно. Спасибо!
Объединить
Это решение + git поддерево == круто. Спасибо!
Кевинмике
@broesch Как это будет работать в ES6? Я задал это как новый вопрос , с некоторыми специфическими для ES6 проблемами, но я был бы так же рад увидеть редактирование здесь!
Тедсковский
15

Извлеките исходный код 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)
wlingke
источник
Это самый лучший метод (для чего мне нужно). Вот рабочий пример, который я создал: gist.github.com/drmikecrowe/4bf0938ea73bf704790f
Майк Кроу,
13

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

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

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

Дагг Наббит
источник
2
Сериализация функций JavaScript как строк никогда не приходила мне в голову. Спасибо за чаевые.
Саймон Кейв
13

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

хриплый
источник
есть асинхронная библиотека
Яцек Пьетал
11

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

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

my_node_script.js

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

my_browser_code.html

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

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

Маркус Амальтея Магнусон
источник
1
Мне нравится такой подход, но я нахожу, что мои статические файлы перемещаются довольно часто. Одно решение, которое я нашел, - это реэкспорт модуля. Например, создать utilites.jsв одной строке module.exports = require('./static/js/utilities');. Таким образом, вам нужно обновить только один путь, если вы перемешиваете вещи вокруг.
Том Макин
Мне нравится эта идея. Просто записка на пути, которая заняла у меня некоторое время, чтобы выяснить. Мой utilities.jsнаходится в sharedпапке под проектом. Использование require('/shared/utilities')дало мне ошибку Cannot find module '/shared/utilities'. Я должен использовать что-то подобное, require('./../../shared/utilities')чтобы это работало. Таким образом, он всегда идет из текущей папки и перемещается вверх, а затем вниз.
новичок
Теперь я вижу, где разместить общий модуль - в статической папке. Спасибо за информацию!
Объединить
9

Если вы используете упаковщики модулей, такие как веб- пакет, для объединения файлов 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
Юйцы
источник
4

Сервер может просто отправлять исходные файлы 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» и возвращает его, чтобы вы могли назначить его там, где хотите, а не загрязнять глобальное пространство имен. В этом примере он назначен атрибуту окна, fooModuleкоторый будет содержать код, экспортированный файлом foo.js.

maerics
источник
2
каждый раз, когда вы используете eval, вы убиваете гнома
Jacek Pietal
1
Я бы использовал window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule).
GingerPlusPlus
2

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

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

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

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

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

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

Питер Руст
источник
1

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

balupton
источник
1
Проект был прекращен - знаете ли вы какие-либо хорошие замены для него? groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
Андерсон Грин
единственный другой, которого я знаю, был мостом, и это были те же самые люди, поэтому также заброшенные. Версия socket.io версии 0.9 также поддерживает обратные вызовы для событий, однако это не то же самое, что общий код now.js, но он работает достаточно хорошо.
Балуптон
Есть также sharejs, который, похоже, активно поддерживается. sharejs.org
Андерсон Грин
1

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

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

farincz
источник
1

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

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

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

Blacksonic
источник
1

Вариант использования: поделитесь конфигурацией вашего приложения между 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'
tanguy_k
источник
1
Спасибо тебе за это.
Microsis
Продолжение: допустим, у меня есть два файла, которые используются совместно для server.js и client.js: shared.jsи helpers.js- shared.jsиспользует функции из helpers.js, поэтому он должен быть const { helperFunc } = require('./helpers')вверху, чтобы он работал на стороне сервера. Проблема на клиенте, он жалуется на то, что он requireне является функцией, но если я обертываю строку require if (typeof module === 'object') { ... }, сервер говорит, что helperFunc () не определен (вне оператора if). Любые идеи, чтобы заставить это работать на обоих?
Microsis
Обновление: я, кажется, получил это, поместив это наверху shared.js: helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;- К сожалению, понадобится строка для каждой экспортируемой функции, но, надеюсь, это хорошее решение?
Microsis
1

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

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

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. Использование модуля

Поскольку это двусторонний загрузчик модулей, мы можем загрузить его с обеих сторон (клиента и сервера). Таким образом, вы можете сделать следующее, но вам не нужно делать оба сразу (не говоря уже в определенном порядке):

  • В узле

В Node все просто:

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

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

Это должно вернуться 2.

Если ваш файл не находится в текущем каталоге Node, обязательно позвоните 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 (или каково ваше решение для развертывания суперскоростного сервера, командной строки, веб-сервера папок).

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

введите описание изображения здесь

Gustavo6046
источник
0

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

(function(vars, global) {
    for (var i in vars) global[i] = vars[i];
})({
    abc: function() {
        ...
    },
    xyz: function() {
        ...
    }
}, typeof exports === "undefined" ? this : exports);
супер
источник