How does it work? — the Art of Code Trace
Comprehends the codes more than you thought.
Table of Contents* What is code trace
* What's dotenv
* Assumption: how to design it
* Dive into the codes
* Find the entry point
* Break down the function
* Final Thoughts
* References
What is code trace
Imagine you’re co-work with others, you can read documents, sync with the code owner, execute the programs via an easy-to-use API or function call, but do you know how does it work? Can you find the bug when the program crashes or doesn’t work?
Code trace is like we are simulating the computer, running the programs line by line. You’re not writing the code, instead, you are reading the code.
The benefits of code trace are not only you can fully understand a service, library, or project, but it can help you to learn:
- How the others coding, naming.
- How to design the projects’ structure.
- Practicing the issue-tracking ability.
In this article, we are going to trace the open-source dotenv line by line, let’s practice together. 🙂
What’s dotnev
From GitHub:
Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env. Storing configuration in the environment separate from code is based on The Twelve-Factor App methodology.
In real life, we won’t deploy your code directly to your application’s production environment, because no code is 100% with zero error, we might separate into the different environments such as:
- preview: a playground for the developers, no worries if anything breaks (yah!)
- staging: an environment that we can deploy a new feature for the customers to do the user acceptance testing, known as UAT.
- production: the environment for real products.
Imagine we use different configurations for the different environments:
# for preview's database
SQL_USERNAME=dexter
SQL_PASSWORD=ok# for staging's database
SQL_USERNAME=admin
SQL_PASSWORD=123456789# for production's database
SQL_USERNAME=prodAdmin
SQL_PASSWORD=asdf12345qwer6789
It’s not recommended that we put all environments’ secerts
into the same file, also, you need to resolve the duplicate naming issue.
In this case, dotenv is a great tool that can help you.
Assumption: how to design it
NOTE:
1. We only trace the basic usage of it.
2. The version we’re tracing is
8.2.0
.
Let’s take a look at the usage:
dotnev loads a file with suffix .env
, then loads into process.env
, which is an object that contains all the user’s environment.
Then in our code, we can use process.env.<secret name>
to get the value, quite easy, isn’t it?
Before we dive into the code, let’s try if we can design a library as elegant as it.
Scroll down once you come up with some ideas.
We can break down the task into:
- Loads a file with a suffix
.env
. - Parses the file line by line.
- Puts the values to
process.env
according to their secret names.
Dive into the codes
Find the entry point
First thing first, take a look at the structure (here we only focusing on the important files or folders):
# /.
├── _lib
│ └── main.js
└── package.json
When we are tracing the code, find the entry point (in this case, we are looking for an export module’s entry point) is the key lead to success. In npm, we can find the entry point of the program in thepackage.json
:
NOTE: From npm Docs:
The main field is a module ID that is the primary entry point to your program. That is, if your package is named
foo
, and a user installs it, and then doesrequire("foo")
, then your main module's exports object will be returned.
Break down the function
Back to the usage mentioned by GitHub README:
require('dotenv').config()
Let’s go to lib/main.js
and navigate to the function config
in line 76 — 110
.
By default, options can be empty, and you can find another function call:
const parsed = parse(
fs.readFileSync(dotenvPath, { encoding }), { debug }
)
Move to another function parse
:
In Line 31 — 74
, the annotation said the function parse
is for parsing a string to an object.
Back to the design aspect, this is an important feature that we’ve missed, considering the simple usage we mentioned before:
SQL_USERNAME=dexter
SQL_PASSWORD=ok
If we can have nested or JSON-like object, it would be easier to read, to extend, like:
SQL_INFO={"USERNAME": "dexter", "PASSWORD": "ok"}
So the function parse
uses Regex to operate the string, turn it into an object in JavaScript, and does a good job on our requirements:
- Loads a file with a suffix
.env
. - Parses the file line by line.
Back to function config
:
Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key]
} else if (debug) {
log(`"${key}" is already defined in \`process.env\` and will not be overwritten`)
}
})
Since the param parsed
have stored all the value-to-key as an Object, we can:
- Use
Object.keys
to get all the keys - Use
Object.prototype.hasOwnProperty.call(obj, key)
to check if theprocess.env
already contain the value. - If can’t found, save the value.
NOTE:
Object.prototype.hasOwnProperty.call(obj, key)
is similar toObj.hasOwnProperty(key)
, but it will fail when the object is created viaObject.create(null)
, which means it has anull prototype chain
. For more info see here.
Our last mission — Puts the values to process.env
according to their secret names, have completed too!
Final Thoughts
Thank you for practicing the code trace with me, it’s quite a pleasant journey.
If you have any questions or issues about the article, just left a comment, you can also find me on Linkedin.
Cheers 🍻