/* 스프레드시트 D 시험 번들 도우미 — TS 를 그 자리에서 CommonJS 로 풀어 모음. * 남의 몫 파일이 아직 빈 몸(「아직 없음」)이면 spreadsheet_d_fakes.cjs 의 가짜로 바꿔 끼움. * load(entry) Node 안에서 모듈을 돌려 exports 를 줌(pytest 도우미가 씀) * node 이 파일 harness 브라우저 시험 틀 번들 → tmp/spreadsheet_d/harness.js (git 밖) * `.cjs` = 루트 package.json 의 "type":"module" 과 무관하게 CommonJS. */ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", "..", ".."); const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); const SS = path.join(ROOT, "A00_Common", "spreadsheet"); const FAKES = path.join(__dirname, "spreadsheet_d_fakes.cjs"); const isStub = (file) => fs.readFileSync(file, "utf8").includes("아직 없음("); /** 가져오기 글 → 모듈 id(파일 경로 · `fake:<이름>` · `css:<경로>`) */ function resolve(spec, from) { if (spec === "@ui/ui_template_elements") return "fake:el"; let abs; if (spec.startsWith("@ui/")) abs = path.join(ROOT, "ui_template", spec.slice(4)); else if (spec.startsWith(".")) abs = path.resolve(path.dirname(from), spec); else throw new Error(`모르는 가져오기: ${spec} (${from})`); if (/\.(css|json|cjs)$/.test(abs)) return (abs.endsWith(".css") ? "css:" : "") + abs; if (!abs.endsWith(".ts")) abs += ".ts"; if (path.dirname(abs) === SS && isStub(abs)) return "fake:" + path.basename(abs, ".ts").replace(/^spreadsheet_/, ""); return abs; } /** 입구부터 딸린 모듈 전부 → id → {code, deps} */ function collect(entry, stubs = []) { const mods = new Map(); const walk = (id) => { if (mods.has(id)) return; let code; if (id.startsWith("fake:")) { const name = id.slice(5); if (name !== "el") stubs.push(name); code = `module.exports = require("${FAKES.replace(/\\/g, "/")}")[${JSON.stringify(name)}];`; mods.set(id, { code, deps: { [FAKES.replace(/\\/g, "/")]: FAKES } }); walk(FAKES); return; } if (id.startsWith("css:")) { const css = fs.readFileSync(id.slice(4), "utf8"); code = `if (typeof document !== "undefined") { const s = document.createElement("style"); s.textContent = ${JSON.stringify(css)}; document.head.append(s); }`; mods.set(id, { code, deps: {} }); return; } const src = fs.readFileSync(id, "utf8"); if (id.endsWith(".json")) code = `module.exports = ${src};`; else if (id.endsWith(".ts")) code = ts.transpileModule(src, { fileName: id, compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, }).outputText; else code = src; const deps = {}; mods.set(id, { code, deps }); for (const m of code.matchAll(/require\("([^"]+)"\)/g)) { const dep = m[1] === FAKES.replace(/\\/g, "/") ? FAKES : resolve(m[1], id); deps[m[1]] = dep; walk(dep); } }; walk(entry); return mods; } /** Node 안에서 돌림 */ function load(entry) { const abs = path.resolve(entry); const id = resolve("./" + path.basename(abs).replace(/\.ts$/, ""), abs); // 빈 몸이면 가짜 const mods = collect(id); const cache = new Map(); const req = (id) => { if (cache.has(id)) return cache.get(id).exports; const m = mods.get(id); const module = { exports: {} }; cache.set(id, module); new Function("exports", "module", "require", m.code)(module.exports, module, (s) => req(m.deps[s]), ); return module.exports; }; return req(id); } /** 브라우저 한 파일 */ function bundle(entry, out) { const stubs = []; const mods = collect(path.resolve(entry), stubs); const ids = [...mods.keys()]; const defs = ids .map((id, i) => { const m = mods.get(id); const map = Object.fromEntries(Object.entries(m.deps).map(([s, d]) => [s, ids.indexOf(d)])); return `[${JSON.stringify(map)}, function (exports, module, require) {\n${m.code}\n}]`; }) .join(",\n"); const text = `(function () { const defs = [${defs}]; const cache = []; function req(i) { if (cache[i]) return cache[i].exports; const module = (cache[i] = { exports: {} }); defs[i][1](module.exports, module, (s) => req(defs[i][0][s])); return module.exports; } window.__ssFakes = ${JSON.stringify([...new Set(stubs)])}; req(${ids.indexOf(path.resolve(entry))}); })();\n`; fs.mkdirSync(path.dirname(out), { recursive: true }); fs.writeFileSync(out, text); return { out, modules: ids.length, fakes: [...new Set(stubs)] }; } module.exports = { load, bundle, ROOT, SS }; if (require.main === module && process.argv[2] === "harness") { const r = bundle( path.join(__dirname, "harness_spreadsheet_d.cjs"), path.join(ROOT, "tmp", "spreadsheet_d", "harness.js"), ); console.log(JSON.stringify(r)); }