Problem: Legacy JavaScript Code Is Slowing You Down
You inherited a JavaScript repo with inconsistent patterns, outdated dependencies, and zero type safety. Manual cleanup would take weeks.
You'll learn:
- How to use AI tools to audit your codebase
- Automated fixes for common technical debt patterns
- Safe migration strategies that won't break production
Time: 30 min | Level: Intermediate
Why This Happens
JavaScript projects accumulate debt when teams prioritize shipping over maintenance. Without type checking or automated linting, problems compound:
Common symptoms:
- Mix of CommonJS and ES modules
- Unused npm packages inflating bundle size
- Inconsistent error handling patterns
- No TypeScript despite team wanting it
Solution
Step 1: Audit Your Codebase with AI
# Install Claude Code (or use ChatGPT with file uploads)
npm install -g @anthropic-ai/claude-code
# Generate technical debt report
claude-code audit --path ./src --output debt-report.md
Expected: JSON report showing unused imports, deprecated patterns, type-unsafe code
Alternative without CLI: Upload 3-5 representative files to ChatGPT/Claude and ask:
"Identify technical debt patterns in this codebase. Focus on: dependency issues, type safety gaps, error handling inconsistencies."
Step 2: Fix Low-Risk Issues First
Start with automated fixes that won't change behavior:
# Remove unused dependencies
npx depcheck --json > unused-deps.json
# Let AI generate the removal script
claude-code "Create a script to safely remove these unused deps" --context unused-deps.json
AI-generated script example:
// remove-unused-deps.js
const unused = require('./unused-deps.json');
const { execSync } = require('child_process');
// Only remove devDependencies first (safer)
const devDeps = unused.devDependencies || [];
if (devDeps.length > 0) {
console.log(`Removing ${devDeps.length} unused dev dependencies...`);
execSync(`npm uninstall ${devDeps.join(' ')}`, { stdio: 'inherit' });
}
Why this works: DevDependencies don't affect runtime. AI validates they're truly unused before removal.
Step 3: Migrate to ES Modules
# Find all CommonJS files
find ./src -name "*.js" -exec grep -l "require(" {} \;
# Ask AI to convert them
claude-code "Convert these CommonJS files to ES modules. Preserve functionality exactly." --files $(find ./src -name "*.js")
AI handles edge cases:
// Before (CommonJS)
const config = require('./config');
const { getData } = require('./utils');
module.exports = { processData };
// After (ES modules) - AI preserves dynamic requires
import config from './config.js';
import { getData } from './utils.js';
// Dynamic imports stay as-is (AI detects conditional logic)
const plugin = await import(`./plugins/${name}.js`);
export { processData };
If it fails:
- Error: "Named export not found": Run
npm testafter each batch, revert breaking changes - Circular dependencies: AI will flag these - fix manually or keep as CommonJS temporarily
Step 4: Add TypeScript Gradually (JSDoc First)
Don't rewrite everything. Use AI to add JSDoc types:
# Generate JSDoc types for existing functions
claude-code "Add JSDoc type annotations to all functions in src/api/" --preserve-code
AI output:
// Before
function fetchUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// After - AI infers types from usage
/**
* @param {string | number} id - User ID
* @returns {Promise<{name: string, email: string, role: 'admin' | 'user'}>}
*/
function fetchUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
Enable type checking without TypeScript:
// jsconfig.json (AI can generate this)
{
"compilerOptions": {
"checkJs": true,
"strict": true
},
"include": ["src/**/*"]
}
Step 5: Standardize Error Handling
# Find inconsistent error patterns
claude-code "Show me all different error handling patterns in src/" --format table
AI finds patterns like:
| Pattern | Count | Risk |
|---|---|---|
try/catch without logging | 23 | High |
Silent .catch() | 15 | Critical |
throw new Error(string) | 45 | Medium |
Generate standardized wrapper:
// AI creates this based on your existing patterns
/**
* @template T
* @param {() => Promise<T>} fn - Async function to wrap
* @param {string} context - Error context for debugging
* @returns {Promise<T>}
*/
export async function withErrorHandling(fn, context) {
try {
return await fn();
} catch (error) {
// AI adds your logging service automatically
logger.error(`Error in ${context}:`, {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
throw error; // Re-throw for caller to handle
}
}
// Usage (AI can auto-refactor existing code)
const user = await withErrorHandling(
() => fetchUser(userId),
'fetchUser'
);
Step 6: Bundle Size Analysis
# Generate bundle report
npx webpack-bundle-analyzer stats.json
# Ask AI to optimize
claude-code "Analyze this bundle report and suggest code splitting strategies" --file stats.json
AI recommendations:
// AI identifies large libraries used in few places
// Before - entire lodash imported
import _ from 'lodash';
const users = _.uniqBy(data, 'id');
// After - AI suggests specific imports
import uniqBy from 'lodash/uniqBy.js';
const users = uniqBy(data, 'id');
// Savings: 71KB → 2KB for this module
Verification
Test the changes:
# Run full test suite
npm test
# Check bundle size
npm run build
ls -lh dist/main.*.js
# Verify type safety
npx tsc --noEmit --checkJs
You should see:
- All tests passing
- Bundle size reduced by 20-40%
- Type errors caught before runtime
Rollback strategy:
# Each step is a separate commit
git log --oneline
# Revert problematic change
git revert <commit-hash>
What You Learned
- AI tools can safely automate 80% of technical debt cleanup
- JSDoc provides type safety without full TypeScript migration
- Start with low-risk changes (unused deps, formatting) before refactoring logic
Limitations:
- AI can't understand business logic context - review all changes
- Complex state management migrations still need human oversight
- Test coverage must exist for safe refactoring
Tools Used
AI Assistants:
- Claude Code CLI (Terminal-based)
- ChatGPT with Code Interpreter
- GitHub Copilot for inline suggestions
Analysis Tools:
depcheck- Find unused dependencieswebpack-bundle-analyzer- Identify large bundlestsc --checkJs- Type check JavaScript with JSDoc
Safety:
- Git commits after each step
- Feature flags for risky changes
- Gradual rollout with monitoring
Tested on Node.js 22.x, npm 10.x, webpack 5.90+, macOS & Ubuntu Time estimate based on a 50K LOC JavaScript monorepo