Like any kind of apps, JavaScript apps also have to be written well.
Otherwise, we run into all kinds of issues later on.
In this article, we’ll look at some best practices we should follow when writing JavaScript modules.
Prefer Named Exports
Named exports have to be imported by the name that the member is exported as.
This is different than default exports which can be imported by any name.
Therefore, named exports are less confusing that default exports.
For example, instead of writing:
export default class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
We write:
export class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
The first example can be imported with any name.
The 2nd has to be imported as Person
.
Autocomplete also works with named exports if the text editor we’re using has that feature.
No work during import
We shouldn’t do anything with our exported code.
It’s easy to get unexpected results if we export something with an expression that does work.
For example, the following export is no good:
config.js
export const config = {
data: JSON.parse(str)
};
The work would still be done after the export is done, so we get the latest value.
When we import it, JSON.parse
is called.
This means that the import will be slower.
If we have:
import { config } from 'config';
Then the JSON.parse
will be run then.
To make JSON.parse
run in a lazy fashion, we can write:
config.js
let parsedData = null;
export const config = {
get data() {
if (parsedData === null) {
parsedData = JSON.parse(str);
}
return parsedData;
}
};
This way, we cache the parsed string in parsedData
.
JSON.parse
only runs if parsedData
is null
.
High Cohesion Modules
Cohesion describes the degree to which components inside a module belong together.
We should make sure that a module belongs together.
This means we should have related entities in one module.
For instance, we can make a math
module with functions that do arithmetic operations.
We can write:
math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => a / b;
All functions do similar things, so they belong in one module.
If we have low cohesion modules, then it’s hard to understand what the module has.
Unrelated things in one module just don’t make sense.
Avoid Long Relative Paths
It’s hard to find a module if they’re nested deeply.
We should avoid deep nesting to avoid long relative paths.
So instead of writing:
import { addDates } from '../../date/add';
import { subtractDates } from '../../date/subtract';
We write:
import { addDates } from './date/add';
import { subtractDates } from './date/subtract';
If we put them in a Node package, we can use absolute paths:
import { addDates } from 'utils/date/add';
import { subtractDates } from 'utils/date/subtract';
We put everything in a utils
package so that we can reference it in an absolute path.
Conclusion
We should make cohesive modules to make understanding them easier.
Also, named exports are better than default ones.
We should export code that does work during export since they’ll run when we import it.