Files
2025-11-29 18:20:21 +08:00

963 lines
20 KiB
Markdown

# Legacy Code Modernization Operation
Update legacy code patterns to modern JavaScript/TypeScript standards and best practices.
## Parameters
**Received from $ARGUMENTS**: All arguments after "modernize"
**Expected format**:
```
scope:"<path>" targets:"<target1,target2>" [compatibility:"<version>"]
```
**Parameter definitions**:
- `scope` (REQUIRED): Path to modernize (e.g., "src/legacy/", "utils/old-helpers.js")
- `targets` (REQUIRED): Comma-separated modernization targets
- `callbacks-to-async` - Convert callbacks to async/await
- `var-to-const` - Replace var with const/let
- `prototypes-to-classes` - Convert prototypes to ES6 classes
- `commonjs-to-esm` - Convert CommonJS to ES modules
- `jquery-to-vanilla` - Replace jQuery with vanilla JS
- `classes-to-hooks` - Convert React class components to hooks
- `legacy-api` - Update deprecated API usage
- `compatibility` (OPTIONAL): Target environment (e.g., "node14+", "es2020", "modern-browsers")
## Workflow
### 1. Analyze Legacy Patterns
Identify legacy code to modernize:
```bash
# Find var usage
grep -r "var " <scope> --include="*.js" --include="*.ts"
# Find callback patterns
grep -r "function.*callback" <scope>
# Find prototype usage
grep -r ".prototype" <scope>
# Find require() usage
grep -r "require\(" <scope>
# Find jQuery usage
grep -r "\$\(" <scope>
```
### 2. Target-Specific Modernization
## Modernization Examples
### Target 1: Callbacks to Async/Await
**When to modernize**:
- Callback hell (deeply nested callbacks)
- Error handling is scattered
- Readability suffers
- Modern runtime supports async/await
**Before** (Callback hell):
```javascript
// database.js
function getUser(userId, callback) {
db.query('SELECT * FROM users WHERE id = ?', [userId], function(err, user) {
if (err) {
return callback(err);
}
db.query('SELECT * FROM posts WHERE author_id = ?', [userId], function(err, posts) {
if (err) {
return callback(err);
}
db.query('SELECT * FROM comments WHERE user_id = ?', [userId], function(err, comments) {
if (err) {
return callback(err);
}
callback(null, {
user: user,
posts: posts,
comments: comments
});
});
});
});
}
// Usage
getUser(123, function(err, data) {
if (err) {
console.error('Error:', err);
return;
}
console.log('User:', data.user);
console.log('Posts:', data.posts);
console.log('Comments:', data.comments);
});
```
**After** (Async/await - Clean and readable):
```typescript
// database.ts
import { query } from './db';
interface User {
id: number;
name: string;
email: string;
}
interface Post {
id: number;
title: string;
content: string;
authorId: number;
}
interface Comment {
id: number;
content: string;
userId: number;
}
interface UserWithContent {
user: User;
posts: Post[];
comments: Comment[];
}
async function getUser(userId: number): Promise<UserWithContent> {
// Parallel execution for better performance
const [user, posts, comments] = await Promise.all([
query<User>('SELECT * FROM users WHERE id = ?', [userId]),
query<Post[]>('SELECT * FROM posts WHERE author_id = ?', [userId]),
query<Comment[]>('SELECT * FROM comments WHERE user_id = ?', [userId])
]);
return { user, posts, comments };
}
// Usage - Much cleaner
try {
const data = await getUser(123);
console.log('User:', data.user);
console.log('Posts:', data.posts);
console.log('Comments:', data.comments);
} catch (error) {
console.error('Error:', error);
}
```
**More callback conversions**:
```javascript
// Before: fs callbacks
const fs = require('fs');
fs.readFile('config.json', 'utf8', function(err, data) {
if (err) {
console.error(err);
return;
}
const config = JSON.parse(data);
fs.writeFile('output.json', JSON.stringify(config), function(err) {
if (err) {
console.error(err);
return;
}
console.log('Done');
});
});
// After: fs promises
import { readFile, writeFile } from 'fs/promises';
try {
const data = await readFile('config.json', 'utf8');
const config = JSON.parse(data);
await writeFile('output.json', JSON.stringify(config));
console.log('Done');
} catch (error) {
console.error(error);
}
```
**Improvements**:
- No callback hell: Flat, linear code
- Better error handling: Single try/catch
- Parallel execution: Promise.all() for performance
- Type safety: Full TypeScript support
- Readability: Much easier to understand
---
### Target 2: var to const/let
**When to modernize**:
- Using old var declarations
- Want block scoping
- Prevent accidental reassignment
- Modern ES6+ environment
**Before** (var - function scoped, hoisted):
```javascript
function processOrders() {
var total = 0;
var count = 0;
for (var i = 0; i < orders.length; i++) {
var order = orders[i];
var price = order.price;
var quantity = order.quantity;
total += price * quantity;
count++;
}
// i is still accessible here (function scoped!)
console.log(i); // orders.length
return { total: total, count: count };
}
// Hoisting issues
function example() {
console.log(x); // undefined (not error)
var x = 10;
}
// Loop issues
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // Always prints 3!
}, 100);
}
```
**After** (const/let - block scoped, not hoisted):
```typescript
function processOrders(): { total: number; count: number } {
let total = 0;
let count = 0;
for (let i = 0; i < orders.length; i++) {
const order = orders[i];
const price = order.price;
const quantity = order.quantity;
total += price * quantity;
count++;
}
// i is NOT accessible here (block scoped)
// console.log(i); // Error: i is not defined
return { total, count };
}
// No hoisting issues
function example() {
console.log(x); // Error: Cannot access 'x' before initialization
const x = 10;
}
// Loop fixed
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // Prints 0, 1, 2 correctly
}, 100);
}
```
**Guidelines**:
- Use `const` by default (immutable binding)
- Use `let` when reassignment needed
- Never use `var` in modern code
- Block scope prevents many bugs
---
### Target 3: Prototypes to ES6 Classes
**When to modernize**:
- Using prototype-based inheritance
- Want cleaner OOP syntax
- Better IDE support needed
- Modern JavaScript environment
**Before** (Prototype pattern):
```javascript
// Animal.js
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a sound');
};
Animal.prototype.getInfo = function() {
return this.name + ' is ' + this.age + ' years old';
};
// Dog.js
function Dog(name, age, breed) {
Animal.call(this, name, age);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
console.log(this.name + ' barks');
};
Dog.prototype.fetch = function() {
console.log(this.name + ' fetches the ball');
};
// Usage
var dog = new Dog('Rex', 3, 'Labrador');
dog.speak(); // Rex barks
console.log(dog.getInfo()); // Rex is 3 years old
```
**After** (ES6 Classes):
```typescript
// Animal.ts
export class Animal {
constructor(
protected name: string,
protected age: number
) {}
speak(): void {
console.log(`${this.name} makes a sound`);
}
getInfo(): string {
return `${this.name} is ${this.age} years old`;
}
}
// Dog.ts
export class Dog extends Animal {
constructor(
name: string,
age: number,
private breed: string
) {
super(name, age);
}
speak(): void {
console.log(`${this.name} barks`);
}
fetch(): void {
console.log(`${this.name} fetches the ball`);
}
getBreed(): string {
return this.breed;
}
}
// Usage
const dog = new Dog('Rex', 3, 'Labrador');
dog.speak(); // Rex barks
console.log(dog.getInfo()); // Rex is 3 years old
console.log(dog.getBreed()); // Labrador
```
**Improvements**:
- Cleaner syntax: More readable
- Better inheritance: extends keyword
- Access modifiers: public, private, protected
- Type safety: Full TypeScript support
- IDE support: Better autocomplete
---
### Target 4: CommonJS to ES Modules
**When to modernize**:
- Using require() and module.exports
- Want tree-shaking benefits
- Modern bundler support
- Better static analysis
**Before** (CommonJS):
```javascript
// utils.js
const crypto = require('crypto');
const fs = require('fs');
function generateId() {
return crypto.randomUUID();
}
function readConfig() {
return JSON.parse(fs.readFileSync('config.json', 'utf8'));
}
module.exports = {
generateId,
readConfig
};
// user-service.js
const { generateId } = require('./utils');
const db = require('./database');
class UserService {
async createUser(data) {
const id = generateId();
return db.users.create({ ...data, id });
}
}
module.exports = UserService;
// index.js
const express = require('express');
const UserService = require('./user-service');
const app = express();
const userService = new UserService();
app.post('/users', async (req, res) => {
const user = await userService.createUser(req.body);
res.json(user);
});
module.exports = app;
```
**After** (ES Modules):
```typescript
// utils.ts
import { randomUUID } from 'crypto';
import { readFileSync } from 'fs';
export function generateId(): string {
return randomUUID();
}
export function readConfig(): Config {
return JSON.parse(readFileSync('config.json', 'utf8'));
}
// user-service.ts
import { generateId } from './utils.js';
import { db } from './database.js';
export class UserService {
async createUser(data: CreateUserInput): Promise<User> {
const id = generateId();
return db.users.create({ ...data, id });
}
}
// index.ts
import express from 'express';
import { UserService } from './user-service.js';
const app = express();
const userService = new UserService();
app.post('/users', async (req, res) => {
const user = await userService.createUser(req.body);
res.json(user);
});
export default app;
```
**Improvements**:
- Tree-shaking: Remove unused exports
- Static imports: Better bundler optimization
- Named exports: More explicit imports
- Top-level await: Possible in ES modules
- Standard: Modern JavaScript standard
---
### Target 5: jQuery to Vanilla JavaScript
**When to modernize**:
- Remove jQuery dependency
- Reduce bundle size
- Modern browsers support native APIs
- Better performance
**Before** (jQuery):
```javascript
// app.js - Heavy jQuery usage
$(document).ready(function() {
// DOM selection
var $button = $('#submit-button');
var $form = $('.user-form');
var $inputs = $form.find('input');
// Event handling
$button.on('click', function(e) {
e.preventDefault();
// Get form data
var formData = {};
$inputs.each(function() {
var $input = $(this);
formData[$input.attr('name')] = $input.val();
});
// AJAX request
$.ajax({
url: '/api/users',
method: 'POST',
data: JSON.stringify(formData),
contentType: 'application/json',
success: function(response) {
// DOM manipulation
var $message = $('<div>')
.addClass('success-message')
.text('User created successfully!');
$form.after($message);
$message.fadeIn().delay(3000).fadeOut();
// Clear form
$inputs.val('');
},
error: function(xhr) {
$('.error-message').text(xhr.responseText).show();
}
});
});
// Show/hide password
$('.toggle-password').on('click', function() {
var $input = $(this).siblings('input');
var type = $input.attr('type');
$input.attr('type', type === 'password' ? 'text' : 'password');
$(this).toggleClass('active');
});
});
```
**After** (Vanilla JavaScript):
```typescript
// app.ts - Modern vanilla JavaScript
document.addEventListener('DOMContentLoaded', () => {
// DOM selection - Native APIs
const button = document.querySelector<HTMLButtonElement>('#submit-button');
const form = document.querySelector<HTMLFormElement>('.user-form');
const inputs = form?.querySelectorAll<HTMLInputElement>('input');
if (!button || !form || !inputs) return;
// Event handling - addEventListener
button.addEventListener('click', async (e) => {
e.preventDefault();
// Get form data - FormData API
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
try {
// Fetch API instead of $.ajax
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(await response.text());
}
const user = await response.json();
// DOM manipulation - Native APIs
const message = document.createElement('div');
message.className = 'success-message';
message.textContent = 'User created successfully!';
form.insertAdjacentElement('afterend', message);
// CSS animations instead of jQuery animations
message.style.opacity = '0';
message.style.display = 'block';
requestAnimationFrame(() => {
message.style.transition = 'opacity 0.3s';
message.style.opacity = '1';
setTimeout(() => {
message.style.opacity = '0';
setTimeout(() => message.remove(), 300);
}, 3000);
});
// Clear form
form.reset();
} catch (error) {
const errorMessage = document.querySelector('.error-message');
if (errorMessage) {
errorMessage.textContent = error.message;
errorMessage.style.display = 'block';
}
}
});
// Show/hide password - Native APIs
const toggleButtons = document.querySelectorAll<HTMLButtonElement>('.toggle-password');
toggleButtons.forEach(toggle => {
toggle.addEventListener('click', () => {
const input = toggle.previousElementSibling as HTMLInputElement;
if (!input) return;
const type = input.type === 'password' ? 'text' : 'password';
input.type = type;
toggle.classList.toggle('active');
});
});
});
```
**Bundle size impact**:
- Before: ~30KB (jQuery minified + gzipped)
- After: ~0KB (native APIs)
- **Savings**: 30KB, faster load time
**Improvements**:
- No jQuery dependency
- Modern native APIs
- Better performance
- TypeScript support
- Smaller bundle size
---
### Target 6: React Class Components to Hooks
**When to modernize**:
- Using class components
- Want simpler code
- Better functional composition
- Modern React patterns
**Before** (Class component):
```typescript
// UserProfile.tsx
import React, { Component } from 'react';
interface Props {
userId: string;
}
interface State {
user: User | null;
loading: boolean;
error: Error | null;
}
class UserProfile extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
user: null,
loading: true,
error: null
};
this.handleRefresh = this.handleRefresh.bind(this);
}
componentDidMount() {
this.loadUser();
}
componentDidUpdate(prevProps: Props) {
if (prevProps.userId !== this.props.userId) {
this.loadUser();
}
}
async loadUser() {
this.setState({ loading: true, error: null });
try {
const response = await fetch(`/api/users/${this.props.userId}`);
const user = await response.json();
this.setState({ user, loading: false });
} catch (error) {
this.setState({ error, loading: false });
}
}
handleRefresh() {
this.loadUser();
}
render() {
const { user, loading, error } = this.state;
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={this.handleRefresh}>Refresh</button>
</div>
);
}
}
export default UserProfile;
```
**After** (Function component with hooks):
```typescript
// UserProfile.tsx
import { useState, useEffect } from 'react';
interface Props {
userId: string;
}
export function UserProfile({ userId }: Props) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
// Extract to custom hook for reusability
const loadUser = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
// Load user on mount and when userId changes
useEffect(() => {
loadUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={loadUser}>Refresh</button>
</div>
);
}
```
**Even better with custom hook**:
```typescript
// hooks/useUser.ts
import { useState, useEffect } from 'react';
export function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const loadUser = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadUser();
}, [userId]);
return { user, loading, error, refresh: loadUser };
}
// UserProfile.tsx - Super clean now!
export function UserProfile({ userId }: { userId: string }) {
const { user, loading, error, refresh } = useUser(userId);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={refresh}>Refresh</button>
</div>
);
}
```
**Improvements**:
- Less boilerplate: No constructor, bind, etc.
- Simpler: Function instead of class
- Reusability: Custom hooks
- Better composition: Hooks compose well
- Modern: Current React best practice
---
## Output Format
```markdown
# Legacy Code Modernization Report
## Targets Modernized: <target1, target2, ...>
**Scope**: <path>
**Compatibility**: <version>
## Before Modernization
**Legacy Patterns Found**:
- var declarations: <count>
- Callback functions: <count>
- Prototype usage: <count>
- CommonJS modules: <count>
- jQuery usage: <count>
- Class components: <count>
**Issues**:
- Callback hell in <count> files
- Poor error handling
- Large bundle size (jQuery dependency)
- Outdated syntax
## Modernization Performed
### Target 1: <target-name>
**Files Modified**: <count>
**Before**:
```javascript
<legacy-code>
```
**After**:
```typescript
<modern-code>
```
**Improvements**:
- <improvement 1>
- <improvement 2>
### Target 2: <target-name>
[Same structure...]
## After Modernization
**Modern Patterns**:
- const/let: <count> conversions
- Async/await: <count> conversions
- ES6 classes: <count> conversions
- ES modules: <count> conversions
- Vanilla JS: <count> jQuery removals
- Function components: <count> conversions
**Metrics**:
- Bundle size: <before>KB → <after>KB (<percentage>% reduction)
- Code quality: Significantly improved
- Maintainability: Much easier
- Performance: <improvement>
## Testing
**Tests Updated**: <count>
**All tests passing**: YES
**New tests added**: <count>
## Breaking Changes
<list-breaking-changes-or-none>
## Migration Guide
**For Consumers**:
```typescript
// Old API
<old-usage>
// New API
<new-usage>
```
## Next Steps
**Further Modernization**:
1. <next-opportunity>
2. <another-opportunity>
---
**Modernization Complete**: Codebase updated to modern standards.
```
## Error Handling
**Incompatible environment**:
```
Error: Target environment does not support <feature>
Target: <compatibility>
Required: <minimum-version>
Options:
1. Use Babel/TypeScript to transpile
2. Update target environment
3. Choose different modernization target
```
**Too many changes**:
```
Warning: Modernizing <count> files is a large change.
Recommendation: Gradual modernization
1. Start with critical paths
2. Modernize incrementally
3. Test thoroughly between changes
4. Review with team
```