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 for importing items.
No Imports that can’t be Resolved
We shouldn’t import modules that can’t be resolved.
This means that we make sure that the module is found before we import them.
For ES6 modules, if we write:
import x from './foo'
then we should make sure foo
has a default export.
If we write:
const { default: x } = require('./foo')
Then we should make sure that we have a foo
module with a module.exports
or exports
inside.
Named Imports
We should make sure that named imports are included in the set of named exports in a module.
For instance, we should have:
export const foo = "bar";
in foo.js
.
Then we can write:
import { foo } from './foo'
or:
export { foo as bar } from './foo'
to import it.
Default Imports
If we want to make a default import, then we should also make sure it exists.
If we have:
export default function () { return 42 }
in foo.js
, or:
export const bar = () => { return null }
in bar.js
, or:
module.exports = function () { /* ... */ }
in baz.js
.
Then we can import them:
import foo from './foo'
or:
import { bar } from './bar'
or:
const baz = require('./baz');
Import a Namespace
We can namespace our exports if they aren’t default exports.
For instance, we can write:
export const a = 1
or:
const b = 2
export { b }
or:
const c = 3
export { c as d }
Given that they’re all in foo.js
, we can import them by writing:
import { a } from './foo';
or:
import { b } from './foo';
or:
import { d } from './foo';
We can also combine more than one of them into one.
Absolute paths
We shouldn’t use absolute paths when we’re importing modules.
Therefore, the path must always have a period if we’re importing project files.
For instance, we shouldn’t write:
import f from '/foo';
or:
const f = require('/foo');
Rather, we should write:
import foo from './foo';
or:
const foo = require('./foo');
If we’re importing a module, then we don’t need the slash and the dot.
For instance, we can write:
import _ from 'lodash';
or:
const _ = require('lodash');
Absolute paths are bad since they don’t work all in all locations.
If we move our project to a different part, then the imports will be broken.
No require Calls with Expressions
Using require
with expressions is a bad idea. We don’t want to give attackers a chance to change the import path to import malicious code.
require
calls are resolved at runtime rather than build time.
Dynamic expressions in require
also makes static analysis harder or to find where a module is used in our codebase.
Therefore, instead of writing:
require(name);
require('../' + name);
require(`../${name}`);
require(name());
We write:
require('../foo');
No Internal Modules
We shouldn’t import internal modules that aren’t meant to be imported by external code.
For instance, we shouldn’t write:
import { settings } from './app/index';
Instead, we write:
import { settings } from './app';
No Webpack Loader Syntax
We shouldn’t use Webpack’s proprietary syntax so if we migrate off Webpackm then we won’t have so many issues.
Instead of writing:
import theme from 'style!css!./theme.css';
We write:
import theme from './theme.css';
Using standard syntax will save us a lot of trouble in the future.
Don’t Import Itself in a Module
We shouldn’t import itself in a module.
So if we have foo.js
, we shouldn’t write:
import foo from './foo';
or:
const foo = require('./foo');
Instead, we should write the following in foo.js
:
import bar from './bar';
We can also write:
const bar = require('./bar');
No Cyclic Imports
We shouldn’t have cyclic imports in our modules.
A cyclic import would be the following:
a.js
:
import './b.js';
b.js
:
import './a.js';
We have 2 modules that import dependencies from each other.
Instead, we should write:
a.js
:
import './c.js';
to break the cycle.
Conclusion
We shouldn’t have cyclic imports in our code.
Also, we shouldn’t have dynamic imports or imports that can’t be imported.
And we should never have absolute imports.