86 lines
3.2 KiB
JavaScript
86 lines
3.2 KiB
JavaScript
/* ============================================================
|
|
build.mjs — zero-dependency bundler + inliner.
|
|
Walks the ES-module graph from src/main.js, wraps each module
|
|
in a tiny require() shim (constrained to the named-import /
|
|
named-export subset this project uses), inlines the CSS, and
|
|
writes a single self-contained, file://-openable
|
|
bubble_chamber.html.
|
|
|
|
Run: node build.mjs
|
|
============================================================ */
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const ROOT = path.dirname(new URL(import.meta.url).pathname);
|
|
const ENTRY = 'src/main.js';
|
|
|
|
const IMPORT_RE = /^\s*import\s*\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]\s*;?\s*$/;
|
|
const EXPORT_RE = /^\s*export\s+(function|const|let|var|class)\s+([A-Za-z0-9_$]+)/;
|
|
|
|
const modules = new Map(); // id -> { code }
|
|
const order = [];
|
|
|
|
function resolve(fromId, spec) {
|
|
const dir = path.posix.dirname(fromId);
|
|
let id = path.posix.normalize(path.posix.join(dir, spec));
|
|
if (!id.endsWith('.js')) id += '.js';
|
|
return id;
|
|
}
|
|
|
|
function load(id) {
|
|
if (modules.has(id)) return;
|
|
const src = readFileSync(path.join(ROOT, id), 'utf8');
|
|
const exports = [];
|
|
const deps = [];
|
|
const lines = src.split('\n').map((line) => {
|
|
const imp = line.match(IMPORT_RE);
|
|
if (imp) {
|
|
const names = imp[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
const depId = resolve(id, imp[2]);
|
|
deps.push(depId);
|
|
return `const { ${names.join(', ')} } = __require(${JSON.stringify(depId)});`;
|
|
}
|
|
const exp = line.match(EXPORT_RE);
|
|
if (exp) {
|
|
exports.push(exp[2]);
|
|
return line.replace(/^\s*export\s+/, '');
|
|
}
|
|
return line;
|
|
});
|
|
let code = lines.join('\n');
|
|
if (exports.length) code += `\nObject.assign(exports, { ${exports.join(', ')} });\n`;
|
|
modules.set(id, { code });
|
|
for (const d of deps) load(d); // depth-first; registry handles cycles/order
|
|
order.push(id);
|
|
}
|
|
|
|
load(ENTRY);
|
|
|
|
let bundle = `(function () {\n`;
|
|
bundle += ` const __cache = {};\n const __reg = {};\n`;
|
|
bundle += ` function __require(id) {\n if (__cache[id]) return __cache[id].exports;\n` +
|
|
` const module = { exports: {} }; __cache[id] = module;\n` +
|
|
` __reg[id](module, module.exports, __require);\n return module.exports;\n }\n`;
|
|
for (const id of [...modules.keys()]) {
|
|
bundle += ` __reg[${JSON.stringify(id)}] = function (module, exports, __require) {\n`;
|
|
bundle += modules.get(id).code + '\n';
|
|
bundle += ` };\n`;
|
|
}
|
|
bundle += ` __require(${JSON.stringify(ENTRY)});\n})();\n`;
|
|
|
|
// inline into index.html
|
|
let html = readFileSync(path.join(ROOT, 'index.html'), 'utf8');
|
|
const css = readFileSync(path.join(ROOT, 'src/style.css'), 'utf8');
|
|
html = html.replace(
|
|
/<link rel="stylesheet" href="src\/style\.css">/,
|
|
`<style>\n${css}\n</style>`
|
|
);
|
|
html = html.replace(
|
|
/<script type="module" src="src\/main\.js"><\/script>/,
|
|
`<script>\n${bundle}</script>`
|
|
);
|
|
html = html.replace('Parametric Generator · v3', 'Parametric Generator · v3 (built)');
|
|
|
|
writeFileSync(path.join(ROOT, 'bubble_chamber.html'), html);
|
|
console.log(`built bubble_chamber.html — ${modules.size} modules, ${(html.length / 1024).toFixed(1)} KB`);
|