diff --git a/.changeset/flags-subpath.md b/.changeset/flags-subpath.md new file mode 100644 index 0000000..bb42008 --- /dev/null +++ b/.changeset/flags-subpath.md @@ -0,0 +1,7 @@ +--- +"@devslab/site-kit": patch +--- + +Move flag locale menu data (`FLAG_COUNTRY`, `LOCALE_FLAGS`, `flagFor`) off the runtime-neutral `.` entry and onto a dedicated `@devslab/site-kit/flags` subpath, so consumers that never render a flag menu no longer bundle ~110 KB of vendored SVG. `src/solid/locale-menu.tsx` already imported `../core/flags.mjs` directly, so this only changes the public barrel. + +Since `0.4.0` has not been published yet, this patch bump lands as `0.4.1` — the fixed release group means the flag locale menu (0.4.0) and this follow-up ship together as `0.4.1`. diff --git a/packages/site-kit/README.ko.md b/packages/site-kit/README.ko.md index 0f0e040..3a95145 100644 --- a/packages/site-kit/README.ko.md +++ b/packages/site-kit/README.ko.md @@ -24,7 +24,9 @@ claim leaf가 검증된 사실 레지스트리를 참조하도록 강제한다. 디스클로저를 렌더링한다 — JavaScript 없이도 동작하며, Solid는 Escape로 닫기와 `onLocaleChange(locale, href)` 콜백을 더한다. `SiteHeader`는 `localeVariant`를 그대로 전달한다. 국기 데이터(`FLAG_COUNTRY`, `LOCALE_FLAGS`, `flagFor`)는 -런타임 중립 entry에서 export되며, 아트워크는 flag-icons에서 벤더링했다(MIT, +런타임 중립 `.` entry가 아니라 전용 서브패스 `@devslab/site-kit/flags`에서 +export된다 — 벤더링한 아트워크가 SVG ~110 KB라 대부분의 소비자는 국기 메뉴를 +렌더링하지 않기 때문이다. 아트워크는 flag-icons에서 벤더링했다(MIT, `flags/LICENSE-flag-icons.txt`). 국기는 `dds-icons` 항목이 아니라 site-kit 데이터다 — 아이콘 세트의 계약이 단색 `currentColor` 스트로크를 요구하기 때문이다. diff --git a/packages/site-kit/README.md b/packages/site-kit/README.md index 34e6ac8..fdd33cb 100644 --- a/packages/site-kit/README.md +++ b/packages/site-kit/README.md @@ -19,4 +19,4 @@ control search indexing, citation crawlers, and model-training crawlers. ## Locale menu variants -`LocaleMenu` renders a native `` by default. `variant="flag"` renders a `
` disclosure whose trigger is the current locale's flag and whose rows are flag + native-name links — it works without JavaScript; Solid adds Escape-to-close and the `onLocaleChange(locale, href)` callback. `SiteHeader` forwards `localeVariant`. Flag data (`FLAG_COUNTRY`, `LOCALE_FLAGS`, `flagFor`) is exported from `@devslab/site-kit/flags`, a dedicated subpath — not the runtime-neutral `.` entry — because the vendored artwork is ~110 KB of SVG and most consumers never render a flag menu. The artwork is vendored from flag-icons (MIT, `flags/LICENSE-flag-icons.txt`). Flags are site-kit data, not `dds-icons` entries, because the icon set's contract requires single-colour `currentColor` strokes. diff --git a/packages/site-kit/package.json b/packages/site-kit/package.json index 137f0d2..d9a88b3 100644 --- a/packages/site-kit/package.json +++ b/packages/site-kit/package.json @@ -34,6 +34,10 @@ "types": "./src/core/index.d.mts", "import": "./src/core/index.mjs" }, + "./flags": { + "types": "./src/core/flags.d.mts", + "import": "./src/core/flags.mjs" + }, "./solid": { "types": "./dist/index.d.ts", "worker": "./dist/solid.server.js", diff --git a/packages/site-kit/scripts/build-flags.mjs b/packages/site-kit/scripts/build-flags.mjs index 7a5a5e1..d61c4bf 100644 --- a/packages/site-kit/scripts/build-flags.mjs +++ b/packages/site-kit/scripts/build-flags.mjs @@ -17,7 +17,8 @@ const FLAG_COUNTRY = { ko: "kr", en: "us", ja: "jp", "zh-HK": "hk", "zh-TW": "tw", hi: "in", vi: "vn", id: "id", th: "th", "pt-BR": "br", fr: "fr", de: "de", es: "es", ar: "sa", }; -const HEADER = "/* Generated by scripts/build-flags.mjs — do not edit. Source: flags/*.svg (flag-icons, MIT) */"; +const HEADER = + "/* Generated by scripts/build-flags.mjs — do not edit. Source: flags/*.svg (flag-icons, MIT). Copyright (c) 2013 Panayiotis Lipiridis */"; const entries = Object.entries(FLAG_COUNTRY).map(([locale, country]) => { const text = readFileSync(join(pkg, "flags", `${country}.svg`), "utf8").trim(); @@ -27,6 +28,10 @@ const entries = Object.entries(FLAG_COUNTRY).map(([locale, country]) => { if (!viewBox) throw new Error(`flags/${country}.svg: no viewBox`); const body = text.replace(/^]*>/, "").replace(/<\/svg>\s*$/, "").trim(); if (/ - +
    diff --git a/scripts/verify-site-kit-release.mjs b/scripts/verify-site-kit-release.mjs index 45bb979..d0ba179 100644 --- a/scripts/verify-site-kit-release.mjs +++ b/scripts/verify-site-kit-release.mjs @@ -51,7 +51,13 @@ try { const installedRoot = join(temp, "node_modules", "@devslab", "site-kit"); const manifest = JSON.parse(await readFile(join(installedRoot, "package.json"), "utf8")); assert.equal(manifest.name, "@devslab/site-kit"); - for (const path of ["dist/solid.js", "dist/index.d.ts", "src/core/index.mjs", "src/core/index.d.mts", "src/tanstack-start.mjs", "src/tanstack-start.d.mts", "styles.css"]) { + for (const path of [ + "dist/solid.js", "dist/index.d.ts", + "src/core/index.mjs", "src/core/index.d.mts", + "src/core/flags.mjs", "src/core/flags.d.mts", + "src/tanstack-start.mjs", "src/tanstack-start.d.mts", + "styles.css", "flags/LICENSE-flag-icons.txt", + ]) { await access(join(installedRoot, path)); } assert.equal(manifest.publishConfig.access, "public"); @@ -59,6 +65,20 @@ try { assert.equal(manifest.peerDependencies["solid-js"], "1.9.15"); const core = await import(pathToFileURL(join(installedRoot, "src", "core", "index.mjs"))); assert.equal(core.LOCALES.length, 14); + assert.equal( + typeof (await import(pathToFileURL(join(installedRoot, "src", "core", "flags.mjs")))).flagFor, + "function", + "src/core/flags.mjs must export flagFor", + ); + assert.ok(manifest.exports["./flags"], "package.json exports must declare a ./flags subpath"); + const subpathProbe = join(temp, "resolve-flags-subpath.mjs"); + await writeFile( + subpathProbe, + 'import { flagFor } from "@devslab/site-kit/flags";\nif (typeof flagFor !== "function") throw new Error("@devslab/site-kit/flags did not resolve to flagFor");\n', + "utf8", + ); + const subpathResult = spawnSync(process.execPath, [subpathProbe], { cwd: temp, encoding: "utf8" }); + if (subpathResult.status !== 0) throw new Error(`@devslab/site-kit/flags subpath resolution failed\n${subpathResult.stdout}\n${subpathResult.stderr}`); console.log("site-kit pack, public publish dry-run, and fresh consumer import passed"); } finally { await rm(temp, { recursive: true, force: true }); diff --git a/tests/browser/site-kit.spec.ts b/tests/browser/site-kit.spec.ts index 07a717e..bacd6d5 100644 --- a/tests/browser/site-kit.spec.ts +++ b/tests/browser/site-kit.spec.ts @@ -19,7 +19,7 @@ const flagFixture = `لينك - +

    واجهة عربية طويلة لا ينبغي أن تتجاوز عرض الشاشة

    `; @@ -110,7 +110,7 @@ for (const width of [1280, 375]) { await page.getByRole("button", { name: "فتح القائمة" }).click(); } const trigger = page.locator(".site-locale-flag__trigger"); - await expect(trigger).toHaveAttribute("aria-label", "العربية"); + await expect(trigger).toHaveAttribute("aria-label", "اللغة: العربية"); const box = (await trigger.boundingBox())!; expect(box.width).toBeGreaterThanOrEqual(44); expect(box.height).toBeGreaterThanOrEqual(44); diff --git a/tests/site-kit-contracts.test.mjs b/tests/site-kit-contracts.test.mjs index 676c15b..4914dd4 100644 --- a/tests/site-kit-contracts.test.mjs +++ b/tests/site-kit-contracts.test.mjs @@ -52,7 +52,11 @@ test("flag locale menu is a native disclosure with tokenised, logical styles", a assert.match(styles, /min-block-size:\s*44px/); assert.doesNotMatch(styles, /#[0-9a-fA-F]{3,8}\b|\brgba?\(|\bhsla?\(/, "site-kit styles must stay on tokens"); const core = await read("packages/site-kit/src/core/index.mjs"); - assert.match(core, /flagFor/); + assert.doesNotMatch(core, /flagFor/); + const manifest = await json("packages/site-kit/package.json"); + assert.ok(manifest.exports["./flags"], "flag data must ship on its own subpath, not the root barrel"); + assert.equal(manifest.exports["./flags"].types, "./src/core/flags.d.mts"); + assert.equal(manifest.exports["./flags"].import, "./src/core/flags.mjs"); assert.match(await read("packages/site-kit/package.json"), /build-flags\.mjs --check/); });