diff --git a/resources/tester/helper_107_ford_free.cjs b/resources/tester/helper_107_ford_free.cjs index 5ee26f04f..e780fdded 100644 --- a/resources/tester/helper_107_ford_free.cjs +++ b/resources/tester/helper_107_ford_free.cjs @@ -3,34 +3,19 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); // 화면 전용 모듈(토스트 · 글) — 기하 계산엔 안 쓰임 const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; @@ -38,11 +23,8 @@ function loadTs(file) { if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_108_drag_box_hit.cjs b/resources/tester/helper_108_drag_box_hit.cjs index f513ccd12..b2ebf8c0e 100644 --- a/resources/tester/helper_108_drag_box_hit.cjs +++ b/resources/tester/helper_108_drag_box_hit.cjs @@ -6,10 +6,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); /** 가로챌 모듈 — 아래에서 채운다(창 모듈을 부르기 전에). */ let STUBS = {}; @@ -74,23 +73,15 @@ globalThis.document = { globalThis.window = { addEventListener: () => {}, removeEventListener: () => {} }; globalThis.requestAnimationFrame = (fn) => fn(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.startsWith("@util/")) return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`)); return loadTs(path.join(path.dirname(full), `${request}.ts`)); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const B06 = (name) => path.join(ROOT, "B06_Section", `${name}.ts`); const base = loadTs(B06("B06_Section_UI_Cross_Panel_Base")); diff --git a/resources/tester/helper_115_8_corridor_loft.cjs b/resources/tester/helper_115_8_corridor_loft.cjs index 4d89e3ba9..5102f7cf0 100644 --- a/resources/tester/helper_115_8_corridor_loft.cjs +++ b/resources/tester/helper_115_8_corridor_loft.cjs @@ -6,7 +6,7 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); -const ts = require(path.join(NM, "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const THREE = require(path.join(NM, "three")); const LIBS = { three: THREE, @@ -14,26 +14,18 @@ const LIBS = { // delaunator 는 ESM 전용 — Node 22 require(esm) 가 내주는 이름공간(default 포함). delaunator: require(path.join(NM, "delaunator", "index.js")), // three 부록(ESM 전용) — Node 22 require(esm). - "three/examples/jsm/utils/BufferGeometryUtils.js": require(path.join(NM, "three", "examples", "jsm", "utils", "BufferGeometryUtils.js")), + "three/examples/jsm/utils/BufferGeometryUtils.js": require( + path.join(NM, "three", "examples", "jsm", "utils", "BufferGeometryUtils.js"), + ), "@kninnug/constrainautor": { __esModule: true, default: require(path.join(NM, "@kninnug", "constrainautor", "lib", "Constrainautor.cjs")), }, }; -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (LIBS[request]) return LIBS[request]; if (request.startsWith("@ui/") || request.startsWith("@config/")) { return new Proxy({}, { get: () => () => undefined }); @@ -43,10 +35,8 @@ function loadTs(file) { : path.join(path.dirname(full), request); if (request.endsWith(".json")) return JSON.parse(fs.readFileSync(target, "utf8")); return loadTs(fs.existsSync(`${target}.ts`) ? `${target}.ts` : path.join(target, "index.ts")); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const B05 = (name) => path.join(ROOT, "B05_Profile", `${name}.ts`); const { buildCorridor } = loadTs(B05("B05_Profile_UI_Corridor_Build")); @@ -179,7 +169,10 @@ function frameOf(bounds, x0, x1, y0, y1) { const x = p[0] + ox; const y = -p[2] + oy; return ( - Math.abs(x - x0) < 1e-3 || Math.abs(x - x1) < 1e-3 || Math.abs(y - y0) < 1e-3 || Math.abs(y - y1) < 1e-3 + Math.abs(x - x0) < 1e-3 || + Math.abs(x - x1) < 1e-3 || + Math.abs(y - y0) < 1e-3 || + Math.abs(y - y1) < 1e-3 ); }; return { boundary: (a, b) => on(a) && on(b) }; @@ -281,7 +274,6 @@ function lostVertices(tris, before, out) { return lost; } - const results = {}; /** 사면(구역 색) 삼각형 가로세로비(가장 긴 변² / 2 · 넓이) — 10 넘는 수 · 가장 큰 값(115-10). */ @@ -329,7 +321,8 @@ function shadeOf(out) { const p = mesh.geometry.getAttribute("position"); const nrm = mesh.geometry.getAttribute("normal"); const keep = mesh.userData.keepColor; - const vkey = (v) => `${Math.round(p.getX(v) * 1e3)},${Math.round(p.getY(v) * 1e3)},${Math.round(p.getZ(v) * 1e3)}`; + const vkey = (v) => + `${Math.round(p.getX(v) * 1e3)},${Math.round(p.getY(v) * 1e3)},${Math.round(p.getZ(v) * 1e3)}`; const face = (t) => { const a = new THREE.Vector3().fromBufferAttribute(p, t * 3); const b = new THREE.Vector3().fromBufferAttribute(p, t * 3 + 1); @@ -364,8 +357,14 @@ function shadeOf(out) { // 같은 자리 정점(두 삼각형 쪽) 법선 차. const other = (key) => (y.a === key ? y.va : y.vb); const jump = Math.max( - deg(new THREE.Vector3().fromBufferAttribute(nrm, x.va), new THREE.Vector3().fromBufferAttribute(nrm, other(x.a))), - deg(new THREE.Vector3().fromBufferAttribute(nrm, x.vb), new THREE.Vector3().fromBufferAttribute(nrm, other(x.kb))), + deg( + new THREE.Vector3().fromBufferAttribute(nrm, x.va), + new THREE.Vector3().fromBufferAttribute(nrm, other(x.a)), + ), + deg( + new THREE.Vector3().fromBufferAttribute(nrm, x.vb), + new THREE.Vector3().fromBufferAttribute(nrm, other(x.kb)), + ), ); if (bend < 25) { jumps.push(jump); @@ -424,7 +423,11 @@ function daylightPoints(out, bounds) { station(40, 40, 0, 0, 1, 96, 1.5), ]; const spec = { bounds, x0: -10, x1: 50, y0: -25, y1: 25, step: 0.5, heightAt }; - const { out, before, positions, heightIndex } = run({ sections, route: STRAIGHT, terrainSpec: spec }); + const { out, before, positions, heightIndex } = run({ + sections, + route: STRAIGHT, + terrainSpec: spec, + }); const tris = trianglesOf(out.root); const gap = gapEdges(tris, out, frameOf(bounds, -10, 50, -25, 25)); // 비탈 끝 오프셋 — 측점(0 · 20 · 40)에서 6 · 9 · 6(+ 노면 반폭) 그대로 · 사이는 단조 3차(115-9): @@ -461,7 +464,7 @@ function daylightPoints(out, bounds) { // 측점 사이(x = 10) 단면은 지형에 맞춰 다시 그리지 않는다 — 노면 끝 0 → 비탈 끝 1 비율 보정. // 꼭대기 양옆 기울기 — 직선 보간이면 +0.15 · −0.15(차 0.3) · 3차면 둘 다 0 근처. const wAt = (x) => widthAt.get(Math.round(x * 100)); - const kink = Math.abs((wAt(20) - wAt(19)) - (wAt(21) - wAt(20))); + const kink = Math.abs(wAt(20) - wAt(19) - (wAt(21) - wAt(20))); results.straight = { aspect: aspectOf(out), stats: out.stats, @@ -513,7 +516,8 @@ function daylightPoints(out, bounds) { const a = pslg.tag[pslg.edges[e * 2]]; const b = pslg.tag[pslg.edges[e * 2 + 1]]; if (pslg.kinds[e] !== PSLG.EDGE_DESIGN) continue; - const fillCut = (a === PSLG.TAG_FILL && b === PSLG.TAG_CUT) || (a === PSLG.TAG_CUT && b === PSLG.TAG_FILL); + const fillCut = + (a === PSLG.TAG_FILL && b === PSLG.TAG_CUT) || (a === PSLG.TAG_CUT && b === PSLG.TAG_FILL); const ax = pslg.x[pslg.edges[e * 2]] + 20; const bx = pslg.x[pslg.edges[e * 2 + 1]] + 20; if (fillCut && Math.abs(ax - bx) > 0.21) crossEdges += 1; @@ -649,7 +653,12 @@ results.hairpin = corner(10); [HALF + 13.5, 92], [40, 92], ]; - const pts = [...half.map(([o, z]) => [-o, z]).reverse(), [-2.5, ROAD_Z], [2.5, ROAD_Z], ...half]; + const pts = [ + ...half.map(([o, z]) => [-o, z]).reverse(), + [-2.5, ROAD_Z], + [2.5, ROAD_Z], + ...half, + ]; st.design.design_line = pts.map(([offset_m, elevation_m]) => ({ offset_m, elevation_m })); return st; }; diff --git a/resources/tester/helper_116_right_click_add.cjs b/resources/tester/helper_116_right_click_add.cjs index 910383465..f757b5973 100644 --- a/resources/tester/helper_116_right_click_add.cjs +++ b/resources/tester/helper_116_right_click_add.cjs @@ -3,11 +3,10 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const session = new Map(); const fakeStorage = { getItem: (k) => session.get(k) ?? null, @@ -22,25 +21,16 @@ const STUBS = { "./ui_template_checkbox.css": {}, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); if (request.startsWith("@")) return new Proxy({}, { get: () => () => {} }); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const B05 = path.join(ROOT, "B05_Profile"); const { addsAtOnce, finishAdd } = loadTs( diff --git a/resources/tester/helper_118_pipe_pick.cjs b/resources/tester/helper_118_pipe_pick.cjs index 40779c370..6b38ffe6a 100644 --- a/resources/tester/helper_118_pipe_pick.cjs +++ b/resources/tester/helper_118_pipe_pick.cjs @@ -4,10 +4,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); let STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, "@ui/ui_template_elements": { showToast: () => undefined }, @@ -103,35 +102,18 @@ globalThis.document = { globalThis.window = { addEventListener: () => {}, removeEventListener: () => {} }; globalThis.requestAnimationFrame = (fn) => fn(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_126_ford_fill.cjs b/resources/tester/helper_126_ford_fill.cjs index 00eb19adf..f4e3f5d37 100644 --- a/resources/tester/helper_126_ford_fill.cjs +++ b/resources/tester/helper_126_ford_fill.cjs @@ -4,33 +4,23 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const toasts = []; const STUBS = { "@ui/ui_template_elements": { showToast: (message) => toasts.push(message) } }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request === "@config/config_frontend") return loadTs(path.join(ROOT, "config", "config_frontend.ts")); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); if (request.startsWith("@")) return new Proxy({}, { get: () => () => {} }); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const B05 = path.join(ROOT, "B05_Profile"); const { addsAtOnce, finishAdd, FORD_WAIT_NOTICE } = loadTs( diff --git a/resources/tester/helper_129_menu_equals_panel.cjs b/resources/tester/helper_129_menu_equals_panel.cjs index 98fd5df57..f5ec01f34 100644 --- a/resources/tester/helper_129_menu_equals_panel.cjs +++ b/resources/tester/helper_129_menu_equals_panel.cjs @@ -5,10 +5,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const STUBS = { "@config/config_frontend": { API_BASE_URL: "/api", API_TIMEOUT_MS: 1000 } }; // 드롭다운 항목 — 브라우저 `new Option(text, value)` 과 같은 칸만. globalThis.Option = class { @@ -20,15 +19,9 @@ globalThis.Option = class { } }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; // 구조물 부품 상자(322-1) — JSON 그대로(트랜스파일이 기본 내보내기로 감쌈) if (request.endsWith(".json")) @@ -37,11 +30,8 @@ function loadTs(file) { if (request.startsWith("@")) return new Proxy({}, { get: () => () => {} }); if (request.endsWith(".css")) return {}; return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const B05 = path.join(ROOT, "B05_Profile"); const T = loadTs(path.join(B05, "B05_Profile_UI_Structures_Panel_Types.ts")); diff --git a/resources/tester/helper_130_b07_plan_manage.cjs b/resources/tester/helper_130_b07_plan_manage.cjs index e7649734e..1477ec432 100644 --- a/resources/tester/helper_130_b07_plan_manage.cjs +++ b/resources/tester/helper_130_b07_plan_manage.cjs @@ -5,11 +5,10 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B07 = path.join(ROOT, "B07_DesignDetail"); -const cache = new Map(); // ── 화면 그릇 흉내 ───────────────────────────────────────────────────────────── class FakeNode { @@ -223,15 +222,9 @@ const API = { }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; // 공용 카드(269) = 진짜 모듈(가짜 그릇 위에서 돎) if (request === "@ui/ui_template_collapsible") @@ -239,11 +232,8 @@ function loadTs(file) { if (request === "./B07_DesignDetail_Api_Fetch") return API; if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const settle = () => new Promise((done) => setTimeout(done, 0)); const { buildDrawingSidePanel } = loadTs(path.join(B07, "B07_DesignDetail_UI_Panels.ts")); diff --git a/resources/tester/helper_135_2_grayscale.cjs b/resources/tester/helper_135_2_grayscale.cjs index 69b3ad7e2..69b47e6d5 100644 --- a/resources/tester/helper_135_2_grayscale.cjs +++ b/resources/tester/helper_135_2_grayscale.cjs @@ -4,19 +4,16 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.resolve(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); -const ts = require(path.join(NM, "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const THREE = require(path.join(NM, "three")); -function loadTs(file) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - new Function("exports", "module", "require", js)(box.exports, box, (r) => - r === "three" ? THREE : require(r), - ); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => { + return (r) => (r === "three" ? THREE : require(r)); + }, +}); const B05 = (name) => path.join(ROOT, "B05_Profile", `${name}.ts`); const { TerrainBandSplit, ORIGINAL_COLORS } = loadTs(B05("B05_Profile_UI_Corridor_Split")); diff --git a/resources/tester/helper_135_3_chute_solid.cjs b/resources/tester/helper_135_3_chute_solid.cjs index 9eb750323..5cb934254 100644 --- a/resources/tester/helper_135_3_chute_solid.cjs +++ b/resources/tester/helper_135_3_chute_solid.cjs @@ -4,9 +4,8 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); -const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); -const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, three: require(path.join(ROOT, "config", "node_modules", "three")), @@ -15,34 +14,18 @@ const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const B05 = (f) => path.join(ROOT, "B05_Profile", `${f}.ts`); const B06 = (f) => path.join(ROOT, "B06_Section", `${f}.ts`); diff --git a/resources/tester/helper_136_chute_form_cache.cjs b/resources/tester/helper_136_chute_form_cache.cjs index bbbc053dc..14f2df533 100644 --- a/resources/tester/helper_136_chute_form_cache.cjs +++ b/resources/tester/helper_136_chute_form_cache.cjs @@ -4,11 +4,10 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const session = new Map(); const fakeStorage = { getItem: (k) => session.get(k) ?? null, @@ -19,15 +18,9 @@ globalThis.window = { sessionStorage: fakeStorage, localStorage: fakeStorage }; globalThis.sessionStorage = fakeStorage; globalThis.localStorage = fakeStorage; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (request.startsWith("@ui/")) return new Proxy({}, { get: () => () => {} }); if (request.startsWith("@util/")) @@ -37,11 +30,8 @@ function loadTs(file) { return request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) : require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const options = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Pipe_Options.ts")); diff --git a/resources/tester/helper_137_design_pick.cjs b/resources/tester/helper_137_design_pick.cjs index c45d0e9f6..208aa4da8 100644 --- a/resources/tester/helper_137_design_pick.cjs +++ b/resources/tester/helper_137_design_pick.cjs @@ -4,10 +4,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, "@ui/ui_template_elements": { showToast: () => undefined }, @@ -116,24 +115,10 @@ globalThis.document = { removeEventListener: () => {}, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; // 화면 꾸밈 — Node 에선 안 씀(138-4 모달 CSS) const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; @@ -141,11 +126,8 @@ function loadTs(file) { if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_139_2_link_freeze.cjs b/resources/tester/helper_139_2_link_freeze.cjs index bb5657f8f..7a820194a 100644 --- a/resources/tester/helper_139_2_link_freeze.cjs +++ b/resources/tester/helper_139_2_link_freeze.cjs @@ -4,39 +4,24 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const noop = new Proxy({}, { get: () => () => "" }); // 조정창 모듈이 불러올 때 문서 이벤트를 검 — 기하엔 안 쓰임 globalThis.document = { addEventListener: () => undefined }; globalThis.window = globalThis; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { // 화면 전용(토스트 · 글 · 설정) — 기하엔 안 쓰임 if (request.startsWith("@ui/") || request.startsWith("@config/")) return noop; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`].find((p) => fs.existsSync(p) && fs.statSync(p).isFile()) ?? `${base}.ts`; diff --git a/resources/tester/helper_139_3_ford_level.cjs b/resources/tester/helper_139_3_ford_level.cjs index d1c9e6c21..e17cfb285 100644 --- a/resources/tester/helper_139_3_ford_level.cjs +++ b/resources/tester/helper_139_3_ford_level.cjs @@ -3,30 +3,20 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const noop = new Proxy({}, { get: () => () => "" }); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (request.startsWith("@ui/") || request.startsWith("@config/")) return noop; if (request.startsWith("@util/")) return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`)); return loadTs(path.join(path.dirname(full), `${request}.ts`)); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const { computeFordLayout, fordAdjustOf } = loadTs( path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Geom.ts"), diff --git a/resources/tester/helper_139_4_buried_outlet.cjs b/resources/tester/helper_139_4_buried_outlet.cjs index 8b8a20cb2..98e709a4b 100644 --- a/resources/tester/helper_139_4_buried_outlet.cjs +++ b/resources/tester/helper_139_4_buried_outlet.cjs @@ -4,26 +4,16 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined } }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; @@ -31,11 +21,8 @@ function loadTs(file) { if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_139_6_line_shift.cjs b/resources/tester/helper_139_6_line_shift.cjs index a8284f2e8..5921bc61a 100644 --- a/resources/tester/helper_139_6_line_shift.cjs +++ b/resources/tester/helper_139_6_line_shift.cjs @@ -3,10 +3,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); class Node { constructor(tag) { @@ -81,22 +80,13 @@ const key = (name) => fn({ key: name, target: null, preventDefault: () => undefined }), ); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const req = (r) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (r) => r.startsWith("@util/") ? loadTs(path.join(ROOT, "common_util", `${r.slice(6)}.ts`)) - : loadTs(path.join(path.dirname(full), `${r}.ts`)); - new Function("exports", "module", "require", js)(box.exports, box, req); - cache.set(full, box.exports); - return box.exports; -} + : loadTs(path.join(path.dirname(full), `${r}.ts`)), +}); const pick = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Design_Pick.ts")); const x = (o) => 100 - o * 12; // +offset = 화면 왼쪽 diff --git a/resources/tester/helper_139_6_pipe_fit.cjs b/resources/tester/helper_139_6_pipe_fit.cjs index 0258b405d..e340f2916 100644 --- a/resources/tester/helper_139_6_pipe_fit.cjs +++ b/resources/tester/helper_139_6_pipe_fit.cjs @@ -5,33 +5,26 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const noop = new Proxy({}, { get: () => () => "" }); globalThis.document = { addEventListener: () => undefined }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) return JSON.parse(fs.readFileSync(full, "utf8")); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const resolve = (base) => - fs.existsSync(base) && fs.statSync(base).isFile() ? base : `${base}.ts`; - const req = (r) => { - if (r.startsWith("@ui/") || r.startsWith("@config/")) return noop; - if (r.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", r.slice(6)))); - return loadTs(resolve(path.join(path.dirname(full), r))); - }; - new Function("exports", "module", "require", js)(box.exports, box, req); - cache.set(full, box.exports); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => { + const resolve = (base) => + fs.existsSync(base) && fs.statSync(base).isFile() ? base : `${base}.ts`; + const req = (r) => { + if (r.startsWith("@ui/") || r.startsWith("@config/")) return noop; + if (r.startsWith("@util/")) + return loadTs(resolve(path.join(ROOT, "common_util", r.slice(6)))); + return loadTs(resolve(path.join(path.dirname(full), r))); + }; + return req; + }, +}); const { computeCrossDesign } = loadTs( path.join(ROOT, "common_util", "common_util_cross_design.ts"), diff --git a/resources/tester/helper_150_detail_pick.cjs b/resources/tester/helper_150_detail_pick.cjs index 9368350c3..444c7950a 100644 --- a/resources/tester/helper_150_detail_pick.cjs +++ b/resources/tester/helper_150_detail_pick.cjs @@ -3,30 +3,21 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); // 조정창 공용 부품(Design_Pick → Cross_Panel_Base · 151-3)이 불러올 때 문서에 귀를 닮 globalThis.document = { addEventListener: () => {}, removeEventListener: () => {} }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const pick = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Detail_Pick.ts")); // ── 그림 복제 흉내 — 평평한 요소 목록 · 「tag.class」 · 「.class」 · 「g.class line」 · 「:not(.class)」 만 ── diff --git a/resources/tester/helper_152_2_link_height.cjs b/resources/tester/helper_152_2_link_height.cjs index 8fa879473..b36bb3b4c 100644 --- a/resources/tester/helper_152_2_link_height.cjs +++ b/resources/tester/helper_152_2_link_height.cjs @@ -4,44 +4,26 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); // 화면 전용 모듈(토스트 · 글) — 기하 계산엔 안 쓰임 const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_152_3_chute_cut.cjs b/resources/tester/helper_152_3_chute_cut.cjs index 9963064cb..2981239e8 100644 --- a/resources/tester/helper_152_3_chute_cut.cjs +++ b/resources/tester/helper_152_3_chute_cut.cjs @@ -4,26 +4,16 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined } }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; @@ -31,11 +21,8 @@ function loadTs(file) { if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_152_4_chute_pick.cjs b/resources/tester/helper_152_4_chute_pick.cjs index d18e1a16d..3ac79ab80 100644 --- a/resources/tester/helper_152_4_chute_pick.cjs +++ b/resources/tester/helper_152_4_chute_pick.cjs @@ -4,10 +4,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); let STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, "@ui/ui_template_elements": { showToast: () => undefined }, @@ -108,35 +107,18 @@ globalThis.document = { globalThis.window = { addEventListener: () => {}, removeEventListener: () => {} }; globalThis.requestAnimationFrame = (fn) => fn(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_152_5_pavement_pick.cjs b/resources/tester/helper_152_5_pavement_pick.cjs index 3752a6297..857c34a2f 100644 --- a/resources/tester/helper_152_5_pavement_pick.cjs +++ b/resources/tester/helper_152_5_pavement_pick.cjs @@ -3,7 +3,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); @@ -37,16 +37,14 @@ class FakeNode { globalThis.document = { createElementNS: (_, tag) => new FakeNode(tag) }; function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(file); } const surface = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Design_Surface.ts")); diff --git a/resources/tester/helper_153_b07_cross_layout.cjs b/resources/tester/helper_153_b07_cross_layout.cjs index 1266a6f49..228cc3d9e 100644 --- a/resources/tester/helper_153_b07_cross_layout.cjs +++ b/resources/tester/helper_153_b07_cross_layout.cjs @@ -4,13 +4,12 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); const B07 = path.join(ROOT, "B07_DesignDetail"); const UI = path.join(ROOT, "ui_template"); -const cache = new Map(); class FakeNode { constructor(tag) { @@ -66,15 +65,9 @@ const byClass = (node, name) => all(node).filter((n) => n.classList.contains(nam globalThis.document = { createElement: (tag) => new FakeNode(tag) }; const toasts = []; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (request === "@ui/ui_template_elements") return { @@ -94,11 +87,8 @@ function loadTs(file) { return fs.existsSync(target) ? loadTs(target) : {}; } return {}; // 이 시험이 안 부르는 바깥 길(API · 설정 · CAD) - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const { buildDrawingSidePanel, buildCrossLayoutControls } = loadTs( path.join(B07, "B07_DesignDetail_UI_Panels.ts"), diff --git a/resources/tester/helper_164_detail_zoom.cjs b/resources/tester/helper_164_detail_zoom.cjs index 6660904f0..3c4b97ed5 100644 --- a/resources/tester/helper_164_detail_zoom.cjs +++ b/resources/tester/helper_164_detail_zoom.cjs @@ -5,6 +5,7 @@ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -41,19 +42,15 @@ const STUBS = { "./B06_Section_UI_Cross_Detail_Cad": { mountNoteCad: () => {}, stationNoLabel: () => "" }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (request.endsWith(".css")) return {}; if (STUBS[request]) return STUBS[request]; return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); function el(tag) { const attrs = {}; diff --git a/resources/tester/helper_165_pavement_flow.cjs b/resources/tester/helper_165_pavement_flow.cjs index fb032a084..114dd599b 100644 --- a/resources/tester/helper_165_pavement_flow.cjs +++ b/resources/tester/helper_165_pavement_flow.cjs @@ -5,23 +5,21 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (CATALOG.isCatalogRequest(request)) return CATALOG; - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (CATALOG.isCatalogRequest(request)) return CATALOG; + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(file); } // 세션(structure-pick) — 메모리 한 벌 diff --git a/resources/tester/helper_166_1_note_options.cjs b/resources/tester/helper_166_1_note_options.cjs index 6f2912ab2..b74d171d3 100644 --- a/resources/tester/helper_166_1_note_options.cjs +++ b/resources/tester/helper_166_1_note_options.cjs @@ -5,28 +5,18 @@ const fs = require("fs"); const path = require("path"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; // 구조물 표(본보기 · 323-7) if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const { structureOptionLines } = loadTs( path.join(ROOT, "B05_Profile", "logic", "B05_Profile_UI_Structures_Balloon.ts"), diff --git a/resources/tester/helper_172_1_loft_wrinkle.cjs b/resources/tester/helper_172_1_loft_wrinkle.cjs index 236f43ef6..4eee129c0 100644 --- a/resources/tester/helper_172_1_loft_wrinkle.cjs +++ b/resources/tester/helper_172_1_loft_wrinkle.cjs @@ -7,28 +7,23 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); -const ts = require(path.join(NM, "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const THREE = require(path.join(NM, "three")); const LIBS = { three: THREE, delaunator: require(path.join(NM, "delaunator", "index.js")), - "three/examples/jsm/utils/BufferGeometryUtils.js": require(path.join(NM, "three", "examples", "jsm", "utils", "BufferGeometryUtils.js")), + "three/examples/jsm/utils/BufferGeometryUtils.js": require( + path.join(NM, "three", "examples", "jsm", "utils", "BufferGeometryUtils.js"), + ), "@kninnug/constrainautor": { __esModule: true, default: require(path.join(NM, "@kninnug", "constrainautor", "lib", "Constrainautor.cjs")), }, }; -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (LIBS[request]) return LIBS[request]; if (request.startsWith("@ui/") || request.startsWith("@config/")) { return new Proxy({}, { get: () => () => undefined }); @@ -38,10 +33,8 @@ function loadTs(file) { : path.join(path.dirname(full), request); if (request.endsWith(".json")) return JSON.parse(fs.readFileSync(target, "utf8")); return loadTs(fs.existsSync(`${target}.ts`) ? `${target}.ts` : path.join(target, "index.ts")); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const B05 = (name) => path.join(ROOT, "B05_Profile", `${name}.ts`); const { buildCorridor } = loadTs(B05("B05_Profile_UI_Corridor_Build")); @@ -134,7 +127,10 @@ function gridTerrain(heightAt) { } const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); - geometry.setAttribute("color", new THREE.BufferAttribute(new Uint8Array(nx * ny * 4).fill(200), 4, true)); + geometry.setAttribute( + "color", + new THREE.BufferAttribute(new Uint8Array(nx * ny * 4).fill(200), 4, true), + ); geometry.setIndex(index); return new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ vertexColors: true })); } @@ -151,7 +147,12 @@ function gapCount(root, out) { const onFrame = (p) => { const x = p[0] + OX; const y = -p[2] + OY; - return Math.abs(x - X0) < 1e-3 || Math.abs(x - X1) < 1e-3 || Math.abs(y - Y0) < 1e-3 || Math.abs(y - Y1) < 1e-3; + return ( + Math.abs(x - X0) < 1e-3 || + Math.abs(x - X1) < 1e-3 || + Math.abs(y - Y0) < 1e-3 || + Math.abs(y - Y1) < 1e-3 + ); }; const uses = new Map(); root.traverse((child) => { @@ -193,7 +194,8 @@ function gapCount(root, out) { function tinHeights(out) { const p = out.root.getObjectByName("terrain-tin-mesh").geometry.getAttribute("position"); const tris = []; - for (let t = 0; t < p.count; t += 3) tris.push([0, 1, 2].map((k) => [p.getX(t + k), p.getY(t + k), p.getZ(t + k)])); + for (let t = 0; t < p.count; t += 3) + tris.push([0, 1, 2].map((k) => [p.getX(t + k), p.getY(t + k), p.getZ(t + k)])); return (x, y) => { const sx = x - OX; const sz = -(y - OY); diff --git a/resources/tester/helper_173_7_inlet_animal.cjs b/resources/tester/helper_173_7_inlet_animal.cjs index 863ba5493..6f783a723 100644 --- a/resources/tester/helper_173_7_inlet_animal.cjs +++ b/resources/tester/helper_173_7_inlet_animal.cjs @@ -4,11 +4,10 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); class FakeNode { constructor(tag) { @@ -97,25 +96,16 @@ const STUBS = { "../../B05_Profile/B05_Profile_UI_Structures_Summary_Row": { buildSummaryRow: () => null }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; if (request.startsWith("@util/")) return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`)); return loadTs(path.join(path.dirname(full), `${request}.ts`)); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const { createSpecForm } = loadTs( path.join(ROOT, "A00_Common", "design", "design_structure_form.ts"), diff --git a/resources/tester/helper_176_1_seam.cjs b/resources/tester/helper_176_1_seam.cjs index 9f364de98..08bdd37e6 100644 --- a/resources/tester/helper_176_1_seam.cjs +++ b/resources/tester/helper_176_1_seam.cjs @@ -7,28 +7,23 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); -const ts = require(path.join(NM, "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const THREE = require(path.join(NM, "three")); const LIBS = { three: THREE, delaunator: require(path.join(NM, "delaunator", "index.js")), - "three/examples/jsm/utils/BufferGeometryUtils.js": require(path.join(NM, "three", "examples", "jsm", "utils", "BufferGeometryUtils.js")), + "three/examples/jsm/utils/BufferGeometryUtils.js": require( + path.join(NM, "three", "examples", "jsm", "utils", "BufferGeometryUtils.js"), + ), "@kninnug/constrainautor": { __esModule: true, default: require(path.join(NM, "@kninnug", "constrainautor", "lib", "Constrainautor.cjs")), }, }; -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (LIBS[request]) return LIBS[request]; if (request.startsWith("@ui/") || request.startsWith("@config/")) { return new Proxy({}, { get: () => () => undefined }); @@ -38,10 +33,8 @@ function loadTs(file) { : path.join(path.dirname(full), request); if (request.endsWith(".json")) return JSON.parse(fs.readFileSync(target, "utf8")); return loadTs(fs.existsSync(`${target}.ts`) ? `${target}.ts` : path.join(target, "index.ts")); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const B05 = (name) => path.join(ROOT, "B05_Profile", `${name}.ts`); const { buildCorridor } = loadTs(B05("B05_Profile_UI_Corridor_Build")); @@ -139,7 +132,10 @@ function gridTerrain(heightAt) { } const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); - geometry.setAttribute("color", new THREE.BufferAttribute(new Uint8Array(nx * ny * 4).fill(200), 4, true)); + geometry.setAttribute( + "color", + new THREE.BufferAttribute(new Uint8Array(nx * ny * 4).fill(200), 4, true), + ); geometry.setIndex(index); return new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ vertexColors: true })); } @@ -156,7 +152,12 @@ function gapCount(root, out) { const onFrame = (p) => { const x = p[0] + OX; const y = -p[2] + OY; - return Math.abs(x - X0) < 1e-3 || Math.abs(x - X1) < 1e-3 || Math.abs(y - Y0) < 1e-3 || Math.abs(y - Y1) < 1e-3; + return ( + Math.abs(x - X0) < 1e-3 || + Math.abs(x - X1) < 1e-3 || + Math.abs(y - Y0) < 1e-3 || + Math.abs(y - Y1) < 1e-3 + ); }; const uses = new Map(); root.traverse((child) => { diff --git a/resources/tester/helper_178_3_slope_tiers.cjs b/resources/tester/helper_178_3_slope_tiers.cjs index 869a134f0..de8e4164f 100644 --- a/resources/tester/helper_178_3_slope_tiers.cjs +++ b/resources/tester/helper_178_3_slope_tiers.cjs @@ -5,31 +5,16 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined } }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; @@ -37,11 +22,8 @@ function loadTs(file) { if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_179_4_detail_table.cjs b/resources/tester/helper_179_4_detail_table.cjs index 743921ada..f0cd798be 100644 --- a/resources/tester/helper_179_4_detail_table.cjs +++ b/resources/tester/helper_179_4_detail_table.cjs @@ -4,7 +4,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -33,21 +33,17 @@ const STUBS = { "./B06_Section_UI_Section_Common": { CROSS_PAD }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const T = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Table.ts")); const input = JSON.parse(fs.readFileSync(0, "utf8")); diff --git a/resources/tester/helper_180_3_b07_guide_toggle.cjs b/resources/tester/helper_180_3_b07_guide_toggle.cjs index 5dd7883f8..3950f80d7 100644 --- a/resources/tester/helper_180_3_b07_guide_toggle.cjs +++ b/resources/tester/helper_180_3_b07_guide_toggle.cjs @@ -3,10 +3,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); class FakeNode { constructor(tag) { @@ -53,15 +52,9 @@ class FakeNode { } globalThis.document = { createElement: (tag) => new FakeNode(tag) }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (request === "@ui/ui_template_elements") return loadTs(path.join(ROOT, "ui_template", "ui_template_elements.ts")); if (request.startsWith(".")) { @@ -69,11 +62,8 @@ function loadTs(file) { return fs.existsSync(target) ? loadTs(target) : {}; } return {}; - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const { buildGuideToggle, guideShown } = loadTs( path.join(ROOT, "B07_DesignDetail", "B07_DesignDetail_UI_GuideToggle.ts"), diff --git a/resources/tester/helper_186_1_outlet_fold.cjs b/resources/tester/helper_186_1_outlet_fold.cjs index 14d84df96..0b754a8b7 100644 --- a/resources/tester/helper_186_1_outlet_fold.cjs +++ b/resources/tester/helper_186_1_outlet_fold.cjs @@ -4,44 +4,26 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); // 화면 전용 모듈(토스트) — 기하 계산엔 안 쓰임 const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_188_1_last_pick.cjs b/resources/tester/helper_188_1_last_pick.cjs index b3e1aa938..ea1bbf61f 100644 --- a/resources/tester/helper_188_1_last_pick.cjs +++ b/resources/tester/helper_188_1_last_pick.cjs @@ -5,10 +5,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const session = new Map(); const fakeStorage = { @@ -29,23 +28,14 @@ const STUBS = { }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => STUBS[request] ?? (request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) - : require(request)); - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + : require(request)), +}); const sel = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts")); const sync = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Selection.ts")); diff --git a/resources/tester/helper_190_1_basin_pick.cjs b/resources/tester/helper_190_1_basin_pick.cjs index a09a1949b..2206c2fae 100644 --- a/resources/tester/helper_190_1_basin_pick.cjs +++ b/resources/tester/helper_190_1_basin_pick.cjs @@ -5,10 +5,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, "@ui/ui_template_elements": { showToast: () => undefined }, @@ -63,35 +62,18 @@ globalThis.document = { createElementNS: (_ns, tag) => new FakeNode(tag), }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), diff --git a/resources/tester/helper_193_1_line_axis.cjs b/resources/tester/helper_193_1_line_axis.cjs index 4be2feca6..6ec7479f9 100644 --- a/resources/tester/helper_193_1_line_axis.cjs +++ b/resources/tester/helper_193_1_line_axis.cjs @@ -3,10 +3,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); class Node { constructor(tag) { @@ -81,22 +80,13 @@ const key = (name) => fn({ key: name, target: null, preventDefault: () => undefined }), ); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const req = (r) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (r) => r.startsWith("@util/") ? loadTs(path.join(ROOT, "common_util", `${r.slice(6)}.ts`)) - : loadTs(path.join(path.dirname(full), `${r}.ts`)); - new Function("exports", "module", "require", js)(box.exports, box, req); - cache.set(full, box.exports); - return box.exports; -} + : loadTs(path.join(path.dirname(full), `${r}.ts`)), +}); globalThis.Element = Node; const pick = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Design_Pick.ts")); diff --git a/resources/tester/helper_194_1_berm_pick.cjs b/resources/tester/helper_194_1_berm_pick.cjs index d0f8a57e2..f2bcccc4f 100644 --- a/resources/tester/helper_194_1_berm_pick.cjs +++ b/resources/tester/helper_194_1_berm_pick.cjs @@ -3,10 +3,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); class Node { constructor(tag) { @@ -81,22 +80,13 @@ const key = (name) => fn({ key: name, target: null, preventDefault: () => undefined }), ); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const req = (r) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (r) => r.startsWith("@util/") ? loadTs(path.join(ROOT, "common_util", `${r.slice(6)}.ts`)) - : loadTs(path.join(path.dirname(full), `${r}.ts`)); - new Function("exports", "module", "require", js)(box.exports, box, req); - cache.set(full, box.exports); - return box.exports; -} + : loadTs(path.join(path.dirname(full), `${r}.ts`)), +}); globalThis.Element = Node; const pick = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Design_Pick.ts")); diff --git a/resources/tester/helper_209_1_detail_select.cjs b/resources/tester/helper_209_1_detail_select.cjs index 9390a94c6..157e36606 100644 --- a/resources/tester/helper_209_1_detail_select.cjs +++ b/resources/tester/helper_209_1_detail_select.cjs @@ -4,7 +4,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -18,21 +18,17 @@ const STUBS = { "./B06_Section_UI_Section_Common": { CROSS_PAD }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const G = loadTs(path.join(B06, "logic/B06_Section_UI_Cross_Detail_Geom.ts")); const S = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Select.ts")); const T = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Table.ts")); diff --git a/resources/tester/helper_215_1_free_profile.cjs b/resources/tester/helper_215_1_free_profile.cjs index e5a7e9181..7021dbcb8 100644 --- a/resources/tester/helper_215_1_free_profile.cjs +++ b/resources/tester/helper_215_1_free_profile.cjs @@ -4,26 +4,12 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { - if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); - return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, +}); const free = loadTs(path.join(ROOT, "A00_Common", "design", "logic", "design_profile_free.ts")); const align = loadTs( diff --git a/resources/tester/helper_216_1_b05_station_panel.cjs b/resources/tester/helper_216_1_b05_station_panel.cjs index ba1547ed4..462a7d347 100644 --- a/resources/tester/helper_216_1_b05_station_panel.cjs +++ b/resources/tester/helper_216_1_b05_station_panel.cjs @@ -5,10 +5,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const session = new Map(); const fakeStorage = { @@ -28,23 +27,14 @@ const STUBS = { }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => STUBS[request] ?? (request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) - : require(request)); - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + : require(request)), +}); const sel = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts")); const sync = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Selection.ts")); diff --git a/resources/tester/helper_219_1_arrow_owner.cjs b/resources/tester/helper_219_1_arrow_owner.cjs index 04aa20ff3..7a8431b8f 100644 --- a/resources/tester/helper_219_1_arrow_owner.cjs +++ b/resources/tester/helper_219_1_arrow_owner.cjs @@ -5,11 +5,10 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const listeners = { window: {}, document: {} }; const on = (where) => (type, fn) => (listeners[where][type] ??= new Set()).add(fn); @@ -61,24 +60,15 @@ const STUBS = { }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => (CATALOG.isCatalogRequest(request) ? CATALOG : null) ?? STUBS[request] ?? (request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) - : require(request)); - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + : require(request)), +}); const tools = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Profile_Panel_Tools.ts")); const base = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Panel_Base.ts")); diff --git a/resources/tester/helper_224_1_picker_top_table.cjs b/resources/tester/helper_224_1_picker_top_table.cjs index 724e246b3..02c68252c 100644 --- a/resources/tester/helper_224_1_picker_top_table.cjs +++ b/resources/tester/helper_224_1_picker_top_table.cjs @@ -5,7 +5,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const UI = path.join(ROOT, "ui_template"); @@ -57,24 +57,14 @@ const STUBS = { }, }; -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.startsWith(".")) return loadTs(`${path.resolve(path.dirname(full), request)}.ts`); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const flush = () => new Promise((resolve) => setTimeout(resolve, 20)); const row = (key, 열, layer, owner) => ({ key, 열, layer, owner }); diff --git a/resources/tester/helper_224_1a_drainage_rehome.cjs b/resources/tester/helper_224_1a_drainage_rehome.cjs index 1f3bbea85..0ebc645ff 100644 --- a/resources/tester/helper_224_1a_drainage_rehome.cjs +++ b/resources/tester/helper_224_1a_drainage_rehome.cjs @@ -5,27 +5,19 @@ * · disconnect = 지금 관찰자 끊음 */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const stub = new Proxy({}, { get: () => () => "" }); /** TS 한 장을 CommonJS 로 — 상대 경로는 진짜(공용 창 · 지도 셈만), 나머지는 빈 함수. */ -function loadTs(full) { - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => /common_util_view_window|B04_PreProcess_UI_MapRender/.test(request) ? loadTs(path.join(path.dirname(full), `${request}.ts`)) - : stub; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + : stub, +}); /** 가짜 창 — 관찰자를 모아 두고 크기를 바꿔 울림. */ function fakeWindow(name) { diff --git a/resources/tester/helper_225_1_pipe_round.cjs b/resources/tester/helper_225_1_pipe_round.cjs index 64919dca3..92f1b33fb 100644 --- a/resources/tester/helper_225_1_pipe_round.cjs +++ b/resources/tester/helper_225_1_pipe_round.cjs @@ -3,10 +3,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const noop = new Proxy({}, { get: () => () => "" }); /** 아주 작은 SVG 가짜 — 글 자리 판정에 쓰는 것만(class 고르기 · 속성 · 부모). */ @@ -47,26 +46,20 @@ class El { } globalThis.document = { addEventListener: () => undefined, createElementNS: (_ns, t) => new El(t) }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) return JSON.parse(fs.readFileSync(full, "utf8")); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const resolve = (base) => - fs.existsSync(base) && fs.statSync(base).isFile() ? base : `${base}.ts`; - const req = (r) => { - if (r.startsWith("@ui/") || r.startsWith("@config/")) return noop; - if (r.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", r.slice(6)))); - return loadTs(resolve(path.join(path.dirname(full), r))); - }; - new Function("exports", "module", "require", js)(box.exports, box, req); - cache.set(full, box.exports); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => { + const resolve = (base) => + fs.existsSync(base) && fs.statSync(base).isFile() ? base : `${base}.ts`; + const req = (r) => { + if (r.startsWith("@ui/") || r.startsWith("@config/")) return noop; + if (r.startsWith("@util/")) + return loadTs(resolve(path.join(ROOT, "common_util", r.slice(6)))); + return loadTs(resolve(path.join(path.dirname(full), r))); + }; + return req; + }, +}); const B06 = (name) => loadTs(path.join(ROOT, "B06_Section", `${name}.ts`)); const { computeCulvertLayout } = B06("B06_Section_UI_Cross_Culvert_Geom"); diff --git a/resources/tester/helper_231_1_pave_reload.cjs b/resources/tester/helper_231_1_pave_reload.cjs index b548502e1..7d05d6c81 100644 --- a/resources/tester/helper_231_1_pave_reload.cjs +++ b/resources/tester/helper_231_1_pave_reload.cjs @@ -4,23 +4,21 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (CATALOG.isCatalogRequest(request)) return CATALOG; - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (CATALOG.isCatalogRequest(request)) return CATALOG; + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(file); } global.requestAnimationFrame = (fn) => fn(); diff --git a/resources/tester/helper_232_1_3d_press.cjs b/resources/tester/helper_232_1_3d_press.cjs index 4d6083250..33d0ab7bd 100644 --- a/resources/tester/helper_232_1_3d_press.cjs +++ b/resources/tester/helper_232_1_3d_press.cjs @@ -6,21 +6,19 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); -const ts = require(path.join(NM, "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const THREE = require(path.join(NM, "three", "build", "three.cjs")); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (request === "three") return THREE; - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (request === "three") return THREE; + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(path.join(ROOT, file)); } /* ── 창 · 캔버스 흉내(듣개 차례 = 창 캡처 → 캔버스 등록 차례) ── */ diff --git a/resources/tester/helper_233_1_profile_pick.cjs b/resources/tester/helper_233_1_profile_pick.cjs index a6f6b058e..26cb179b9 100644 --- a/resources/tester/helper_233_1_profile_pick.cjs +++ b/resources/tester/helper_233_1_profile_pick.cjs @@ -5,19 +5,17 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); -const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(path.join(ROOT, file)); } const store = new Map(); diff --git a/resources/tester/helper_233_2_view_pick.cjs b/resources/tester/helper_233_2_view_pick.cjs index a774d28c4..0dc35175b 100644 --- a/resources/tester/helper_233_2_view_pick.cjs +++ b/resources/tester/helper_233_2_view_pick.cjs @@ -9,20 +9,19 @@ const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); const ts = require(path.join(NM, "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const THREE = require(path.join(NM, "three", "build", "three.cjs")); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (request === "three") return THREE; - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (request === "three") return THREE; + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(path.join(ROOT, file)); } /* ── 가짜 DOM(라벨 캔버스 · [상세] 값 칸 · 조각) ── */ diff --git a/resources/tester/helper_233_3_release.cjs b/resources/tester/helper_233_3_release.cjs index 413e18288..e73539659 100644 --- a/resources/tester/helper_233_3_release.cjs +++ b/resources/tester/helper_233_3_release.cjs @@ -5,19 +5,17 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); -const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(path.join(ROOT, file)); } /* 창 키 듣기 · 창(모달) 흉내 */ diff --git a/resources/tester/helper_234_1_structure_station.cjs b/resources/tester/helper_234_1_structure_station.cjs index a4698de53..2d68c1598 100644 --- a/resources/tester/helper_234_1_structure_station.cjs +++ b/resources/tester/helper_234_1_structure_station.cjs @@ -4,21 +4,19 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request} (${file})`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request} (${file})`); + }, + }).loadTs(path.join(ROOT, file)); } /* 가짜 DOM — 폼 칸(input · label · span)만. */ diff --git a/resources/tester/helper_259_detail_region.cjs b/resources/tester/helper_259_detail_region.cjs index 1ce888c3e..c63fd68ff 100644 --- a/resources/tester/helper_259_detail_region.cjs +++ b/resources/tester/helper_259_detail_region.cjs @@ -4,7 +4,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -18,21 +18,17 @@ const STUBS = { "./B06_Section_UI_Section_Common": { CROSS_PAD }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const Z = loadTs(path.join(ROOT, "A00_Common", "b_svg_zoom_pan.ts")); const G = loadTs(path.join(B06, "logic/B06_Section_UI_Cross_Detail_Geom.ts")); const S = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Select.ts")); diff --git a/resources/tester/helper_260_note_cad.cjs b/resources/tester/helper_260_note_cad.cjs index fd8965aed..1676a7684 100644 --- a/resources/tester/helper_260_note_cad.cjs +++ b/resources/tester/helper_260_note_cad.cjs @@ -4,7 +4,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); // 인자 `load` = 표준입력 {pipes, structures, types} 를 세션 · 서버 흉내로 `loadNoteThings`(265-2) @@ -27,18 +27,14 @@ const STUBS = { "../B05_Profile/logic/B05_Profile_UI_Structures_Balloon": { structureOptionLines: () => [] }, }; -function loadTs(file) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; throw new Error(`흉내 없는 모듈: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); // 관 길이 정수(265-3) = 실제 `pipeLengthWhole` · 조합 참조 읽기(265-2) = 실제(둘 다 import 없음) STUBS["./logic/B06_Section_UI_Cross_Culvert_Const"] = loadTs( diff --git a/resources/tester/helper_261_3_detail_pick.cjs b/resources/tester/helper_261_3_detail_pick.cjs index 8ef57da1e..4d5b1cab1 100644 --- a/resources/tester/helper_261_3_detail_pick.cjs +++ b/resources/tester/helper_261_3_detail_pick.cjs @@ -5,7 +5,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -19,21 +19,17 @@ const STUBS = { "./B06_Section_UI_Section_Common": { CROSS_PAD }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const G = loadTs(path.join(B06, "logic/B06_Section_UI_Cross_Detail_Geom.ts")); const S = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Select.ts")); diff --git a/resources/tester/helper_266_detail_home.cjs b/resources/tester/helper_266_detail_home.cjs index 2492ddfdb..7335fff9c 100644 --- a/resources/tester/helper_266_detail_home.cjs +++ b/resources/tester/helper_266_detail_home.cjs @@ -5,7 +5,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -19,15 +19,11 @@ const STUBS = { validElevation: (s) => s.valid !== false && Number.isFinite(s.elevation_m), }, }; -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => STUBS[request] ?? {}; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => STUBS[request] ?? {}, +}); // 휠 방향과 무관한 시험(처음으로 · 범위) — 337 기본 밂 뒤에도 당김 수치 그대로(pull 박음) globalThis.localStorage = { getItem: (k) => (k === "aislo:pref:input-prefs" ? '{"wheelZoom":"pull"}' : null), diff --git a/resources/tester/helper_270_1_cross_map.cjs b/resources/tester/helper_270_1_cross_map.cjs index f8814f58d..d835968b4 100644 --- a/resources/tester/helper_270_1_cross_map.cjs +++ b/resources/tester/helper_270_1_cross_map.cjs @@ -6,19 +6,17 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); -const ts = require(path.join(NM, "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(path.join(ROOT, file)); } globalThis.window = { addEventListener() {}, removeEventListener() {} }; diff --git a/resources/tester/helper_271_detail_notify.cjs b/resources/tester/helper_271_detail_notify.cjs index 53a1174b2..d84bc67eb 100644 --- a/resources/tester/helper_271_detail_notify.cjs +++ b/resources/tester/helper_271_detail_notify.cjs @@ -7,7 +7,7 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); -const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require(path.join(__dirname, "helper_322_catalog_module.cjs")); const P = "P463"; @@ -140,35 +140,23 @@ const STUBS = { "design_save.ts": { savingDesign: () => false }, }; -const cache = new Map(); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; -function loadTs(full) { - const stub = STUBS[path.basename(full)]; - if (stub) return stub; - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const near = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + fileStub: (full) => STUBS[path.basename(full)], + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, near); - return box.exports; -} + }, +}); const store = loadTs(path.join(ROOT, "B06_Section", "B06_Section_Section_Store.ts")); const missing = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Missing_Stations.ts")); diff --git a/resources/tester/helper_271_facility_lane.cjs b/resources/tester/helper_271_facility_lane.cjs index d50a92687..6e33e51a8 100644 --- a/resources/tester/helper_271_facility_lane.cjs +++ b/resources/tester/helper_271_facility_lane.cjs @@ -5,7 +5,7 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); -const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require(path.join(__dirname, "helper_322_catalog_module.cjs")); function memoryStorage() { @@ -39,25 +39,15 @@ const STUBS = { "B06_Section_UI_Page_Persist.ts": anyModule({ bermSpansFromStructures: () => [] }), }; -const cache = new Map(); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; -function loadTs(full) { - const stub = STUBS[path.basename(full)]; - if (stub) return stub; - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const near = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + fileStub: (full) => STUBS[path.basename(full)], + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (request.endsWith(".json")) return JSON.parse(fs.readFileSync(path.join(path.dirname(full), request), "utf8")); @@ -66,10 +56,8 @@ function loadTs(full) { if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, near); - return box.exports; -} + }, +}); const design = path.join(ROOT, "A00_Common", "design"); const { createFacilityLane } = loadTs(path.join(design, "design_facility_lane.ts")); diff --git a/resources/tester/helper_271_straighten_pick.cjs b/resources/tester/helper_271_straighten_pick.cjs index f2e0572a5..8b610ac8b 100644 --- a/resources/tester/helper_271_straighten_pick.cjs +++ b/resources/tester/helper_271_straighten_pick.cjs @@ -4,28 +4,19 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); // 화면 틀 파일(@ui 별칭)은 Node 가 못 읽음 — 도구줄 그림만 쓰므로 빈 그림으로 갈음 const STUBS = { "./B05_Profile_UI_RouteEdit_Chrome": { HISTORY_ICON: { undo: "", redo: "" } } }; -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const near = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, near); - return box.exports; -} + }, +}); const LOGIC = path.join(ROOT, "B05_Profile", "logic"); const align = loadTs(path.join(LOGIC, "B05_Profile_UI_Profile_Alignment.ts")); diff --git a/resources/tester/helper_272_1_station_list.cjs b/resources/tester/helper_272_1_station_list.cjs index 11aa038b2..df883a640 100644 --- a/resources/tester/helper_272_1_station_list.cjs +++ b/resources/tester/helper_272_1_station_list.cjs @@ -3,22 +3,20 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); function loadTs(file) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (CATALOG.isCatalogRequest(request)) return CATALOG; - throw new Error(`stub 없음: ${request} (${file})`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (CATALOG.isCatalogRequest(request)) return CATALOG; + throw new Error(`stub 없음: ${request} (${file})`); + }, + }).loadTs(path.join(ROOT, file)); } const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); diff --git a/resources/tester/helper_273_2_preview_stations.cjs b/resources/tester/helper_273_2_preview_stations.cjs index 00589f01a..8dbe4da29 100644 --- a/resources/tester/helper_273_2_preview_stations.cjs +++ b/resources/tester/helper_273_2_preview_stations.cjs @@ -3,22 +3,23 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - new Function("exports", "module", "require", js)(box.exports, box, (name) => { - if (CATALOG.isCatalogRequest(name)) return CATALOG; - if (!stubs[name]) throw new Error(`stub 없음: ${name} (${file})`); - return stubs[name]; - }); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => { + return (name) => { + if (CATALOG.isCatalogRequest(name)) return CATALOG; + if (!stubs[name]) throw new Error(`stub 없음: ${name} (${file})`); + return stubs[name]; + }; + }, + }).loadTs(path.join(ROOT, file)); } const station = loadTs("common_util/common_util_structure_station.ts"); diff --git a/resources/tester/helper_280_1_combo_pick.cjs b/resources/tester/helper_280_1_combo_pick.cjs index af16257b4..9b4ffd3f9 100644 --- a/resources/tester/helper_280_1_combo_pick.cjs +++ b/resources/tester/helper_280_1_combo_pick.cjs @@ -5,7 +5,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const UI = path.join(ROOT, "ui_template"); @@ -143,24 +143,14 @@ const STUBS = { }, }; -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (request in STUBS) return STUBS[request]; if (request.startsWith(".")) return loadTs(`${path.resolve(path.dirname(full), request)}.ts`); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const item = (key, 열, 경로) => ({ key, 열, 경로 }); const GROUPS = [ diff --git a/resources/tester/helper_280_3_route_undo_facilities.cjs b/resources/tester/helper_280_3_route_undo_facilities.cjs index a92db1a09..74fc8bb1c 100644 --- a/resources/tester/helper_280_3_route_undo_facilities.cjs +++ b/resources/tester/helper_280_3_route_undo_facilities.cjs @@ -5,7 +5,7 @@ * 결과 = 표준출력 JSON. 짝 = `test_280_3_route_undo_facilities.py`. */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); const ROOT = path.join(__dirname, "..", ".."); @@ -52,14 +52,9 @@ globalThis.fetch = (url, init) => const STUBS = {}; const cache = new Map(); -function loadTs(full) { - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { load, loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; const key = Object.keys(STUBS).find((name) => request.endsWith(name)); if (key) return STUBS[key]; @@ -71,11 +66,8 @@ function loadTs(full) { return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`)); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} -const load = (...parts) => loadTs(path.join(ROOT, ...parts)); + }, +}); const sampler = load("common_util", "common_util_surface_sampler.ts"); const bytes = fs.readFileSync(process.argv[3]); diff --git a/resources/tester/helper_280_4_fit.cjs b/resources/tester/helper_280_4_fit.cjs index c5acd1a52..d0df2862e 100644 --- a/resources/tester/helper_280_4_fit.cjs +++ b/resources/tester/helper_280_4_fit.cjs @@ -3,27 +3,17 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) - : require(request); - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + : require(request), +}); const LOGIC = path.join(ROOT, "B05_Profile", "logic"); const fit = loadTs(path.join(LOGIC, "B05_Profile_RouteEdit_StationFit.ts")); diff --git a/resources/tester/helper_280_4_stations.cjs b/resources/tester/helper_280_4_stations.cjs index fa2d44782..b2c9991ca 100644 --- a/resources/tester/helper_280_4_stations.cjs +++ b/resources/tester/helper_280_4_stations.cjs @@ -3,26 +3,13 @@ * 짝 = `test_280_4_stations.py`. */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); -function loadTs(full) { - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { - if (CATALOG.isCatalogRequest(request)) return CATALOG; - if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); - return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, +}); const logic = loadTs(path.join(ROOT, "B05_Profile", "logic", "B05_Profile_Sections_Stations.ts")); const station = loadTs(path.join(ROOT, "common_util", "common_util_structure_station.ts")); diff --git a/resources/tester/helper_280_5_b07_settings.cjs b/resources/tester/helper_280_5_b07_settings.cjs index be4ebe47d..dc3a440fa 100644 --- a/resources/tester/helper_280_5_b07_settings.cjs +++ b/resources/tester/helper_280_5_b07_settings.cjs @@ -5,13 +5,12 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); const B07 = path.join(ROOT, "B07_DesignDetail"); const UI = path.join(ROOT, "ui_template"); -const cache = new Map(); class FakeNode { constructor(tag) { @@ -190,15 +189,9 @@ const STUBS = { }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; if (request === "@ui/ui_template_collapsible") @@ -208,11 +201,8 @@ function loadTs(file) { return fs.existsSync(target) ? loadTs(target) : {}; } return {}; - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const { buildDrawingSidePanel } = loadTs(path.join(B07, "B07_DesignDetail_UI_Panels.ts")); const { attachCollapsible } = loadTs(path.join(UI, "ui_template_collapsible.ts")); diff --git a/resources/tester/helper_281_1_drainage_zoom_range.cjs b/resources/tester/helper_281_1_drainage_zoom_range.cjs index 8d066aab7..3f1007d5f 100644 --- a/resources/tester/helper_281_1_drainage_zoom_range.cjs +++ b/resources/tester/helper_281_1_drainage_zoom_range.cjs @@ -4,29 +4,19 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); /** TS 한 장을 CommonJS 로 옮겨 실행한다. `@ui/` 는 색 함수만 쓰므로 빈 값으로 갈음한다. */ -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); if (request.startsWith("@ui/")) return { themeColor: () => "" }; return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const zoom = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Drainage_ZoomRange.ts")); const render = loadTs(path.join(ROOT, "B04_PreProcess", "B04_PreProcess_UI_MapRender.ts")); diff --git a/resources/tester/helper_283_route_reset_confirm.cjs b/resources/tester/helper_283_route_reset_confirm.cjs index 44629461d..a71e3ff41 100644 --- a/resources/tester/helper_283_route_reset_confirm.cjs +++ b/resources/tester/helper_283_route_reset_confirm.cjs @@ -4,21 +4,19 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request} (${file})`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request} (${file})`); + }, + }).loadTs(path.join(ROOT, file)); } const reset = loadTs("B05_Profile/logic/B05_Profile_Route_Reset.ts"); diff --git a/resources/tester/helper_284_1_pill_pick.cjs b/resources/tester/helper_284_1_pill_pick.cjs index 0bf09a76f..f2bcbb35a 100644 --- a/resources/tester/helper_284_1_pill_pick.cjs +++ b/resources/tester/helper_284_1_pill_pick.cjs @@ -4,23 +4,21 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (CATALOG.isCatalogRequest(request)) return CATALOG; - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (CATALOG.isCatalogRequest(request)) return CATALOG; + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(file); } const selection = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"), { diff --git a/resources/tester/helper_286_1_ford_pavement.cjs b/resources/tester/helper_286_1_ford_pavement.cjs index 117cd3199..af326b295 100644 --- a/resources/tester/helper_286_1_ford_pavement.cjs +++ b/resources/tester/helper_286_1_ford_pavement.cjs @@ -3,23 +3,21 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (CATALOG.isCatalogRequest(request)) return CATALOG; - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (CATALOG.isCatalogRequest(request)) return CATALOG; + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request}`); + }, + }).loadTs(file); } const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); diff --git a/resources/tester/helper_294_1_chute_remove_pick.cjs b/resources/tester/helper_294_1_chute_remove_pick.cjs index e18dc83b0..c295909a0 100644 --- a/resources/tester/helper_294_1_chute_remove_pick.cjs +++ b/resources/tester/helper_294_1_chute_remove_pick.cjs @@ -5,7 +5,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); @@ -165,14 +165,11 @@ globalThis.document = { querySelector: (s) => body.querySelector(s), }; -function loadTs(file) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - new Function("exports", "module", "require", js)(box.exports, box, require); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: () => require, +}); const dock = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Adjust_Dock.ts")); // ── 폼 흉내 — 명세 상자 = fieldset[data-spec-container] > legend(글 + [삭제]) ────────── diff --git a/resources/tester/helper_294_7_ford_slab_pick.cjs b/resources/tester/helper_294_7_ford_slab_pick.cjs index b1e235a3e..82057408d 100644 --- a/resources/tester/helper_294_7_ford_slab_pick.cjs +++ b/resources/tester/helper_294_7_ford_slab_pick.cjs @@ -4,7 +4,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -30,19 +30,15 @@ const STUBS = { "./B06_Section_UI_Page_Persist": { isBermType: (typeId) => typeId === "berm" }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const SP = loadTs(path.join(B06, "B06_Section_UI_Cross_Shape_Pick.ts")); const PO = loadTs(path.join(B06, "B06_Section_UI_Cross_Pick_Order.ts")); const SEL = loadTs(path.join(B06, "B06_Section_UI_Page_Structures_Selection.ts")); diff --git a/resources/tester/helper_295_pick_order_detail.cjs b/resources/tester/helper_295_pick_order_detail.cjs index 64f1f61f6..e702ec903 100644 --- a/resources/tester/helper_295_pick_order_detail.cjs +++ b/resources/tester/helper_295_pick_order_detail.cjs @@ -4,7 +4,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); @@ -18,21 +18,17 @@ const STUBS = { "./B06_Section_UI_Section_Common": { CROSS_PAD }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const P = loadTs(path.join(B06, "B06_Section_UI_Cross_Pick_Order.ts")); const G = loadTs(path.join(B06, "logic/B06_Section_UI_Cross_Detail_Geom.ts")); const S = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Select.ts")); diff --git a/resources/tester/helper_298_3_save_shape.cjs b/resources/tester/helper_298_3_save_shape.cjs index 3eeb98d67..43c5aa17e 100644 --- a/resources/tester/helper_298_3_save_shape.cjs +++ b/resources/tester/helper_298_3_save_shape.cjs @@ -4,7 +4,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const calls = []; @@ -60,18 +60,14 @@ const STUBS = { }, }; -function loadTs(full) { - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; throw new Error(`stub 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const save = loadTs(path.join(ROOT, "A00_Common", "design", "design_save.ts")); const ctx = (extra = {}) => ({ diff --git a/resources/tester/helper_298_add_reanalyze.cjs b/resources/tester/helper_298_add_reanalyze.cjs index 8edfbfa25..9e15f843e 100644 --- a/resources/tester/helper_298_add_reanalyze.cjs +++ b/resources/tester/helper_298_add_reanalyze.cjs @@ -4,35 +4,25 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); const toasts = []; const STUBS = { "@ui/ui_template_elements": { showToast: (message, kind) => toasts.push([message, kind]) }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request === "@config/config_frontend") return loadTs(path.join(ROOT, "config", "config_frontend.ts")); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); if (request.startsWith("@")) return new Proxy({}, { get: () => () => {} }); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const B05 = path.join(ROOT, "B05_Profile"); const fill = loadTs(path.join(B05, "B05_Profile_UI_Drainage_Ford_Fill.ts")); diff --git a/resources/tester/helper_311_panel_folded.cjs b/resources/tester/helper_311_panel_folded.cjs index 545a7a2f3..1f07b89b8 100644 --- a/resources/tester/helper_311_panel_folded.cjs +++ b/resources/tester/helper_311_panel_folded.cjs @@ -4,10 +4,9 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); // ── 화면 그릇 흉내 — 요소 · 클래스 · 속성 · 글자 · 자식 ────────────────────────── class FakeNode { @@ -134,26 +133,17 @@ const STUBS = { "./B06_Section_UI_Page_Common": { L: (key) => key }, }; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request === "@ui/ui_template_collapsible") return loadTs(path.join(ROOT, "ui_template", "ui_template_collapsible.ts")); if (request.endsWith(".css")) return {}; if (request.startsWith(".")) return loadTs(`${path.resolve(path.dirname(full), request)}.ts`); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const out = {}; const titleOf = (n) => n.children.find((c) => c.tagName === "h3")?.textContent ?? ""; diff --git a/resources/tester/helper_322_1_catalog.cjs b/resources/tester/helper_322_1_catalog.cjs index f1724f663..3db295cfd 100644 --- a/resources/tester/helper_322_1_catalog.cjs +++ b/resources/tester/helper_322_1_catalog.cjs @@ -6,22 +6,14 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); -const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); -function loadTs(full) { - if (full.endsWith(".json")) return JSON.parse(fs.readFileSync(full, "utf8")); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - const need = (spec) => loadTs(path.join(path.dirname(full), spec)); - new Function("exports", "module", "require", js)(box.exports, box, need); - return box.exports; -} +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + cache: false, + requireFor: (full) => (spec) => loadTs(path.join(path.dirname(full), spec)), +}); const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const catalog = loadTs(path.join(ROOT, "common_util", "common_util_structure_catalog.ts")); diff --git a/resources/tester/helper_323_9_definition_stamp.cjs b/resources/tester/helper_323_9_definition_stamp.cjs index d0b9627e8..4af4fb1fa 100644 --- a/resources/tester/helper_323_9_definition_stamp.cjs +++ b/resources/tester/helper_323_9_definition_stamp.cjs @@ -3,7 +3,7 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); @@ -54,16 +54,9 @@ globalThis.fetch = async (url, init = {}) => { throw new Error(`모르는 요청 ${at}`); }; -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (request === "@config/config_frontend") return { API_BASE_URL: "", API_TIMEOUT_MS: 1000 }; if (request.endsWith(".json")) return require(path.join(path.dirname(full), request)); @@ -71,11 +64,8 @@ function loadTs(file) { if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); if (request.startsWith("@")) return new Proxy({}, { get: () => () => undefined }); return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); const api = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_Api_Structures.ts")); // 472-6 — B06 [저장] = 한 본문 구조물 조각(옛 기본 창구 `flushPendingStructures` 걷음) · 서버 창구 = 가짜 PUT diff --git a/resources/tester/helper_323_9_twin_cards_3d.cjs b/resources/tester/helper_323_9_twin_cards_3d.cjs index 472538640..bd5b2d495 100644 --- a/resources/tester/helper_323_9_twin_cards_3d.cjs +++ b/resources/tester/helper_323_9_twin_cards_3d.cjs @@ -5,9 +5,8 @@ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); -const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); -const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, three: require(path.join(ROOT, "config", "node_modules", "three")), @@ -16,34 +15,18 @@ const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".json")) { - const data = JSON.parse(fs.readFileSync(full, "utf8")); - cache.set(full, data); - return data; - } - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2020, - esModuleInterop: true, - }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); const { buildCorridorStructures } = loadTs( path.join(ROOT, "B05_Profile", "B05_Profile_UI_Corridor_Structures.ts"), diff --git a/resources/tester/helper_342_3_batched.cjs b/resources/tester/helper_342_3_batched.cjs index 1190cdc09..ad33d6788 100644 --- a/resources/tester/helper_342_3_batched.cjs +++ b/resources/tester/helper_342_3_batched.cjs @@ -3,29 +3,19 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const cache = new Map(); -function loadTs(file) { - const full = path.resolve(file); - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + requireFor: (full) => (request) => { if (request.endsWith(".json")) return require(path.join(path.dirname(full), request)); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); if (request.startsWith("@ui/")) return { themeColor: () => "" }; return require(request); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - cache.set(full, box.exports); - return box.exports; -} + }, +}); (async () => { const { batched } = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Drainage_Pipes.ts")); diff --git a/resources/tester/helper_344_2_route_pairs.cjs b/resources/tester/helper_344_2_route_pairs.cjs index ce4aa2f28..546769a9f 100644 --- a/resources/tester/helper_344_2_route_pairs.cjs +++ b/resources/tester/helper_344_2_route_pairs.cjs @@ -3,21 +3,19 @@ */ const fs = require("fs"); const path = require("path"); -const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { - const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { - if (stubs[request]) return stubs[request]; - throw new Error(`stub 없음: ${request} (${file})`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; + return makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { + if (stubs[request]) return stubs[request]; + throw new Error(`stub 없음: ${request} (${file})`); + }, + }).loadTs(path.join(ROOT, file)); } const straightJudge = loadTs("B05_Profile/logic/B05_Profile_Profile_Straight.ts"); diff --git a/resources/tester/helper_ts_stub_loader.cjs b/resources/tester/helper_ts_stub_loader.cjs index baabafd03..d41c94492 100644 --- a/resources/tester/helper_ts_stub_loader.cjs +++ b/resources/tester/helper_ts_stub_loader.cjs @@ -7,7 +7,7 @@ * const { load, loadTs } = makeLoader({ root: ROOT, stubs: STUBS, ui: () => anyModule() }); * const store = load("B06_Section", "B06_Section_Section_Store.ts"); * - * 풀이 순서: 카탈로그 · STUBS · @config(가짜 주소) · @ui(`ui` 쪽 값 · 기본 showToast) · .css · .json · @util · 상대 경로 · node. + * 풀이 순서(기본): 카탈로그 · STUBS · @config(가짜 주소) · @ui(`ui` 쪽 값 · 기본 showToast) · .css · .json · @util · 상대 경로 · node. */ const fs = require("fs"); const path = require("path"); @@ -28,16 +28,23 @@ function memoryStorage() { }; } -function makeLoader({ root, stubs = {}, ui = () => ({ showToast: () => undefined }) }) { +/* 옵션 — 옮겨 온 옛 헬퍼가 저마다 달리 쓰던 것(헬퍼 동작 그대로). + * requireFor(full) 풀이 함수를 헬퍼가 직접 지음(기본 풀이 대신) · 인자 없으면 진짜 require + * interop esModuleInterop 로 트랜스파일 + * cache:false 같은 파일을 부를 때마다 새로 읽음(기본은 한 번만) + * fileStub(full) 파일 경로로 통째 대신할 값(없으면 undefined) */ +function makeLoader({ + root, + stubs = {}, + ui = () => ({ showToast: () => undefined }), + requireFor, + interop = false, + cache: useCache = true, + fileStub, +}) { const cache = new Map(); - function loadTs(full) { - if (cache.has(full)) return cache.get(full); - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { + function defaultRequire(full) { + return (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; const key = Object.keys(stubs).find((name) => request.endsWith(name)); if (key) return stubs[key]; @@ -50,7 +57,25 @@ function makeLoader({ root, stubs = {}, ui = () => ({ showToast: () => undefined if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); }; + } + function loadTs(file) { + const full = path.resolve(file); + if (useCache && cache.has(full)) return cache.get(full); + const stub = fileStub && fileStub(full); + if (stub) return stub; + if (full.endsWith(".json")) { + const data = JSON.parse(fs.readFileSync(full, "utf8")); + if (useCache) cache.set(full, data); + return data; + } + const compilerOptions = { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }; + if (interop) compilerOptions.esModuleInterop = true; + const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions }).outputText; + const box = { exports: {} }; + if (useCache) cache.set(full, box.exports); + const localRequire = requireFor ? requireFor(full) : defaultRequire(full); new Function("exports", "module", "require", js)(box.exports, box, localRequire); + if (useCache) cache.set(full, box.exports); return box.exports; } return { loadTs, load: (...parts) => loadTs(path.join(root, ...parts)) };