Understanding export and import in CommonJS

·

5 min read

I'm pretty sure this is not the first time that I got confused by the various exports' syntax in CommonJS and definitely will not be the last. Hence, this article will hopefully be useful for my future self whenever I need a reference.

Scenario

Consider a simple scenario where there you have 2 files; index.js and reference.js where index.js will import reference.js to use some object exported by reference.js.

// reference.js
const address = {
    street: 'old middle road',
    postal: 123456
}

exports.address = address;

// index.js
const address = require('./reference.js');

console.log(address);

The output would be

{ address: { street: 'old middle road', postal: 123456 } }

Confusion

Trying to access the object value street by calling

console.log(address.street);

which result as

undefined

What happened? Essentially, the exported object actually nested another address object, hence, in order to access the value, I should run

console.log(address.address.street);

Which would have resulted in

old middle road

It's actually quite obvious when I had the imported address object printed out, but it just didn't register to me straightaway

Walkthrough

Let's try to figure out what actually happens, and see how we can understand a little more what's happening

For each of the javascript file, we can think of it as being a standalone module which can be imported and exported.

Let's take a look at reference.js

const address = {
    street: 'old middle road',
    postal: 123456
}

console.log(module);

---

// output
Module {
  id: 'C:\\Users\\Joseph\\Desktop\\scripts\\reference.js',
  path: 'C:\\Users\\Joseph\\Desktop\\scripts',
  exports: {},
  filename: 'C:\\Users\\Joseph\\Desktop\\scripts\\reference.js',
  loaded: false,
  children: [],
  paths: [
    'C:\\Users\\Joseph\\Desktop\\scripts\\node_modules',
    'C:\\Users\\Joseph\\Desktop\\node_modules',
    'C:\\Users\\Joseph\\node_modules',
    'C:\\Users\\node_modules',
    'C:\\node_modules'
  ]
}

Let's zoom into Module.exports where we have yet to export anything from reference.js, hence, it shows an empty object. Now, let's export it like we did previously.

const address = {
    street: 'old middle road',
    postal: 123456
}

exports.address = address;

console.log(module);

---

// output
Module {
  id: 'C:\\Users\\Joseph\\Desktop\\scripts\\reference.js',
  path: 'C:\\Users\\Joseph\\Desktop\\scripts',
  exports: { address: { street: 'old middle road', postal: 123456 } },
  filename: 'C:\\Users\\Joseph\\Desktop\\scripts\\reference.js',
  loaded: false,
  children: [],
  paths: [
    'C:\\Users\\Joseph\\Desktop\\scripts\\node_modules',
    'C:\\Users\\Joseph\\Desktop\\node_modules',
    'C:\\Users\\Joseph\\node_modules',
    'C:\\Users\\node_modules',
    'C:\\node_modules'
  ]
}

Notice that it now shows the address object being exported as part of the Module object.

As mentioned, it wasn't obvious to me before that there was another nested level of address object due to the way I exported the object. What I did was to add another level of property before I exported address object

// this
const address = {
    street: 'old middle road',
    postal: 123456
}

exports.address = address;

// is the same as doing this
module.exports = {
    address: {
        street: 'old middle road',
        postal: 123456
    }
};

// or doing this
module.exports = { address };

And what I really wanted was

module.exports = {
    street: 'old middle road',
    postal: 123456
};

// or
module.exports = address;

So if this was exported correctly, the Module object output should be

Module {
  id: 'C:\\Users\\Joseph\\Desktop\\scripts\\reference.js',
  path: 'C:\\Users\\Joseph\\Desktop\\scripts',
  exports: { street: 'old middle road', postal: 123456 },
  filename: 'C:\\Users\\Joseph\\Desktop\\scripts\\reference.js',
  loaded: false,
  children: [],
  paths: [
    'C:\\Users\\Joseph\\Desktop\\scripts\\node_modules',
    'C:\\Users\\Joseph\\Desktop\\node_modules',
    'C:\\Users\\Joseph\\node_modules',
    'C:\\Users\\node_modules',
    'C:\\node_modules'
  ]
}

And to use it by importing from another javascript file, it would simply be just

// index.js
const address = require('./reference.js');

console.log(address.street);

// output
old middle road

I feel that another point of confusion was that the exported property name was defined as module.exports.address = address and I had it imported as const address = require('./reference.js');

// reference.js
module.exports.address = address

// index.js
const address = require('./reference.js');

Had I imported it with a different value, it would have been less confusing

// index.js
const reference = require('./reference.js');

// accessing
console.log(reference.address.street); // old middle road

Well, that's all nice and well, but what if I need to export or import multiple objects?

One way is simply to export as multiple property

const address = {
    street: 'old middle road',
    postal: 123456
}

const employment = {
    company: 'google',
    yearStart: 2000,
    yearEnd: 2011
}

module.exports.address = address;
module.exports.employment = employment;

// Module output
Module {
  exports: {
    address: { street: 'old middle road', postal: 123456 },
    employment: { company: 'google', yearStart: 2000, yearEnd: 2011 }
  }
}

Or to export as module.exports which would have resulted in the same Module output as above

module.exports = { address, employment };

And import to use as such

const reference = require('./reference.js');

console.log(reference.address.street); // old middle road
console.log(reference.employment.company); // google

Or we could use destructure way, in other words, named import

const { address, employment } = require('./reference.js');

console.log(address.street); // old middle road
console.log(employment.company); // google

To view what is it like to import from the external file is to log out the import

// given export in reference.js
module.exports.address = address;

// index.js
console.log(require('./reference.js'));

// would result in
{ address: { street: 'old middle road', postal: 123456 } }
// given export in reference.js
module.exports.address = address;
module.exports.employment = employment;
// or
module.exports = { address, employment };

// index.js
console.log(require('./reference.js'));

// would result in
{
  address: { street: 'old middle road', postal: 123456 },
  employment: { company: 'google', yearStart: 2000, yearEnd: 2011 }
}

The imported object should have matched the exports in Module output

Conclusion

I talked about how does the various export look like and exporting single or multiple values. How logging out the Module or import statement would have helped to view the exact value that was exported or imported. Hope that this gives you a little more insights of what's happening behind the scene.