Home > Back-end >  Should NPM packages be required at top of main.js or inside functions? - node.js
Should NPM packages be required at top of main.js or inside functions? - node.js

Time:07-04

I can't find a specific answer to this question anywhere.

Using ParseServer with Back4App.
I want to know the best place to require NPM packages, and what the difference between these two methods are.

main.js - Global file require example

const otpGenerator = require('otp-generator');

Parse.Cloud.define("TEST", async (request) => {
  let code = otpGenerator.generate(10, {alphabets: false, specialChars: false});
  console.log(code);
  });

main.js - Function require example

Parse.Cloud.define("TEST", async (request) => {
  const otpGenerator = require('otp-generator');

  let code = otpGenerator.generate(10, {alphabets: false, specialChars: false});
  console.log(code);
  });

Both work as far as small tests as above go...

CodePudding user response:

In the Node/CommonJS context require() indicates that you are loading the module at runtime.

Assuming we have a module A which contains a require(moduleB) (Which could either be in node_modules or a local file). Then when the require statement is reached then essentially the following happens:

  1. Execution halts in the calling context of Module A
  2. Node finds and evaluates the specified Module B (parsing etc)
  3. Execution continues at the top of Module B
  4. Assuming that parsing and execution of Module B occurs without problem then execution is returned to Module A, with the contents of module.exports provided, essentially, as the return value from the call to require()

(Note this is very much a simplification, and there's a lot of details I'm glossing over, but this is broadly it).

Thus this treats loading the module as akin to calling a function. (This is in contrast to standard ECMAScript modules that are evaluated 'in advance' at parse time, thus they have to be at the top of the file).

Now given this we can see why the location of a call to require() can be very significant.

In the example you gave then it is functionally irrelevant because there is a single block and that is the only thing being executed. Also the package being imported is presumably well behaved and not performing a bunch of side effects. However in other cases it becomes more significant.

If I saw code like this:

if(someCondition){
    require('./my-module1.js')
} else {
    const something = require('some-module')
}

Or some other pattern where we are scattering calls to require throughout the code. Then I would consider this a potential bad code smell as it indicates that we are using require to load chunks of code, presumably with side effects, rather than encapsulating what we need within well defined functions and classes etc.

It also obscures what code gets loaded and when, which could make for a debugging nightmare.

Whereas if we have all of our require() calls at the top of the file then we are:

  1. Eliminating any potential ambiguity of what gets loaded/executed and when
  2. Clearly indicating up top of the code file just what the dependencies of the following code is.
  3. Following established conventions of other code (e.g. ES modules, other languages) that load dependencies at parse time

It does get a little more complicated as there may be certain circumstances in which we want dynamic loading of code e.g. for optimisation purposes, but for the sake of code clarity and cleanliness the default should be to put all require() calls at the top of the code and outside of any execution blocks.

For further details on refer to the NodeJS documentation:

PS: In the specific example of requiring modules for tests, as in your example, then whether you want the modules to be loaded at the point of setting up your tests or when the test itself runs, would depend on several factors. By including the require call inside the test you are essentially also testing the required code, and you would see any errors in it appearing under that test, however if you include it outside then you are essentially taking that required code for granted and sharing it between all your tests.

CodePudding user response:

Standard practice is to put all import/requires at the top, unless you have a good reason not to, such as it being an optional dependency.

CodePudding user response:

Importing the packages at the top is a good practice. This makes it visually clear which libraries you will be using, and loading the data before using it avoids compilation and performance problems.

  • Related