Skip to content

fix(sheet): keep global CSS, imports and cascade layers in written order - #701

Open
owjs3901 wants to merge 3 commits into
mainfrom
fix/global-order
Open

owjs3901 wants to merge 3 commits into
mainfrom
fix/global-order

Conversation

@owjs3901

Copy link
Copy Markdown
Contributor

문제

CSS에서는 명시도가 같으면 나중에 쓴 규칙이 이기고, 먼저 선언한 layer가 약합니다. 그런데 전역 CSS가 작성 순서가 아니라 텍스트 정렬 순서로 출력되어, 작성자의 의도와 반대 결과가 나왔습니다.

  • CSS-01: 같은 파일의 globalCss`body{color:red}` → globalCss`body{color:blue}`가 body{color:blue}body{color:red}로 출력되어 red가 이겼습니다. @import(z.css, a.css → a.css, z.css)와 @font-face도 같은 문제였습니다.
  • CSS-02: reset을 base보다 먼저 썼는데 @layer base,reset;이 출력되어 layer 우선순위가 뒤집혔습니다.
  • 추가 발견: 객체형 globalCss({ body: { color: 'red' } }) 뒤에 globalCss({ body: { color: 'blue' } })를 쓰면 body{color:blue;color:red}로 출력되었고, 같은 명시도의 selector(.z, .a)도 알파벳 순으로 재배열되었습니다.

변경

  • extractor: 새 StyleCollector가 파일의 스타일을 받으면서 전역 규칙(ExtractCss, ExtractImport, ExtractFontFace, 전역 selector 선언)에 작성 순번을 매기고, layer를 처음 쓰인 순서대로 기록합니다. 중첩 layer는 부모 뒤에 옵니다. 기록한 순서는 새 ExtractStyleValue::LayerOrder로 sheet에 전달됩니다.
  • sheet
    • 파일별 전역 규칙, @import, @font-face를 작성 순서의 목록으로 저장합니다. 파일을 다시 추출하면 해당 파일의 목록을 교체합니다.
    • 같은 규칙이 여러 번 나오면 원문 그대로 모두 둡니다(red, blue, red → 마지막 red가 이김). 중복된 @import와 @font-face는 마지막 위치 하나만 남깁니다. 동일한 규칙이므로 cascade 결과는 같습니다.
    • 객체 전역 선언은 selector 그룹과 breakpoint 안에서 파일별 작성 순서를 따릅니다.
    • custom layer prelude는 파일(경로순)별로 처음 쓰인 순서를 따르고, 기록되지 않은 layer만 뒤에 정렬해서 붙입니다. per-file CSS에만 쓰이는 layer도 prelude에 선언됩니다.
  • sheet JSON은 기존 형식과 호환됩니다. 목록 형태는 그대로이고, 새 필드는 기본값을 가집니다.

새로 생기는 오류

없음. 빌드 에러를 새로 추가하지 않았습니다.

출력 변화

  • custom layer를 쓰는 파일이 있으면 prelude(@layer reset,base;)가 앞에 추가됩니다.
  • keyframes 이름 hash가 한 번 바뀝니다(내부 구조체 필드 추가). Button 스냅샷을 갱신했습니다.

검증

  • cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace 통과
  • 추가 테스트: test_global_rules_keep_written_order, test_global_rules_and_layers_update_per_file, style_collector 단위 테스트, test_layer_pipeline의 layer 순서 케이스
  • WASM을 다시 빌드한 뒤 bun test 5474 pass, 커버리지 100%
  • probe 재현 결과
    • global-order.tsx: body{color:red}body{color:blue}
    • layers.tsx: @layer reset,base,utilities;
    • 객체 global: body{color:red;color:blue}.z{...}.a{...}, @layer zz,aa;

Refs #685

owjs3901 and others added 3 commits September 30, 2026 20:54
Global rules, @import and @font-face were stored in sorted sets and custom layers were declared alphabetically, so a later rule or layer could lose to an earlier one. The extractor now numbers global rules and records the first-use order of layers; the sheet emits them in that order.

Refs #685

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #685

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
CI reformats sources to one-line calls before tarpaulin, which put the reported misses on the repeated add_css early return and a font-face struct field. Cover the repeated add_css/add_import/add_font_face calls and build the font face in one statement.

Refs #685

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@github-actions

Copy link
Copy Markdown
Contributor

Changepacks

@devup-ui/wasm@1.0.82 → 1.0.83 - bindings/devup-ui-wasm/package.json

Patch

  • globalCss: theme tokens ($text) resolve to var(--text) in global rules instead of being written as invalid $text; imports given as { url, query } objects with unquoted keys are emitted instead of dropped; an '@layer name' key puts the rules it holds in that cascade layer (layers nest as base.reset) instead of flattening them into invalid declarations, and a layer name that is not one identifier is a build error
  • Global CSS keeps the order it is written in: globalCss rules, @import and @font-face now appear in source order within each file (the later of two equal rules wins as written, repeated imports and font faces count where they are written last), object globalCss declarations on the same selector keep their written order, and custom cascade layers are declared in the order files first use them (nested layers after their parent) instead of alphabetical order
  • Theme CSS: a color only one variant defines is defined in that variant; a variant renders dark only when it is named dark or listed as dark in the new theme.colorScheme, others light, and light-dark() only encodes a light default with one dark variant; theme names that are not CSS identifiers are quoted in [data-theme]; typography emits fontStyle and textTransform; color variables come out in a fixed order. $tokens with dashes (text-primary) resolve instead of breaking into var(--text)-primary, and dotted length and shadow names are declared with dashes like their references. registerTheme reports, with the theme path, a token name the $token syntax cannot reach and two tokens of a variant that become the same CSS variable (a-b and a.b)

@devup-ui/bun-plugin@1.0.21 → 1.0.22 - packages/bun-plugin/package.json

Patch

  • Auto-update: depends on '@devup-ui/plugin-utils' via a local workspace dependency

@devup-ui/components@0.1.59 → 0.1.60 - packages/components/package.json

Patch

  • Auto-update: depends on '@devup-ui/vite-plugin' via a local workspace dependency

@devup-ui/eslint-plugin@1.0.21 → 1.0.22 - packages/eslint-plugin/package.json

Patch

  • no-duplicate-value, no-useless-responsive, no-useless-tailing-nulls, no-typography-token-prefix and prefer-media-shorthand only report and fix values the build reads as styles: style props of Box, Flex and the other style components and the arguments of css, globalCss and keyframes, through style objects, responsive arrays, conditions and spreads. Arrays and keys in props the component passes through (data-, aria-, event handlers, HTML attributes, props, styleVars), in arguments of other functions and under imports/fontFaces/params are left alone, where autofix used to rewrite them; styles of a component nested in another's prop are checked too. css-utils-literal-only reads a css() or keyframes() result held in a const of any scope as static, as the build does

@devup-ui/next-plugin@1.0.89 → 1.0.90 - packages/next-plugin/package.json

Patch

  • Auto-update: depends on '@devup-ui/plugin-utils' via a local workspace dependency

@devup-ui/plugin-utils@1.0.16 → 1.0.17 - packages/plugin-utils/package.json

Patch

  • Theme CSS: a color only one variant defines is defined in that variant; a variant renders dark only when it is named dark or listed as dark in the new theme.colorScheme, others light, and light-dark() only encodes a light default with one dark variant; theme names that are not CSS identifiers are quoted in [data-theme]; typography emits fontStyle and textTransform; color variables come out in a fixed order. $tokens with dashes (text-primary) resolve instead of breaking into var(--text)-primary, and dotted length and shadow names are declared with dashes like their references. registerTheme reports, with the theme path, a token name the $token syntax cannot reach and two tokens of a variant that become the same CSS variable (a-b and a.b)

@devup-ui/rsbuild-plugin@1.0.66 → 1.0.67 - packages/rsbuild-plugin/package.json

Patch

  • Auto-update: depends on '@devup-ui/plugin-utils' via a local workspace dependency

@devup-ui/vite-plugin@1.0.72 → 1.0.73 - packages/vite-plugin/package.json

Patch

  • Auto-update: depends on '@devup-ui/plugin-utils' via a local workspace dependency

@devup-ui/webpack-plugin@1.0.70 → 1.0.71 - packages/webpack-plugin/package.json

Patch

  • Auto-update: depends on '@devup-ui/plugin-utils' via a local workspace dependency

@codecov

codecov Bot commented Sep 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
...xtractor/src/extract_style/extract_static_style.rs 100.00% <ø> (ø)
...extractor/src/extract_style/extract_style_value.rs 100.00% <100.00%> (ø)
libs/extractor/src/extract_style/mod.rs 100.00% <ø> (ø)
.../extractor/extract_global_style_from_expression.rs 100.00% <100.00%> (ø)
...tor/src/extractor/extract_style_from_expression.rs 100.00% <ø> (ø)
...tractor/src/extractor/extract_style_from_stylex.rs 100.00% <100.00%> (ø)
libs/extractor/src/lib.rs 100.00% <ø> (ø)
libs/extractor/src/style_collector.rs 100.00% <100.00%> (ø)
libs/extractor/src/visit.rs 100.00% <100.00%> (ø)
libs/sheet/src/lib.rs 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant