簡體   English   中英

如何在 Node.js 和瀏覽器之間共享代碼?

[英]How can I share code between Node.js and the browser?

我正在創建一個帶有 JavaScript 客戶端(在瀏覽器中運行)和一個 Node.js 服務器的小型應用程序,使用 WebSocket 進行通信。

我想在客戶端和服務器之間共享代碼。 我剛剛開始使用 Node.js,至少可以說,我對現代 JavaScript 的了解有點生疏。 所以我仍然在思考 CommonJS require() 函數。 如果我使用“導出”對象創建包,那么我看不到如何在瀏覽器中使用相同的 JavaScript 文件。

我想創建一組在兩端使用的方法和類,以便於編碼和解碼消息以及其他鏡像任務。 但是,Node.js/CommonJS 打包系統似乎使我無法創建可在雙方使用的 JavaScript 文件。

我還嘗試使用 JS.Class 來獲得更緊密的 OO 模型,但我放棄了,因為我無法弄清楚如何讓提供的 JavaScript 文件與 require() 一起使用。 我在這里遺漏了什么嗎?

如果你想編寫一個可以同時使用客戶端和服務器端的模塊,我有一篇關於快速簡便方法的簡短博客文章: 為 Node.js 和瀏覽器編寫,本質上如下( thiswindow ):

(function(exports){

    // Your code goes here

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

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

或者,有一些項目旨在在客戶端實現 Node.js API,例如 Marak 的gemini

您可能還對DNode感興趣,它允許您公開 JavaScript 函數,以便可以使用簡單的基於 JSON 的網絡協議從另一台機器調用它。

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

查看在 Node.js 模塊模式、AMD 模塊模式和瀏覽器中全局執行此操作的 jQuery 源代碼:

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

不要忘記 JavaScript 函數的字符串表示表示該函數的源代碼。 您可以簡單地以封裝的方式編寫您的函數和構造函數,以便它們可以被 toString() 處理並發送到客戶端。

另一種方法是使用構建系統,將公共代碼放在單獨的文件中,然后將它們包含在服務器和客戶端腳本中。 我正在通過 WebSockets 將這種方法用於簡單的客戶端/服務器游戲,其中服務器和客戶端都運行本質上相同的游戲循環,並且客戶端每次都與服務器同步以確保沒有人在作弊。

我的游戲構建系統是一個簡單的Bash腳本,它通過 C 預處理器運行文件,然后通過 sed 清理一些垃圾 cpp 留下的東西,所以我可以使用所有正常的預處理器內容,如 #include、#define、#ifdef , 等等。

我建議查看Node.jsRequireJS 適配器 問題是 Node.js 默認使用的 CommonJS 模塊模式不是異步的,這會阻止在 Web 瀏覽器中加載。 RequireJS 使用 AMD 模式,它既異步又兼容服務器和客戶端,只要你使用r.js適配器。

也許這不完全符合問題,但我想我會分享這個。

我想制作幾個簡單的字符串實用函數,在 String.prototype 上聲明,可用於節點和瀏覽器。 我只是將這些函數保存在一個名為 utility.js 的文件中(在一個子文件夾中),並且可以輕松地從瀏覽器代碼中的腳本標簽中引用它,並在我的 Node.js 腳本中使用 require(省略 .js 擴展名) :

my_node_script.js

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

my_browser_code.html

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

我希望這對我以外的人有用。

如果您使用諸如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

服務器可以簡單地將 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);
  }
});

關鍵是客戶端可以將外部代碼包裝到一個匿名函數中以立即運行(閉包),該函數創建“exports”對象並返回它,以便您可以將它分配到您想要的位置,而不是污染全局命名空間。 在這個例子中,它被分配給窗口屬性fooModule ,它將包含文件foo.js導出的代碼。

之前的解決方案都沒有將 CommonJS 模塊系統帶入瀏覽器。

正如在其他的答案中提到,有喜歡的資產管理公司/打包解決方案Browserify堆垛機和有像RPC解決方案dnodenowjs

但是我找不到用於瀏覽器的 CommonJS 實現(包括require()函數和exports / module.exports對象等)。 所以我寫了自己的,后來發現有人寫得比我好: https : //github.com/weepy/brequire 它被稱為 Brequire(瀏覽器要求的縮寫)。

從受歡迎程度來看,資產管理器符合大多數開發人員的需求。 但是,如果您需要 CommonJS 的瀏覽器實現, Brequire可能會滿足要求。

2015 更新:我不再使用 Brequire(幾年沒有更新了)。 如果我只是在編寫一個小的開源模塊並且我希望任何人都能夠輕松使用,那么我將遵循類似於 Caolan 的回答(上面)的模式——我寫了一篇關於它幾年的博客文章前。

但是,如果我正在為私人使用或在 CommonJS 上標准化的社區(如Ampersand社區)編寫模塊,那么我只會以 CommonJS 格式編寫它們並使用Browserify

如果你想用類似 Node.js 的風格編寫你的瀏覽器,你可以嘗試dualify

沒有瀏覽器代碼編譯,因此您可以不受限制地編寫應用程序。

將您的代碼編寫為RequireJS模塊,將您的測試編寫為Jasmine測試。

通過這種方式,代碼可以使用 RequireJS 加載到任何地方,並且測試可以在瀏覽器中使用 jasmine-html 和 Node.js 中的jasmine-node 運行,而無需修改代碼或測試。

這是一個工作示例

用例:在 Node.js 和瀏覽器之間共享您的應用程序配置(這只是一個說明,可能不是最佳方法,具體取決於您的應用程序)。

問題:您不能使用window (在 Node.js 中不存在)或global (在瀏覽器中不存在)。

編輯:現在我們可以使用globalThis和 Node.js >= 12。

解決方案:

  • 文件 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'

我編寫了一個簡單的模塊,可以導入(在 Node 中使用 require 或在瀏覽器中使用 script 標簽),您可以使用它從客戶端和服務器加載模塊。

示例用法

1. 定義模塊

將以下內容放在文件log2.js ,在您的靜態 Web 文件文件夾中:

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 的當前目錄中,請確保使用靜態 Web 文件文件夾(或模塊所在的任何位置)的路徑調用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,我建議您查看 Python 3 的http.server模塊(或任何您的超快速、命令行、文件夾 Web 服務器部署解決方案)。

如果一切順利,會出現:

在此處輸入圖片說明

now.js也值得一看。 它允許您從客戶端調用服務器端,從服務器端調用客戶端函數

我寫了這個,如果要將所有變量設置為全局范圍,使用起來很簡單:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM