Repository navigation
[전수조사/C] styled/compat 런타임 계약: as, prop 전달, theme, 크래시 #689
Copy link
Copy link
Open
Labels
bugSomething isn't workingSomething isn't working
Description
styled-components v6 / Emotion 11(최신 기준)에서 옮겨 온 코드가 그대로 동작하지 않거나 런타임에 크래시하는 문제들입니다. 런타임 추가 없이 해결합니다.
전체 계획과 결정 사항은 추적 이슈(#682)에 있습니다. 조사 기준 커밋: 77daad74 (현재 main d0b84255).
결정에 따른 작업
- (필수)
as/forwardedAs지원 - (필수) prop 전달(런타임 없음): 스타일 함수가 읽는 prop은 DOM에 안 넘김(태그의 유효 속성이면 넘김, 속성 목록은 컴파일러 안에만),
$prop 제외,shouldForwardProp은 빌드 타임 계산(불가하면 빌드 에러), 해석 가능한 컴포넌트는 호출하는 곳에서 버려질 prop 제거, spread로 들어오는 모르는 prop은 그대로 전달(문서화) - (필수) theme:
useTheme/withTheme은 실제 theme 객체를 context로 제공(스타일은 계속 CSS 변수). 토큰으로 못 바꾸는 theme 조회(theme.space[2])는 변수로 매핑하거나 빌드 에러 - (권장) 런타임 전용 API: cache/server/ServerStyleSheet/StyleSheetManager 옵션은 아무 일도 안 하는 호환 구현, stylis 플러그인은 빌드 에러+대안(logical property), test-utils/native는 미지원 문서화
발견 항목
심각도: P0 = 흔한 사용에서 조용히 틀리거나 크래시, P1 = 흔한 패턴이 명시적 에러로 막히거나 드물게 조용히 틀림, P2 = 드문 경우·도구/문서 불일치, P3 = 있으면 좋음.
필수 (14)
- EMO-05 (P0, MISMATCH) Default alias and generated declarations cover only a subset of @emotion/react while migration docs say the pa
- 근거: packages/react/src/compat/emotion.d.ts:7-15 declares only css/keyframes/Global/ThemeProvider/useTheme/withTheme; probes retain jsx, ClassNames and CacheProvider imports on @emotion/react; import_alias_visit.rs:202-218 has no equivalents and :537-559 retains them; migration/overview/page.mdx:10,60,72-83 promises all calls compile and the original dependency can be removed
- 수정 방향: make generated ambient declarations accurately include every retained export and stop claiming uninstallability until all runtime exports are absorbed; jsx/ClassNames need real compat implementations or extraction
- EMO-06 (P0, CRASH) ThemeProvider/useTheme/withTheme do not preserve Emotion's theme-object contract
- 근거: ThemeProvider.tsx:11-12 wraps children in a div and flattens values; theme-vars.ts:44-58 returns recursive Proxy objects; react-theme-function.tsx retains the theme function, for which Object.entries(function) yields no override;
{current.colors.primary}/{theme.color}receives a Proxy object rather thanhotpink/red - 수정 방향: either implement actual context-compatible runtime semantics or narrow/document the shim as style-interpolation-only and reject value-level use/functions; do not expose it under the full Emotion type/API contract otherwise
- 근거: ThemeProvider.tsx:11-12 wraps children in a div and flattens values; theme-vars.ts:44-58 returns recursive Proxy objects; react-theme-function.tsx retains the theme function, for which Object.entries(function) yields no override;
- EMO-08 (P0, WRONG) Emotion prop forwarding rules and shouldForwardProp are ignored (KNOWN-3)
- 근거: styled-options-forward.tsx emits
({..., ...rest}) => <h1 {...rest}>, sotone,invalidThing, and validdata-okall reach the host; styled-default-filter.tsx likewise leavescolor,customStyleProp,theme, and validaria-labelin rest - 수정 방향: generate string-tag rest filtering equivalent to @emotion/is-prop-valid plus theme exclusion, then apply statically analyzable shouldForwardProp; reject non-static predicates rather than silently ignoring them
- 근거: styled-options-forward.tsx emits
- EMO-10 (P0, WRONG) styled
asis forwarded instead of changing the rendered tag (KNOWN-2)- 근거: styled-as.tsx remains
<Button as="a">; transformed Button always renders<button {...rest}>, forwardingasto that host - 수정 방향: lower styled render target through the existing polymorphic-element path and coordinate
asconsumption with shouldForwardProp
- 근거: styled-as.tsx remains
- EMO-11 (P0, CRASH) withComponent survives on a generated arrow function and throws
- 근거: styled-with-component.tsx emits
const Section = (...) => <section...>; export const Aside = Section.withComponent("aside"); Section has no method - 수정 방향: recognize
.withComponent(target)before replacing the styled binding, copy the extracted style set to a component rendered as target, and preserve target prop types
- 근거: styled-with-component.tsx emits
- EMO-14 (P0, CRASH) Non-mappable theme reads retain code that executes without a real Emotion theme (KNOWN-4)
- 근거: styled-theme-template.tsx proves simple paths map to
var(--colors-primary), but ThemeProvider/useTheme supply only flattened variables/proxies; calls such astheme.space[2]ortheme.brand()cannot reproduce Emotion values and can throw - 수정 방향: recognize exact static property/index reads (including flattened arrays) and diagnose callable/non-coercible reads at build time; do not leave them to a runtime without an object theme
- 근거: styled-theme-template.tsx proves simple paths map to
- RT-20 (P0, MISMATCH) Compat ThemeProvider changes DOM structure and rejects upstream theme forms
- 근거: it always inserts
<div style="display:contents">(ThemeProvider.tsx:11-12). This is invalid in structures such as table/select, changes child/descendant selectors and DOM relationships, and does not accept function themes or merge theme objects. Styled-components and Emotion use context, support nested/function themes, and expose merged objects: https://styled-components.co… - 수정 방향: Use a context provider for JS semantics; scope CSS variables without a universal invalid wrapper (or explicitly reject/document incompatible placements and function themes).
- 근거: it always inserts
- RT-21 (P0, WRONG) Compat useTheme returns an empty non-reactive Proxy, not the theme object
- 근거: runtime probe:
String(t.colors.brand) -> var(--colors-brand), butJSON.stringify(t) -> {},Object.keys(t) -> [],{...t} -> {}. It never reads provider context or re-renders. JS consumers (serialization, spreading, branching, arithmetic, canvas colors, external libraries) silently receive proxy paths rather than configured values. - 수정 방향: Back compat theming with React context containing the real merged object; reserve CSS-variable accessors for extracted style expressions only.
- 근거: runtime probe:
- SC-17 (P0, CRASH) Alias rewriting converts retained type-only imports into runtime imports
- 근거:
53-default-theme.tsxsource hasimport { type DefaultTheme, ThemeProvider }; output splits ThemeProvider to compat but emitsimport { DefaultTheme } from "styled-components".60-exported-types.tsxsimilarly emits value imports for fourtypespecifiers.generate_transformed_importrebuilds every retainedImportSpecifierviapush_specifierwithout preserving decla… - 수정 방향: Preserve
import typeat declaration/specifier granularity when reconstructing retained and redirected imports; add mixed default/value/type import regression cases.
- 근거:
- SC-06 (P1, CRASH)
ThemeConsumeris absent and survives as a runtime styled-components dependency- 근거:
22-theme-consumer.tsxretainsimport { ThemeConsumer } from "styled-components";devup_equivalenthas no ThemeConsumer mapping (import_alias_visit.rs:202-218), compat exports omit it (packages/react/src/compat/index.ts:10-20), and the ambient module omits it (compat/styled-components.d.ts:1-18). A dependency-free alias migration therefore cannot resolve/build it. - 수정 방향: Export a compat consumer backed by the same theme mechanism as ThemeProvider/useTheme and add it to runtime aliasing and ambient types.
- 근거:
- SC-07 (P1, MISMATCH)
useThemeandwithThemereturn CSS references, not the supplied theme- 근거:
20-use-theme.tsx/21-with-theme.tsxredirect to compat.useStyledThemealways returns a global recursive proxy (use-styled-theme.ts:4-15); readingprimarystringifies tovar(--primary)(theme-vars.ts:44-58) regardless of the provider's{primary:'red'}.withThemeinjects the same proxy (with-theme.tsx:6-11). Devup documents this divergence atpage.mdx:93-103… - 수정 방향: Back compat theming with context containing raw merged values while continuing to expose CSS variables for extracted CSS; useTheme/withTheme should return the raw current value.
- 근거:
- SC-15 (P1, CRASH) v6.4
createThemeis neither aliased nor typed- 근거:
59-create-theme.tsxkeepsimport { createTheme } from "styled-components"while redirecting ThemeProvider; it then evaluatescreateThemeat runtime.devup_equivalentlacks the export (import_alias_visit.rs:202-218), compat runtime and ambient declarations omit it, and the transformed styled interpolation becomes a runtime variable sourced from that retained dependency… - 수정 방향: Add a styled-components-specific createTheme compat implementation and extractor recognition, or issue an explicit build error naming the unsupported v6.4 API rather than retaining a hidden runtime dependency.
- 근거:
- SC-11 (P2, CRASH) v5
.withComponent()crashes after extraction- 근거:
48-with-component.tsxleavesconst Y = X.withComponent("button")after replacingXwith a plain function. That function has nowithComponent, so module evaluation throws. The API is removed in v6 but is part of v5 compatibility. - 수정 방향: Either compile
.withComponent(target)as a definition-time target substitution for v5 source or emit an explicit build error instead of a runtime crash.
- 근거:
- SC-14 (P2, CRASH)
keyframes().getName()becomes a string method call and crashes- 근거:
61-keyframes-get-name.tsxtransformsconst spin = keyframes\...`toconst spin = "a"but leavesspin.getName(), causingTypeError: spin.getName is not a function` at module evaluation. - 수정 방향: Rewrite known
.getName()calls on extracted keyframes bindings to the generated name string, and error on unsupported runtime model use.
- 근거:
권장 (7)
- EMO-09 (P1, WRONG) styled options label and target are silently ignored
- 근거: styled-options-label-target.tsx outputs only generated class
a, with neither readable label nor stableemo-targetclass - 수정 방향: consume label as metadata and attach target as an additional stable marker class; coordinate target with component-selector lowering
- 근거: styled-options-label-target.tsx outputs only generated class
- RT-22 (P1, WRONG) Theme flattening silently collides and can recurse forever
- 근거: both
{a:{b:'red'}, 'a-b':'blue'}keys map to--a-b; runtime probe ends with only blue. Arrays flatten to numeric segments, arbitrary keys are not escaped, and cyclic objects recurse without detection (theme-vars.ts:21-34). - 수정 방향: Validate/escape segments, reject collisions/cycles with clear errors, and define array/null support.
- 근거: both
- RT-23 (P1, MISMATCH) withTheme drops upstream prop/ref/static behavior
- 근거: wrapper always overwrites
themewith the proxy, types remove callertheme, and it neither sets a displayName nor hoists component statics (with-theme.tsx:6-11). React-18 refs are not forwarded. Upstream usesComponentPropsWithRef, resolves explicit/provider theme, setsWithTheme(...), and hoists statics: https://github.com/styled-components/styled-components/blob/main… - 수정 방향: Match upstream prop/theme precedence, forward refs for the supported React range, set displayName, and hoist non-React statics.
- 근거: wrapper always overwrites
- RT-24 (P1, MISMATCH) Styled-components shims silently ignore APIs that alter application behavior
- 근거:
isStyledComponentalways returns false;StyleSheetManagerignores every option; ServerStyleSheet has noinstanceand always emits empty tags/elements (styled-compat.ts). Upstream documents true styled detection,shouldForwardProp, plugins, targets/nonces, and the commonsheet.instance/collect/get-tags SSR flow: https://styled-components.com/docs/api and https://styl… - 수정 방향: Implement detectable markers and supported manager behavior; add
instance/document static-CSS SSR behavior, or throw actionable errors for unsupported options.
- 근거:
- RT-26 (P1, MISMATCH) StyleX attrs has an incompatible public return type
- 근거: local signature declares
style?: Record<string,string>(stylex.ts:33-36), while officialattrsreturns serializedstyle?: string; ambient declarations replace the upstream package's types. - 수정 방향: Give
attrsits documented string style type and add compile-time contract tests against StyleX's published declarations.
- 근거: local signature declares
- SC-05 (P1, WRONG) Theme scoping fails for function themes, component overrides, and global targets
- 근거:
19-theme-function-nesting.tsxleavestheme={outer => ...}at runtime, butThemeProvideracceptsStyledThemeand runsObject.entriesover the function, yielding no overrides (ThemeProvider.tsx:6-12,theme-vars.ts:21-34); the inner background remains the outer white, not black.56-theme-prop.tsxemits.a{color:var(--primary)}and forwardstheme={{primary:'blue'… - 수정 방향: Add real nested theme state sufficient to evaluate function themes and per-component overrides, prevent
themefrom reaching the DOM, and materialize provider-dependent global values at a scope that the global target can actually inherit/use.
- 근거:
- SC-12 (P2, WRONG)
defaultPropsdoes not provide styled interpolation defaults on React 19- 근거:
49-default-props.tsxleavesX.defaultProps = {$tone:'red'}on a generated plain function and computes the CSS variable fromrest.$tone. The package directly depends on React^19.3(packages/react/package.json:68-70), where function-component defaultProps is removed;$toneis therefore undefined. styled-components v6 supported this surface (v7 calls out its removal). - 수정 방향: During extraction, fold a styled component's
defaultPropsinto the generated wrapper's props before dynamic style evaluation, or reject it explicitly when targeting React 19.
- 근거:
선택 (2)
- SC-04 (P2, MISMATCH)
withConfigsilently dropsdisplayNameandcomponentId- 근거:
28-config-display-name.tsxand29-config-component-id.tsxgenerate identical anonymous wrappers/classes to an unconfigured component.take_styled_modifiersrecognizeswithConfigbut records only attrs and discards the config (extract_style_from_styled.rs:383-409). Devup's migration page says.withConfig()is dropped (page.mdx:45,126). - 수정 방향: Preserve display metadata on the generated component and map
componentIdto a stable marker/identity class; keep the known prop-filter work separate.
- 근거:
- SC-10 (P2, MISMATCH)
isStyledComponentalways lies for compiled styled components- 근거:
47-is-styled.tsxredirects the call to compat, whereisStyledComponent(_target)unconditionally returnsfalse(styled-compat.ts:38-40). Devup documents that result atpage.mdx:124. - 수정 방향: Attach a non-rendered marker/property to generated styled wrappers and check it in the compat helper; this marker can also support KNOWN-8's planned selector identity.
- 근거:
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
fix(extractor): keep type-only imports type-only and read keyframes g…
test(extractor): cover shouldForwardProp forms and element prop filte…
fix(react): type withTheme forwardRef props for the package build
fix(react): add a no-op Emotion CacheProvider and redirect its import
test(extractor): cover composition and styled branches reported by Linux