Real-Time Application Development
WebSocket Real-Time Communication
WebSocket Server with ws Library:
// Create WebSocket server using ws library
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
// Connection management
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
console.log('New client connected, current connections:', clients.size);
// Message handling
ws.on('message', (message) => {
console.log('Received message:', message);
// Broadcast message to all clients
clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
// Connection close
ws.on('close', () => {
clients.delete(ws);
console.log('Client disconnected, remaining connections:', clients.size);
});
});
console.log('WebSocket server started on port 8080');Real-Time Communication with Socket.io:
// Socket.io server-side
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
// Room management
const rooms = {};
io.on('connection', (socket) => {
console.log('New user connected:', socket.id);
// Join room
socket.on('joinRoom', (roomId) => {
if (!rooms[roomId]) {
rooms[roomId] = [];
}
rooms[roomId].push(socket.id);
socket.join(roomId);
console.log(`User ${socket.id} joined room ${roomId}`);
});
// Room message
socket.on('roomMessage', ({ roomId, message }) => {
io.to(roomId).emit('message', message);
});
// Disconnect
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
// Clean up room information
for (const roomId in rooms) {
rooms[roomId] = rooms[roomId].filter(id => id !== socket.id);
if (rooms[roomId].length === 0) {
delete rooms[roomId];
}
}
});
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});Message Queues
RabbitMQ Message Queue Implementation:
// Producer
const amqp = require('amqplib');
async function produce() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queue = 'task_queue';
await channel.assertQueue(queue, { durable: true });
const message = 'Hello RabbitMQ!';
channel.sendToQueue(queue, Buffer.from(message), { persistent: true });
console.log(` [x] Sent '${message}'`);
setTimeout(() => {
connection.close();
process.exit(0);
}, 500);
}
produce();
// Consumer
async function consume() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queue = 'task_queue';
await channel.assertQueue(queue, { durable: true });
channel.prefetch(1); // Fair dispatch
console.log(' [*] Waiting for messages, press CTRL+C to exit');
channel.consume(queue, (msg) => {
if (msg !== null) {
console.log(` [x] Received '${msg.content.toString()}'`);
// Simulate work
setTimeout(() => {
console.log(' [x] Done');
channel.ack(msg);
}, 1000);
}
});
}
consume();Kafka Message Queue Implementation:
// Producer
const { Kafka } = require('kafkajs');
const kafka = new Kafka({
clientId: 'my-app',
brokers: ['localhost:9092']
});
const producer = kafka.producer();
async function runProducer() {
await producer.connect();
await producer.send({
topic: 'test-topic',
messages: [
{ value: 'Hello Kafka!' }
]
});
await producer.disconnect();
}
runProducer();
// Consumer
const consumer = kafka.consumer({ groupId: 'test-group' });
async function runConsumer() {
await consumer.connect();
await consumer.subscribe({ topic: 'test-topic', fromBeginning: true });
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
console.log({
topic,
partition,
offset: message.offset,
value: message.value.toString()
});
}
});
}
runConsumer();Real-Time Data Push
Server-Sent Events Implementation:
// SSE server-side
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
// Set SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send initial data
res.write('event: connected\n');
res.write('data: Welcome to SSE server!\n\n');
// Send data periodically
const intervalId = setInterval(() => {
const data = { time: new Date().toISOString() };
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
// Clean up on client disconnect
req.on('close', () => {
clearInterval(intervalId);
console.log('Client disconnected');
});
});
app.listen(3000, () => {
console.log('SSE server running at http://localhost:3000/events');
});
// SSE client-side
const eventSource = new EventSource('http://localhost:3000/events');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received message:', data);
};
eventSource.addEventListener('connected', (event) => {
console.log('Connection established:', event.data);
});
eventSource.onerror = (error) => {
console.error('SSE error:', error);
};Real-Time Collaborative Editing
Simple CRDT Implementation for Collaborative Editing:
// Simplified CRDT implementation (based on LWW-Element-Set)
class CRDTSet {
constructor() {
this.addSet = new Map(); // Set of add operations
this.removeSet = new Map(); // Set of remove operations
this.timestamp = 0; // Logical clock
}
add(element) {
this.timestamp++;
this.addSet.set(element, this.timestamp);
this.removeSet.delete(element);
}
remove(element) {
this.timestamp++;
if (this.addSet.has(element)) {
this.removeSet.set(element, this.timestamp);
}
}
getElements() {
const result = [];
for (const [element, addTime] of this.addSet) {
if (!this.removeSet.has(element) || this.removeSet.get(element) < addTime) {
result.push(element);
}
}
return result;
}
merge(other) {
// Merge add set
for (const [element, time] of other.addSet) {
if (!this.addSet.has(element) || this.addSet.get(element) < time) {
this.addSet.set(element, time);
}
}
// Merge remove set
for (const [element, time] of other.removeSet) {
if (!this.removeSet.has(element) || this.removeSet.get(element) < time) {
this.removeSet.set(element, time);
}
}
// Update logical clock
this.timestamp = Math.max(this.timestamp, other.timestamp);
}
}
// Usage example
const crdt1 = new CRDTSet();
const crdt2 = new CRDTSet();
crdt1.add('Hello');
crdt2.add('World');
crdt1.merge(crdt2);
console.log(crdt1.getElements()); // ['Hello', 'World']Simplified Operational Transformation (OT) Implementation:
// Simplified Operational Transformation (OT) implementation
class TextOperation {
constructor() {
this.ops = []; // Operation sequence
}
insert(position, text) {
this.ops.push({ type: 'insert', position, text });
}
delete(position, length) {
this.ops.push({ type: 'delete', position, length });
}
apply(text) {
let result = text.split('');
// Apply operations in reverse to maintain correct positions
for (let i = this.ops.length - 1; i >= 0; i--) {
const op = this.ops[i];
if (op.type === 'insert') {
result.splice(op.position, 0, op.text);
} else if (op.type === 'delete') {
result.splice(op.position, op.length);
}
}
return result.join('');
}
transform(other) {
const transformedOps = [];
let thisIndex = 0;
let otherIndex = 0;
while (thisIndex < this.ops.length && otherIndex < other.ops.length) {
const thisOp = this.ops[thisIndex];
const otherOp = other.ops[otherIndex];
if (thisOp.type === 'insert' && otherOp.type === 'insert') {
if (thisOp.position <= otherOp.position) {
transformedOps.push(thisOp);
thisIndex++;
} else {
transformedOps.push(otherOp);
otherIndex++;
}
} else if (thisOp.type === 'insert' && otherOp.type === 'delete') {
if (thisOp.position <= otherOp.position) {
transformedOps.push(thisOp);
thisIndex++;
} else {
// Adjust delete position
const adjustedOp = {
type: 'delete',
position: otherOp.position + thisOp.text.length,
length: otherOp.length
};
transformedOps.push(adjustedOp);
thisIndex++;
otherIndex++;
}
}
// Handle other cases...
}
// Add remaining operations
while (thisIndex < this.ops.length) {
transformedOps.push(this.ops[thisIndex++]);
}
while (otherIndex < other.ops.length) {
transformedOps.push(other.ops[otherIndex++]);
}
return new TextOperation().fromArray(transformedOps);
}
fromArray(ops) {
this.ops = ops;
return this;
}
}
// Usage example
const op1 = new TextOperation();
op1.insert(0, 'Hello');
const op2 = new TextOperation();
op2.insert(5, ' World');
// Transform op2 relative to op1
const transformedOp2 = op2.transform(op1);
console.log(transformedOp2.ops); // Should insert " World" at position 5
// Apply transformed operation
const text = '';
const result = transformedOp2.apply(text);
console.log(result); // 'Hello World'Security and Permission Control in Real-Time Applications
Secure WebSocket Implementation:
// Secure WebSocket server with ws library
const WebSocket = require('ws');
const jwt = require('jsonwebtoken');
const wss = new WebSocket.Server({ port: 8080 });
const SECRET_KEY = 'your-secret-key';
// Connection authentication
wss.on('connection', (ws, req) => {
// Extract token from query parameters
const token = req.url.split('token=')[1];
if (!token) {
ws.close(1008, 'Authentication token required');
return;
}
try {
// Verify JWT
const decoded = jwt.verify(token, SECRET_KEY);
ws.user = decoded; // Store user information
console.log('User authenticated:', decoded.username);
// Message handling
ws.on('message', (message) => {
// Check permissions
if (message.startsWith('admin:') && !decoded.roles.includes('admin')) {
ws.send(JSON.stringify({ error: 'Permission denied' }));
return;
}
// Process message...
console.log('Received message:', message);
});
ws.on('close', () => {
console.log('User disconnected:', decoded.username);
});
} catch (err) {
ws.close(1008, 'Invalid token');
}
});
console.log('Secure WebSocket server started on port 8080');Socket.io Permission Control:
// Socket.io permission control implementation
const socketioJwt = require('socketio-jwt');
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
// JWT authentication middleware
io.use(socketioJwt.authorize({
secret: 'your-secret-key',
handshake: true
}));
// Protected namespace
const adminNamespace = io.of('/admin');
adminNamespace.use((socket, next) => {
if (socket.decoded_token.roles.includes('admin')) {
next();
} else {
next(new Error('Admin access required'));
}
});
adminNamespace.on('connection', (socket) => {
console.log('Admin connected:', socket.decoded_token.username);
socket.on('manageUsers', (data) => {
// Handle user management operations
console.log('Admin operation:', data);
});
});
// Regular namespace
io.on('connection', (socket) => {
console.log('User connected:', socket.decoded_token.username);
socket.on('sendMessage', (message) => {
// Broadcast message to all users
io.emit('newMessage', {
user: socket.decoded_token.username,
message
});
});
});Microservices Architecture
Microservices Decomposition and Design Principles
Microservices Decomposition Example:
ecommerce-platform/
├── user-service/ # User service
├── product-service/ # Product service
├── order-service/ # Order service
├── payment-service/ # Payment service
└── api-gateway/ # API gatewayMicroservices Design Principles:
- Single Responsibility Principle: Each service handles one business function
- Independent Deployment: Services can be deployed and scaled independently
- Lightweight Communication: Use REST or gRPC for communication
- Data Autonomy: Each service owns its database
- Fault Tolerance: Loose coupling between services to avoid cascading failures
Inter-Service Communication
RESTful API Communication:
// User service API
const express = require('express');
const app = express();
app.use(express.json());
let users = [
{ id: 1, name: 'John', email: 'john@example.com' }
];
// Get user
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).send('User not found');
res.json(user);
});
// Create user
app.post('/users', (req, res) => {
const user = {
id: users.length + 1,
name: req.body.name,
email: req.body.email
};
users.push(user);
res.status(201).json(user);
});
app.listen(3000, () => {
console.log('User service running at http://localhost:3000');
});
// Order service calling user service
const axios = require('axios');
async function getUser(userId) {
try {
const response = await axios.get(`http://localhost:3000/users/${userId}`);
return response.data;
} catch (error) {
console.error('Failed to get user:', error.message);
throw error;
}
}
// Usage example
getUser(1).then(user => {
console.log('User info:', user);
});gRPC Communication Implementation:
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc CreateUser (CreateUserRequest) returns (UserResponse);
}
message UserRequest {
int32 id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message UserResponse {
int32 id = 1;
string name = 2;
string email = 3;
}// User service (gRPC)
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition).UserService;
const users = [
{ id: 1, name: 'John', email: 'john@example.com' }
];
function getUser(call, callback) {
const user = users.find(u => u.id === call.request.id);
if (!user) {
callback({
code: grpc.status.NOT_FOUND,
message: 'User not found'
});
return;
}
callback(null, user);
}
function createUser(call, callback) {
const user = {
id: users.length + 1,
name: call.request.name,
email: call.request.email
};
users.push(user);
callback(null, user);
}
const server = new grpc.Server();
server.addService(userProto.service, { getUser, createUser });
server.bindAsync(
'0.0.0.0:50051',
grpc.ServerCredentials.createInsecure(),
(err, port) => {
if (err) throw err;
console.log(`gRPC server running on port ${port}`);
server.start();
}
);// Order service (gRPC client)
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition).UserService;
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
function getUser(userId) {
return new Promise((resolve, reject) => {
client.getUser({ id: userId }, (err, response) => {
if (err) {
reject(err);
return;
}
resolve(response);
});
});
}
// Usage example
getUser(1)
.then(user => console.log('User info:', user))
.catch(err => console.error('Failed to get user:', err));Service Discovery and Registration
Consul Service Registration:
// Service registration with Consul
const consul = require('consul')();
const serviceName = 'user-service';
const serviceId = `user-service-${process.pid}`;
const servicePort = 3000;
// Register service
consul.agent.service.register({
name: serviceName,
id: serviceId,
port: servicePort,
check: {
http: `http://localhost:${servicePort}/health`,
interval: '10s',
timeout: '5s'
}
}, (err) => {
if (err) throw err;
console.log(`Service ${serviceName} registered with Consul`);
});
// Health check endpoint
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
app.listen(servicePort, () => {
console.log(`Service running at http://localhost:${servicePort}`);
});
// Service discovery
function discoverService(serviceName) {
consul.agent.service.list((err, services) => {
if (err) throw err;
const service = Object.values(services).find(s => s.Service === serviceName);
if (!service) {
throw new Error(`Service ${serviceName} not found`);
}
return `http://${service.Address}:${service.Port}`;
});
}
// Usage example
discoverService('user-service').then(url => {
console.log('User service address:', url);
});Etcd Service Registration:
// Service registration with Etcd
const Etcd3 = require('etcd3');
const etcd = new Etcd3();
const serviceName = 'payment-service';
const serviceId = `payment-service-${process.pid}`;
const serviceKey = `/services/${serviceName}/${serviceId}`;
const serviceTtl = 10; // TTL in seconds
// Register service
async function registerService() {
// Set initial TTL
await etcd.put(serviceKey).value(JSON.stringify({
name: serviceName,
id: serviceId,
port: 3001
})).ttl(serviceTtl);
// Periodically refresh TTL
setInterval(async () => {
try {
await etcd.keepAlive(serviceKey);
console.log(`Service ${serviceName} heartbeat maintained`);
} catch (err) {
console.error('Failed to maintain service heartbeat:', err);
}
}, serviceTtl * 1000 / 2);
}
registerService();
// Health check endpoint
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
app.listen(3001, () => {
console.log(`Payment service running at http://localhost:3001`);
});Distributed Transactions and Message Consistency
Saga Pattern Implementation:
// Saga coordinator implementation
class SagaCoordinator {
constructor() {
this.sagas = new Map();
}
async execute(sagaId, steps) {
this.sagas.set(sagaId, { steps, current: 0, status: 'started' });
try {
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
await this.executeStep(sagaId, step);
this.sagas.get(sagaId).current = i + 1;
}
this.sagas.get(sagaId).status = 'completed';
console.log(`Saga ${sagaId} completed`);
} catch (error) {
console.error(`Saga ${sagaId} failed:`, error.message);
await this.compensate(sagaId);
this.sagas.get(sagaId).status = 'failed';
}
}
async executeStep(sagaId, step) {
console.log(`Executing step ${step.name}`);
// Call actual service
// Simulate possible failure
if (Math.random() < 0.2) {
throw new Error(`Step ${step.name} failed`);
}
// Simulate delay
await new Promise(resolve => setTimeout(resolve, 100));
}
async compensate(sagaId) {
const saga = this.sagas.get(sagaId);
// Roll back from current step
for (let i = saga.current - 1; i >= 0; i--) {
const step = saga.steps[i];
console.log(`Compensating step ${step.name}`);
// Call compensation operation
// Simulate delay
await new Promise(resolve => setTimeout(resolve, 50));
}
}
}
// Usage example
const coordinator = new SagaCoordinator();
const orderSaga = [
{ name: 'Create order', compensate: 'Cancel order' },
{ name: 'Deduct inventory', compensate: 'Restore inventory' },
{ name: 'Process payment', compensate: 'Refund payment' }
];
coordinator.execute('order-123', orderSaga);Eventual Consistency with Message Queues:
// Achieve eventual consistency using message queues
const amqp = require('amqplib');
async function setupEventHandlers() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const exchange = 'events';
await channel.assertExchange(exchange, 'topic', { durable: true });
// Order service - Publish events
async function createOrder(order) {
// 1. Save order to database
console.log('Saving order:', order);
// 2. Publish order created event
channel.publish(
exchange,
'order.created',
Buffer.from(JSON.stringify({
type: 'order.created',
data: order
}))
);
console.log('Order created event published');
}
// Inventory service - Consume events
await channel.assertQueue('inventory.queue', { durable: true });
channel.bindQueue('inventory.queue', exchange, 'order.created');
channel.consume('inventory.queue', async (msg) => {
if (msg !== null) {
const event = JSON.parse(msg.content.toString());
if (event.type === 'order.created') {
console.log('Processing inventory deduction:', event.data);
// Inventory deduction logic...
}
channel.ack(msg);
}
});
// Payment service - Consume events
await channel.assertQueue('payment.queue', { durable: true });
channel.bindQueue('payment.queue', exchange, 'order.created');
channel.consume('payment.queue', async (msg) => {
if (msg !== null) {
const event = JSON.parse(msg.content.toString());
if (event.type === 'order.created') {
console.log('Processing payment:', event.data);
// Payment logic...
}
channel.ack(msg);
}
});
return { createOrder };
}
setupEventHandlers().then(({ createOrder }) => {
// Simulate order creation
createOrder({ id: 1, userId: 1, amount: 100 });
});Microservices Monitoring and Logging
Prometheus Monitoring Implementation:
// Integrate Prometheus with Express application
const express = require('express');
const promBundle = require('express-prom-bundle');
const metricsMiddleware = promBundle({
includeMethod: true,
includePath: true,
customLabels: { project_name: 'user_service' },
promClient: { collectDefaultMetrics: {} }
});
const app = express();
app.use(metricsMiddleware);
// Business routes
app.get('/users', (req, res) => {
// Simulate business processing
setTimeout(() => {
res.json([{ id: 1, name: 'John' }]);
}, 100);
});
// Start server
app.listen(3000, () => {
console.log('User service running at http://localhost:3000');
console.log('Prometheus metrics endpoint: http://localhost:3000/metrics');
});ELK Logging Integration:
// Integrate ELK with winston and winston-elasticsearch
const winston = require('winston');
require('winston-elasticsearch');
const esTransportOpts = {
level: 'info',
clientOpts: {
node: 'http://localhost:9200',
log: 'trace'
}
};
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Elasticsearch(esTransportOpts)
]
});
// Use logger in business code
logger.info('User service started', { service: 'user-service', pid: process.pid });
logger.error('Database connection failed', { error: 'Connection timeout' });
// Simulate business logs
setInterval(() => {
logger.info('Processing user request', {
userId: Math.floor(Math.random() * 100),
action: 'get_user'
});
}, 5000);Advanced CLI Tool Development
Interactive Command-Line Development
Interactive CLI with Inquirer.js:
const inquirer = require('inquirer');
async function promptUser() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'username',
message: 'Enter username:',
validate: input => input ? true : 'Username cannot be empty'
},
{
type: 'password',
name: 'password',
message: 'Enter password:',
mask: '*'
},
{
type: 'list',
name: 'role',
message: 'Select role:',
choices: ['Administrator', 'Developer', 'Tester'],
filter: val => {
const roles = { 'Administrator': 'admin', 'Developer': 'dev', 'Tester': 'test' };
return roles[val];
}
},
{
type: 'checkbox',
name: 'permissions',
message: 'Select permissions:',
choices: [
{ name: 'Read', value: 'read' },
{ name: 'Write', value: 'write' },
{ name: 'Delete', value: 'delete' }
]
},
{
type: 'confirm',
name: 'confirm',
message: 'Confirm submission?',
default: false
}
]);
console.log('User input:', answers);
}
promptUser();Advanced Interactive Example:
const inquirer = require('inquirer');
async function setupProject() {
// Step 1: Select project type
const { projectType } = await inquirer.prompt([
{
type: 'list',
name: 'projectType',
message: 'Select project type:',
choices: ['Web Application', 'API Service', 'Command-Line Tool']
}
]);
// Step 2: Dynamic questions based on project type
let questions = [
{
type: 'input',
name: 'projectName',
message: 'Enter project name:',
validate: input => /^[a-zA-Z0-9_-]+$/.test(input)
? true
: 'Project name can only contain letters, numbers, underscores, and hyphens'
}
];
if (projectType === 'Web Application') {
questions.push({
type: 'checkbox',
name: 'frameworks',
message: 'Select frontend frameworks:',
choices: ['React', 'Vue', 'Angular']
});
} else if (projectType === 'API Service') {
questions.push({
type: 'list',
name: 'database',
message: 'Select database:',
choices: ['MongoDB', 'MySQL', 'PostgreSQL']
});
}
const answers = await inquirer.prompt(questions);
console.log('Project configuration:', { projectType, ...answers });
// Step 3: Confirm creation
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Confirm project creation?',
default: true
}
]);
if (confirm) {
console.log('Starting project creation...');
// Actual project creation logic...
} else {
console.log('Project creation cancelled');
}
}
setupProject();File Generation and Template Engines
Handlebars Template Engine:
const fs = require('fs');
const handlebars = require('handlebars');
// 1. Read template file
const templateSource = fs.readFileSync('template.hbs', 'utf8');
// 2. Compile template
const template = handlebars.compile(templateSource);
// 3. Prepare data
const data = {
projectName: 'my-awesome-project',
author: 'John Doe',
year: new Date().getFullYear(),
dependencies: ['express', 'lodash', 'axios']
};
// 4. Generate file content
const output = template(data);
// 5. Write to file
fs.writeFileSync('generated/README.md', output);
console.log('File generation completed');EJS Template Engine:
const fs = require('fs');
const ejs = require('ejs');
// 1. Read template file
const templateSource = fs.readFileSync('template.ejs', 'utf8');
// 2. Render template
const output = ejs.render(templateSource, {
projectName: 'my-awesome-cli',
commands: [
{ name: 'init', description: 'Initialize project' },
{ name: 'build', description: 'Build project' },
{ name: 'deploy', description: 'Deploy project' }
]
});
// 3. Write to file
fs.writeFileSync('generated/HELP.md', output);
console.log('Help document generated');Plugin System Design
Extensible CLI Architecture:
my-cli/
├── src/
│ ├── core/ # Core functionality
│ │ ├── cli.js # CLI entry point
│ │ ├── pluginManager.js # Plugin manager
│ │ └── utils.js # Utility functions
│ ├── plugins/ # Built-in plugins
│ │ ├── init/ # Init plugin
│ │ │ └── index.js
│ │ └── config/ # Config plugin
│ │ └── index.js
│ └── index.js # Main entry point
└── package.jsonPlugin Manager Implementation:
// pluginManager.js
class PluginManager {
constructor() {
this.plugins = new Map();
this.hooks = new Map();
}
// Register plugin
register(pluginName, plugin) {
if (this.plugins.has(pluginName)) {
throw new Error(`Plugin ${pluginName} already registered`);
}
this.plugins.set(pluginName, plugin);
console.log(`Plugin ${pluginName} registered successfully`);
// Execute plugin initialization hook
if (typeof plugin.init === 'function') {
plugin.init(this);
}
}
// Execute command
async executeCommand(command, args) {
// Find matching plugin
for (const [name, plugin] of this.plugins) {
if (plugin.commands && plugin.commands[command]) {
console.log(`Executing command ${command} from plugin ${name}`);
return await plugin.commands[command](args);
}
}
throw new Error(`Unknown command: ${command}`);
}
// Register hook
registerHook(hookName, callback) {
if (!this.hooks.has(hookName)) {
this.hooks.set(hookName, []);
}
this.hooks.get(hookName).push(callback);
}
// Trigger hook
async triggerHook(hookName, ...args) {
if (this.hooks.has(hookName)) {
for (const callback of this.hooks.get(hookName)) {
await callback(...args);
}
}
}
}
module.exports = PluginManager;Built-in Plugin Example:
// plugins/init/index.js
module.exports = {
name: 'init',
commands: {
init: async (args) => {
console.log('Initializing project...');
// Actual initialization logic...
return { success: true };
}
},
// Plugin lifecycle hook
init(pluginManager) {
// Register hooks
pluginManager.registerHook('preInit', () => {
console.log('init plugin: Preparing initialization');
});
pluginManager.registerHook('postInit', () => {
console.log('init plugin: Initialization completed');
});
}
};CLI Main Entry Point:
// src/index.js
const PluginManager = require('./core/pluginManager');
const path = require('path');
async function main() {
const pluginManager = new PluginManager();
// Load built-in plugins
await loadBuiltinPlugins(pluginManager);
// Load external plugins
await loadExternalPlugins(pluginManager);
// Parse command-line arguments
const { command, args } = parseArgs(process.argv.slice(2));
// Execute command
try {
await pluginManager.triggerHook('preCommand', command, args);
const result = await pluginManager.executeCommand(command, args);
await pluginManager.triggerHook('postCommand', command, args, result);
if (result && result.success) {
process.exit(0);
} else {
process.exit(1);
}
} catch (error) {
console.error(`Command execution failed: ${error.message}`);
await pluginManager.triggerHook('commandError', command, args, error);
process.exit(1);
}
}
// Load built-in plugins
async function loadBuiltinPlugins(pluginManager) {
const builtinPlugins = [
require('../plugins/init'),
require('../plugins/config')
];
for (const plugin of builtinPlugins) {
pluginManager.register(plugin.name, plugin);
}
}
// Load external plugins (from node_modules or specified directory)
async function loadExternalPlugins(pluginManager) {
// Example: Load from globally installed plugins
try {
const myPlugin = require('my-cli-plugin');
pluginManager.register('my-plugin', myPlugin);
console.log('External plugin my-cli-plugin loaded successfully');
} catch (err) {
console.log('External plugin my-cli-plugin not found');
}
}
// Simple command-line argument parsing
function parseArgs(args) {
if (args.length === 0) {
return { command: 'help', args: [] };
}
return {
command: args[0],
args: args.slice(1)
};
}
// Start CLI
main();Global and Local Installation Implementation
package.json Configuration:
{
"name": "my-cli",
"version": "1.0.0",
"bin": {
"mycli": "./bin/mycli.js"
},
"preferGlobal": true,
"dependencies": {
"chalk": "^4.1.2",
"commander": "^9.4.1",
"inquirer": "^8.2.4",
"winston": "^3.8.2"
},
"devDependencies": {
"eslint": "^8.30.0",
"jest": "^29.3.1"
}
}Global Installation Script (bin/mycli.js):
#!/usr/bin/env node
const path = require('path');
const { program } = require('commander');
const chalk = require('chalk');
const pluginManager = require('../src/core/pluginManager');
// Set CLI basic information
program
.name('mycli')
.description('A powerful command-line tool')
.version('1.0.0');
// Register built-in commands
program
.command('init <project-name>')
.description('Initialize new project')
.action(async (projectName) => {
console.log(chalk.blue(`Initializing project: ${projectName}`));
// Actual initialization logic...
});
program
.command('config [key] [value]')
.description('Manage configuration')
.action((key, value) => {
console.log(chalk.green(`Configuration operation: key=${key}, value=${value}`));
// Actual configuration logic...
});
// Plugin commands will be dynamically registered
// Parse command-line arguments
program.parse(process.argv);
// Show help if no command provided
if (process.argv.length < 3) {
program.help();
}
// Dynamically load plugins and register commands
(async () => {
// Initialize plugin manager
const pluginManagerInstance = new PluginManager();
// Load plugins (same as previous implementation)
await loadBuiltinPlugins(pluginManagerInstance);
await loadExternalPlugins(pluginManagerInstance);
// Dynamically register plugin commands with Commander
pluginManagerInstance.plugins.forEach((plugin, name) => {
if (plugin.commands) {
for (const [commandName, commandFn] of Object.entries(plugin.commands)) {
program
.command(commandName)
.description(`Command provided by plugin ${name}`)
.action(async (args) => {
try {
await commandFn(args);
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
});
}
}
});
})();Local Installation Usage Example:
// Use as local dependency in a project
const mycli = require('my-cli');
// Or run directly with npx
// npx mycli init my-projectCLI Tool Performance Optimization
Caching Mechanism Implementation:
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
class CacheManager {
constructor(cacheDir = '.mycli-cache') {
this.cacheDir = path.join(process.cwd(), cacheDir);
this.ensureCacheDir();
}
ensureCacheDir() {
if (!fs.existsSync(this.cacheDir)) {
fs.mkdirSync(this.cacheDir, { recursive: true });
}
}
getCacheKey(input) {
return crypto.createHash('md5').update(JSON.stringify(input)).digest('hex');
}
async getFromCache(key) {
const cacheFile = path.join(this.cacheDir, `${key}.json`);
if (fs.existsSync(cacheFile)) {
const data = fs.readFileSync(cacheFile, 'utf8');
return JSON.parse(data);
}
return null;
}
async saveToCache(key, data) {
const cacheFile = path.join(this.cacheDir, `${key}.json`);
fs.writeFileSync(cacheFile, JSON.stringify(data), 'utf8');
}
async cachedOperation(key, operationFn) {
// Try to get from cache
const cachedData = await this.getFromCache(key);
if (cachedData) {
console.log('Reading data from cache');
return cachedData;
}
// Perform actual operation
console.log('Performing actual operation...');
const result = await operationFn();
// Save to cache
await this.saveToCache(key, result);
return result;
}
}
// Usage example
(async () => {
const cache = new CacheManager();
const expensiveOperation = async () => {
// Simulate expensive operation
await new Promise(resolve => setTimeout(resolve, 2000));
return { data: 'Result of expensive operation' };
};
const result = await cache.cachedOperation(
'expensive-op-123', // Cache key
expensiveOperation
);
console.log('Result:', result);
})();Parallel Processing Optimization:
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
const pLimit = require('p-limit');
// Concurrency control example
async function processFiles(files) {
// Limit concurrency to 4
const limit = pLimit(4);
const tasks = files.map(file =>
limit(async () => {
console.log(`Starting to process file: ${file}`);
// Simulate file processing (e.g., code formatting, testing)
await exec(`eslint --fix ${file}`);
console.log(`Finished processing file: ${file}`);
})
);
await Promise.all(tasks);
console.log('All files processed');
}
// Usage example
processFiles(['file1.js', 'file2.js', 'file3.js', 'file4.js', 'file5.js']);Incremental Build Optimization:
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
class IncrementalBuilder {
constructor(srcDir, distDir) {
this.srcDir = srcDir;
this.distDir = distDir;
this.manifest = this.loadManifest();
}
loadManifest() {
const manifestFile = path.join(this.distDir, '.build-manifest.json');
if (fs.existsSync(manifestFile)) {
return JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
}
return {};
}
saveManifest() {
const manifestFile = path.join(this.distDir, '.build-manifest.json');
fs.writeFileSync(manifestFile, JSON.stringify(this.manifest), 'utf8');
}
getFileHash(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
return crypto.createHash('md5').update(content).digest('hex');
}
async buildFile(srcFile) {
const relativePath = path.relative(this.srcDir, srcFile);
const destFile = path.join(this.distDir, relativePath);
// Ensure destination directory exists
fs.mkdirSync(path.dirname(destFile), { recursive: true });
// Calculate source file hash
const currentHash = this.getFileHash(srcFile);
// Check if rebuild is needed
if (this.manifest[relativePath] === currentHash) {
console.log(`Skipping unchanged file: ${relativePath}`);
return;
}
// Perform build (simple file copy as example)
fs.copyFileSync(srcFile, destFile);
console.log(`Building file: ${relativePath}`);
// Update manifest
this.manifest[relativePath] = currentHash;
}
async build() {
const files = this.getAllSourceFiles();
for (const file of files) {
await this.buildFile(file);
}
this.saveManifest();
console.log('Build completed');
}
getAllSourceFiles() {
const files = [];
this.walkDir(this.srcDir, files);
return files;
}
walkDir(dir, files) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
this.walkDir(fullPath, files);
} else if (entry.isFile() && path.extname(entry.name) === '.js') {
files.push(fullPath);
}
}
}
}
// Usage example
(async () => {
const builder = new IncrementalBuilder('./src', './dist');
await builder.build();
})();Performance Monitoring and Analysis:
const { performance, PerformanceObserver } = require('perf_hooks');
// Performance monitoring setup
function setupPerformanceMonitoring() {
const obs = new PerformanceObserver((items) => {
items.getEntries().forEach((entry) => {
console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
});
});
obs.observe({ entryTypes: ['measure'] });
// Mark key stages
performance.mark('start-cli');
}
// Record performance at key CLI points
function trackCliPerformance() {
setupPerformanceMonitoring();
// Simulate CLI startup process
performance.mark('cli-init-start');
// Initialization logic...
performance.mark('cli-init-end');
performance.measure('CLI Initialization', 'cli-init-start', 'cli-init-end');
performance.mark('command-parse-start');
// Command parsing logic...
performance.mark('command-parse-end');
performance.measure('Command Parsing', 'command-parse-start', 'command-parse-end');
performance.mark('plugin-load-start');
// Plugin loading logic...
performance.mark('plugin-load-end');
performance.measure('Plugin Loading', 'plugin-load-start', 'plugin-load-end');
// End mark
performance.mark('end-cli');
performance.measure('Total Execution Time', 'start-cli', 'end-cli');
}
// Call in CLI main entry point
trackCliPerformance();Through the above implementations, we have built a feature-rich, performance-optimized Node.js command-line tool framework that supports plugin extensions, caching mechanisms, parallel processing, and incremental builds. Developers can quickly create their own CLI tools based on this framework and extend functionality or optimize performance as needed.



