2

Currently I am working on a personal project and am running into a problem understanding the functionality of module.exports within my app...

My app structure looks like this:

app
├── common
│   ├── cmd
│   │   ├── run-cmd.js
├── routes
│   ├── system
│   │   ├── temp.js

I am hoping to achieve a way to store reusable functions in a separate location in my app (in this example run-cmd.js) and have them easily accessible throughout the rest of my app (temp.js) without having to specifically reference the file path (example: require(../../folder/folder/file)

Here is run-cmd.js:

const { spawn } = require("child_process");

module.exports.runCmd = (cmd, callback) => {
    var command = spawn(cmd, [], {shell:true});
    const REGEX_LINE_BREAK = /(\r\n|\n|\r)/gm;
    var result = '';

    command.stdout.on('data', function(data) {
        console.log(data.toString())
        result += data.toString().replace(REGEX_LINE_BREAK, "");
    });

    command.on('close', (code) => {
        console.log("code: " + code)
        return callback(result);
    });
} 

Here is temp.js:

var express = require('express'),
    router = express.Router(),
    runCmd = require('../../common/cmd/run-cmd');

// Add a binding to handle '/temp'
router
    .get('/temp', (req, res) => {
        runCmd('vcgencmd measure_temp', function(result) {
            console.log("Result we are prepping to send: " + result);
            res.json({result: result});
        });
    });

module.exports = router;

I feel like the require() example above defeats the purpose of the flexibility of using module.exports... where doing something like require(./run-cmd) or even require(from-root/path/run-cmd) would be much better to reference in other locations throughout my app. Additionally, in this current example I get an error saying TypeError: runCmd is not a function.

Can anyone help clear some of my confusion with using module.exports and tell me where I am going wrong?

scapegoat17
  • 5,509
  • 14
  • 55
  • 90
  • Did you look at what `require('path/to/runCmd')` gives you? It's an *object*, the defined `module.exports`, which *contains* the `runCmd` function. For absolute paths, have you read e.g. https://stackoverflow.com/q/21229944/3001761? – jonrsharpe Mar 20 '21 at 15:06
  • @jonrsharpe, thanks for responding. In my current example above I am using `require('path/to/runCmd')` and as noted i am getting `TypeError: runCmd is not a function`. – scapegoat17 Mar 20 '21 at 15:09
  • Correct; again, it's not a function, it's an *object*. Log it out, take a look. – jonrsharpe Mar 20 '21 at 15:10

1 Answers1

1

There are several ways to achieve what you wish. One simple but effective solution is to add an attribute to the global object. This has to be done in your main (entry) js file, before any require statements:

(Paths below are built assuming your entry js file is in the app folder)

global.__runCmdPath = __dirname + '/common/cmd/run-cmd';

Then, in any other module from which you would like to require runCmd you could simply:

const runCmd = require(__runCmdPath);

You could even set, as attribute of the global object, the base directory of the scripts containing the functions you'd like to export, and then just append the single script-name, like:

const runCmd = require(__scriptsDir + '/run-cmd');
const runCmd2 = require(__scriptsDir + '/run-cmd2');

Another strategy would be to add the scripts folder to the NODE_PATH environmental variable, with the following command for UNIX:

export NODE_PATH=./common/cmd

and for Windows:

set NODE_PATH=./common/cmd

Then, you may command node app.js and this way, you will simply be able to require('run-cmd') everywhere. This will work as long as you remember to re-set the variable everytime you start a new shell session.

For what regards TypeError: runCmd is not a function., this will continue to occur if you try to call runCmd directly, from where you have required it. This is because require() returns an exports object, and your exported methods all belong to this object.

If you would like to call the runCmd method you exported, in this case, you'll have to do it this way:

runCmd.runCmd('vcgencmd measure_temp', function(result) {
            console.log("Result we are prepping to send: " + result);
            res.json({result: result});
        });

Thus, you might want to re-name the constant containing your exports object, or the exported name of the runCmd function, to whatever suits you best.

BiOS
  • 2,282
  • 3
  • 10
  • 24
  • 1
    This is a great answer and I really appreciate the response. My only question is in regards to your solution for my path issue.. Is there any drawbacks to doing it this way? You referenced `"several ways"`... does this seem to be the most practiced or recommended way to achieve what i am trying to do? – scapegoat17 Mar 20 '21 at 20:34
  • 1
    Thanks very much for your feedback! The above is indeed the strategy I would recommend, and one of the mostly commonly used. The only drawback being that the *global object* has already got some attributes that should be changed, so not all names are available. You can consult those [here](https://nodejs.org/api/globals.html). I have modified my answer to include another strategy, which may help you achieving and even cleaner import, but as a drawback will require you to set an environment variable every time you start a new shell session. – BiOS Mar 21 '21 at 09:32
  • I meant "attributes that *should not* be changed" – BiOS Mar 21 '21 at 09:40
  • This is a great explanation and I see this as a working solution. I ended up going with the global route as I feel that it works best for my situation. Thank you again! – scapegoat17 Mar 23 '21 at 12:43