From be0661062f22bc6adf5a9069c7496b85e78b6b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 9 Nov 2026 19:23:29 +0900 Subject: [PATCH] fix(storybook): load local Noto Sans KR and this checkout's UI theme - Dev server: rewrite the @hm-design/ui dist font URL to the /fonts static directory so Preview no longer gets a 403 from the Vite fs allow list. - When node_modules/@hm-design/ui is linked to another checkout, resolve the five @hm-design/ui entry points to this checkout's packages/ui source so the current tokens are shown instead of a stale palette. Checkouts whose links already point at their own packages/ui keep the dist-based resolution. --- .storybook/main.ts | 61 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index 0b2c1b2..73605cf 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,5 +1,57 @@ import type { StorybookConfig } from "@storybook/react-vite" -import { readFileSync } from "node:fs" +import { readFileSync, realpathSync } from "node:fs" +import { fileURLToPath } from "node:url" +import path from "node:path" + +// Checkouts that share another checkout's node_modules (a linked git worktree whose node_modules is a junction) also +// inherit that checkout's node_modules/@hm-design/* links. Those links point at the other checkout's packages/ui and its +// compiled dist, so Storybook would show the other checkout's tokens (a stale palette) instead of this checkout's. +// Only in that case, resolve the five @hm-design/ui entry points to this checkout's own source. Nothing is built, the +// other checkout's node_modules and dist are only read, and a checkout whose links already point at its own +// packages/ui keeps the unchanged dist-based resolution. +const checkoutRoot = fileURLToPath(new URL("../", import.meta.url)) +const localUiRoot = path.join(checkoutRoot, "packages/ui") +function uiLinkedElsewhere() { + try { + return realpathSync(path.join(checkoutRoot, "node_modules/@hm-design/ui")) !== realpathSync(localUiRoot) + } catch { + return false + } +} +const localUiAliases = [ + [/^@hm-design\/ui$/, "src/index.ts"], + [/^@hm-design\/ui\/theme$/, "src/theme/index.ts"], + [/^@hm-design\/ui\/theme\.css$/, "src/theme.css"], + [/^@hm-design\/ui\/tailwind\.css$/, "src/tailwind.css"], + [/^@hm-design\/ui\/dashboard-status\.css$/, "src/patterns/dashboard-status/dashboard-status.css"], +].map(([find, file]) => ({ find: find as RegExp, replacement: path.join(localUiRoot, file as string) })) + +// @hm-design/ui's dist/theme.css declares the bundled font with a relative URL. When node_modules/@hm-design/ui is +// symlinked to a checkout outside this Vite root, Vite rewrites that URL to /@fs/ and the dev preview +// gets a 403 ("outside of Vite serving allow list"). Point only that font URL at the /fonts static directory the +// Manager already uses, instead of widening server.fs.allow. theme.css is inlined through @import, so the rewrite +// runs on the already-resolved CSS and is limited to the exact ".../ui/dist/fonts/NotoSansKR-variable.ttf" URL. +// Dev server only: the static build emits the font as a bundled asset. +const UI_DIST_FONT_URL = /[^"'()\\\s]*\/ui\/dist\/fonts\/NotoSansKR-variable\.ttf/g +const FONT_STATIC_URL = "/fonts/NotoSansKR-variable.ttf" + +const hmUiThemeFontPath = { + name: "hm-ui-theme-font-path", + apply: "serve" as const, + enforce: "post" as const, + transform(code: string, id: string) { + const file = id.split("?")[0] + if (!file.endsWith(".css") || !code.includes("NotoSansKR-variable.ttf")) return null + const next = code.replace(UI_DIST_FONT_URL, FONT_STATIC_URL) + if (next === code) { + if (!code.includes(FONT_STATIC_URL)) { + this.warn(`[hm-ui-theme-font-path] ${file}: found NotoSansKR-variable.ttf but not at ".../ui/dist/fonts/"; the URL was left unchanged and Preview may fail to load Noto Sans KR.`) + } + return null + } + return { code: next, map: null } + }, +} const config: StorybookConfig = { stories: ["../src/**/*.stories.@(ts|tsx)"], @@ -21,6 +73,13 @@ const config: StorybookConfig = { viteFinal: (config) => { config.server ??= {} config.server.watch = { ...config.server.watch, ignored: ["**/_local/**", "**/storybook-static/**"] } + config.plugins = [...(config.plugins ?? []), hmUiThemeFontPath] + if (uiLinkedElsewhere()) { + config.resolve ??= {} + const existing = config.resolve.alias + const current = Array.isArray(existing) ? existing : Object.entries(existing ?? {}).map(([find, replacement]) => ({ find, replacement })) + config.resolve.alias = [...localUiAliases, ...current] + } return config }, framework: "@storybook/react-vite",