Understanding export and import in CommonJS
Table of contents
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.