This site lists the protips that we shared with students during our courses
This blog post came about after a discussion with a seasoned developer that took our entire course, worked as an instructor, and still went:
WHA?! I had no idea that it worked like that
To the defense of the instructor, I want to say that this is very typical for how you learn and is well-documented through the Dunning-Kruger. When we learn a topic we have to abstract some of the underlying layers away to make sense of it all. Once we see this deeper layer of how things work, we often realize how little we work.
So no shame should fall on the instructor, but rather congratulations as a new level of understanding were reached!
Ok - so what was the curtain that was opened for this poor, unnamed instructor?
require
do?When we want to use a function from one file in another we use require
to import it into our file. Like this:
// in include.js
const add = (num1, num2) => num1 + num2;
module.exports = {
add
}
// in index.js
const adder = require('./include');
console.log(adder.add(1,3))
If you create these two files in a directory and run node index.js
(or node .
) we expect, and get: 4
printed to the console.
But what happens if we run node include.js
? Think about it before you read on.
…
No - you didn’t think. You just read on. Think.
…
Ok - here we go. Nothing happens. Because include.js
doesn’t do anything. It just declares a function add
that is exported and never used. Let’s add a console.log
and test it out:
const add = (num1, num2) => num1 + num2;
console.log(add(3, 4));
module.exports = {
add,
};
Now that we run node include.js
the add
function is used and hence a glorious 7
is printed.
Let’s rerun node .
and see what happens. What do you think?
Well, this might surprise you but you get both 7
and 4
printed. Why is that?
require
do?From the documentation we learn that:
The basic functionality of require is that it reads a JavaScript file, executes the file, and then proceeds to return the exports object.
The code gets executed from top to bottom, and then any exported functions are returned and stored in whatever variable you might have.
This might first surprise you but is not so surprising once you think that JavaScript is an interpreting scripting language. To Node, for example, we pass a file with node apa.js
, node will execute the file, line by line starting from the top.
So a file with only function declarations (like our original include.js
, before the console.log(add(3, 4));
) just has a bunch (eehh one…) of function declarations. Since no one is calling that function nothing happens. Adding the console.log(add(3, 4));
will call add
and hence 7
is printed.
If we then proceed to include include.js
using const adder = require('./include');
the same thing happen:
require('./include');
will “reads a JavaScript file, executes the file, and then .. return the exports object”include.js
file holds a console.log
-statement it will get executed and 7
is printed to the console.included.js
is processed the module.exports
is returned and stored in the adder
constant.import/export
?The same thing happens for import/export
because it’s really all JavaScript can do. It can just execute line by line in files. Let’s test it out:
// in include2.mjs
export const add = (num1, num2) => num1 + num2;
console.log(add(3,4))
// in index2.mjs
import { add } from './include2.mjs';
console.log(add(1, 3));
(Note the file extension .mjs
which stands for Module JavaScript that is the modern way of writing JavaScript code. However, Node used the CommonJS loader (via require
) by default, but we can force Node to use ECMAScript modules by using the .mjs
extension. This is wicked cool since that means that we can use async/await
and all those cool things in Node script, but that is beside the point.)
Ok - back on track… with those two files we can now go node index2.mjs
and see that we get the 7
and 4
printed again. Because it works the same way; the file is read, executed line by line and then the exports are exported.
Ok - let’s see how this works in action, in an Express API for example:
// in index.js
const express = require('express')
const app = express()
const port = 3000
const getRootHandler = (req, res) => {
res.send('Hello World!')
}
app.get('/', getRootHandler)
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
console.log('OOOI, being executed over here');
That is the Hello World version of Express (with a small addition by me :), but what happens when you start this with node index.js
(you will need to create a node application too mkdir demo && cd demo && npm init -y
):
node index.js
will read the filerequire('express')
will read that package and execute the files includedExpress()
executes the main function of the Express package.getRootHandler
./
yet.app.listen
function.app.listen
.Hence is printed:
OOOI, being executed over here
Example app listening at http://localhost:3000
I hope you found this enlightening and useful. Should you have found it confusing I’m sure this article will do a better job than me explaining it.