#!/usr/bin/env node /** * Simple ES-module → IIFE bundler for GrechkaCraft * Resolves import/export, wraps in single IIFE, no external deps. * Usage: node build.js > game.js */ const fs = require('fs'); const path = require('path'); const SRC = path.join(__dirname, 'src'); const ENTRY = 'main.js'; const visited = new Set(); const chunks = []; const fileExports = {}; const fileImports = {}; function resolveImportPath(fromPath, importerDir) { let resolved = path.resolve(importerDir, fromPath); if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.js')) { resolved += '.js'; } return resolved; } function collectExports(content) { const exports = []; const re1 = /export\s*\{([^}]+)\}/g; let m; while ((m = re1.exec(content)) !== null) { const names = m[1].split(',').map(s => { const parts = s.trim().split(/\s+as\s+/); return { local: parts[0].trim(), exported: (parts[1] || parts[0]).trim() }; }); exports.push(...names); } const re2 = /export\s+(?:async\s+)?(?:const|let|var|function|class)\s+(\w+)/g; while ((m = re2.exec(content)) !== null) { exports.push({ local: m[1], exported: m[1] }); } if (/export\s+default\s+/.test(content)) { exports.push({ local: '__default__', exported: 'default' }); } return exports; } function collectImports(content) { const imports = []; const re = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g; let m; while ((m = re.exec(content)) !== null) { const names = m[1].split(',').map(s => { const parts = s.trim().split(/\s+as\s+/); return { local: (parts[1] || parts[0]).trim(), imported: parts[0].trim() }; }); imports.push({ names, from: m[2] }); } const re2 = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g; while ((m = re2.exec(content)) !== null) { imports.push({ names: [{ local: m[1], imported: 'default' }], from: m[2] }); } return imports; } function processFile(filePath) { const relPath = path.relative(SRC, filePath); if (visited.has(relPath)) return; visited.add(relPath); let content = fs.readFileSync(filePath, 'utf8'); const dir = path.dirname(filePath); fileExports[relPath] = collectExports(content); fileImports[relPath] = collectImports(content); for (const imp of fileImports[relPath]) { const depPath = resolveImportPath(imp.from, dir); processFile(depPath); } // Strip import statements content = content.replace(/import\s*\{[^}]+\}\s*from\s*['"][^'"]+['"];?/g, ''); content = content.replace(/import\s+\w+\s+from\s*['"][^'"]+['"];?/g, ''); // Strip export keyword but keep declarations content = content.replace(/export\s+default\s+/, 'const __default__ = '); content = content.replace(/export\s+(?:async\s+)?(const|let|var|function|class)\s/g, '$1 '); content = content.replace(/export\s*\{[^}]*\};?/g, ''); chunks.push({ relPath, content }); } processFile(path.join(SRC, ENTRY)); let output = '// GrechkaCraft — auto-bundled from ES modules\n'; output += '(function() {\n'; output += '"use strict";\n\n'; for (const chunk of chunks) { output += `// === ${chunk.relPath} ===\n`; output += chunk.content.trim(); output += '\n\n'; } output += '})();\n'; process.stdout.write(output);