Feed your internal perfectionist by having perfect import hygiene.

The Problem

Nowadays, we work with javascript or typescript modules, and we almost always end up having quite a few imports in each file. Sometimes, it ends up being quite difficult combing and understanding which import brings what in. To make it even worse, it adds to the unnecessary noise that each PR is already struggling with.

Good to know before jumping in

Most IDEs worth their salt know how to at least remove imports that are not used in the file, I highly recommend using that option since it helps stay clean. TsLint(R.I.P 🪦) and EsLint have rules that you can enable to make sure nothing gets merged that imports modules without using them. Make sure you serve the minimum amount of code necessary.

Solution

There are three parts to the solution:

  1. Setup Prettier
  2. Setup Prettier Import Order Plugin
  3. Do work

Part 1 - Setup Prettier

Right now, our imports look something like this:

TYPESCRIPT
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { EventLoggingService, EventType} from 'business-logic';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, zip } from 'rxjs';
import { fadeInOutTrigger, fadeInTrigger } from '@shared/animations/animations';
import { filter, map, tap, withLatestFrom } from 'rxjs/operators';
import { SearchPopupService } from '../search-popup.service';
import { HomePageReview } from '../home-core/data/homepage-data';
import { DateTimeRangeInputStyles } from '@shared/date-time-range-input/date-time-range-input-styles';
TYPESCRIPT
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { EventLoggingService, EventType} from 'business-logic';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, zip } from 'rxjs';
import { fadeInOutTrigger, fadeInTrigger } from '@shared/animations/animations';
import { filter, map, tap, withLatestFrom } from 'rxjs/operators';
import { SearchPopupService } from '../search-popup.service';
import { HomePageReview } from '../home-core/data/homepage-data';
import { DateTimeRangeInputStyles } from '@shared/date-time-range-input/date-time-range-input-styles';

Nothing wrong with this, but we can do better. We can enable our IDE to at least order imports automatically. After we do that, they look just a tad better.

TYPESCRIPT
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { fadeInOutTrigger, fadeInTrigger } from '@shared/animations/animations';
import { DateTimeRangeInputStyles } from '@shared/date-time-range-input/date-time-range-input-styles';
import { EventLoggingService, EventType} from 'business-logic';
import { BehaviorSubject, zip } from 'rxjs';
import { filter, map, tap, withLatestFrom } from 'rxjs/operators';
import { HomePageReview } from '../home-core/data/homepage-data';
import { SearchPopupService } from '../search-popup.service';
TYPESCRIPT
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { fadeInOutTrigger, fadeInTrigger } from '@shared/animations/animations';
import { DateTimeRangeInputStyles } from '@shared/date-time-range-input/date-time-range-input-styles';
import { EventLoggingService, EventType} from 'business-logic';
import { BehaviorSubject, zip } from 'rxjs';
import { filter, map, tap, withLatestFrom } from 'rxjs/operators';
import { HomePageReview } from '../home-core/data/homepage-data';
import { SearchPopupService } from '../search-popup.service';

Now that the settings are correct, let's install Prettier: npm install --save-dev prettier. We want to use save-dev because we only need it for development.

Now, for what it's worth, this is my usual .prettierrc.js file.

TYPESCRIPT
module.exports = {
semi: true,
trailingComma: 'none',
singleQuote: true,
printWidth: 100,
tabWidth: 2,
endOfLine: 'lf',
bracketSpacing: true,
bracketSameLine: true
};
TYPESCRIPT
module.exports = {
semi: true,
trailingComma: 'none',
singleQuote: true,
printWidth: 100,
tabWidth: 2,
endOfLine: 'lf',
bracketSpacing: true,
bracketSameLine: true
};

This is where developers spend eons arguing what's best. I don't care that much as long as it's consistent across the whole team.

Part 2 - Setup Prettier plugins

Now, this is where the magic 🧙‍♂️ comes in. Thanks to people at Trivago, we have this plugin available to us: prettier-plugin-sort-imports. Just install it with npm install --save-dev @trivago/prettier-plugin-sort-imports;

You can find more options in the documentation, but this is my goto:

TYPESCRIPT
module.exports = {
semi: true,
trailingComma: 'none',
singleQuote: true,
printWidth: 100,
tabWidth: 2,
endOfLine: 'lf',
bracketSpacing: true,
bracketSameLine: true,
importOrder: [
// Place angular import at the top
'^@angular$',
'rxjs',
// External dependencies
'^\\w',
// This project's own aliases
'^(@shared|@core|@env|@test-utilities)(/.*|$)',
// This project's extra libraries
'^(business-logic|@ui)(/.*|$)',
// Every import starting with ./ or ~/
'^[./|~/]'
],
importOrderSeparation: true,
importOrderParserPlugins: ['decorators-legacy', 'typescript']
};
TYPESCRIPT
module.exports = {
semi: true,
trailingComma: 'none',
singleQuote: true,
printWidth: 100,
tabWidth: 2,
endOfLine: 'lf',
bracketSpacing: true,
bracketSameLine: true,
importOrder: [
// Place angular import at the top
'^@angular$',
'rxjs',
// External dependencies
'^\\w',
// This project's own aliases
'^(@shared|@core|@env|@test-utilities)(/.*|$)',
// This project's extra libraries
'^(business-logic|@ui)(/.*|$)',
// Every import starting with ./ or ~/
'^[./|~/]'
],
importOrderSeparation: true,
importOrderParserPlugins: ['decorators-legacy', 'typescript']
};

You'll notice that I added 3 more options for import, importOrderSeparation and importOrderParserPlugins. This is tailored for an Angular project, but you can easily tailor it to anything.

Now, assuming you have the prettify-my-code-on-save option enabled in your IDE, if you do a quick Cmd + S, your imports should look like this:

TYPESCRIPT
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { BehaviorSubject, zip } from 'rxjs';
import { filter, map, tap, withLatestFrom } from 'rxjs/operators';

import { EventLoggingService, EventType} from 'business-logic';

import { fadeInOutTrigger, fadeInTrigger } from '@shared/animations/animations';
import { DateTimeRangeInputStyles } from '@shared/date-time-range-input/date-time-range-input-styles';

import { SearchPopupService } from '../search-popup.service';
import { HomePageReview } from '../home-core/data/homepage-data';
TYPESCRIPT
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { BehaviorSubject, zip } from 'rxjs';
import { filter, map, tap, withLatestFrom } from 'rxjs/operators';

import { EventLoggingService, EventType} from 'business-logic';

import { fadeInOutTrigger, fadeInTrigger } from '@shared/animations/animations';
import { DateTimeRangeInputStyles } from '@shared/date-time-range-input/date-time-range-input-styles';

import { SearchPopupService } from '../search-popup.service';
import { HomePageReview } from '../home-core/data/homepage-data';

To me, this is so much Prettier(pun intended 🥁). Imports are grouped into categories defined by me, and they get a nice empty line in between categories. Sweet!

Part 3 - Do Work

Again, this is highly subjective. Please customise it however you want. In the end, it's just a tool. 🛠

Want more?

If you want to know more about this, or something doesn't make any sense, please let me know.

Also, if you have questions, you know where to find me… On the internet!

Be kind to each other! 🧡

Wanna read more?