Bagaimana saya bisa membagikan kode antara Node.js dan browser?

242

Saya membuat aplikasi kecil dengan klien JavaScript (dijalankan di browser) dan server Node.js, berkomunikasi menggunakan WebSocket.

Saya ingin membagikan kode antara klien dan server. Saya baru saja mulai dengan Node.js dan pengetahuan saya tentang JavaScript modern sedikit berkarat, untuk sedikitnya. Jadi saya masih memikirkan fungsi CommonJS membutuhkan (). Jika saya membuat paket saya dengan menggunakan objek 'ekspor', maka saya tidak bisa melihat bagaimana saya bisa menggunakan file JavaScript yang sama di browser.

Saya ingin membuat seperangkat metode dan kelas yang digunakan pada kedua ujungnya untuk memfasilitasi pengodean dan pendekodean pesan, dan tugas-tugas cermin lainnya. Namun, sistem pengemasan Node.js / CommonJS tampaknya menghalangi saya untuk membuat file JavaScript yang dapat digunakan di kedua sisi.

Saya juga mencoba menggunakan JS.Class untuk mendapatkan model OO yang lebih ketat, tetapi saya menyerah karena saya tidak tahu bagaimana cara mendapatkan file JavaScript yang disediakan agar berfungsi dengan memerlukan (). Apakah ada sesuatu yang saya lewatkan di sini?

Gua Simon
sumber
4
Terima kasih semuanya telah memposting jawaban tambahan untuk pertanyaan ini. Ini jelas merupakan topik yang akan dengan cepat berubah dan berkembang.
Simon Cave

Jawaban:

168

Jika Anda ingin menulis modul yang dapat digunakan baik sisi klien dan sisi server, saya punya posting blog pendek tentang metode cepat dan mudah: Menulis untuk Node.js dan browser , pada dasarnya yang berikut (di mana thissama dengan window) :

(function(exports){

    // Your code goes here

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

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

Atau ada beberapa proyek yang bertujuan untuk mengimplementasikan API Node.js di sisi klien, seperti Marini's gemini .

Anda mungkin juga tertarik dengan DNode , yang memungkinkan Anda mengekspos fungsi JavaScript sehingga dapat dipanggil dari komputer lain menggunakan protokol jaringan sederhana berbasis JSON.

Caolan
sumber
Luar biasa. Terima kasih atas informasinya, Caolan.
Gua Simon
2
Artikel yang sangat bagus, Caolan. Saya mengerti, itu berhasil, sekarang saya berputar lagi. Fantastis!
Michael Dausmann
2
Saya menggunakan RequireJs dalam proyek saya sendiri, yang akan memungkinkan saya untuk berbagi modul saya di klien dan server. Kita akan lihat bagaimana hasilnya.
kamranicus
5
@Caolan bahwa tautan sudah mati
Kamal Reddy
5
Tautan gemini sudah mati.
borisdiakur
42

Epeli memiliki solusi yang bagus di sini http://epeli.github.com/piler/ yang bahkan berfungsi tanpa pustaka, cukup masukkan ini dalam file yang disebut share.js

(function(exports){

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

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

Di sisi server cukup gunakan:

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

share.test();

Dan di sisi klien hanya memuat file js dan kemudian gunakan

share.test();
broesch
sumber
10
Saya suka jawaban ini lebih baik daripada yang diterima karena lebih baik dijelaskan untuk pemula seperti saya.
Howie
Di folder Express saya selain folder statis (publik), saya juga punya folder bernama 'shared' yang juga dapat diakses dari klien seperti folder 'publik' seperti itu: app.use (express.static ('public')) ; app.use (express.static ('shared')); Dan posting Anda memperluas gagasan saya berbagi file dengan klien dan server. Inilah yang saya butuhkan. Terima kasih!
Gabungkan
Solusi ini + git subtree == mengagumkan. Terima kasih!
kevinmicke
@broesch Bagaimana cara kerjanya di ES6? Saya telah mengajukan ini sebagai pertanyaan baru , dengan beberapa masalah khusus ES6, tetapi saya akan senang melihat hasil edit di sini!
Tedskovsky
15

Periksa kode sumber jQuery yang menjadikan ini berfungsi dalam pola modul Node.js, pola modul AMD, dan global di peramban:

(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
sumber
Ini adalah metode terbaik (untuk apa yang saya butuhkan). Berikut ini contoh kerja yang saya buat: gist.github.com/drmikecrowe/4bf0938ea73bf704790f
Mike Crowe
13

Jangan lupa bahwa representasi string dari fungsi JavaScript mewakili kode sumber untuk fungsi itu. Anda cukup menulis fungsi dan konstruktor Anda dengan cara enkapsulasi sehingga mereka bisa menjadiString () dan dikirim ke klien.

Cara lain untuk melakukannya adalah menggunakan sistem build, meletakkan kode umum dalam file terpisah, dan kemudian memasukkannya ke dalam skrip server dan klien. Saya menggunakan pendekatan itu untuk permainan klien / server sederhana melalui WebSockets di mana server dan klien menjalankan keduanya pada dasarnya lingkaran permainan yang sama dan klien menyinkronkan dengan server setiap centang untuk memastikan tidak ada yang curang.

Sistem build saya untuk gim ini adalah skrip Bash sederhana yang menjalankan file melalui prepro C dan kemudian melalui sed untuk membersihkan beberapa sampah sampah cpp di belakang, jadi saya dapat menggunakan semua hal preprosesor normal seperti #include, #define, #ifdef , dll.

Dagg Nabbit
sumber
2
Menerialisasi fungsi javascript sebagai string tidak pernah terpikir oleh saya. Terima kasih atas tipnya.
Gua Simon
13

Saya akan merekomendasikan melihat ke dalam adaptor RequireJS untuk Node.js . Masalahnya adalah bahwa pola modul CommonJS yang digunakan Node.js secara default tidak asinkron, yang memblokir pemuatan di browser web. RequireJS menggunakan pola AMD, yang asinkron dan kompatibel dengan server dan klien, selama Anda menggunakan r.jsadaptor.

Serak
sumber
ada perpustakaan async
Jacek Pietal
11

Mungkin ini tidak sepenuhnya sejalan dengan pertanyaan, tetapi saya pikir saya akan membagikan ini.

Saya ingin membuat beberapa fungsi utilitas string sederhana, dideklarasikan pada String.prototype, tersedia untuk node dan browser. Saya cukup menyimpan fungsi-fungsi ini dalam file yang disebut utilities.js (dalam subfolder) dan dapat dengan mudah mereferensikan keduanya dari tag-skrip dalam kode browser saya, dan dengan menggunakan require (menghilangkan ekstensi .js) di skrip Node.js saya :

my_node_script.js

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

my_browser_code.html

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

Saya harap ini adalah informasi yang berguna untuk orang lain selain saya.

Markus Amalthea Magnuson
sumber
1
Saya suka pendekatan ini, tetapi saya menemukan file statis saya bergerak cukup banyak. Salah satu solusi yang saya temukan adalah mengekspor kembali modul. Misalnya, buat utilites.jsdengan satu baris module.exports = require('./static/js/utilities');. Dengan cara ini Anda hanya perlu memperbarui satu jalur jika Anda mengacak barang-barang.
Tom Makin
Aku suka ide ini. Hanya sebuah catatan di jalan yang membuat saya perlu waktu untuk mencari tahu. Folder saya utilities.jsada di sharedbawah proyek. Menggunakan require('/shared/utilities')memberi saya kesalahan Cannot find module '/shared/utilities'. Saya harus menggunakan sesuatu seperti ini require('./../../shared/utilities')untuk membuatnya bekerja. Jadi, selalu berjalan dari folder saat ini dan melakukan perjalanan ke root lalu turun.
pemain baru
Sekarang saya melihat di mana menempatkan modul bersama - di folder statis. Terimakasih atas infonya!
Kombinasikan
9

Jika Anda menggunakan bundler modul seperti webpack untuk membundel file JavaScript untuk digunakan di browser, Anda bisa menggunakan kembali modul Node.js Anda untuk antarmuka yang berjalan di browser. Dengan kata lain, modul Node.js Anda dapat dibagi antara Node.js dan browser.

Misalnya, Anda memiliki kode sum.js berikut:

Modul Node.js normal: sum.js

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

module.exports = sum

Gunakan modul di 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

Gunakan kembali di 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
sumber
4

Server hanya dapat mengirim file sumber JavaScript ke klien (browser) tetapi masalahnya adalah bahwa klien harus menyediakan lingkungan "ekspor" mini sebelum dapat exec kode dan menyimpannya sebagai modul.

Cara sederhana untuk membuat lingkungan seperti itu adalah dengan menggunakan penutupan. Misalnya, server Anda menyediakan file sumber melalui HTTP like http://example.com/js/foo.js. Browser dapat memuat file yang diperlukan melalui XMLHttpRequest dan memuat kode seperti ini:

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

Kuncinya adalah bahwa klien dapat membungkus kode asing menjadi fungsi anonim untuk segera dijalankan (penutupan) yang menciptakan objek "ekspor" dan mengembalikannya sehingga Anda dapat menetapkannya di tempat yang Anda inginkan, daripada mencemari namespace global. Dalam contoh ini, ditugaskan ke atribut jendela fooModuleyang akan berisi kode yang diekspor oleh file foo.js.

maerics
sumber
2
setiap kali Anda menggunakan eval, Anda membunuh gnome
Jacek Pietal
1
Saya akan menggunakan window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule).
GingerPlusPlus
2

Tidak ada solusi sebelumnya yang membawa sistem modul CommonJS ke browser.

Seperti disebutkan dalam jawaban lain, ada solusi manajer aset / paket seperti Browserify atau Piler dan ada solusi RPC seperti dnode atau nowjs .

Tetapi saya tidak dapat menemukan implementasi CommonJS untuk browser (termasuk require()fungsi dan exports/ module.exportsobjek, dll.). Jadi saya menulis sendiri, hanya untuk menemukan setelah itu bahwa orang lain telah menulisnya lebih baik daripada saya: https://github.com/weepy/brequire . Ini disebut Brequire (kependekan dari Browser membutuhkan).

Dilihat berdasarkan popularitas, manajer aset sesuai dengan kebutuhan kebanyakan pengembang. Namun, jika Anda memerlukan implementasi browser CommonJS, Brequire mungkin akan sesuai dengan tagihan.

Pembaruan 2015: Saya tidak lagi menggunakan Brequire (belum diperbarui dalam beberapa tahun). Jika saya hanya menulis modul kecil, open-source dan saya ingin siapa saja dapat menggunakan dengan mudah, maka saya akan mengikuti pola yang mirip dengan jawaban Caolan (di atas) - Saya menulis posting blog tentang hal itu beberapa tahun lalu.

Namun, jika saya menulis modul untuk penggunaan pribadi atau untuk komunitas yang distandarisasi pada CommonJS (seperti komunitas Ampersand ) maka saya hanya akan menulisnya dalam format CommonJS dan menggunakan Browserify .

Peter Rust
sumber
1

now.js juga patut dilihat. Ini memungkinkan Anda untuk memanggil sisi server dari sisi klien, dan fungsi sisi klien dari sisi server

balupton
sumber
1
Proyek telah dihentikan - apakah Anda tahu ada penggantian yang baik untuk itu? groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
Anderson Green
satu-satunya yang saya tahu adalah jembatan dan itu oleh orang yang sama, jadi juga ditinggalkan. Versi 0.9 dari socket.io juga mendukung panggilan balik untuk acara - namun tidak seperti kode berbagi now.js, tetapi berfungsi cukup baik.
balupton
Ada juga sharejs, yang tampaknya dipelihara secara aktif. sharejs.org
Anderson Green
1

Jika Anda ingin menulis peramban dengan gaya seperti Node.js, Anda dapat mencoba memilah .

Tidak ada kompilasi kode browser, sehingga Anda dapat menulis aplikasi Anda tanpa batasan.

farincz
sumber
1

Tulis kode Anda sebagai modul RequireJS dan tes Anda sebagai tes Jasmine .

Kode cara ini dapat dimuat di mana saja dengan RequireJS dan tes dijalankan di browser dengan jasmine-html dan dengan jasmine-node di Node.js tanpa perlu memodifikasi kode atau tes.

Ini adalah contoh yang bagus untuk ini.

Blacksonic
sumber
1

Use case: bagikan konfigurasi aplikasi Anda antara Node.js dan browser (ini hanya ilustrasi, mungkin bukan pendekatan terbaik tergantung pada aplikasi Anda).

Masalah: Anda tidak dapat menggunakan window(tidak ada di Node.js) atauglobal (tidak ada di browser).

Larutan:

  • File config.js:

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

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

    Anda sekarang dapat membuka alat dev dan mengakses variabel global config

  • Di Node.js (app.js):

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

    import config from './config';
    console.log(config.foo); // Prints 'bar'
tanguy_k
sumber
1
Terima kasih untuk ini.
Microsis
Tindak lanjut: Katakanlah saya memiliki dua file yang dibagi antara server.js dan client.js: shared.jsdan helpers.js- shared.jsmenggunakan fungsi dari helpers.js, jadi perlu const { helperFunc } = require('./helpers')di bagian atas, agar dapat bekerja di sisi server. Masalahnya adalah pada klien, ia mengeluh tentang requiretidak menjadi fungsi, tetapi jika saya membungkus baris memerlukan if (typeof module === 'object') { ... }, server mengatakan bahwa helperFunc () tidak didefinisikan (di luar pernyataan if). Ada ide untuk membuatnya bekerja pada keduanya?
Microsis
Pembaruan: Sepertinya saya sudah membuatnya bekerja dengan menempatkan ini di atas shared.js: helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;- Akan membutuhkan garis untuk setiap fungsi yang diekspor, tapi mudah-mudahan ini solusi yang baik?
Microsis
1

Saya menulis modul sederhana , yang dapat diimpor (baik menggunakan wajib di Node, atau tag skrip di browser), yang dapat Anda gunakan untuk memuat modul baik dari klien dan dari server.

Contoh penggunaan

1. Menentukan modul

Tempatkan yang berikut dalam file log2.js, di dalam folder file web statis Anda:

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;

Sederhana seperti itu!

2. Menggunakan modul

Karena ini adalah pemuat modul bilateral , kami dapat memuatnya dari kedua sisi (klien dan server). Oleh karena itu, Anda dapat melakukan hal berikut, tetapi Anda tidak perlu melakukan keduanya sekaligus (apalagi dalam urutan tertentu):

  • Di Node

Di Node, itu sederhana:

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

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

Ini harus kembali 2.

Jika file Anda tidak ada di direktori Node saat ini, pastikan untuk memanggil loader.setRootdengan path ke folder file web statis Anda (atau di mana pun modul Anda berada).

  • Di browser:

Pertama, tentukan halaman 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>

Pastikan Anda tidak membuka file secara langsung di browser Anda; karena menggunakan AJAX, saya sarankan Anda melihat http.servermodul Python 3 (atau apa pun supercepat Anda, baris perintah, solusi penyebaran folder server web) sebagai gantinya.

Jika semuanya berjalan dengan baik, ini akan muncul:

masukkan deskripsi gambar di sini

Gustavo6046
sumber
0

Saya menulis ini, mudah digunakan jika Anda ingin mengatur semua variabel ke lingkup global:

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