DEV Community

Sh Raj
Sh Raj

Posted on

Remove Unused Imports in JavaScript/TypeScript Projects

🧹 Complete Guide: How to Remove Unused Imports in JavaScript/TypeScript Projects

Table of Contents


Why Remove Unused Imports?

Unused imports in your codebase can lead to several issues:

🚫 Problems with Unused Imports

  • Bundle Size: Increases your final bundle size with unnecessary code
  • Performance: Slower build times and runtime performance
  • Maintenance: Creates confusion about actual dependencies
  • Code Quality: Makes code harder to read and understand
  • Tree Shaking: Prevents proper dead code elimination

βœ… Benefits of Clean Imports

  • Smaller Bundles: Faster loading times for users
  • Better Performance: Optimized build and runtime performance
  • Cleaner Code: Easier to understand what's actually being used
  • Improved Maintainability: Clear dependency relationships

Manual vs Automated Approaches

Manual Removal

❌ Cons:

  • Time-consuming for large codebases
  • Error-prone
  • Hard to maintain consistency
  • Requires constant vigilance

Automated Removal

βœ… Pros:

  • Fast and efficient
  • Consistent across the entire codebase
  • Can be integrated into development workflow
  • Catches issues automatically

Method 1: ESLint with unused-imports Plugin (Recommended)

This is the most popular and effective method for JavaScript/TypeScript projects.

πŸ› οΈ Installation

For npm:

npm install --save-dev eslint-plugin-unused-imports
Enter fullscreen mode Exit fullscreen mode

For yarn:

yarn add --dev eslint-plugin-unused-imports
Enter fullscreen mode Exit fullscreen mode

For pnpm:

pnpm add --save-dev eslint-plugin-unused-imports
Enter fullscreen mode Exit fullscreen mode

For bun:

bun add --dev eslint-plugin-unused-imports
Enter fullscreen mode Exit fullscreen mode

βš™οΈ Configuration

ESLint Flat Config (ESLint 9+)

Create or update your eslint.config.mjs:

import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
import unusedImports from "eslint-plugin-unused-imports";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

const eslintConfig = [
  {
    ignores: [
      ".next/**",
      ".wrangler/**", 
      "node_modules/**",
      "dist/**",
      "build/**",
      "*.config.js",
      "*.config.mjs",
      "**/*.d.ts"
    ]
  },
  ...compat.extends("next/core-web-vitals", "next/typescript"),
  {
    plugins: {
      "unused-imports": unusedImports,
    },
    rules: {
      // Disable the base rule as it can report incorrect errors
      "@typescript-eslint/no-unused-vars": "off",

      // Enable unused imports detection
      "unused-imports/no-unused-imports": "error",

      // Enable unused variables detection with customization
      "unused-imports/no-unused-vars": [
        "warn",
        {
          "vars": "all",
          "varsIgnorePattern": "^_",
          "args": "after-used", 
          "argsIgnorePattern": "^_"
        }
      ],
    }
  }
];

export default eslintConfig;
Enter fullscreen mode Exit fullscreen mode

Legacy ESLint Config (.eslintrc.json)

{
  "extends": ["next/core-web-vitals"],
  "plugins": ["unused-imports"],
  "rules": {
    "@typescript-eslint/no-unused-vars": "off",
    "unused-imports/no-unused-imports": "error",
    "unused-imports/no-unused-vars": [
      "warn",
      {
        "vars": "all",
        "varsIgnorePattern": "^_",
        "args": "after-used",
        "argsIgnorePattern": "^_"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“ Package.json Scripts

Add these convenient scripts to your package.json:

{
  "scripts": {
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
    "lint:unused": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
    "lint:src": "eslint src --ext .js,.jsx,.ts,.tsx"
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸš€ Usage

# Check for unused imports (don't fix)
npm run lint:src
# or with bun
bun run lint:src

# Automatically remove unused imports
npm run lint:unused  
# or with bun
bun run lint:unused

# Fix entire project
npm run lint:fix
# or with bun  
bun run lint:fix
Enter fullscreen mode Exit fullscreen mode

🎯 What Gets Detected

Unused Imports (Errors - Auto-fixable):

import { React } from 'react'; // ❌ Unused
import { Button, Card } from '@/components/ui'; // ❌ Card unused
// Will become:
// import { Button } from '@/components/ui'; // βœ… Fixed

const MyComponent = () => {
  return <Button>Click me</Button>;
};
Enter fullscreen mode Exit fullscreen mode

Unused Variables (Warnings):

function handleSubmit(data: FormData, _metadata: unknown) {
  const result = processData(data); // ❌ Warning: 'result' unused
  const _temp = calculate(); // βœ… OK: prefixed with '_'

  // Do something...
}
Enter fullscreen mode Exit fullscreen mode

Method 2: TypeScript Compiler

TypeScript compiler can help detect unused imports with proper configuration.

πŸ“‹ tsconfig.json Configuration

{
  "compilerOptions": {
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "strict": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", ".next", "dist"]
}
Enter fullscreen mode Exit fullscreen mode

πŸ” Usage

# Check for unused imports/variables
npx tsc --noEmit

# With specific config
npx tsc --noEmit --noUnusedLocals --noUnusedParameters
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Only detects issues, doesn't auto-fix
  • May not catch all unused import cases
  • Requires manual removal

Method 3: IDE/Editor Built-in Features

VS Code

  • Built-in: TypeScript extension shows gray text for unused imports
  • Auto-fix: Ctrl/Cmd + Shift + O to organize imports
  • Settings: Enable "typescript.preferences.organizeImports.enabled"
  • Extensions:
    • ESLint extension (works with unused-imports plugin)
    • TypeScript Importer
    • Auto Import - ES6, TS, JSX, TSX

WebStorm/IntelliJ IDEA

  • Built-in: Highlights unused imports in gray
  • Auto-fix: Ctrl/Cmd + Alt + O to optimize imports
  • Settings: Enable "Optimize imports on the fly"

Vim/Neovim

  • Plugins:
    • coc-eslint
    • ALE (Asynchronous Lint Engine)
    • nvim-lspconfig with ESLint

Method 4: Other Third-party Tools

unimported

A specialized tool for finding unused dependencies and files.

# Install
npm install -g unimported

# Usage  
unimported
Enter fullscreen mode Exit fullscreen mode

depcheck

Checks for unused dependencies in package.json.

# Install
npm install -g depcheck

# Usage
depcheck
Enter fullscreen mode Exit fullscreen mode

ts-unused-exports

Finds unused exports in TypeScript projects.

# Install
npm install -g ts-unused-exports

# Usage
ts-unused-exports
Enter fullscreen mode Exit fullscreen mode

Best Practices

🎯 Development Workflow

  1. Pre-commit Hooks: Use husky + lint-staged
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "git add"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. IDE Configuration: Set up auto-fix on save
// VS Code settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.organizeImports": true
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Regular Cleanup: Run cleanup weekly
# Add to package.json
"scripts": {
  "cleanup": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
}
Enter fullscreen mode Exit fullscreen mode

🎨 Code Conventions

Use Underscore Prefix for Intentionally Unused:

function handleClick(_event: MouseEvent, data: FormData) {
  // _event is intentionally unused but required by API
  processFormData(data);
}
Enter fullscreen mode Exit fullscreen mode

Import Organization:

// βœ… Good: Organized imports
import React from 'react';
import { NextPage } from 'next';

import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';

import { useAuth } from '@/hooks/use-auth';
import { formatDate } from '@/lib/utils';
Enter fullscreen mode Exit fullscreen mode

Avoid Barrel Export Issues:

// ❌ Can cause unused import issues
import * as Icons from 'lucide-react';

// βœ… Better: Import only what you need
import { Heart, MessageCircle, Share } from 'lucide-react';
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Common Issues

1. False Positives with Dynamic Imports

// This might be flagged as unused
import { ComponentType } from './dynamic-component';

// Solution: Use ESLint disable comment
import { ComponentType } from './dynamic-component'; // eslint-disable-line unused-imports/no-unused-imports

const DynamicComponent = dynamic(() => import('./dynamic-component'));
Enter fullscreen mode Exit fullscreen mode

2. Type-only Imports

// βœ… Correct way for TypeScript
import type { User } from './types';
import { type APIResponse, fetchUser } from './api';
Enter fullscreen mode Exit fullscreen mode

3. Plugin Not Working

  • Ensure ESLint is properly configured
  • Check if plugin is correctly installed
  • Verify file extensions in scripts
  • Make sure TypeScript parser is configured

4. Performance Issues with Large Codebases

// Use ignore patterns in ESLint config
{
  ignores: [
    "node_modules/**",
    "dist/**", 
    ".next/**",
    "coverage/**"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Debugging Steps

  1. Check ESLint is running:
npx eslint --version
npx eslint . --ext .ts,.tsx --dry-run
Enter fullscreen mode Exit fullscreen mode
  1. Verify plugin installation:
npm list eslint-plugin-unused-imports
Enter fullscreen mode Exit fullscreen mode
  1. Test with single file:
npx eslint src/components/example.tsx --fix
Enter fullscreen mode Exit fullscreen mode

Integration with CI/CD

GitHub Actions

name: Code Quality

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'

    - name: Install dependencies
      run: npm ci

    - name: Check for unused imports
      run: npm run lint:src

    - name: Auto-fix and check diff
      run: |
        npm run lint:fix
        if [[ `git status --porcelain` ]]; then
          echo "❌ Found unused imports. Please run 'npm run lint:fix'"
          exit 1
        fi
Enter fullscreen mode Exit fullscreen mode

Pre-commit Hook (Husky)

# Install husky
npm install --save-dev husky lint-staged

# Setup
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"
Enter fullscreen mode Exit fullscreen mode
// package.json
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Real-world Example: Unstory Project

In the Unstory social media platform, we implemented unused import removal with the following setup:

Project Structure

unstory/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ app/           # Next.js app directory
β”‚   β”œβ”€β”€ components/    # React components
β”‚   β”œβ”€β”€ hooks/         # Custom hooks
β”‚   β”œβ”€β”€ lib/           # Utility functions
β”‚   └── types/         # TypeScript types
β”œβ”€β”€ eslint.config.mjs  # ESLint configuration
└── package.json       # Scripts and dependencies
Enter fullscreen mode Exit fullscreen mode

Results

  • Removed 167 unused imports in initial cleanup
  • Bundle size reduced by ~15KB
  • Build time improved by 8%
  • Code maintainability significantly improved

Before/After Example

// ❌ Before: Multiple unused imports
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { Button, Card, Input, Label, Separator } from '@/components/ui';
import { formatDate, slugify, debounce } from '@/lib/utils';
import { User, Post, Comment } from '@/types';

// βœ… After: Only necessary imports
import React, { useState } from 'react';
import { Button } from '@/components/ui';
import { User } from '@/types';

const UserProfile = ({ user }: { user: User }) => {
  const [isEditing, setIsEditing] = useState(false);

  return (
    <div>
      <h1>{user.name}</h1>
      <Button onClick={() => setIsEditing(!isEditing)}>
        {isEditing ? 'Save' : 'Edit'}
      </Button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

Removing unused imports is essential for maintaining a clean, performant codebase. The ESLint with unused-imports plugin approach is the most effective solution because it:

  • βœ… Automatically detects and fixes unused imports
  • βœ… Integrates seamlessly with existing development workflows
  • βœ… Customizable rules for different project needs
  • βœ… IDE integration for real-time feedback
  • βœ… CI/CD integration for automated checks

Quick Start Commands

# Install the plugin
bun add --dev eslint-plugin-unused-imports

# Clean up your codebase  
bun run lint:unused

# Set up automated workflow
# Add pre-commit hooks and CI/CD integration
Enter fullscreen mode Exit fullscreen mode

By implementing these practices, you'll maintain a cleaner codebase, improve performance, and enhance developer experience. Start with the ESLint plugin approachβ€”it's the most comprehensive and developer-friendly solution available.


This guide was created for the Unstory project and can be adapted for any JavaScript/TypeScript project. Happy coding! πŸš€

Top comments (0)