File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
export interface TokenEntry {
/** 그룹을 뗀 토큰 이름 (`--fox-color-background-default` → `background-default`) */
name: string;
/**
* 현재 테마·뷰포트에서 실제로 해석된 값 (`#FFFFFF`, `1.6rem`).
*
* 선언 원문이 아니라 계산값을 쓴다 — 색상은 Lightning CSS가 `light-dark()`를
* `var(--lightningcss-light, …) var(--lightningcss-dark, …)`로 폴리필해 내보내므로
* 원문이 읽기 어렵고, 크기는 alias가 `var(--fox-…)` 참조라 원문만으로는 값을 알 수
* 없다. 계산값은 지금 화면에 실제로 적용되는 값과 정확히 일치한다.
*/
value: string;
/** 스타일에서 참조할 커스텀 프로퍼티 전체 이름 */
property: string;
}
export type TokenRegistry = Record<string, TokenEntry[]>;
const PREFIX = "--fox-";
/**
* 문서의 모든 스타일시트를 훑어 `--fox-*` 커스텀 프로퍼티를 그룹별로 모은다.
*
* 테스트 페이지가 토큰 목록을 따로 들고 있지 않게 하려는 것이 목적이다 — `tokens/`에
* 값을 추가하면 여기에 자동으로 나타나고, 목록이 두 곳에 존재해 어긋날 일이 없다.
* CSSOM은 선언 순서를 보존하므로 SCSS map에 적은 순서가 그대로 유지된다.
*
* 브라우저에서만 동작한다(문서가 있어야 한다).
*/
export function readTokenRegistry(): TokenRegistry {
const registry: TokenRegistry = {};
const seen = new Set<string>();
// 이름·순서는 CSSOM 규칙에서, 값은 계산 스타일에서 가져온다.
const computed = getComputedStyle(document.documentElement);
for (const sheet of Array.from(document.styleSheets)) {
let rules: CSSRuleList;
try {
rules = sheet.cssRules;
} catch {
// 교차 출처 스타일시트는 규칙을 읽을 수 없다 — 우리 토큰은 동일 출처이므로 건너뛴다.
continue;
}
for (const rule of Array.from(rules)) {
if (!(rule instanceof CSSStyleRule) || rule.selectorText !== ":root") {
continue;
}
for (const property of Array.from(rule.style)) {
if (!property.startsWith(PREFIX) || seen.has(property)) {
continue;
}
seen.add(property);
const rest = property.slice(PREFIX.length);
const separator = rest.indexOf("-");
if (separator <= 0) {
continue;
}
(registry[rest.slice(0, separator)] ??= []).push({
name: rest.slice(separator + 1),
value:
computed.getPropertyValue(property).trim() ||
rule.style.getPropertyValue(property).trim(),
property,
});
}
}
}
return registry;
}