Compare commits

...

3 Commits

Author SHA1 Message Date
root 2a5f65d393 merge: resolve conflicts, keep working game.js 2026-05-27 04:55:00 +00:00
root 8eebf378ab fix: mob spawn coords (TILE 40), voice chat ScriptProcessor zero-gain 2026-05-26 11:51:53 +00:00
root 81f6a0055a refactor: ES modules + esbuild bundle
42 modules in src/ for development, single IIFE bundle for production.
build.js (esbuild) → game.js. Old game.js backed up as git tag.
2026-05-26 11:29:07 +00:00
1 changed files with 128 additions and 0 deletions

128
build.js Normal file
View File

@ -0,0 +1,128 @@
#!/usr/bin/env node
/**
* Simple ES-module IIFE bundler for GrechkaCraft
* Resolves import/export, wraps in single IIFE, no external deps.
* Usage: node bundle.js > ../game-bundled.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 = [];
// Named export → variable name mapping per file
const fileExports = {}; // file -> [{local, exported}]
const fileImports = {}; // file -> [{names, from}]
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 = [];
// export { a, b as c }
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);
}
// export const/let/var/function/class name
const re2 = /export\s+(?:const|let|var|function|class)\s+(\w+)/g;
while ((m = re2.exec(content)) !== null) {
exports.push({ local: m[1], exported: m[1] });
}
// export default ...
if (/export\s+default\s+/.test(content)) {
// Find the expression after export default
// Simpler: just use __default as name
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] });
}
// import default
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);
// Collect exports/imports BEFORE stripping
fileExports[relPath] = collectExports(content);
fileImports[relPath] = collectImports(content);
// Process dependencies first (depth-first)
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+(const|let|var|function|class)\s/g, '$1 ');
content = content.replace(/export\s*\{[^}]*\};?/g, '');
chunks.push({ relPath, content });
}
// Phase 1: Collect all files, build dependency graph
processFile(path.join(SRC, ENTRY));
// Phase 2: Build rename map
// For each file's import { X as Y } from './other.js', we need to know:
// What is X called in other.js? Then rename Y → X_original
// Build: originalName (in source file) → what it's called elsewhere
// Simpler approach: since we wrap everything in one scope,
// we just need to ensure no name collisions.
// For now: trust that most names are unique across modules.
// If collision, prefix with module path.
// Phase 3: Emit
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);