diff --git a/resources/tester/helper_280_2_step_nav.cjs b/resources/tester/helper_280_2_step_nav.cjs index 003add5f3..3e62d8c86 100644 --- a/resources/tester/helper_280_2_step_nav.cjs +++ b/resources/tester/helper_280_2_step_nav.cjs @@ -2,9 +2,8 @@ * 트랜스파일해 가짜 DOM 위에서 ① 주소마다 줄 차례 ② [<] [>] = 이웃 단계 단추 누름(꺼진 단계 = 알림만)을 * 돌려 JSON 한 벌로 냄. 확장자 `.cjs` 까닭은 `helper_design_view_parity.cjs` 머리와 같음. */ -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, "..", ".."); @@ -148,20 +147,16 @@ const STUBS = { }; globalThis.__DEV = false; -function loadTs(file) { - const src = fs.readFileSync(file, "utf8").replace(/import\.meta\.env\.DEV/g, "globalThis.__DEV"); - const js = ts.transpileModule(src, { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + rewrite: (js) => js.replace(/import\.meta\.env\.DEV/g, "globalThis.__DEV"), + requireFor: () => (request) => { if (request.endsWith(".css")) return {}; if (STUBS[request]) return STUBS[request]; throw new Error(`대역 없음: ${request}`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + }, +}); help = loadTs(path.join(ROOT, "ui_template", "ui_template_help.ts")); const layout = loadTs(path.join(ROOT, "ui_template", "ui_template_workflow_layout.ts")); diff --git a/resources/tester/helper_298_3_page_dispose.cjs b/resources/tester/helper_298_3_page_dispose.cjs index 453f7c36f..77f5449cd 100644 --- a/resources/tester/helper_298_3_page_dispose.cjs +++ b/resources/tester/helper_298_3_page_dispose.cjs @@ -2,22 +2,13 @@ * `design_leave` 를 그 자리에서 트랜스파일 · 페이지를 다섯 번 다시 세운 뒤 창 듣개 수 · 옛 노드 풀림. * `node --expose-gc` 로 부름 · 결과 JSON 을 stdout 으로. */ -const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); +const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); -const session = new Map(); -const storage = { - getItem: (key) => (session.has(key) ? session.get(key) : null), - setItem: (key, value) => session.set(key, String(value)), - removeItem: (key) => session.delete(key), - key: (index) => [...session.keys()][index] ?? null, - get length() { - return session.size; - }, -}; +const storage = memoryStorage(); const win = new EventTarget(); const doc = new EventTarget(); const counts = { window: 0, document: 0 }; @@ -54,24 +45,17 @@ const STUBS = { "../router": { currentRoute: () => "/b05-profile", setLeaveGuard: (fn) => (guard = fn) }, }; -const loaded = new Map(); -function loadTs(file) { - if (loaded.has(file)) return loaded.get(file); - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, - }).outputText; - const box = { exports: {} }; - loaded.set(file, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + target: ts.ScriptTarget.ES2022, + requireFor: (full) => (request) => { if (request in STUBS) return STUBS[request]; if (request.startsWith("@ui/")) return loadTs(path.join(ROOT, "ui_template", `${request.slice(4)}.ts`)); - if (request.startsWith(".")) return loadTs(path.join(path.dirname(file), `${request}.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 drag = loadTs(path.join(ROOT, "ui_template", "ui_template_overlay_drag.ts")); const leave = loadTs(path.join(ROOT, "A00_Common", "design", "design_leave.ts")); diff --git a/resources/tester/helper_322_0_golden.cjs b/resources/tester/helper_322_0_golden.cjs index 680623b4c..c8a78b601 100644 --- a/resources/tester/helper_322_0_golden.cjs +++ b/resources/tester/helper_322_0_golden.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 input = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const clone = (v) => JSON.parse(JSON.stringify(v)); @@ -137,27 +137,11 @@ const resolveFile = (base) => [base, `${base}.ts`, base.replace(/\.js$/, ".ts"), path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; -const cache = new Map(); -function loadTs(target) { - const full = path.resolve(target); - if (STUBS.has(full)) return STUBS.get(full); - if (cache.has(full)) return cache.get(full); - if (full.endsWith(".css")) return {}; // 화면 꼴(vite 몫) - 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 need = (spec) => { +const { loadTs } = makeLoader({ + root: ROOT, + interop: true, + fileStub: (full) => STUBS.get(full) ?? (full.endsWith(".css") ? {} : undefined), // .css = 화면 꼴(vite 몫) + requireFor: (full) => (spec) => { if (spec === "three") return require(path.join(ROOT, "config", "node_modules", "three")); const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) @@ -165,10 +149,8 @@ function loadTs(target) { return loadTs(resolveFile(path.join(ROOT, dir, spec.slice(prefix.length)))); if (spec.startsWith(".")) return loadTs(resolveFile(path.join(path.dirname(full), spec))); return require(spec); - }; - new Function("exports", "module", "require", js)(box.exports, box, need); - return box.exports; -} + }, +}); /* ── 견본 횡단 ───────────────────────────────────────────────────────────── */ const plainAt = (at) => ({ ...clone(input.plain_section), chainage_m: at, kind: "regular" }); diff --git a/resources/tester/helper_345_4_corridor_worker.cjs b/resources/tester/helper_345_4_corridor_worker.cjs index fa6edd906..056180c4a 100644 --- a/resources/tester/helper_345_4_corridor_worker.cjs +++ b/resources/tester/helper_345_4_corridor_worker.cjs @@ -5,20 +5,12 @@ 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(); -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.replace(/import\.meta\.url/g, '"file:///x.js"'); - const box = { exports: {} }; - cache.set(full, box.exports); - const localRequire = (request) => { +const { loadTs } = makeLoader({ + root: ROOT, + rewrite: (js) => js.replace(/import\.meta\.url/g, '"file:///x.js"'), + requireFor: (full) => (request) => { if (request.startsWith("@ui/") || request.startsWith("@config/")) return new Proxy({}, { get: () => () => undefined }); const target = request.startsWith("@util/") @@ -26,10 +18,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")); diff --git a/resources/tester/helper_347_pipe_id_reconcile.cjs b/resources/tester/helper_347_pipe_id_reconcile.cjs index 98b4032a3..ed131c80b 100644 --- a/resources/tester/helper_347_pipe_id_reconcile.cjs +++ b/resources/tester/helper_347_pipe_id_reconcile.cjs @@ -1,30 +1,25 @@ /* 347 덧 관 고정 id — `reconcilePipes`(되돌리기 · 구조물 폼으로 관 목록이 바뀔 때)가 id 를 따라 옮기는지. * `B05_Profile_UI_Drainage_Parts.ts` 를 그 자리에서 트랜스파일 · 그 밖 import 는 빈 껍데기(틀 = helper_281_1). */ -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 stub = new Proxy(function () {}, { get: () => stub, apply: () => stub }); -function loadTs(file, real) { - const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { - compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, - }).outputText; - const box = { exports: {} }; - const localRequire = (request) => - real.some((name) => request.endsWith(name)) - ? loadTs(path.join(path.dirname(file), `${request}.ts`), ["common_util_pipe_match"]) // 472-6 - : stub; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} +const TOP = path.join(ROOT, "B05_Profile", "B05_Profile_UI_Drainage_Parts.ts"); +// 진짜로 부를 import — 맨 위 = 고름 · 그 밑 = 관 짝 맞춤(472-6) · 그 밖 = 빈 껍데기 +const realOf = (full) => (full === TOP ? ["design_selection"] : ["common_util_pipe_match"]); +const { loadTs } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => + realOf(full).some((name) => request.endsWith(name)) + ? loadTs(path.join(path.dirname(full), `${request}.ts`)) + : stub, +}); -const { adoptDraftPipes, reconcilePipes } = loadTs( - path.join(ROOT, "B05_Profile", "B05_Profile_UI_Drainage_Parts.ts"), - ["design_selection"], -); +const { adoptDraftPipes, reconcilePipes } = loadTs(TOP); const before = [ { chainage_m: 100, reason: "stream", id: "a1" }, { chainage_m: 200, reason: "user", id: "b2" }, diff --git a/resources/tester/helper_427_1_follow_seat.cjs b/resources/tester/helper_427_1_follow_seat.cjs index 9cedb64f9..05e36c435 100644 --- a/resources/tester/helper_427_1_follow_seat.cjs +++ b/resources/tester/helper_427_1_follow_seat.cjs @@ -4,24 +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"); 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) => { +const { loadTs: loadFull, load } = makeLoader({ + root: ROOT, + cache: false, + requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; - if (request.startsWith(".")) return loadTs(path.join(path.dirname(file), `${request}.ts`)); - throw new Error(`stub 없음: ${request} (${file})`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + if (request.startsWith(".")) return loadFull(path.join(path.dirname(full), `${request}.ts`)); + throw new Error(`stub 없음: ${request} (${path.relative(ROOT, full)})`); + }, +}); +const loadTs = (file) => load(file); // 뿌리 기준 상대 경로 const cases = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const station = loadTs("common_util/common_util_structure_station.ts"); diff --git a/resources/tester/helper_459_route_seat.cjs b/resources/tester/helper_459_route_seat.cjs index c140ca4f3..a10b27f30 100644 --- a/resources/tester/helper_459_route_seat.cjs +++ b/resources/tester/helper_459_route_seat.cjs @@ -4,30 +4,22 @@ */ 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(file) { - const full = path.join(ROOT, 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: loadFull, load } = makeLoader({ + root: ROOT, + 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")); - if (request.startsWith(".")) return loadTs(path.join(path.dirname(file), `${request}.ts`)); - throw new Error(`stub 없음: ${request} (${file})`); - }; - new Function("exports", "module", "require", js)(box.exports, box, localRequire); - return box.exports; -} + if (request.startsWith(".")) return loadFull(path.join(path.dirname(full), `${request}.ts`)); + throw new Error(`stub 없음: ${request} (${path.relative(ROOT, full)})`); + }, +}); +const loadTs = (file) => load(file); // 뿌리 기준 상대 경로 const grade = loadTs("B05_Profile/logic/B05_Profile_Pipeline_Grade.ts"); const carry = loadTs("B05_Profile/logic/B05_Profile_Grade_Carry.ts"); diff --git a/resources/tester/helper_ts_stub_loader.cjs b/resources/tester/helper_ts_stub_loader.cjs index 30eb9b369..7115b4d5c 100644 --- a/resources/tester/helper_ts_stub_loader.cjs +++ b/resources/tester/helper_ts_stub_loader.cjs @@ -32,7 +32,9 @@ function memoryStorage() { * requireFor(full, file) 풀이 함수를 헬퍼가 직접 지음(기본 풀이 대신) · 인자 없으면 진짜 require * interop esModuleInterop 로 트랜스파일 * cache:false 같은 파일을 부를 때마다 새로 읽음(기본은 한 번만) - * fileStub(full) 파일 경로로 통째 대신할 값(없으면 undefined) */ + * fileStub(full) 파일 경로로 통째 대신할 값(없으면 undefined) + * target 트랜스파일 대상(기본 ES2020) + * rewrite(js) 트랜스파일한 글 손봄(`import.meta` 처럼 CommonJS 가 못 읽는 것) */ function makeLoader({ root, stubs = {}, @@ -41,6 +43,8 @@ function makeLoader({ interop = false, cache: useCache = true, fileStub, + target = ts.ScriptTarget.ES2020, + rewrite = (js) => js, }) { const cache = new Map(); function defaultRequire(full) { @@ -68,9 +72,11 @@ function makeLoader({ if (useCache) cache.set(full, data); return data; } - const compilerOptions = { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }; + const compilerOptions = { module: ts.ModuleKind.CommonJS, target }; if (interop) compilerOptions.esModuleInterop = true; - const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions }).outputText; + const js = rewrite( + ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions }).outputText, + ); const box = { exports: {} }; if (useCache) cache.set(full, box.exports); const localRequire = requireFor ? requireFor(full, file) : defaultRequire(full);