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


242

Я створюю невелику програму з клієнтом JavaScript (запуск у браузері) та сервером Node.js, спілкуючись за допомогою WebSocket.

Я хотів би поділитися кодом між клієнтом і сервером. Я лише розпочав роботу з Node.js, і мої знання сучасного JavaScript, як мінімум, іржаві. Таким чином, я все ще мою голову навколо CommonJS вимагають () функції. Якщо я створюю свої пакунки за допомогою об’єкта "експортування", я не можу побачити, як я міг би використовувати ті самі файли JavaScript у браузері.

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

Я також спробував за допомогою JS.Class отримати більш жорстку модель OO, але я відмовився, тому що не міг зрозуміти, як змусити надані JavaScript файли працювати з Requ (). Щось тут мені не вистачає?


4
Дякую всім за те, що опублікували додаткові відповіді на це питання. Це, очевидно, тема, яка швидко змінюватиметься та розвиватиметься.
— Simon Cave

Відповіді:


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 на стороні клієнта, наприклад, Marak в Близнюках .

Вас також може зацікавити DNode , яка дозволяє відкрити функцію JavaScript, щоб її можна було викликати з іншої машини за допомогою простого мережевого протоколу на основі JSON.


Відмінно. Дякую за інформацію, Каолан.
— Simon Cave

2
Дійсно чудова стаття Caolan. Я зрозумів це, це спрацювало, зараз я знову катаюсь. Фантастичний!
— Майкл Доусман

2
Я використовую RequireJs у власному проекті, який дозволить мені ділитися своїми модулями на клієнті та сервері. Ми побачимо, як це виходить.
— kamranicus

5
@Caolan це посилання мертве
— Камал Редді

5
Посилання близнюків мертве.
— borisdiakur

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

10
Мені подобається ця відповідь краще, ніж прийнята, тому що вона краще пояснюється для новачків, як я.
— Howie

У моїй папці Express, окрім статичної (загальнодоступної) папки, я також маю папку з назвою "shared", яка також доступна клієнту, як-от папка "public", як: app.use (express.static ("public")) ; app.use (express.static ('спільний')); І ваша публікація поширює мою ідею обміну файлами з клієнтом і сервером. Це саме те, що мені було потрібно. Дякую!
— Поєднайте

Це рішення + git subtree == приголомшливо. Дякую!
— kevinmicke

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

Це найкращий метод (для того, що мені було потрібно). Ось робочий приклад, який я створив: gist.github.com/drmikecrowe/4bf0938ea73bf704790f
— Майк Кроу

13

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

Інший спосіб зробити це - скористатися системою збірки, помістити загальний код в окремі файли, а потім включити їх як в серверний, так і в клієнтський сценарії. Я використовую такий підхід для простої гри клієнт / сервер через WebSockets, де і сервер, і клієнт запускають по суті один і той же ігровий цикл, і клієнт синхронізує з сервером кожен галочку, щоб переконатися, що ніхто не обманює.

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


2
Послідовність функцій javascript як рядків мені ніколи не бувало. Дякую за пораду.
— Simon Cave

13

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


є бібліотека асинхронізації
— Jacek Pietal

11

Можливо, це не зовсім відповідає питанню, але я подумав, що поділюсь цим.

Я хотів зробити пару простих функцій утиліти рядків, оголошених на String.prototype, доступними як для вузла, так і для браузера. Я просто зберігаю ці функції у файлі під назвою utilities.js (у підпапці) і можу легко посилатись на нього як із тегу скрипту в коді браузера, так і за допомогою вимагаю (опускаючи розширення .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

Якщо ви використовуєте пакети модулів, такі як 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

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

Ключовим моментом є те, що клієнт може перетворити іноземний код в анонімну функцію, яку слід запустити негайно (закриття), яка створює об'єкт "експортування" та повертає його, щоб ви могли призначити його куди завгодно, а не забруднювати глобальну область імен. У цьому прикладі він призначається атрибуту вікна, fooModuleякий буде містити код, експортований файлом foo.js.


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.exportsоб'єкти тощо). Тож я написав своє, лише щоб потім виявити, що хтось ще написав це краще, ніж я: https://github.com/weepy/brequire . Він називається Brequire (скорочено для браузера).

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

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

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


1

now.js також варто подивитися. Це дозволяє зателефонувати на сторону сервера з боку клієнта, а функції на стороні клієнта - з боку сервера


1
Проект припинено - чи знаєте ви якісь хороші заміни для нього? groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
— Anderson Green

єдиний інший, кого я знаю, був міст, і це були ті самі люди, тому також покинуті. Версія 0.9 socket.io також підтримує зворотні дзвінки для подій, однак, як і код спільного використання now.js, однак він працює досить добре.
— balupton

Є також sharejs, який, схоже, активно підтримується. sharejs.org
— Anderson Green

1

Якщо ви хочете написати свій веб-переглядач у стилі Node.js, ви можете спробувати його підтвердити .

Компіляція коду браузера не існує, тому ви можете писати заявку без обмежень.


1

Напишіть свій код як модулі RequireJS, а свої тести - як тести Жасмін .

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

Ось робочий приклад для цього.


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'

1
Дякую за це.
— Мікроз

Подальші дії: Скажімо, у мене є два файли, які спільно використовуються server.js та client.js: shared.jsі helpers.js- shared.jsвикористовує функції від helpers.js, тому це потрібно const { helperFunc } = require('./helpers')вгорі, щоб він працював на стороні сервера. Проблема є у клієнта, він скаржиться на те, що він requireне є функцією, але якщо я if (typeof module === 'object') { ... }перегорнув рядок вимагання , сервер каже, що helperFunc () не визначено (за межами оператора if). Будь-які ідеї, щоб він працював над обома?
— Мікроз

Оновлення: я, здається, змусив його працювати, розміщуючи це у верхній частині shared.js: helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;- Чи потрібна буде лінія для кожної експортованої функції, на жаль, але, сподіваємось, це хороше рішення?
— Мікроз

1

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

Приклад використання

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 (або все, що є вашим супершвидким, командним рядком, рішенням розгортання веб-сервера папок).

Якщо все піде добре, це з’явиться:

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


0

Я написав це, його просто використовувати, якщо ви хочете встановити всі змінні в глобальній області:

(function(vars, global) {
    for (var i in vars) global[i] = vars[i];
})({
    abc: function() {
        ...
    },
    xyz: function() {
        ...
    }
}, typeof exports === "undefined" ? this : exports);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.