| 만난버그 부분은 수정중...!!
Zero-runtimeStylesheets inTypeScript.
Use TypeScript as your preprocessor.
Write type‑safe, locally scoped classes, variables and themes
then generate static CSS files at build time.
All the styling APIs in Vanilla Extract take a style object as input.
Describing styles as a JavaScript object enables much better use of TypeScript through your styling code, as the styles are typed data-structures like the rest of your application code.
It also brings type-safety and autocomplete to CSS authoring (via csstype).
Type-safe static CSS
Q. 리액트, vanila-extract 로 css를 구현했는데, 로컬에서는 문제가없었는데 배포만 하면 스타일이 초기화되는 문제가발생했어,
확인해보니 뭐 예를들어 타이틀이 2가지가 있으면, 하나는 기본 타이틀 Title이고, 그 Title을 import 해서 커스텀해서 PriceTitle 을 만들었더니 PriceTitle을 여러번 들어오면 Title의 기본 속성이 다 덮어씌여지는거야,
그래서 보다보니 Title의 className과 PriceTitle className은 달랐지만, Title의 해시값이 chunk에 계속 덮여씌어지더라고, 그래서 새로운 해시값의 청크로 새로운 기본 Title의 스타일이 적용되는거였어!
정상적인 부분
문제가 되는 부분
Q. chunk가 뭐지??
A. Webpack에서 "chunk"란 코드를 작은 조각으로 나누는 것을 의미합니다. Webpack은 이러한 chunk를 생성하고, 이를 통해 여러분의 애플리케이션을 더욱 효율적으로 로딩할 수 있게 도와줍니다.
Entry Chunk: 이는 애플리케이션의 진입점입니다. 여기에는 애플리케이션 로직이 포함되어 있습니다.
Normal Chunk: 이는 애플리케이션의 비동기적으로 로딩되는 부분입니다. 이런 코드 조각은 애플리케이션의 시작 시에는 필요하지 않지만, 특정 조건이나 상황에서 필요할 수 있습니다.
Initial Chunk: 이는 애플리케이션이 처음 시작될 때 로딩되는 코드입니다. 여기에는 애플리케이션의 핵심 로직이 포함됩니다.
Webpack은 이런 chunk들을 생성하고 관리하면서, 필요한 시점에 적절하게 로딩하게 합니다. 이러한 과정은 코드 스플리팅(code splitting), 비동기 로딩(async loading) 등과 같은 기능을 가능하게 합니다.
이 문제를 해결하기 위해 가설을 생각하고 검증하는 과정을 거쳤다.
가설1. title이라는 className이 다 같아서 그런거 아닐까 ? NO!
가설2.전역스타일이 아니여서 적용이안되는걸까?
가설3. composition 으로 분리하면 ?
가설4. 그렇다면 Import 순서 문제아닐까?
가설5. createTheme 을 이용해서 한정된 범위에서 스타일을 적용하면 hash 값이 다르게 아니면 chunk가 다르게 들어가지않을까 ?
모두 해결되지않아서, 결국에는 개별적인 Title 스타일을 갖기로 하여 해결하였다...!
import { myStyle } from './app.css.ts'
function App() {
return (
<>
<div className={myStyle}>
style test!
</div>
</>
)
}
export default App
//app.css.ts
import { style, globalStyle } from '@vanilla-extract/css';
export const myStyle = style({
display: 'flex',
paddingTop: '3px'
});
globalStyle('body', {
margin: 0
});
변환 시 ⬇️
.app_myStyle__sznanj0 {
display: flex;
padding-top: 3px;
}
body {
margin: 0;
}
// styles.css.ts
import { style } from '@vanilla-extract/css';
export const myStyle = style({
// cast to pixels
padding: 10,
marginTop: 25,
// unitless properties
flexGrow: 1,
opacity: 0.5
});
변환 시 ⬇️
.styles_myStyle__1hiof570 {
padding: 10px;
margin-top: 25px;
flex-grow: 1;
opacity: 0.5;
}
PascalCase
+ removing -
import { style } from '@vanilla-extract/css';
export const myStyle = style({
WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)'
});
변환 시 ⬇️
.styles_myStyle__1hiof570 {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
vars
keyimport { style } from '@vanilla-extract/css';
const myStyle = style({
vars: {
'--my-global-variable': 'purple'
}
});
변환 시 ⬇️
.styles_myStyle__1hiof570 {
--my-global-variable: purple;
}
vars
key also accepts scoped CSS variables, created via the createVar API.import { style, createVar } from '@vanilla-extract/css';
const myVar = createVar();
const myStyle = style({
vars: {
[myVar]: 'purple'
}
});
변환 시 ⬇️
.styles_myStyle__1hiof571 {
--myVar__1hiof570: purple;
}
@media
@media
키 안의 스타일은 CSS 규칙 순서 우선순위에 따라 항상 다른 스타일보다 우선순위가 높음import { style } from '@vanilla-extract/css';
const myStyle = style({
'@media': {
'screen and (min-width: 768px)': {
padding: 10
},
'(prefers-reduced-motion)': {
transitionProperty: 'color'
}
}
});
변환 시 ⬇️
@media screen and (min-width: 768px) {
.styles_myStyle__1hiof570 {
padding: 10px;
}
}
@media (prefers-reduced-motion) {
.styles_myStyle__1hiof570 {
transition-property: color;
}
}
import { style } from '@vanilla-extract/css';
const myStyle = style({
':hover': {
color: 'pink'
},
':first-of-type': {
color: 'blue'
},
'::before': {
content: ''
}
});
변환 시 ⬇️
.styles_myStyle__1hiof570:hover {
color: pink;
}
.styles_myStyle__1hiof570:first-of-type {
color: blue;
}
.styles_myStyle__1hiof570::before {
content: "";
}
selectors
+ &
import { style } from '@vanilla-extract/css';
const link = style({
selectors: {
'&:hover:not(:active)': {
border: '2px solid aquamarine'
},
'nav li > &': {
textDecoration: 'underline'
}
}
});
변환 시 ⬇️
.styles_link__1hiof570:hover:not(:active) {
border: 2px solid aquamarine;
}
nav li > .styles_link__1hiof570 {
text-decoration: underline;
}
import { style } from '@vanilla-extract/css';
export const parent = style({});
export const child = style({
selectors: {
[`${parent}:focus &`]: {
background: '#fafafa'
}
}
});
변환 시 ⬇️
styles_parent__1hiof570:focus .styles_child__1hiof571 {
background: #fafafa;
}
import { style } from '@vanilla-extract/css';
export const child = style({
background: 'blue',
get selectors() {
return {
[`${parent} &`]: {
color: 'red'
}
};
}
});
export const parent = style({
background: 'yellow',
selectors: {
[`&:has(${child})`]: {
padding: 10
}
}
});
변환 시 ⬇️
.styles_child__1hiof570 {
background: blue;
}
.styles_parent__1hiof571 .styles_child__1hiof570 {
color: red;
}
.styles_parent__1hiof571 {
background: yellow;
}
.styles_parent__1hiof571:has(.styles_child__1hiof570) {
padding: 10px;
}
get selectors()는 JavaScript의 getter 함수로, 객체 속성에 접근할 때마다 특정 작업을 수행할 수 있게 해줍니다. 여기서는 child의 selectors 속성을 불러올 때마다 parent 스타일을 참조하여 새로운 선택자를 생성하게 됩니다. 이를 통해 parent와 child가 서로를 참조하는 관계를 생성합니다.
import { style } from '@vanilla-extract/css';
const base = style({ padding: 12 });
const primary = style([base, { background: 'blue' }]);
const secondary = style([base, { background: 'aqua' }]);
변환 시 ⬇️
.styles_base__1hiof570 {
padding: 12px;
}
.styles_primary__1hiof571 {
background: blue;
}
.styles_secondary__1hiof572 {
background: aqua;
}
const base = style({ padding: 12 });
export const primary = style([base, { background: 'blue' }]);
export const text = style({
selectors: {
[`${primary} &`]: {
// color: '#000'
}
}
});
<div className={primary}>
<div className={text}>
style test!
</div>
</div>
<a className={css.recipeTest({isActive: true})} ></a>
export const recipeTest = recipe({
base: {
display: 'block',
textDecoration: 'underline',
outline: 'none',
cursor: 'pointer',
WebkitTapHighlightColor: 'transparent',
//공통 스타일
},
variants: {
isActive: {
true: {
textDecoration: 'underline',
},
false: {
textDecoration: 'none',
},
},
},
})
import { assignInlineVars } from '@vanilla-extract/dynamic'
<div
className={css.dynamicStyle}
style={assignInlineVars({
[css.dynamicVar]: `${resize}`,
})}
/>
import { createVar, globalStyle, style } from '@vanilla-extract/css'
export const dynamicVar = createVar()
export const dynamicStyle = style({
marginBottom: dynamicVar,
transition: 'margin-bottom 0.3s ease',
})
이부분은 emotion to vanila-extract로 마이그레이션을 진행하며 겪었던 주로 사용하는 익숙하지 않았던 부분을 정리해보고자 한다
selectors: {
'&:active': {
backgroundColor: 'red'
transition: 'background-color 0s',
},
},
':disabled': {
backgroundColor: 'gray',
color: 'white',
},
export const root = style({
display: 'flex',
flexDirection: 'column',
selectors: {
[`li:last-child &`]: {
marginRight: '1rem',
},
[`li:first-child &`]: {
marginLeft: '1rem',
},
},
})
export const item = style({
marginRight: '1rem',
selectors: {
[`&:last-child`]: {
marginRight: '0',
},
},
})
<section
className={[
css.container,
...(props.className ? [props.className] : []),
].join(' ')}
>
==
<section
className={[
css.container,
props.className ? props.className : ""),
].join(' ')}
>
paddingBottom: [
`calc(constant(safe-area-inset-bottom) + 1.25rem)`,
`calc(env(safe-area-inset-bottom) + 1.25rem)`,
],
globalStyle({selector},{style})
을 이용하여 처리export const scrollerbase = style({})
globalStyle(`${scrollerbase} li + li`, {
marginLeft: '0.5rem',
})
globalStyle(`${scrollerbase} > :last-child`, {
marginRight: '0.75rem',
})
이렇게 해도 되지만, 전역 스타일을 하는것이기에 좀더 좋은방법은 무엇일까 ?
import { ReactNode } from 'react'
import * as css from './FleaMarketFilterChipList.css'
type ListProps = {
children?: ReactNode
key?: string
}
const ChipList = (props: ListProps) => {
return (
<li key={props.key} className={css.chipItem}>
{props.children}
</li>
)
}
export default ChipList
export const chipItem = style({
selectors: {
[`${scrollerbase} > & + &`]: { marginLeft: '0.5rem', },
[`${scrollerbase} > &:last-child`]: { marginRight: '0.75rem', },
},
})
일단, 원래는 emotion 을 사용하다 vanila-extract 로 전환되고 실제로 성능최적화를 이룰 수 있었기에, 아주 만족스러운 결과였지만, 아직 생긴지 2년쯤 지났기에, 레퍼런스가 많지않아 어려웠던 점이 있다. 하지만 대부분은 github issues 혹은 discussion 을 보면 되기에 문제가 되지않았다. 낯설었을 뿐...!
일단은, 타입스크립트로 작성되었기에, 타입추론이 가능해서 너무 편리했다.
css in js 를 유지하며 성능 까지 챙기기엔 괜찮은 것 같다. 하지만 예상하지 못한 버그를 만날 수 있다는점..?! 그래도 지금까진 쉽게 배울 수 있고, 다음 프로젝트에서도 한번 사용해보려 한다!
감사합니다. 이런 정보를 나눠주셔서 좋아요.