diff --git a/resources/tester/spreadsheet/spreadsheet_size.mjs b/resources/tester/spreadsheet/spreadsheet_size.mjs index 57eff220..618be74c 100644 --- a/resources/tester/spreadsheet/spreadsheet_size.mjs +++ b/resources/tester/spreadsheet/spreadsheet_size.mjs @@ -1,6 +1,10 @@ -// 스프레드시트 크기 재기 — vite 로 줄여 묶은 뒤 gzip 크기(바이트). `test_spreadsheet_size.py` 가 부름. -// node spreadsheet_size.mjs <저장소 뿌리> → {"engine": {js, css}, "total": {js, css}} -// 앱 번들에 이미 있는 `@ui/ui_template_elements*` 는 빼고 잼(스프레드시트가 새로 더하는 몫만). +// 스프레드시트 크기 재기 — gzip 크기(바이트). `test_spreadsheet_size.py` 가 부름. +// node spreadsheet_size.mjs <저장소 뿌리> → {"engine": {js, css}, "total": {js, css}, "static": [...]} +// engine = 엔진 파일만 줄여 묶은 크기 · total = 실제 앱 빌드에서 스프레드시트를 열 때 받는 조각 +// (스프레드시트 조각 + 끌어오는 공용 조각 + css · 첫 화면 `index.html` 조각은 뺌). +// static = 스프레드시트 조각을 정적으로 끌어오는 조각(비어야 함 — 늦게만 받음). +import fs from "node:fs"; +import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { gzipSync } from "node:zlib"; @@ -25,7 +29,7 @@ const ENGINE = [ "spreadsheet_fill", ]; -async function measure(entries) { +async function engineSize() { const out = await build({ configFile: false, logLevel: "silent", @@ -35,22 +39,60 @@ async function measure(entries) { write: false, minify: true, target: "es2022", - cssCodeSplit: false, - lib: { entry: entries, formats: ["es"] }, - rollupOptions: { external: [/ui_template_elements/] }, + lib: { entry: ENGINE.map((name) => path.join(dir, `${name}.ts`)), formats: ["es"] }, }, }); - const outputs = (Array.isArray(out) ? out : [out]).flatMap((o) => o.output); - const size = { js: 0, css: 0 }; - for (const o of outputs) { - const body = o.type === "chunk" ? o.code : o.source; - const gz = gzipSync(typeof body === "string" ? body : Buffer.from(body)).length; - if (o.fileName.endsWith(".css")) size.css += gz; - else if (o.fileName.endsWith(".js")) size.js += gz; + let js = 0; + for (const o of (Array.isArray(out) ? out : [out]).flatMap((o) => o.output)) { + if (o.type === "chunk") js += gzipSync(o.code).length; } - return size; + return { js, css: 0 }; } -const engine = await measure(ENGINE.map((name) => path.join(dir, `${name}.ts`))); -const total = await measure([path.join(dir, "spreadsheet.ts")]); -console.log(JSON.stringify({ engine, total })); +async function appSize() { + const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "aislo-sheet-size-")); + try { + await build({ + configLoader: "runner", + configFile: path.join(root, "vite.config.ts"), + logLevel: "silent", + build: { outDir, emptyOutDir: true, manifest: true }, + }); + const manifest = JSON.parse(fs.readFileSync(path.join(outDir, ".vite/manifest.json"), "utf8")); + const key = Object.keys(manifest).find((k) => k.endsWith("spreadsheet/spreadsheet.ts")); + const gz = (file) => gzipSync(fs.readFileSync(path.join(outDir, file))).length; + // M02 가 이미 받은 조각은 새 몫이 아님 — 첫 화면 · M02 페이지 · M02 표(ui_template_sheet 등) + // 의 정적 import 사슬. 산출근거 카드는 집계표를 연 뒤에만 열림. + const first = new Set(); + const mark = (k) => { + if (first.has(k)) return; + first.add(k); + for (const i of manifest[k].imports ?? []) mark(i); + }; + const page = Object.keys(manifest).find((k) => k.endsWith("M02_MasterTemplete_UI_Page.ts")); + mark("index.html"); + mark(page); + for (const d of manifest[page].dynamicImports ?? []) if (d !== key) mark(d); + const size = { js: 0, css: 0 }; + const seen = new Set(); + const walk = (k) => { + if (seen.has(k) || first.has(k)) return; + seen.add(k); + const e = manifest[k]; + size.js += gz(e.file); + for (const c of e.css ?? []) size.css += gz(c); + for (const i of e.imports ?? []) walk(i); // 그 밖 공용 조각은 셈(넉넉히) + }; + walk(key); + const statics = Object.entries(manifest) + .filter(([, v]) => (v.imports ?? []).includes(key)) + .map(([k]) => k); + return { total: size, static: statics }; + } finally { + fs.rmSync(outDir, { recursive: true, force: true }); + } +} + +const engine = await engineSize(); +const { total, static: statics } = await appSize(); +console.log(JSON.stringify({ engine, total, static: statics })); diff --git a/resources/tester/spreadsheet/test_spreadsheet_size.py b/resources/tester/spreadsheet/test_spreadsheet_size.py index 5fad6bd2..1cb79355 100644 --- a/resources/tester/spreadsheet/test_spreadsheet_size.py +++ b/resources/tester/spreadsheet/test_spreadsheet_size.py @@ -1,7 +1,8 @@ """F — 스프레드시트 크기 예산 · M02 만 늦게 불러옴(계약 `spreadsheet_types.ts` 머리). 예산(압축 gzip 뒤) — 엔진 A+B 22 KB · 화면 C+D+E 28 KB · css 4 KB · 합 54 KB. -엔진은 파일마다 따로 묶어 더하므로 실제보다 조금 크게 잼(넘치면 넘친 것). +엔진 = 엔진 파일만 따로 묶은 크기 · 합 = 실제 앱 빌드에서 스프레드시트를 열 때 받는 조각 +(공용 조각까지 넉넉히 셈) · 화면 = 합 − 엔진. """ import json @@ -36,6 +37,7 @@ def test_크기_예산(): print( f"gzip — 엔진 {engine} · 화면 {view} · css {total['css']} · 합 {total['js'] + total['css']}" ) + assert size["static"] == [], f"스프레드시트 조각을 정적으로 끌어옴 — {size['static']}" assert engine <= BUDGET["engine"], f"엔진 {engine} B > {BUDGET['engine']}" assert view <= BUDGET["view"], f"화면 {view} B > {BUDGET['view']}" assert total["css"] <= BUDGET["css"], f"css {total['css']} B > {BUDGET['css']}"