Compare commits
3 Commits
f14b61d7d9
...
2a5f65d393
| Author | SHA1 | Date |
|---|---|---|
|
|
2a5f65d393 | |
|
|
8eebf378ab | |
|
|
81f6a0055a |
|
|
@ -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);
|
||||||
Loading…
Reference in New Issue