π§Ή 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)