268

I am creating a small application with a JavaScript client (run in the browser) and a Node.js server, communicating using WebSocket.

I would like to share code between the client and the server. I have only just started with Node.js and my knowledge of modern JavaScript is a little rusty, to say the least. So I am still getting my head around the CommonJS require() function. If I am creating my packages by using the 'export' object, then I cannot see how I could use the same JavaScript files in the browser.

I want to create a set of methods and classes that are used on both ends to facilitate encoding and decoding messages, and other mirrored tasks. However, the Node.js/CommonJS packaging systems seems to preclude me from creating JavaScript files that can be used on both sides.

I also tried using JS.Class to get a tighter OO model, but I gave up because I couldn't figure out how to get the provided JavaScript files to work with require(). Is there something am I missing here?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Simon Cave
  • 3,971
  • 4
  • 24
  • 28
  • 4
    Thank you everyone for posting additional answers to this question. This is clearly a topic which will rapidly change and evolve. – Simon Cave Sep 22 '11 at 19:21
  • For sharing code between NodeJs server and TypeScript / JavaScript frontend, https://blog.logrocket.com/make-sharing-typescript-code-types-quick-easy/ seems helpful. – Ryan Aug 26 '22 at 21:03

15 Answers15

182

If you want to write a module that can be used both client side and server side, I have a short blog post on a quick and easy method: Writing for Node.js and the browser, essentially the following (where this is the same as window):

(function(exports){

    // Your code goes here

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

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

Alternatively there are some projects aiming to implement the Node.js API on the client side, such as Marak's gemini.

You might also be interested in DNode, which lets you expose a JavaScript function so that it can be called from another machine using a simple JSON-based network protocol.

FZs
  • 16,581
  • 13
  • 41
  • 50
Caolan
  • 3,909
  • 1
  • 18
  • 8
  • 2
    Really great article Caolan. I understood it, it worked, now I'm rolling again. Fantastic! – Michael Dausmann Apr 12 '11 at 11:23
  • 2
    I am using [RequireJs](http://requirejs.org/docs/node.html) in my own project, which will allow me to share my modules on the client and server. We'll see how it works out. – kamranicus Jan 27 '12 at 05:44
  • What happens when the existing website has a global counter `window.exports = 123;` ? I'm not sure if testing against `typeof .. 'undefined'` is the best approach, would testing for the positive case be better? – AnnanFay Nov 22 '14 at 17:59
  • 5
    The gemini link is dead. – borisdiakur Feb 12 '15 at 11:05
  • This is useful - but how you do go about handling the imports too? For example: `import foo from './foo'; export { baz: function() { return foo(); } };` – Ian Jul 15 '16 at 10:05
  • @Caolan How would this work in ES6? I've asked this as a [new question](https://stackoverflow.com/questions/59736730/how-to-share-javascript-code-between-front-and-back-end-es6), with some ES6-specific problems, but I'd be just as glad to see an edit here! – ultraGentle Jan 14 '20 at 15:22
55

Epeli has a nice solution here http://epeli.github.com/piler/ that even works without the library, just put this in a file called share.js

(function(exports){

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

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

On the server side just use:

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

share.test();

And on the client side just load the js file and then use

share.test();
Simon Robb
  • 1,668
  • 1
  • 14
  • 25
broesch
  • 2,442
  • 1
  • 19
  • 9
  • 12
    I like this answer better than the accepted one because it's better explained for newbies like myself. – Howie Jul 11 '14 at 09:01
  • In my Express folder besides the static (public) folder, I have also a folder named 'shared' which is also accessible from the client like the 'public' folder like that: app.use(express.static('public')); app.use(express.static('shared')); And your post extends my idea of sharing files with the client and the server. This is exactly what I needed. Thank you! – Combine Jan 29 '17 at 09:46
  • This solution + git subtree == awesome. Thanks! – kevinmicke Aug 24 '19 at 20:32
  • @broesch How would this work in ES6? I've asked this as a [new question](https://stackoverflow.com/questions/59736730/how-to-share-javascript-code-between-front-and-back-end-es6), with some ES6-specific problems, but I'd be just as glad to see an edit here! – ultraGentle Jan 14 '20 at 15:22
  • I've spent a lot of time investigating how to share functions/variables between js files which are used client-side and server-side. This was the only thing which worked. My solution is based on Nuxt. Well done! – Sergiu Mare Jun 14 '22 at 08:20
17

Checkout the jQuery source code that makes this work in the Node.js module pattern, AMD module pattern, and global in the browser:

(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)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
wlingke
  • 4,699
  • 4
  • 36
  • 52
  • 2
    This is the best method (for what I needed). Here's a working example I created: https://gist.github.com/drmikecrowe/4bf0938ea73bf704790f – Mike Crowe Jun 27 '14 at 14:24
13

Don't forget that the string representation of a JavaScript function represents the source code for that function. You could simply write your functions and constructors in an encapsulated way so they can be toString()'d and sent to the client.

Another way to do it is use a build system, put the common code in separate files, and then include them in both the server and client scripts. I'm using that approach for a simple client/server game via WebSockets where the server and client both run essentially the same game loop and the client synchronises up with the server every tick to make sure nobody's cheating.

My build system for the game is a simple Bash script that runs the files through the C preprocessor and then through sed to clean up some junk cpp leaves behind, so I can use all the normal preprocessor stuff like #include, #define, #ifdef, etc.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dagg Nabbit
  • 75,346
  • 19
  • 113
  • 141
13

I would recommend looking into the RequireJS adapter for Node.js. The problem is that the CommonJS module pattern Node.js uses by default isn't asynchronous, which blocks loading in the web browser. RequireJS uses the AMD pattern, which is both asynchronous and compatible with both server and client, as long as you use the r.js adapter.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Husky
  • 5,757
  • 2
  • 46
  • 41
12

Maybe this is not entirely in line with the question, but I thought I'd share this.

I wanted to make a couple of simple string utility functions, declared on String.prototype, available to both node and the browser. I simply keep these functions in a file called utilities.js (in a subfolder) and can easily reference it both from a script-tag in my browser code, and by using require (omitting the .js extension) in my Node.js script:

my_node_script.js

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

my_browser_code.html

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

I hope this is useful information to someone other than me.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Markus Amalthea Magnuson
  • 8,415
  • 4
  • 41
  • 49
  • 1
    I like this approach but I find my static files get moved around quite a lot. One solution I have found is to re-export the module. For example, create `utilites.js` with a single line `module.exports = require('./static/js/utilities');`. This way you only need to update one path if you shuffle stuff around. – Tom Makin Apr 30 '14 at 11:09
  • I like this idea. Just a note on the path which took me a while to figure out. My `utilities.js` is in `shared` folder under the project. Using `require('/shared/utilities')` gave me the error `Cannot find module '/shared/utilities'`. I have to use something like this `require('./../../shared/utilities')` to make it work. So, it always goes from current folder and travel up to root then down. – newman Mar 12 '15 at 15:55
  • Now I see where to place the shared module - in the static folder. Thanks for the info! – Combine Nov 19 '16 at 11:40
  • Why is this not a more widely shared answer? This is so simple, and worked for me when no other suggestions did, even in modern browsers! – Patronics Mar 27 '21 at 01:25
11

If you use use module bundlers such as webpack to bundle JavaScript files for usage in a browser, you can simply reuse your Node.js module for the frontend running in a browser. In other words, your Node.js module can be shared between Node.js and the browser.

For example, you have the following code sum.js:

Normal Node.js module: sum.js

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

module.exports = sum

Use the module in 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

Reuse it in the 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
  • 27,235
  • 10
  • 114
  • 113
4

The server can simply send JavaScript source files to the client (browser) but the trick is that the client will have to provide a mini "exports" environment before it can exec the code and store it as a module.

A simple way to make such an environment is to use a closure. For example, say your server provides source files via HTTP like http://example.com/js/foo.js. The browser can load the required files via an XMLHttpRequest and load the code like so:

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

The key is that client can wrap the foreign code into an anonymous function to be run immediately (a closure) which creates the "exports" object and returns it so you can assign it where you'd like, rather than polluting the global namespace. In this example, it is assigned to the window attribute fooModule which will contain the code exported by the file foo.js.

maerics
  • 151,642
  • 46
  • 269
  • 291
3

Use case: share your app configuration between Node.js and the browser (this is just an illustration, probably not the best approach depending on your app).

Problem: you cannot use window (does not exist in Node.js) nor global (does not exist in the browser).

Edit: now we can thx to globalThis and Node.js >= 12.

Obsolete solution:

  • File config.js:

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

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

    You can now open the dev tools and access the global variable config

  • In Node.js (app.js):

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

      import config from './config';
      console.log(config.foo); // Prints 'bar'
    
tanguy_k
  • 11,307
  • 6
  • 54
  • 58
  • Followup: Let's say I have two files that are shared between server.js and client.js: `shared.js` and `helpers.js` -- `shared.js` uses functions from `helpers.js`, so it needs `const { helperFunc } = require('./helpers')` at the top, for it to work server-side. Problem is on the client, it complains about `require` not being a function, but if I wrap the require line in `if (typeof module === 'object') { ... }`, server says that helperFunc() is not defined (outside of the if statement). Any ideas to get it working on both? – Microsis Sep 17 '19 at 18:32
  • Update: I seem to have gotten it working by placing this at the top of `shared.js`: `helperFunc = (typeof exports === 'undefined') ? helperFunc : require('./helpers').helperFunc;` -- Will need a line for each exported function unfortunately but hopefully it's a good solution? – Microsis Sep 17 '19 at 20:51
  • With Babel or Typescript (using vite.js) gives Uncaught SyntaxError: The requested module '/src/uti/index.js?t=1616864600284' does not provide an export named 'default' `export default` will error in Node – Marius Mar 27 '21 at 17:05
2

None of the previous solutions bring the CommonJS module system to the browser.

As mentioned in the other answers, there are asset manager/packager solutions like Browserify or Piler and there are RPC solutions like dnode or nowjs.

But I couldn't find an implementation of CommonJS for the browser (including a require() function and exports / module.exports objects, etc.). So I wrote my own, only to discover afterwards that someone else had written it better than I had: https://github.com/weepy/brequire. It's called Brequire (short for Browser require).

Judging by popularity, asset managers fit the needs of most developers. However, if you need a browser implementation of CommonJS, Brequire will probably fit the bill.

2015 Update: I no longer use Brequire (it hasn't been updated in a few years). If I'm just writing a small, open-source module and I want anyone to be able to easily use, then I'll follow a pattern similar to Caolan's answer (above) -- I wrote a blog post about it a couple years ago.

However, if I'm writing modules for private use or for a community that is standardized on CommonJS (like the Ampersand community) then I'll just write them in CommonJS format and use Browserify.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Peter Rust
  • 402
  • 4
  • 14
1

If you want to write your browser in Node.js-like style you can try dualify.

There is no browser code compilation, so you can write your application without limitations.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
farincz
  • 4,943
  • 1
  • 28
  • 38
  • Link is dead. The link is SO DEAD that DuckDuckGo and Google search engines don't find anything when searching up "dualifyjs". [DDG search](https://duckduckgo.com/?q=dualifyjs) [Google search](https://www.google.com/search?hl=en&q=dualifyjs). Looking through all 8 pages of Google search results for just "dualify", nothing there seems to mention a JavaScript library. I found one [GitHub repo with the name "Dualify"](https://github.com/KennethKho/Dualify), but it's completely empty. I found one [GitHub repo that just cloned this question and its answers](https://github.com/dalong0514/ITstudy) – Samathingamajig Jun 10 '21 at 05:14
  • it's 8 years only answer and many things changed in Javascript world. For example nowdays node support ecma script modules (.mjs) (https://nodejs.org/api/esm.html) – farincz Jun 10 '21 at 10:40
1

Write your code as RequireJS modules and your tests as Jasmine tests.

This way code can be loaded everywhere with RequireJS and the tests be run in the browser with jasmine-html and with jasmine-node in Node.js without the need to modify the code or the tests.

Here is a working example for this.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
VuesomeDev
  • 4,095
  • 2
  • 34
  • 44
1

I wrote a simple module, that can be imported (either using require in Node, or script tags in the browser), that you can use to load modules both from the client and from the server.

Example usage

1. Defining the module

Place the following in a file log2.js, inside your static web files folder:

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;

Simple as that!

2. Using the module

Since it is a bilateral module loader, we can load it from both sides (client and server). Therefore, you can do the following, but you don't need to do both at once (let alone in a particular order):

  • In Node

In Node, it's simple:

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

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

This should return 2.

If your file isn't in Node's current directory, make sure to call loader.setRoot with the path to your static web files folder (or wherever your module is).

  • In the browser:

First, define the web page:

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

Make sure you don't open the file directly in your browser; since it uses AJAX, I suggest you take a look at Python 3's http.server module (or whatever your superfast, command line, folder web server deployment solution is) instead.

If everything goes well, this will appear:

enter image description here

wallabra
  • 412
  • 8
  • 17
1

now.js is also worth a look. It allows you to call server-side from the client-side, and client-side functions from the server-side

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
balupton
  • 47,113
  • 32
  • 131
  • 182
  • 1
    The project has been discontinued - do you know of any good replacements for it? https://groups.google.com/forum/#!msg/nowjs/FZXWZr22vn8/UzTMPD0tdVQJ – Anderson Green Dec 31 '12 at 04:50
  • the only other one I know was bridge and it was by the same people, so also abandoned. The 0.9 version of socket.io also supports callbacks for events - however nothing like the sharing code of now.js though, but it works well enough. – balupton Jan 01 '13 at 04:44
  • There's also sharejs, which appears to be actively maintained. http://sharejs.org/ – Anderson Green Jan 01 '13 at 05:42
0

I wrote this, it is simple to use if you want to set all variables to the global scope:

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