Làm cách nào tôi có thể chia sẻ mã giữa Node.js và trình duyệt?


241

Tôi đang tạo một ứng dụng nhỏ với máy khách JavaScript (chạy trên trình duyệt) và máy chủ Node.js, giao tiếp bằng WebSocket.

Tôi muốn chia sẻ mã giữa máy khách và máy chủ. Tôi mới chỉ bắt đầu với Node.js và kiến ​​thức về JavaScript hiện đại của tôi hơi thô lỗ, để nói rằng ít nhất. Vì vậy, tôi vẫn đang tìm hiểu về hàm CommonJS request (). Nếu tôi đang tạo các gói của mình bằng cách sử dụng đối tượng 'xuất', thì tôi không thể thấy cách tôi có thể sử dụng cùng các tệp JavaScript trong trình duyệt.

Tôi muốn tạo một tập hợp các phương thức và các lớp được sử dụng ở cả hai đầu để tạo điều kiện cho việc mã hóa và giải mã tin nhắn và các tác vụ được nhân đôi khác. Tuy nhiên, các hệ thống đóng gói Node.js / CommonJS dường như ngăn cản tôi tạo các tệp JavaScript có thể được sử dụng ở cả hai bên.

Tôi cũng đã thử sử dụng JS.Class để có được mô hình OO chặt chẽ hơn, nhưng tôi đã từ bỏ vì tôi không thể tìm ra cách để các tệp JavaScript được cung cấp hoạt động với yêu cầu (). Có cái gì tôi đang thiếu ở đây?


4
Cảm ơn tất cả mọi người đã đăng câu trả lời bổ sung cho câu hỏi này. Đây rõ ràng là một chủ đề sẽ nhanh chóng thay đổi và phát triển.
Hang Simon

Câu trả lời:


169

Nếu bạn muốn viết một mô-đun có thể được sử dụng cho cả phía máy khách và phía máy chủ, tôi có một bài đăng blog ngắn về phương pháp nhanh chóng và dễ dàng: Viết cho Node.js và trình duyệt , về cơ bản là như sau ( thisgiống như window) :

(function(exports){

    // Your code goes here

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

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

Ngoài ra, có một số dự án nhằm triển khai API Node.js ở phía máy khách, chẳng hạn như gemini của Marak .

Bạn cũng có thể quan tâm đến DNode , cho phép bạn hiển thị một hàm JavaScript để có thể gọi nó từ một máy khác bằng giao thức mạng dựa trên JSON đơn giản.


Thông minh. Cảm ơn thông tin, Caolan.
Hang Simon

2
Caolan bài viết thực sự tuyệt vời. Tôi hiểu nó, nó hoạt động, bây giờ tôi lại lăn. Tuyệt diệu!
Michael Dausmann

2
Tôi đang sử dụng RequireJs trong dự án của riêng tôi, điều này sẽ cho phép tôi chia sẻ các mô-đun của mình trên máy khách và máy chủ. Chúng ta sẽ thấy nó hoạt động ra sao.
kamranicus

5
@Caolan liên kết đó đã chết
Kamal Reddy

5
Liên kết gemini đã chết.
boronomiakur

43

Epeli có một giải pháp hay tại đây http://epeli.github.com/piler/ thậm chí hoạt động mà không cần thư viện, chỉ cần đặt cái này vào một tệp có tên share.js

(function(exports){

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

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

Về phía máy chủ chỉ cần sử dụng:

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

share.test();

Và về phía khách hàng chỉ cần tải tệp js và sau đó sử dụng

share.test();

10
Tôi thích câu trả lời này tốt hơn câu trả lời được chấp nhận vì nó được giải thích tốt hơn cho những người mới như tôi.
Howie

Trong thư mục Express của tôi bên cạnh thư mục tĩnh (công khai), tôi cũng có một thư mục có tên 'shared' cũng có thể truy cập được từ máy khách như thư mục 'công khai' như thế: app.use (express.static ('public')) ; app.use (express.static ('chia sẻ')); Và bài đăng của bạn mở rộng ý tưởng của tôi về việc chia sẻ tệp với khách hàng và máy chủ. Đây chính xác là những gì tôi cần. Cảm ơn bạn!
Kết hợp

Giải pháp này + git Subree == tuyệt vời. Cảm ơn!
kevinmicke

@broesch Làm thế nào điều này sẽ làm việc trong ES6? Tôi đã hỏi đây là một câu hỏi mới , với một số vấn đề cụ thể về ES6, nhưng tôi rất vui khi thấy một chỉnh sửa ở đây!
Tedskovsky

15

Kiểm tra mã nguồn jQuery để thực hiện công việc này trong mẫu mô-đun Node.js, mẫu mô-đun AMD và toàn cầu trong trình duyệt:

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

Đây là phương pháp tốt nhất (cho những gì tôi cần). Đây là một ví dụ hoạt động tôi đã tạo: gist.github.com/drmikecrowe/4bf0938ea73bf704790f
Mike Crowe

13

Đừng quên rằng biểu diễn chuỗi của hàm JavaScript biểu thị mã nguồn cho hàm đó. Bạn chỉ có thể viết các hàm và hàm tạo của mình theo cách được đóng gói để chúng có thể là toString () 'd và được gửi đến máy khách.

Một cách khác để làm điều đó là sử dụng một hệ thống xây dựng, đặt mã chung vào các tệp riêng biệt và sau đó đưa chúng vào cả tập lệnh máy chủ và máy khách. Tôi đang sử dụng phương pháp đó cho một trò chơi máy khách / máy chủ đơn giản thông qua WebSockets, nơi cả máy chủ và máy khách đều chạy cùng một vòng lặp trò chơi và máy khách đồng bộ hóa với máy chủ mỗi lần đánh dấu để đảm bảo không ai gian lận.

Hệ thống xây dựng của tôi cho trò chơi là một tập lệnh Bash đơn giản chạy các tệp thông qua bộ tiền xử lý C và sau đó qua sed để dọn sạch một số lá cpp rác phía sau, vì vậy tôi có thể sử dụng tất cả các công cụ tiền xử lý thông thường như #include, #define, #ifdef , Vân vân.


2
Nối tiếp các chức năng javascript như chuỗi không bao giờ xảy ra với tôi. Cảm ơn vì tiền hỗ trợ.
Hang Simon

13

Tôi sẽ khuyên bạn nên nhìn vào các bộ chuyển đổi RequireJS cho Node.js . Vấn đề là mẫu mô-đun CommonJS mà Node.js sử dụng theo mặc định không đồng bộ, điều này chặn tải trong trình duyệt web. RequireJS sử dụng mẫu AMD, không đồng bộ và tương thích với cả máy chủ và máy khách, miễn là bạn sử dụng r.jsbộ chuyển đổi.


có thư viện async
Jacek Pietal

11

Có thể điều này không hoàn toàn phù hợp với câu hỏi, nhưng tôi nghĩ tôi sẽ chia sẻ điều này.

Tôi muốn tạo một vài hàm tiện ích chuỗi đơn giản, được khai báo trên String.prototype, có sẵn cho cả nút và trình duyệt. Tôi chỉ cần giữ các hàm này trong một tệp có tên là Utility.js (trong thư mục con) và có thể dễ dàng tham chiếu cả hai từ thẻ script trong mã trình duyệt của tôi và bằng cách sử dụng yêu cầu (bỏ qua phần mở rộng .js) trong tập lệnh Node.js của tôi :

my_node_script.js

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

my_browser_code.html

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

Tôi hy vọng đây là thông tin hữu ích cho người khác ngoài tôi.


1
Tôi thích cách tiếp cận này nhưng tôi thấy các tệp tĩnh của tôi bị di chuyển xung quanh khá nhiều. Một giải pháp tôi đã tìm thấy là tái xuất mô-đun. Ví dụ: tạo utilites.jsvới một dòng duy nhất module.exports = require('./static/js/utilities');. Bằng cách này, bạn chỉ cần cập nhật một đường dẫn nếu bạn xáo trộn mọi thứ xung quanh.
Tom Makin

Tôi thích ý tưởng này. Chỉ là một ghi chú trên con đường mà tôi phải mất một thời gian để tìm ra. My utilities.jsđang ở trong sharedthư mục của dự án. Sử dụng require('/shared/utilities')đã cho tôi lỗi Cannot find module '/shared/utilities'. Tôi phải sử dụng một cái gì đó như thế này require('./../../shared/utilities')để làm cho nó hoạt động. Vì vậy, nó luôn đi từ thư mục hiện tại và đi lên đến root rồi xuống.
newman

Bây giờ tôi thấy nơi đặt mô-đun chia sẻ - trong thư mục tĩnh. Cảm ơn bạn về thông tin!
Kết hợp

9

Nếu bạn sử dụng các gói mô-đun như gói webpack để gói các tệp JavaScript để sử dụng trong trình duyệt, bạn chỉ cần sử dụng lại mô-đun Node.js của mình cho giao diện đang chạy trong trình duyệt. Nói cách khác, mô-đun Node.js của bạn có thể được chia sẻ giữa Node.js và trình duyệt.

Ví dụ: bạn có mã sum.js sau:

Mô-đun Node.js bình thường: sum.js

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

module.exports = sum

Sử dụng mô-đun trong 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

Tái sử dụng nó trong frontend

import sum from 'path-to-sum.js'
console.log('Sum of 2 and 5: ', sum(2, 5)) // Sum of 2 and 5:  7

4

Máy chủ có thể chỉ cần gửi các tệp nguồn JavaScript đến máy khách (trình duyệt) nhưng mẹo là máy khách sẽ phải cung cấp một môi trường "xuất khẩu" nhỏ trước khi có thể exec mã và lưu trữ dưới dạng mô-đun.

Một cách đơn giản để tạo ra một môi trường như vậy là sử dụng một bao đóng. Ví dụ: giả sử máy chủ của bạn cung cấp các tệp nguồn thông qua HTTP như thế nào http://example.com/js/foo.js. Trình duyệt có thể tải các tệp cần thiết thông qua XMLHttpRequest và tải mã như vậy:

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

Điều quan trọng là khách hàng có thể bọc mã nước ngoài thành một hàm ẩn danh để được chạy ngay lập tức (một bao đóng) tạo ra đối tượng "xuất khẩu" và trả về nó để bạn có thể gán nó ở nơi bạn muốn, thay vì làm ô nhiễm không gian tên toàn cầu. Trong ví dụ này, nó được gán cho thuộc tính window fooModulesẽ chứa mã được xuất bởi tệp foo.js.


2
Mỗi khi bạn sử dụng eval, bạn sẽ giết một gnome
Jacek Pietal

1
Tôi sẽ sử dụng window.fooModule = {}; (new Function('exports', xhr.responseText))(window.fooModule).
GingerPlusPlus

2

Không có giải pháp nào trước đây đưa hệ thống mô-đun CommonJS lên trình duyệt.

Như đã đề cập trong các câu trả lời khác, có các giải pháp quản lý / đóng gói tài sản như Browserify hoặc Piler và có các giải pháp RPC như dnode hoặc nowjs .

Nhưng tôi không thể tìm thấy việc triển khai CommonJS cho trình duyệt (bao gồm cả require()hàm và exports/ module.exportsđối tượng, v.v.). Vì vậy, tôi đã tự viết, chỉ để khám phá sau đó rằng người khác đã viết nó tốt hơn tôi có: https://github.com/weepy/enamequire . Nó được gọi là Brequire (viết tắt của Trình duyệt yêu cầu).

Đánh giá theo mức độ phổ biến, các nhà quản lý tài sản phù hợp với nhu cầu của hầu hết các nhà phát triển. Tuy nhiên, nếu bạn cần triển khai CommonJS trên trình duyệt, Brequire có thể sẽ phù hợp với dự luật.

Cập nhật 2015: Tôi không còn sử dụng Brequire nữa (nó đã không được cập nhật trong một vài năm). Nếu tôi chỉ viết một mô-đun nguồn mở nhỏ và tôi muốn mọi người có thể dễ dàng sử dụng, thì tôi sẽ làm theo một mô hình tương tự như câu trả lời của Caolan (ở trên) - Tôi đã viết một bài đăng trên blog về nó vài năm trước đây

Tuy nhiên, nếu tôi đang viết các mô-đun để sử dụng riêng hoặc cho một cộng đồng được chuẩn hóa trên CommonJS (như cộng đồng Ampersand ) thì tôi sẽ chỉ viết chúng theo định dạng CommonJS và sử dụng Browserify .


1

now.js cũng đáng xem. Nó cho phép bạn gọi phía máy chủ từ phía máy khách và các chức năng phía máy khách từ phía máy chủ


1
Dự án đã bị ngừng - bạn có biết bất kỳ sự thay thế tốt nào cho nó không? Groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ
Anderson Green

người duy nhất tôi biết là cây cầu và cũng bởi cùng một người, nên cũng bị bỏ rơi. Phiên bản 0.9 của socket.io cũng hỗ trợ gọi lại cho các sự kiện - tuy nhiên không có gì giống như mã chia sẻ của now.js, nhưng nó hoạt động đủ tốt.
balupton

Ngoài ra còn có sharejs, dường như được duy trì tích cực. sharejs.org
Anderson Green

1

Nếu bạn muốn viết trình duyệt của mình theo kiểu giống Node.js, bạn có thể thử dualify .

Không có trình biên dịch mã trình duyệt, vì vậy bạn có thể viết ứng dụng của mình mà không bị giới hạn.


1

Viết mã của bạn dưới dạng các mô-đun RequireJS và các bài kiểm tra của bạn dưới dạng các bài kiểm tra của Jasmine .

Cách mã này có thể được tải ở mọi nơi với RequireJS và các bài kiểm tra sẽ được chạy trong trình duyệt với jasmine-html và với nút hoa nhài trong Node.js mà không cần phải sửa đổi mã hoặc các bài kiểm tra.

Dưới đây là một ví dụ làm việc cho điều này.


1

Trường hợp sử dụng: chia sẻ cấu hình ứng dụng của bạn giữa Node.js và trình duyệt (đây chỉ là một minh họa, có thể không phải là cách tiếp cận tốt nhất tùy thuộc vào ứng dụng của bạn).

Vấn đề: bạn không thể sử dụng window(không tồn tại trong Node.js) cũng nhưglobal (không tồn tại trong trình duyệt).

Giải pháp:

  • Tệp config.js:

    var config = {
      foo: 'bar'
    };
    if (typeof module === 'object') module.exports = config;
  • Trong trình duyệt (index.html):

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

    Bây giờ bạn có thể mở các công cụ dev và truy cập vào biến toàn cục config

  • Trong Node.js (app.js):

    const config = require('./config');
    console.log(config.foo); // Prints 'bar'
  • Với Babel hoặc TypeScript:

    import config from './config';
    console.log(config.foo); // Prints 'bar'

1
Cảm ơn vì điều này.
microsis

Theo dõi: Giả sử tôi có hai tệp được chia sẻ giữa server.js và client.js: shared.jshelpers.js- shared.jssử dụng các hàm từ đó helpers.js, do đó, nó cần const { helperFunc } = require('./helpers')ở trên cùng, để nó hoạt động ở phía máy chủ. Vấn đề là ở máy khách, nó phàn nàn về việc requirekhông phải là một hàm, nhưng nếu tôi bọc dòng yêu cầu if (typeof module === 'object') { ... }, máy chủ nói rằng helperFunc () không được xác định (bên ngoài câu lệnh if). Bất kỳ ý tưởng để làm cho nó làm việc trên cả hai?
microsis

Cập nhật: Tôi dường như đã làm cho nó hoạt động bằng cách đặt nó ở đầu shared.js: helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;- Sẽ cần một dòng cho mỗi chức năng xuất không may nhưng hy vọng đó là một giải pháp tốt?
microsis

1

Tôi đã viết một mô-đun đơn giản , có thể được nhập (sử dụng yêu cầu trong Node hoặc thẻ script trong trình duyệt), mà bạn có thể sử dụng để tải các mô-đun cả từ máy khách và từ máy chủ.

Ví dụ sử dụng

1. Xác định mô-đun

Đặt các mục sau vào một tệp log2.js, bên trong thư mục tệp web tĩnh của bạn:

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;

Đơn giản như vậy!

2. Sử dụng mô-đun

Vì nó là một trình tải mô-đun song phương , chúng tôi có thể tải nó từ cả hai phía (máy khách và máy chủ). Do đó, bạn có thể thực hiện các thao tác sau, nhưng bạn không cần thực hiện cả hai cùng một lúc (hãy để một mình theo một thứ tự cụ thể):

  • Trong nút

Trong Node, nó đơn giản:

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

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

Điều này sẽ trở lại 2.

Nếu tệp của bạn không có trong thư mục hiện tại của Node, hãy đảm bảo gọi loader.setRootbằng đường dẫn đến thư mục tệp web tĩnh của bạn (hoặc bất cứ nơi nào mô-đun của bạn).

  • Trong trình duyệt:

Đầu tiên, xác định trang 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>

Hãy chắc chắn rằng bạn không mở tệp trực tiếp trong trình duyệt của bạn; vì nó sử dụng AJAX, tôi khuyên bạn nên xem http.servermô-đun của Python 3 (hoặc bất cứ giải pháp triển khai máy chủ web thư mục nào, siêu tốc độ của bạn).

Nếu mọi thứ suôn sẻ, điều này sẽ xuất hiện:

nhập mô tả hình ảnh ở đây


0

Tôi đã viết điều này, nó rất đơn giản để sử dụng nếu bạn muốn đặt tất cả các biến thành phạm vi toàn cầu:

(function(vars, global) {
    for (var i in vars) global[i] = vars[i];
})({
    abc: function() {
        ...
    },
    xyz: function() {
        ...
    }
}, typeof exports === "undefined" ? this : exports);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.