styled-components의 createGlobalStyle을 활용하여 GlobalStyle을 적용하려고 한다.
개발환경
- React 18
- TypeScript
- Styled-Components
- webpack
styled-nomalize & styled-reset 설치
npm i styled-normalize
npm i styled-reset
styled-normalize
브라우저 간의 기본 스타일 차이를 줄이면서 일관된 스타일을 제공하는 normalize.css를 CSS-in-JS 환경에서 사용할 수 있게 하는 유틸리티
styled-reset
모든 브라우저의 기본 스타일을 제거하여 일관된 초기 상태를 제공하는 reset.css를 CSS-in-JS 환경에서 사용할 수 있게 하는 유틸리티
webpack.config.js 설정
폰트 파일을 번들링하기 위해 Webpack 설정에서 module: rules에 loader 추가하기
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
use: [
{
loader: "file-loader",
options: {
name: "assets/fonts/[name].[hash:8].[ext]",
},
},
],
},
GlobalStyle.ts 파일 생성
- assets/fonts 폴더에 Noto Sans woff 파일을 넣어놓고 다음과 같이 font-face를 정의했다.
- html 또는 body에 font-family를 적용하면 브라우저는 페이지 로딩 시 한 번만 계산하고, 하위 요소들은 그 스타일을 상속받아 사용한다. 이는 전체 선택자(*)를 사용하는 것보다 계산을 덜 필요로 하여 더 효율적이다.
- font-face 정의만으로는 폰트 적용이 되지 않고 font-family를 적용해주어야 한다.
import { createGlobalStyle } from "styled-components";
import { normalize } from "styled-normalize";
import { reset } from "styled-reset";
import ThinNotoSansKr from "assets/fonts/NotoSansKR-Thin.woff";
import ExtraLightNotoSansKr from "assets/fonts/NotoSansKR-ExtraLight.woff";
import LightNotoSansKr from "assets/fonts/NotoSansKR-Light.woff";
import RegularNotoSansKr from "assets/fonts/NotoSansKR-Regular.woff";
import MediumNotoSansKr from "assets/fonts/NotoSansKR-Medium.woff";
import SemiBoldNotoSansKr from "assets/fonts/NotoSansKR-SemiBold.woff";
import ExtraBoldNotoSansKr from "assets/fonts/NotoSansKR-ExtraBold.woff";
import BlackNotoSansKr from "assets/fonts/NotoSansKR-Black.woff";
/**
* 글로벌 스타일
*/
const GlobalStyles = createGlobalStyle`
${reset};
${normalize};
* {
box-sizing: border-box;
font-family: 'Noto Sans CJK KR';
font-weight: 500;
letter-spacing: -0.5px;
font-size: 12px;
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 100;
src: url(${ThinNotoSansKr}) format('woff');
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 200;
src: url(${ExtraLightNotoSansKr}) format('woff');
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 300;
src: url(${LightNotoSansKr}) format('woff');
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 400;
src: url(${RegularNotoSansKr}) format('woff');
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 500;
src: url(${MediumNotoSansKr}) format('woff');
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 600;
src: url(${SemiBoldNotoSansKr}) format('woff');
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 700;
src: url(${ExtraBoldNotoSansKr}) format('woff');
}
@font-face {
font-family: 'Noto Sans CJK KR';
font-style: normal;
font-weight: 800;
src: url(${BlackNotoSansKr}) format('woff');
}
body {
padding: 0;
margin: 0;
font-family: 'Noto Sans CJK KR'; // body 태그에 폰트 적용
}
button {
border: none;
background-color: transparent;
}
`;
export default GlobalStyles;
import 오류 발생
위와 같이 파일을 작성하면 아래와 같이 "모듈 또는 해당 형식 선언을 찾을 수 없습니다"라는 오류가 발생한다.
fonts.d.ts 파일 생성
TypeScript에서 .ttf, .woff 파일 등 모듈로 사용될 파일 타입을 선언해 주어야 한다.
// types/fonts.d.ts
declare module "*.ttf" {
const value: any;
export = value;
}
declare module "*.otf" {
const value: any;
export = value;
}
declare module "*.woff" {
const value: any;
export = value;
}
declare module "*.woff2" {
const value: any;
export = value;
}
declare module "*.eot" {
const value: any;
export = value;
}
tsconfig.json 파일 수정
include 부분에 타입 지정한 파일명을 추가해준다.
// tsconfig.json
{
"compilerOptions": {
// ...
},
"include": ["src", "fonts.d.ts"],
// ...
}
GlobalStyles.ts 파일 적용
Bootstrap.tsx 또는 index.ts, App.tsx 등 최상단 파일에서 GlobalStyles.tsx 파일 호출 및 글로벌 스타일 적용한다.
import { GlobalStyle } from "./styles/GlobalStyle";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<>
<GlobalStyle />
<App />
</>
);
특정 파일에서 사용시 따로 파일을 import할 필요 없이 바로 사용 가능하다.
const Header = styled.div`
font-family: Noto Sans CJK KR;
font-weight: 500;
font-size: 12px;
`;
문제 발생 - font를 지속적으로 로드
컴포넌트들을 로드할 때마다 fonts들을 로드하며 폰트들이 모두 풀렸다가 다시 Noto Sans가 적용되는 현상이 발생했다.
개발자 도구 → 네트워크 탭에서 폰트를 지속적으로 로드하는 것을 확인할 수 있었다.
원인을 몰라 GlobalStyle.ts 파일 설정, webpack의 config, file-loader, 리액트 랜더링등 여러가지를 시도를 했었다.
결국 아래와 같은 원인을 찾을 수 있었다.
Styled-Components는 스타일이 Render 될 때 마다 head 태그의 style 태그를 변경한다. 즉, 새로운 스타일이 등장할 때마다 폰트를 재요청하는 현상이 나타난 것
해결방법은 font-face 부분만 별도 css로 분리하는 것이라고 한다.
font-face를 css 파일로 분리
/* styles/fonts.css */
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 100;
src: url("assets/fonts/NotoSansKR-Thin.woff") format("woff");
}
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 200;
src: url("assets/fonts/NotoSansKR-ExtraLight.woff") format("woff");
}
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 300;
src: url("assets/fonts/NotoSansKR-Light.woff") format("woff");
}
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 400;
src: url("assets/fonts/NotoSansKR-Regular.woff") format("woff");
}
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 500;
src: url("assets/fonts/NotoSansKR-Medium.woff") format("woff");
}
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 600;
src: url("assets/fonts/NotoSansKR-SemiBold.woff") format("woff");
}
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 700;
src: url("assets/fonts/NotoSansKR-ExtraBold.woff") format("woff");
}
@font-face {
font-family: "Noto Sans CJK KR";
font-style: normal;
font-weight: 800;
src: url("assets/fonts/NotoSansKR-Black.woff") format("woff");
}
// styles/GlobalStyles.ts
import { createGlobalStyle } from "styled-components";
import { normalize } from "styled-normalize";
import { reset } from "styled-reset";
/**
* 글로벌 스타일
* 폰트는 styles/fonts.css에서 관리
*/
const GlobalStyles = createGlobalStyle`
${reset};
${normalize};
* {
box-sizing: border-box;
}
html, body{
font-size: 12px;
font-weight: 500;
letter-spacing: -0.5px;
font-family: 'Noto Sans CJK KR', sans-serif;
padding: 0;
margin: 0;
}
button {
border: none;
background-color: transparent;
}
`;
export default GlobalStyles;
최상단에서 GlobalStyle과 font-face가 정의된 css를 함께 불러서 적용한다.
// Bootstrap.tsx
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "react-query";
import { ToastContainer } from "react-toastify";
import "styles/fonts.css";
import GlobalStyles from "styles/GlobalStyles";
import App from "./App";
const rootElement = document.getElementById("app") as HTMLElement;
const root = createRoot(rootElement);
const queryClient = new QueryClient();
root.render(
<QueryClientProvider client={queryClient}>
<GlobalStyles />
<App />
<ToastContainer />
</QueryClientProvider>
);
참고자료
'React' 카테고리의 다른 글
[React] html2pdf.js를 활용해 PDF 다운로드 구현하기 (0) | 2024.07.17 |
---|---|
[React] SVG 불러오기 (Webpack5 설정 + SVGR) (0) | 2024.07.02 |
[React] SVG 파일 index.ts에서 export 하기 - SVGR (0) | 2024.05.22 |
[React] npm start시 port 번호 변경하는 방법 (0) | 2024.03.06 |
[React-Query] 파리미터를 통해 레이어 선택 불러오기, 캐싱고민 (0) | 2024.02.08 |