Як я можу поділитися кодом між 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.