π§Ή Complete Guide: How to Remove Unused Imports in JavaScript/TypeScript Projects
Table of Contents
- Why Remove Unused Imports?
- Manual vs Automated Approaches
- Method 1: ESLint with unused-imports Plugin (Recommended)
- Method 2: TypeScript Compiler
- Method 3: IDE/Editor Built-in Features
- Method 4: Other Third-party Tools
- Best Practices
- Troubleshooting
- Integration with CI/CD
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
For yarn:
yarn add --dev eslint-plugin-unused-imports
For pnpm:
pnpm add --save-dev eslint-plugin-unused-imports
For bun:
bun add --dev eslint-plugin-unused-imports
βοΈ 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;
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": "^_"
}
]
}
}
π 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"
}
}
π 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
π― 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>;
};
Unused Variables (Warnings):
function handleSubmit(data: FormData, _metadata: unknown) {
const result = processData(data); // β Warning: 'result' unused
const _temp = calculate(); // β
OK: prefixed with '_'
// Do something...
}
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"]
}
π Usage
# Check for unused imports/variables
npx tsc --noEmit
# With specific config
npx tsc --noEmit --noUnusedLocals --noUnusedParameters
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
depcheck
Checks for unused dependencies in package.json.
# Install
npm install -g depcheck
# Usage
depcheck
ts-unused-exports
Finds unused exports in TypeScript projects.
# Install
npm install -g ts-unused-exports
# Usage
ts-unused-exports
Best Practices
π― Development Workflow
- Pre-commit Hooks: Use husky + lint-staged
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"git add"
]
}
}
- IDE Configuration: Set up auto-fix on save
// VS Code settings.json
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
}
}
- Regular Cleanup: Run cleanup weekly
# Add to package.json
"scripts": {
"cleanup": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
}
π¨ Code Conventions
Use Underscore Prefix for Intentionally Unused:
function handleClick(_event: MouseEvent, data: FormData) {
// _event is intentionally unused but required by API
processFormData(data);
}
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';
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';
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'));
2. Type-only Imports
// β
Correct way for TypeScript
import type { User } from './types';
import { type APIResponse, fetchUser } from './api';
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/**"
]
}
Debugging Steps
- Check ESLint is running:
npx eslint --version
npx eslint . --ext .ts,.tsx --dry-run
- Verify plugin installation:
npm list eslint-plugin-unused-imports
- Test with single file:
npx eslint src/components/example.tsx --fix
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
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"
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}
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
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>
);
};
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
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)