Welcome to the first entry in a new series that’s probably going to stop with this first entry, or maybe not.

The series is called Charl’s Unwritten Rules of Software Development, or cursd.

I am also planning another series called Charl’s Unwritten Rules of Applied Machine Learning that is on its part probably going to remain in the planning stage, or maybe not.

In this first CURSD post, I would like to document my hitherto unwritten rule for ordering your TypeScript (or JavaScript) import groups.

Of course you are already making use of tslint’s ordered-imports rule, which means your within group ordering is consistent.

However, on that page you’ll see, amongst other things:

Groups of imports are delineated by blank lines. You can use this rule to group imports however you like, e.g. by first- vs. third-party or thematically or you can define groups based upon patterns in import path names.

The CURSD ordering of TypeScript import groups

Henceforth, the CURSD group ordering rule is:

Sort TypeScript / JavaScript import groups from standard to local. In other words, sort the groups from base through to custom, or in more other words from far to near.

We take our inspiration from the Python PEP8 recommendation:

Imports should be grouped in the following order:

Standard library imports. Related third party imports. Local application/library specific imports.

In Python that distinction is usually quite easy.

In JavaScript and TypeScript projects, it’s not always as clear-cut, but it’s usually possible to group from the most foundational to the most local code modules.

The example below should help clear this up.

Example

In the example below, based on a component in an actual product, I have divided the imports up into three groups. At the start we have React and Redux, followed by the material-ui group, followed by our local modules and components.

If the list of imports became substantially longer, one could further divide up the local group into react components and non-component code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// in this module, we use React and Redux as our base libraries
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch } from "react-redux";

// on top of that, we use these material-ui components
import Button from "@material-ui/core/Button";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";

// and on top of that, we have our own components and local modules
import { setTroloThing } from "../../thing";
import { OurSpecialComponent } from "./components/our_special_component";
import { ISomeInterface } from "./hra_types";

Motivation

We do this for at least two good reasons:

  1. This adds a light-weight hierarchical structure to the imports, which facilitates the understanding of the dependency network between all of the modules in your project.
  2. As is almost always the case, having conventions such as these frees up a few more mental cycles when writing code.

Bonus level: typescript-eslint rule for ordered import groups

Thanks to the suggestion by Frank van Wijk in the comments below, together with a bit of elbow grease on our part, we have now migrated from tslint to typescript-eslint.

As a component of that, I can now share the typescript-eslint rule that will enforce the import group ordering exactly as in the example above.

It looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// this is part of our .eslintrc.js under the "rules" object
"import/order": [
  "error",
  {
    groups: ["builtin", "external", "internal", ["index", "sibling", "parent", "object"]],
    // default is builtin, external; but we want to divide up externals into groups also
    pathGroupsExcludedImportTypes: ["builtin"],
    // define material-ui group that will appear separately after other main externals
    pathGroups: [{ pattern: "@material-ui/**", group: "external", position: "after" }],
    "newlines-between": "always-and-inside-groups",
    alphabetize: { order: "asc", caseInsensitive: true },
  },
],