grechka-game/build.js

107 lines
3.2 KiB
JavaScript

#!/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);