feat: number units, compat layers, module loading and strict build-time values (#676, #677, #678) - #681
Merged
Conversation
… units vanilla-extract styles add px to numbers (in .css.ts files and in style/globalStyle/keyframes calls of ordinary modules) and StyleX adds px/ms to static and dynamic numbers, instead of Devup UI's spacing scale. The unitless property list now matches vanilla-extract's, so strokeWidth, columns, zoom, orphans and similar keep bare numbers everywhere. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…gine Remove the static .css.ts fast path so development and production builds evaluate stylesheets the same way and produce the same class names and CSS. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ponents object styles vanilla-extract: name styles after the variable holding them instead of by call order, generate file-unique names for vars, containers, layers, fonts and theme variables, follow vanilla-extract's createVar/fallbackVar/createTheme/createGlobalTheme/contract/layer semantics, resolve styleVariants variants and transitive composition, and export plain values as data. Emotion and styled-components: numbers in object styles mean pixels, several style arguments or arrays merge, styled(tag, options) is a factory, and css() composes class names passed with rule objects. '@layer' records and a global '@layer' cover nested and responsive declarations, and typography presets yield to declarations written beside them. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Layered properties of any kind are written with the regular writer, one layer at a time, so a layer keeps breakpoints, selectors and at-rules. A typography atom skips the declarations its value lists as written beside it from the breakpoint they start at. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…graphy precedence Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The loop left a region tarpaulin maps past the function on Linux; the output is unchanged. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Tarpaulin's Linux report still attributed a line past the function to its closures; the output is unchanged. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The vec! expansion is the region tarpaulin maps past the function on Linux; the output is unchanged. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Reading a rule list cannot fail, so the error path tarpaulin kept reporting as uncovered past the function is gone; the output is unchanged. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Its output is asserted by test_font_faces; tarpaulin's Linux report maps the loop exit onto a line it never counts however the loop is written. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Shaped like compose so tarpaulin's Linux report maps its loop, and the coverage exclusion is gone again; the output is unchanged. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ctor The callers store the rules, so the helper ends in its value; the output is unchanged. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Tarpaulin merges the unit-test build and the library build that other targets link; the latter never ran the font-face helper, which left one region at zero. The helper is split into single-rule and list parts. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
A number on a custom property was read as the spacing scale (--columns: 4 became 16px), which broke theme tokens and var() math. The vanilla-extract API now also runs through the library build in an integration test. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…function Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
resolve took impl Fn, so the always-selector caller got its own copy whose plain class-name branch never runs; CI coverage reported that branch as uncovered. A fn pointer keeps one copy. createVar returns to its original shape. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… the library Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…every styled import styled.x.attrs(a).withConfig(c) now extracts like styled.x: withConfig is dropped and the attrs are merged over the props in order, objects spread and functions called with the props. A file importing styled from several libraries extracts all of them instead of only the last. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
A dynamic value inside '@layer' now carries the layer, and the layer is part of the class and variable name key, so a layered declaration no longer shares a class (and loses its layer) with the same unlayered one. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
css(base, cond && over) and ternaries of rule objects now merge into per-property ternaries (color: cond ? 'blue' : 'red'), since the order of two atomic classes is decided by the stylesheet, not by composition. Conditional class parts join as cond ? cls : '' so a falsy part no longer prints 'false', and Emotion's number-to-px conversion reaches conditional branches. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The declarations of a conditional typography preset now sit in the typography layer nested in their own origin (t, oN.t, b.t), like the theme's typography classes. A declaration written directly therefore wins whenever it applies, including one that only applies on a condition, which yielding alone could not decide. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
A .css.ts file can now import other stylesheets and plain modules. Imports are resolved through a resolver the bundler supplies (extract_with_modules). A stylesheet is extracted the way the bundler extracts it and its output evaluated, so the names it exports are the ones its own CSS uses; other modules are run as they are. Every export form is supported, cycles are reported, the output keeps importing the stylesheets and side-effect imports it had, and the files read are returned as dependencies to watch. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…t as constants - An object, array or enum constant that code the build sees changes (a member assignment, delete, ++, push, sort, Object.assign, or handing it to a function the build does not know) is read at runtime: elements read its members as CSS variables, and css(), styled() and element spreads taking it whole are build errors naming where it is changed. Handing on a string or number it holds, or putting it in a top-level const nothing changes, keeps it constant - The module declaring an imported constant and the modules re-exporting it are checked too, and values computed at build time do not read a changed binding - Values computed at build time cannot read window, document, navigator, location, process or globalThis properties the build does not define, so the CSS never holds the build environment's answer Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Objects, arrays and enums that visible code changes are read at runtime, taking one whole is a build error naming where it changes, and values computed at build time cannot read window, document, navigator, location, process or unknown globalThis properties Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… functions change - A spread argument of css() reading a changed binding is reported like any other argument, and the check for the argument Object.assign and friends change no longer hides in a match guard coverage cannot count Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…nt values - A changed object a namespace import or another object holds is a build error where css(), styled() or a spread takes it whole, where it was composed as a class; a value read from a changed object is never read as its declared value; CommonJS modules, element changes in for...of loops and forEach/map callbacks, and methods using this count as changes - export default, module.exports, Object.freeze, Array.from, Object.keys and element props other than ref only read an object, so token objects stay static classes; a namespace import itself is never changed; notes tell a change from handing an object on and name the binding where it is changed - Values computed at build time never read Intl or locale methods, directly or through variables and imports; a value reading a statement that threw or a module the build cannot load is not computed instead of taking a branch on a stand-in; a module checking the environment at its top still gives its other exports; console calls no longer stop a module or a .css.ts stylesheet; only the modules a computed value reads are loaded - content, font-size-adjust and math-depth keep their numbers instead of scaling them to px Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…teral-only - A constant or an import the file changes through a member assignment, delete, ++, a destructuring target, a changing method or Object.assign is reported, as the build no longer reads it as written Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… values - Iteration, methods using this, CommonJS modules and namespace imports can change an object; export default, module.exports, Object.freeze and element props only read it; locale methods and Intl are unavailable, and nothing is computed from a failed statement or an unloadable module Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Values are computed from the file's own const, function and enum declarations with exact built-ins; imports are injected as the literals their modules declare and no other module runs. Uncertain code stays a CSS variable on elements and is a build error in css(), globalCss(), keyframes() and styled(). Constant conditions fold only where styles read them, objects holding booleans, undefined or functions are no longer taken as changed, times are milliseconds and counters keep their numbers. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…iteral-only Report calls of imported functions and of functions the build does not run, uncertain built-ins and members, changing elements in for...of loops and forEach/map callbacks, methods using this, and objects handed to unknown code, as the build decides them. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…amespaces Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…olds Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The Update Versions PR removes it once released, so a change here would conflict with that PR. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Contributor
Changepacks@devup-ui/wasm@1.0.81 → 1.0.82 - bindings/devup-ui-wasm/package.jsonPatch
@devup-ui/bun-plugin@1.0.20 → 1.0.21 - packages/bun-plugin/package.jsonPatch
@devup-ui/components@0.1.58 → 0.1.59 - packages/components/package.jsonPatch
@devup-ui/eslint-plugin@1.0.20 → 1.0.21 - packages/eslint-plugin/package.jsonPatch
@devup-ui/next-plugin@1.0.88 → 1.0.89 - packages/next-plugin/package.jsonPatch
@devup-ui/plugin-utils@1.0.15 → 1.0.16 - packages/plugin-utils/package.jsonPatch
@devup-ui/react@1.0.43 → 1.0.44 - packages/react/package.jsonPatch
@devup-ui/reset-css@1.0.30 → 1.0.31 - packages/reset-css/package.jsonPatch
@devup-ui/rsbuild-plugin@1.0.65 → 1.0.66 - packages/rsbuild-plugin/package.jsonPatch
@devup-ui/vite-plugin@1.0.71 → 1.0.72 - packages/vite-plugin/package.jsonPatch
@devup-ui/webpack-plugin@1.0.69 → 1.0.70 - packages/webpack-plugin/package.jsonPatch
|
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
undefined, null and false give the default element, a component or a member such as motion.div renders as named, a condition between names becomes an element for each, and any other value is read at runtime once, with the default element when it is empty. Member expressions rendered nothing, templates took their first part, lowercase variables became tags and lookups built every element. Build-time values read Math only through members with a known key and toString only called at once without a radix, so aliases no longer reach approximate functions. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ss-utils-literal-only Values in every rule object and in the CSS text of css, globalCss, keyframes and createGlobalStyle are checked, tagged templates included, as are the StyleX functions devup-ui compiles; parts css() composes as classes stay unchecked, and styled() is left out as it keeps values as CSS variables. Aliases of Math and toString read other than called at once are reported, as the build reads them. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
A dynamic as renders what it gives, styled() keeps uncertain values as CSS variables like elements, and a number for a time in a .css.ts file gets px as in vanilla-extract. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…imports read A const holding what keyframes() or css() gives is read by the other style APIs and computed selector keys; the package imported whole compiles; styled() renders any base; top-level aliases of compiled imports compile and are removed, other runtime reads of removed imports and unknown selectors keys are build errors. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…t as static Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
종합 PR
#676, #677, #678을 하나로 합쳤습니다. 이 브랜치는 main에 바로 병합할 수 있습니다.
세 PR은 #675 브랜치 위에 쌓여 있었는데, #675가 main에 squash로 병합되면서 모두 충돌했습니다. 이 브랜치는 세 PR의 커밋 99개를 main 위로 rebase했습니다. #675와 #679는 이미 main에 있으므로 이 PR의 변경에는 들어 있지 않습니다.
ab73e172)의 트리가 main에 squash된 #675와 같음을 확인한 뒤, fix(css): compose at-rules, fix cascade order and the vanilla-extract WASM crash #675 커밋은 빼고 rebase했습니다.changepack_log_css_output_fixes.json은 main의 내용 그대로 두었습니다. Update Versions PR(Update Versions #680)이 지우는 파일이라, 여기서 바꾸면 #680과 충돌합니다.member_value의 fallback)을 테스트로 채웠습니다.아래는 세 PR의 원래 본문입니다. 제목만 한 단계씩 내렸습니다.
#676: fix: vanilla-extract/StyleX number units and full engine for .css.ts
문제
style({ padding: 8 })→padding: 32px(VE 원본은8px)fontSize: 16→64px,transitionDuration: 300→1200px.css.ts가 아닌 일반 모듈에서 쓴 VEstyle()/globalStyle()/keyframes()도 똑같이 ×4가 적용됨strokeWidth={2}가8px,columns,zoom,orphans,column-count등도 px로 바뀜.css.ts정적 fast path의 비결정성style({...})만 처리할 수 있었습니다. vars, 템플릿 리터럴,createVar, 계산값은 결국 Boa가 필요합니다.수정
JSON.stringify에 native replacer(pixelify)를 넘깁니다.px를 붙입니다.--*/var(키, 배열 인덱스,vars는 그대로 둡니다.style()/globalStyle()/keyframes()는 import 별칭 단계에서 숫자 리터럴을"8px"로 치환합니다.getNumberSuffix를 동일하게 옮겼습니다.ms, 나머지는px입니다.Math.round(x*10000)/10000입니다.((v) => typeof v === "number" ? v + "px" : v)(arg)로 감쌉니다.MAINTAIN_VALUE_PROPERTIES를 vanilla-extract의 unitless 목록으로 확장했습니다.strokeWidth,columns,columnCount,zoom,orphans,widows,gridArea,borderImage*,maskBorder*, SVG opacity 계열 등strokeWidth={2}가8px에서2가 됩니다.static-vanilla.ts와 관련 옵션을 제거했습니다..css.ts가 있으면 항상 full 엔진을 씁니다(없으면 기존처럼 lite)..css.ts에 약 +20ms, 이후 파일당 1ms 미만입니다.@devup-ui/wasm,@devup-ui/next-pluginPatch테스트
test_vanilla_extract_numbers_follow_vanilla_extract_unitstest_stylex_numbers_follow_stylex_unitstest_style_to_json_adds_vanilla_extract_unitstest_vanilla_extract_numbers_become_px_outside_stylesheetstest_js_number_literalcargo fmt/clippy -D warnings(기본 + lite wasm 타깃),cargo test --workspace전부 통과bun test5231 pass, 100% 커버리지 / 회귀 테스트 6 pass / next-plugin tsc·eslint 통과.css.ts:font-size:16px,padding:8px,line-height:1.5,column-count:3keyframes:width:10pxtransition-duration:300ms, 동적height에 px IIFE 적용참고
createVar/createContainer이름의 파일 간 충돌 가능성#677: fix: vanilla-extract/Emotion/styled-components compat, global layers and typography precedence
문제
미검증 후보로 남겨 둔 버그를 모두 재현해서 확인했습니다. 확인하는 과정에서 같은 계열 버그도 함께 찾았습니다.
css({ padding: 8 })가32px로 나옴(원래8px)styled('div', { shouldForwardProp })(...)가 컴포넌트 대신 React 요소를 export함(런타임 크래시)styled.div(a, b)는 변환되지 않음styled.div([a, b])는 스타일이 사라짐css(a, b)는""가 됨createVar는--var-0,createContainer는__container_0__로 고정되어 파일마다 같은 이름이 나옴layer()의 값이"undefined"가 됨createGlobalTheme이 모두--color-brand-global_theme_0-0을 만들어 서로 덮어씀styleVariants: 같은 파일 안에서tone.primary가undefined라서 조합과 셀렉터가 깨짐. map 함수 형태를 무시함{ sm: style(...) }처럼 중첩된 호출이 있으면 그 뒤의 스타일이 전부 엉뚱한 이름에 붙음createThemeContractexport는 ReferenceError가 남createVar()가var()로 감싸지 않은 이름을 반환해서`calc(${v} * 2)`가 깨짐createTheme(tokens, debugId), 인자 3개짜리createGlobalTheme, 대입하지 않은createGlobalTheme이 무시됨'@layer': {...}를 무시함.css.ts에서 TypeScript 변환이export {};를 남겨 실행이 실패함@layer와 반응형을 함께 쓰면 레이어가 사라짐@layer가 통째로 사라짐globalCss({ h1: { typography, fontSize } })에서 프리셋이 명시한 값을 모든 breakpoint에서 이김수정
vanilla_extract.rs재작성)_veN으로 이름 붙이고, 일반 데이터 export는 값을 그대로 코드로 냅니다.{debugId}-{파일번호}-{n}형식으로 파일마다 고유하게, 결정론적으로 만듭니다.createVar/fallbackVar/createTheme/createGlobalTheme/contract/layer동작을 VE와 똑같이 맞췄습니다.globalLayer,createGlobalThemeContract,assignVars,globalFontFace,@property선언을 추가했습니다.p,bg등)은 스케일을 유지합니다.styled(tag, options)를 팩토리로 인식합니다.css()가 클래스 참조와 규칙 객체를 함께 조합합니다.'@layer': { [name]: rules }레코드 형태를 지원합니다.@layer가 중첩·반응형 선언까지 적용됩니다.--columns: 4가16px이 되던 문제).테스트
cargo fmt,clippy -D warnings(기본 빌드와 lite wasm) 통과cargo test --workspace통과bun test5231 pass(커버리지 100%), 회귀 테스트 6 pass참고 (아직 남은 한계)
.css.ts끼리 import하는 경우는 여전히 지원하지 않습니다. 모듈 로더가 필요해서 별도로 설계해야 합니다..attrs()/.withConfig()와,cond && style같은 조건부 조합 조각은 지원하지 않습니다.styledimport가 여러 개 있으면 마지막 것만 인식합니다(기존 한계).커버리지
libs/extractor/tests/vanilla_extract.rs통합 테스트로 VE API 전체를 라이브러리 빌드를 거쳐 실행합니다.CollectedStyles::resolve의 selector 인자를impl Fn에서fn포인터로 바꿔 단형화된 복사본이 하나만 생기게 했습니다. CI는 rustfmtmax_width = 100000으로 다시 포맷한 뒤 tarpaulin을 돌리기 때문에, 보고된 줄 번호(801)가 로컬 소스가 아니라 이 분기를 가리키고 있었습니다.#678: feat: module loading for stylesheets, imported constants, and remaining compat gaps
요약
#677에서 남은 한계로 적었던 항목을 모두 해결하고, import한 상수를 정적 값으로 처리하는 최적화를 추가했습니다.
이후 라운드에서 StyleX 조건부 값, 빌드 타임 계산값(full 엔진), 상수 스타일 객체를 지원하고, 빌드가 읽을 수 없는 스타일은 조용히 버리지 않고 위치가 붙은 빌드 에러로 보고합니다.
모듈 로더 (
.css.ts가 다른 모듈을 import)setModuleResolver((specifier, importer) => ({ path, code }))extract_with_modulescreateModuleResolver로 이를 등록합니다. 해석 대상은 상대 경로, tsconfigpaths,theme.css→theme.css.ts보완입니다.import "./theme.css"를 유지해서 해당 CSS가 번들에 포함되게 합니다.export { a as b },export … from,export *,export * as nsOutput.dependencies로 반환되고, 각 플러그인이 watch 대상으로 등록합니다.addWatchFile, webpack/next/rsbuildaddDependency, Turbopack coordinator는 응답에 포함합니다..css.ts의 side-effect import(import './global.css')가 사라지던 기존 버그도 고쳤습니다.import한 상수 인라인 (제안된 최적화)
<Box color={PRIMARY} />에서PRIMARY가 다른 모듈의const문자열/숫자이면, CSS 변수 대신 정적 클래스로 처리합니다.as const객체의 멤버 접근, alias, re-export, default호환성
.attrs():.withConfig(): 제거합니다(런타임 옵션뿐입니다).styledimport가 있어도 모두 추출합니다.css(base, cond && {...}), 삼항,styled.div(...))은 속성별 삼항으로 병합합니다.cond ? cls : ''로 합칩니다. 기존에는"false"가 출력되거나 스타일이 통째로 사라졌습니다.레이어 · 타이포그래피 (cascade)
@layer안의 동적 값도 레이어 안에 둡니다.t/oN.t/b.t).검증
cargo test --workspace통과bun test전체 통과, 변경 파일 커버리지 100%bun run build,verify:dist통과createModuleResolver로 실제 end-to-end 확인:추가: 남은 동작 해결
createModuleResolver가 bare specifier를 번들러처럼node_modules에서 해석합니다.exports: 문자열, 배열, 조건(import>module>default>require>node),*패턴,null차단module→main→index순서,.cjs포함module/exports/require모듈을 평가합니다. default는__esModuleinterop를 따릅니다.void 0placeholder는 제외).require바인딩도 추적합니다..tsx/.js모듈은 각자의 source type으로 파싱합니다.value || {...},value ?? {...}를 지원합니다.styled도css()처럼 클래스를 조합합니다.감사 라운드: 찾아서 고친 누락
css(),globalCss(),keyframes(),stylex.create()는 런타임에만 알 수 있는 값을 빌드 에러로 보고합니다. 기존에는 아무도 설정하지 않는 CSS 변수를 출력하거나, 빈 선언(color:)을 남기거나, 선언을 버렸습니다.const는 resolver 없이도 인라인하고, 상수에 대한 템플릿과 산술 연산을 접습니다(${SIZE * 2}px). StyleX 파일에도 적용됩니다.globalCss/createGlobalStyle/keyframes템플릿의 보간(리터럴, 상수, 테마 읽기)을 CSS에 씁니다.${mixin},${p => cond && mixin})은 그 클래스를 조합합니다. 기존에는 mixin 자신과 다음 선언까지 조용히 사라졌습니다.dependencies를 유지하므로, import한 모듈이 바뀌면 다시 빌드합니다.css-utils-literal-only규칙이 빌드가 인라인하는 상수를 허용합니다.개선 라운드
.stylex.ts관례)에서 import한defineVars/createThemeContract/defineConsts/createTheme값을 그 모듈이 생성하는 이름으로 해석합니다. StyleX namespace/default import를 모두 인식합니다..css.ts는 그 예외를 보고합니다. 기존에는 런타임에 여전히style()을 호출하는 출력으로 폴백했습니다.file:line:column: message형식으로 보고합니다. 위치는 import alias 변환 전의 원본 위치로 매핑합니다..css.ts스타일시트 2개 모두 실패 없이 추출합니다.StyleX 조건부 값
defineVars/createTheme값에 조건 객체를 쓸 수 있습니다.default,@media,@supports,@container([DARK]같은 상수 키 포함)types.*()래퍼는 있어도 되고 없어도 됩니다.빌드 타임 계산값과 StyleX 빌드 에러
Math.*호출을 빌드 타임에 접습니다(Math.max(SIZE, 20),Math.round,Math.PI). 반올림은 JavaScript 규칙을 따르고, 다른 값으로 가려진Math는 건드리지 않습니다.css(),globalCss(),keyframes(), StyleX가 모듈 실행 시 상수로 계산하는 값(darken(0.1, PRIMARY), helper 호출)은 full 엔진(Boa)에서 계산해 정적 클래스로 만듭니다.Date와Math.random은 쓸 수 없습니다.styled는 기존처럼 런타임 CSS 변수를 씁니다.hasBuildTimeValues를 export합니다. Next.js는 후보 파일에 계산이 필요하면 full 엔진을 선택하고, lite 엔진의 에러는 full 엔진을 안내합니다.create,defineVars,defineConsts,createTheme,keyframes,positionTry,viewTransitionClass,include는 읽을 수 없는 입력(spread, 계산된 키, 객체가 아닌 인자, 없는 테마 키, 구조 분해한create())을 그대로 출력하거나 버리지 않고 위치가 붙은 빌드 에러로 보고합니다.stylex.props()/attrs()는 다른 곳에서 컴파일된 스타일(styleprop, spread, 다른 모듈의 namespace)을 런타임에 합칩니다. 같은 파일의 namespace는 정적으로 해석합니다.상수 스타일 객체와 빌드가 읽을 수 없는 스타일
css(base),{ ...base, color: 'red' },_hover: hover,space[2],Size.M,<Box {...base} _hover={hover} />props={...}같은 런타임 prop은 원래 객체를 그대로 받습니다._hover)에 준 객체가 아닌 값, 계산된 키, 객체가 아닌globalCss/keyframes인자,styled객체 형식 안의 props 함수. 알 수 없는 객체의 JSX spread는 기존처럼 런타임에 그대로 전달합니다.css(x)/styled.div(x)는 그 값을 클래스로 조합합니다(기존에는 버려졌습니다).onClick이 사라졌습니다).bg={theme.colors[key]}는 CSS 변수가 됩니다(기존에는 사라졌습니다).const나 import를 거친 값(토큰 파일의const DARK = darken(...)), spread, shorthand 속성, 계산된 키, 런타임 조건도 계산합니다.hasBuildTimeValues(filename, code, package, importAliases)는 먼저 module resolver로 상수를 인라인하므로, 정말 평가가 필요한 값이 없으면 Next.js는 lite 엔진을 유지합니다(이 저장소에서 파일당 약 0.4 ms).enum에서 panic하지 않습니다.남은 개선 항목
css()/styled()가 조합하는 바인딩이 모듈이 계산하는 규칙을 담고 있으면 그 규칙을 적용합니다.const base = makeStyles(); css(base),styled.div(base)css()결과, 스타일시트에서 import한 클래스)은 그대로 클래스로 조합합니다.styled.div(...),styled(...)(...)조합도 확인합니다.sourcesContent는 원본 코드입니다. 이전에는 치환된 코드를 기준으로 해서, 여러 줄 계산값 뒤의 줄이 어긋났습니다.css-utils-literal-only: 엔진이 읽는 범위에 맞췄습니다.Math(단Math.random제외),String등)로 상수에서 계산한 값은 허용합니다.Date,Math.random, 매개변수처럼 런타임에만 아는 함수는 보고합니다.cargo test --workspace, clippy(full·lite·wasm32) 통과추가 점검에서 찾은 버그
as가 JSX 자식 위치에서 코드를 망가뜨리던 버그 (main에도 있음):as={Link},as={cond ? 'a' : 'b'}처럼 리터럴이 아닌as를 JSX 자식으로 쓰면 결과가 틀렸습니다.<section>이 사라짐), 뒤의 다른 식({on}, 형제의title)이 대체됐습니다.<Box>가 출력에 남아 런타임에 "Cannot run on the runtime" 에러가 났습니다.items.map(... <Box key={id} as={C} />))에서는 key 없는 fragment로 감싸져 React key 경고가 났습니다.f('#336699')→f('#369'),'10px 0px'→'10px 0'),--i가var(--i)가 됐습니다.await,new, 대입, 쉼표 식, tagged template 값이 조용히 사라졌습니다. 이제 CSS 변수로 전달하고, 함수·클래스는 에러로 보고합니다.<Box {...register('email')} />처럼 다시 읽으면 값이 달라질 수 있는 spread를 spread,className,style에서 3번 평가했습니다. 훅이면 상태가 3개로 갈라집니다.await/yield도 지원합니다.jsx()호출도 같습니다.globalCss()를 값으로 쓰면 잘못된 코드가 나오던 버그 (main에도 있음):const a = globalCss(...)가const a = ;가 됐습니다. 이제void 0이 됩니다.globalCss(),keyframes(),<Global>의 런타임 조건은 조용히 사라졌습니다. 이제 빌드 에러입니다.const DARK = true; DARK ? 'white' : 'black')은 해당 분기로 접습니다.계산값 확장
styled객체의 spread·셀렉터·계산된 키,css(...parts),css()의 템플릿 인자를 full 엔진이 계산합니다.css(styles.card))는 더 이상 모듈을 실행하거나 full 엔진을 선택하게 만들지 않습니다.css(tokens.nested.x))도 상수 경로로 판별합니다.Cannot compose에러는 spread 인자 이름(...rest)을 보여 줍니다.css-utils-literal-only는 값 안에 쓴 콜백의 매개변수([1, 2].map((n) => n * 4))를 허용합니다.추가 점검 검증
cargo test --workspace가 통과하고, clippy(full·lite·wasm32)는 경고가 없습니다.as, spread 1회 평가, 계산값을 직접 확인했습니다.lite 엔진 제거
사용하는 곳이 우리 플러그인뿐이어서 엔진을 Boa가 들어간 하나로 합쳤습니다.
@devup-ui/wasm은 빌드를 하나만 만듭니다.@devup-ui/wasm/liteexport와 lite 빌드, 엔진을 고르는 데만 쓰던hasBuildTimeValues를 없앴습니다.vanilla-extractfeature를 없애고 Boa를 extractor·sheet·wasm crate의 일반 의존성으로 바꿨습니다.@devup-ui/wasm을 불러옵니다. 그래서 dev 서버 실행 중에 추가한 파일도 다른 파일과 똑같이 처리됩니다.css-utils-literal-onlyREADME, changepack에서 lite와 엔진 선택 설명을 지웠습니다.런타임에 바뀌는 값은 빌드 때 굳히지 않음
빌드 때 미리 계산한 값이 런타임에 계산한 값과 다를 수 있는 두 경우를 막았습니다.
1. 코드가 바꾸는 객체·배열·enum 상수
빌드가 볼 수 있는 코드에서 바꾸는 상수는 선언한 값으로 읽지 않습니다. 확인하는 파일은 스타일을 쓰는 파일, 상수를 선언한 모듈, 중간에 re-export하는 모듈입니다.
delete,++push·sort같은 메서드,Object.assigndarken(colors.primary)const에 담기. 예:const theme = { colors }css(obj),styled.div(obj),<Box {...obj} />처럼 객체 전체를 받으면 빌드 에러가 납니다. 에러에는 바꾸는 위치(src/tokens.ts:10:26)를 적습니다.2. 실행 환경에 따라 달라지는 코드
Boa로 계산할 때 아래 값을 읽으면 에러가 납니다. 그래서 그 값에 의존하는 계산은 빌드 때 하지 않습니다.
window,document,navigator,location,process,globalThis에서 정의되지 않은 속성typeof window === 'undefined' ? ...,process.env.NODE_ENV,globalThis.BRAND ?? 'red'css()에서 빌드 에러가 납니다.lite 제거·런타임 값 검증
5db2b827(lite 제거),8866988a(런타임 값).bfd18e60에서 커버리지가 두 줄 모자라 테스트를 보강했습니다.fmt,clippy -D warnings(host·wasm32),test --workspacemutations::tests: 바꾸는 코드, 넘기기, 읽기, 전역 이름을 가린 경우를 분류하는지 확인test_changed_constants스냅샷 21개 케이스const에 담는 경우, 순환 참조window·process·globalThis런타임 값 보호 보완
앞 섹션의 보호를 점검해 찾은 빈틈을 모두 고쳤습니다.
에러 없이 틀린 결과가 나오던 경우
tokens.base)나 다른 객체(const theme = { base })에 든 바뀌는 객체를css()·spread로 통째로 받으면, 클래스 조합으로 넘어가 스타일이 사라졌습니다. 이제 빌드 에러입니다.for...of안에서 바꾸기forEach·map콜백에서 바꾸기this를 쓰는 메서드 호출정상 코드가 상수로 읽히지 않던 경우
export default { colors },module.exports = { colors }Object.freeze(...),Object.keys,Array.from<ThemeProvider theme={theme}>가 있어도theme는 상수입니다.ref결정론
toLocaleString,toLocaleUpperCase,localeCompare,Intl!isBrowser ? 'white' : 'black'같은 분기가 빌드 때 계산됐습니다.const isBrowser = typeof window !== 'undefined'가 있는 모듈console호출 때문에 모듈이나.css.ts전체가 실패하지 않습니다.성능과 기타
apps/landing실측: resolver 호출 20회 → 0회, 2.39ms → 1.17mscontent,font-size-adjust,math-depth의 숫자는 px로 바꾸지 않습니다. 예:content: '1234.5'는 전에4938px로 바뀌었습니다.css-utils-literal-only가 파일 안에서 바꾸는 상수·import를 보고합니다.검증
c2f61a40(publish·e2e·benchmark)clippy -D warnings(host·wasm32),test --workspacemutations::tests스냅샷 5개: 바꾸기, 넘기기, 읽기,this, 전역 이름을 가린 경우test_values_known_only_at_runtime21개 케이스: 위 경우 전부와.css.ts의console확실한 코드만 빌드 때 실행
원칙: 결과가 확실하고 정적인 코드만 Boa가 실행합니다. 조금이라도 애매하면 요소에서는 CSS 변수로 남기고,
css()·globalCss()·keyframes()·styled()에서는 빌드 에러로 알립니다.const·함수·enum이 리터럴·상수에서 정확한 내장 기능(String,Number,JSON, 문자열·배열 메서드,Math.abs/ceil/floor/round/trunc/sign/max/min/sqrt/fround/imul/clz32와Math상수)으로 계산하는 값ModuleLoader미사용).window,Date,Intl,typeof IntersectionObserver등),Math.random과 근사Math함수(sin,pow등),**, 인자가 있는toString,toLocale*/localeCompare/normalize,this,new, class, 정규식,try, getter/setter, async/generator, JSX,obj[key](), 모듈 top-level 바인딩을 쓰는 함수. PRELUDE가 백스톱으로 남아 있습니다.&&/||/??는 스타일 인자·스타일 prop 안에서만 접습니다. 앱 코드는 바인딩을 그대로 읽습니다.undefined·함수 멤버를 가진 객체를 더 이상 "바뀜"으로 보지 않습니다.css()/styled()가 합성하는 모르는 바인딩: 계산하지 못하면STYLE_OBJECT빌드 에러로 알립니다.transition/animation의 duration·delay 숫자는 ×4 없이ms로 해석하고(300→300ms), counter 속성은 숫자를 그대로 둡니다.css-utils-literal-only: 같은 규칙을 적용합니다. import 함수 호출, 빌드가 실행하지 않는 함수, 부정확한 내장 기능을 보고합니다. 파일 안의 변경 판정(for...of·forEach/map요소 변경,this메서드, 모르는 함수에 넘기기)도 빌드와 같습니다. 다른 모듈에서 바꾸는 경우는 lint로 볼 수 없어 문서에 적었습니다.동적
as수정과 lint 검사 범위 확장as:as에 들어온 값대로 렌더합니다.undefined·null·false는 기본 요소가 됩니다(Box는div,Text는span). 이전에는 아무것도 렌더하지 않았습니다.motion.div같은 점 접근은 그 이름으로 렌더합니다. 이전에는<motion />.div가 되어 아무것도 렌더하지 않았습니다.key는 각 요소에 붙습니다.`h${level}`, 호출, 조회)은 런타임에 한 번만 읽고, 비어 있으면 기본 요소가 됩니다:((DevupAs0) => <DevupAs0 />)(expr || "div"). 이전에는 템플릿이 첫 조각(<h>)만 쓰고, 소문자 변수가 그대로 태그가 되고, 조회는 모든 요소를 만들어 props와 children을 항목 수만큼 실행했습니다.Math·toString우회 차단: 빌드 타임 계산은Math를 키가 정해진 멤버로만,toString은 인자 없이 바로 호출할 때만 읽습니다.const { sin } = Math같은 별칭으로 근사 함수가 계산되던 틈을 막았습니다.css-utils-literal-only:css·globalCss·keyframes·createGlobalStyle의 모든 인자에 있는 규칙 객체와 CSS 텍스트(tagged template 포함), devup-ui가 컴파일하는 StyleX 함수의 값까지 검사합니다.css()가 클래스로 합성하는 부분과styled()의 값(CSS 변수가 됨)은 검사하지 않습니다.Math·toString규칙도 빌드와 같습니다.styled()는 불확실한 값을 요소처럼 CSS 변수로 두고, 스타일 객체 전체를 계산할 수 없을 때만 빌드 에러입니다..css.ts에서 시간 숫자는 vanilla-extract처럼px가 붙으므로'300ms'처럼 단위를 적어야 합니다.스타일 결과를 값으로 사용, 제거된 import 참조 검사
같은 파일의
const에 담긴keyframes()/css()결과를 쓰지 못하던 한계를 고쳤고, 그 과정에서 찾은 버그도 함께 고쳤습니다.const fadeIn = keyframes(...)와const card = css(...)를css(),globalCss(),keyframes(),styled(),<Global>, 스타일 props에서 빌드 타임 값으로 읽습니다. 규칙 객체 안과 계산된 selector 키도 포함합니다. CSS 텍스트 안의 class는 지금처럼 mixin으로 합성합니다. 섀도잉된 이름,let, 구조분해 바인딩은 치환하지 않습니다.import * as Devup와 default import에서Devup.css,Devup.keyframes,Devup.createGlobalStyle,Devup.styled,<Devup.Global>을 컴파일합니다.styled()base:styled(motion.div)는 멤버 이름 그대로 렌더합니다.forwardRef(...)나 변수 같은 그 밖의 값은 선언 위치에서 한 번 읽습니다.null, 숫자, boolean,undefinedbase와 컴파일되지 않는styled는 빌드 에러입니다.const myCss = css,export const C = Box,[Box], 단독styled('div'). top-level alias(const myCss = css,const Row = Flex)는 원본처럼 컴파일하고 선언을 지웁니다. 남는 런타임 참조는 모두 위치를 가리키는 빌드 에러가 됩니다. 섀도잉된 이름과 타입 위치는 제외합니다.selectors키가 조용히 버려지던 것을 빌드 에러로 바꿨습니다. 상수로 계산되는 키(['.' + card + ':hover &'])는 읽습니다.css-utils-literal-only가 module-levelconst에 담긴 devupcss()/keyframes()결과를 정적 값으로 봅니다. named import와 namespace import 모두 해당합니다.styled['div']는 빌드 에러입니다.@devup-ui/componentsButton 스냅샷은 spinner keyframes가 CSS 변수 대신 정적 class로 바뀐 결과를 반영했습니다.