WebGL-MCP Server
by grokadegames
Verified
import { promises as fs } from 'fs';
import * as path from 'path';
import * as zlib from 'zlib';
import { promisify } from 'util';
const gzip = promisify(zlib.gzip);
export interface BuildAnalysis {
totalSize: number;
compressedSize: number;
files: BuildFileAnalysis[];
suggestions: string[];
templateUsed?: string;
templateConfig?: {
scaleToFit?: boolean;
optimizeForPixelArt?: boolean;
hasLoadingBar?: boolean;
mobileOptimized?: boolean;
};
}
export interface BuildFileAnalysis {
path: string;
size: number;
compressedSize: number;
type: string;
suggestions: string[];
}
export class WebGLBuildAnalyzer {
async analyzeBuild(buildPath: string): Promise<BuildAnalysis> {
const analysis: BuildAnalysis = {
totalSize: 0,
compressedSize: 0,
files: [],
suggestions: []
};
try {
const files = await this.getAllFiles(buildPath);
for (const file of files) {
const fileAnalysis = await this.analyzeFile(file, buildPath);
analysis.files.push(fileAnalysis);
analysis.totalSize += fileAnalysis.size;
analysis.compressedSize += fileAnalysis.compressedSize;
}
// Check for HTML template usage
const indexFile = files.find(f => path.basename(f).toLowerCase() === 'index.html');
if (indexFile) {
const htmlAnalysis = await this.analyzeHTMLTemplate(indexFile);
analysis.templateUsed = htmlAnalysis.templateName;
analysis.templateConfig = htmlAnalysis.config;
// Add template-specific suggestions
if (htmlAnalysis.suggestions.length > 0) {
analysis.suggestions.push(...htmlAnalysis.suggestions);
}
}
// Analyze overall build
this.analyzeBuildStructure(analysis);
} catch (error) {
console.error('Error analyzing build:', error);
throw error;
}
return analysis;
}
private async getAllFiles(dirPath: string): Promise<string[]> {
const files: string[] = [];
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
files.push(...await this.getAllFiles(fullPath));
} else {
files.push(fullPath);
}
}
return files;
}
private async analyzeHTMLTemplate(filePath: string): Promise<{
templateName: string;
config: {
scaleToFit?: boolean;
optimizeForPixelArt?: boolean;
hasLoadingBar?: boolean;
mobileOptimized?: boolean;
};
suggestions: string[];
}> {
const content = await fs.readFile(filePath, 'utf8');
const suggestions: string[] = [];
let templateName = 'Unknown';
const config: {
scaleToFit?: boolean;
optimizeForPixelArt?: boolean;
hasLoadingBar?: boolean;
mobileOptimized?: boolean;
} = {};
// Check for Better Minimal WebGL Template
if (content.includes('BetterMinimal') ||
(content.includes('scaleToFit') && content.includes('data-pixel-art'))) {
templateName = 'Better Minimal WebGL Template';
// Check for scaling configuration
config.scaleToFit = !content.includes('scaleToFit = false');
// Check for pixel art optimization
if (content.includes('data-pixel-art="true"')) {
config.optimizeForPixelArt = true;
}
// Check for loading bar
if (content.includes('progressHandler') && content.includes('linear-gradient')) {
config.hasLoadingBar = true;
}
// Check for mobile optimization
if (content.includes('iPhone|iPad|iPod|Android')) {
config.mobileOptimized = true;
}
// Generate template-specific suggestions
if (!config.scaleToFit) {
suggestions.push('Consider enabling scale-to-fit for better user experience on different screen sizes.');
}
if (!config.hasLoadingBar) {
suggestions.push('Enable the loading bar to provide visual feedback during asset loading.');
}
if (!config.mobileOptimized) {
suggestions.push('Enable mobile optimizations for better performance on mobile devices.');
}
} else if (content.includes('UnityLoader') || content.includes('unityInstance')) {
templateName = 'Unity Default Template';
suggestions.push('Consider using the Better Minimal WebGL Template for improved scaling and mobile support.');
suggestions.push('The Better Minimal WebGL Template provides better canvas scaling and improved mobile device support.');
}
return { templateName, config, suggestions };
}
private async analyzeFile(filePath: string, buildPath: string): Promise<BuildFileAnalysis> {
const content = await fs.readFile(filePath);
const compressedContent = await gzip(content);
const relativePath = path.relative(buildPath, filePath);
const extension = path.extname(filePath).toLowerCase();
const analysis: BuildFileAnalysis = {
path: relativePath,
size: content.length,
compressedSize: compressedContent.length,
type: this.getFileType(extension),
suggestions: []
};
// Analyze specific file types
switch (analysis.type) {
case 'texture':
this.analyzeTexture(analysis, content);
break;
case 'shader':
this.analyzeShader(analysis, content);
break;
case 'javascript':
this.analyzeJavaScript(analysis, content);
break;
}
return analysis;
}
private getFileType(extension: string): string {
switch (extension) {
case '.jpg':
case '.jpeg':
case '.png':
case '.webp':
case '.gif':
return 'texture';
case '.glsl':
case '.vert':
case '.frag':
return 'shader';
case '.js':
return 'javascript';
case '.wasm':
return 'webassembly';
case '.html':
return 'html';
case '.css':
return 'stylesheet';
case '.json':
return 'data';
default:
return 'other';
}
}
private analyzeTexture(analysis: BuildFileAnalysis, content: Buffer): void {
// Check texture size - large textures may need optimization
if (content.length > 1024 * 1024) {
analysis.suggestions.push(`Large texture (${(content.length / (1024 * 1024)).toFixed(2)} MB). Consider using compression or smaller textures.`);
}
// Check compression savings
const compressionRatio = analysis.size / analysis.compressedSize;
if (compressionRatio < 1.2) {
analysis.suggestions.push('Texture has low compression ratio. Consider using WebP format for better compression.');
}
}
private analyzeShader(analysis: BuildFileAnalysis, content: Buffer): void {
const shaderText = content.toString('utf8');
// Check for potential shader optimizations
if (shaderText.includes('pow(') || shaderText.includes('exp(') || shaderText.includes('log(')) {
analysis.suggestions.push('Shader uses expensive operations (pow, exp, log). Consider optimizing for better performance.');
}
// Check for precision qualifiers
if (!shaderText.includes('precision ')) {
analysis.suggestions.push('Shader missing precision qualifiers. Define precision for better performance and consistency.');
}
}
private analyzeJavaScript(analysis: BuildFileAnalysis, content: Buffer): void {
const jsText = content.toString('utf8');
// Check for large JS files
if (content.length > 5 * 1024 * 1024) {
analysis.suggestions.push(`Large JavaScript file (${(content.length / (1024 * 1024)).toFixed(2)} MB). Consider code splitting or minification.`);
}
// Check for WebGL context creation
if (jsText.includes('getContext("webgl")') && !jsText.includes('getContext("webgl2")')) {
analysis.suggestions.push('Using WebGL 1.0. Consider upgrading to WebGL 2.0 for better performance and features.');
}
// Check for memory management issues
if (jsText.includes('new Uint8Array(') || jsText.includes('new Float32Array(')) {
if (!jsText.includes('delete') && !jsText.includes('dispose')) {
analysis.suggestions.push('Creating typed arrays without apparent cleanup. Watch for memory leaks.');
}
}
}
private analyzeBuildStructure(analysis: BuildAnalysis): void {
// Group files by type
const fileTypes = analysis.files.reduce((types, file) => {
types[file.type] = (types[file.type] || 0) + file.size;
return types;
}, {} as Record<string, number>);
// Check total size
if (analysis.totalSize > 100 * 1024 * 1024) {
analysis.suggestions.push(`Large build size (${(analysis.totalSize / (1024 * 1024)).toFixed(2)} MB). Consider optimizing assets and code splitting.`);
}
// Check texture usage
if (fileTypes['texture'] && fileTypes['texture'] > analysis.totalSize * 0.5) {
analysis.suggestions.push(`Textures account for over 50% of build size. Consider using texture compression or lower resolution textures.`);
}
// Check JavaScript size
if (fileTypes['javascript'] && fileTypes['javascript'] > 20 * 1024 * 1024) {
analysis.suggestions.push(`Large JavaScript size (${(fileTypes['javascript'] / (1024 * 1024)).toFixed(2)} MB). Consider code splitting and tree shaking.`);
}
// Check WebAssembly usage
if (!fileTypes['webassembly']) {
analysis.suggestions.push('No WebAssembly detected. Consider using WebAssembly for performance-critical code.');
}
// Template-specific optimization suggestions
if (analysis.templateUsed === 'Better Minimal WebGL Template') {
analysis.suggestions.push('Using Better Minimal WebGL Template. Good choice for optimal WebGL performance and compatibility.');
} else if (analysis.templateUsed === 'Unknown') {
analysis.suggestions.push('Using an unknown HTML template. Consider using Better Minimal WebGL Template for optimal WebGL performance.');
}
}
}