<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>WONILLISM's Blog</title>
    <link>https://wonillism.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 14 May 2026 13:29:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>WONILLISM</managingEditor>
    <image>
      <title>WONILLISM's Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/3758137/attach/7625d97119cb4f5aa4451b9f7785a5e6</url>
      <link>https://wonillism.tistory.com</link>
    </image>
    <item>
      <title>[Monorepo] 디자인시스템(3) - palette, typography</title>
      <link>https://wonillism.tistory.com/350</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 디자인 시스템을 구성하기 위해 vanilla-extract를 선택했다. 이번에는 실제로 vanilla-extract를 사용하여 디자인 시스템을 구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 내가 직접 디자인을 할 능력이 안돼서, 피그마로 제공되는 디자인을 가져와서 구성해보자.&lt;br /&gt;굳이 여러개 찾아볼 필요가 있을까 싶어서, MUI에서 Preview로 제공되는 디자인을 가져와서 구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.figma.com/design/o6An3mreHWsVwMfANZd6Bc/Material-UI-for-Figma-(and-MUI-X)-(Community)?node-id=6026-46349&amp;amp;node-type=canvas&amp;amp;t=2Ze6Vx12vG6FSBaV-0&quot;&gt;MUI Design System&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;vanilla-extract로 theme 구성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vanilla-extract.style/documentation/theming/&quot;&gt;theme&lt;/a&gt;&lt;br /&gt;위를 참고해서 theme을 구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;color palette를 보면 크게 아래와 같이 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;text : 텍스트의 기본 색상&lt;/li&gt;
&lt;li&gt;primary : 주요 요소(브랜드) 색상, 버튼, 링크, 강조 요소 등에 사용&lt;/li&gt;
&lt;li&gt;secondary : 보조 색상, 부차적인 요소, 액션에 사용&lt;/li&gt;
&lt;li&gt;error : 오류 색상&lt;/li&gt;
&lt;li&gt;warning : 경고 색상&lt;/li&gt;
&lt;li&gt;info : 정보성 메시지나, 알림 색상&lt;/li&gt;
&lt;li&gt;success : 성공적인 작업, 긍정적 상태 색상&lt;/li&gt;
&lt;li&gt;background : 페이지나, 컴포넌트의 배경색&lt;/li&gt;
&lt;li&gt;action : 사용자 인터렉션과 관련된 요소(호버, 포커스, 선택된 상태 등)의 색상&lt;/li&gt;
&lt;li&gt;common : 자주 사용되는 공통 색상(주로 흰색, 검정색)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색상을 정의하는 인터페이스를 정의해보자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;export interface Color {
  50?: string;
  100?: string;
  200?: string;
  300?: string;
  400?: string;
  500?: string;
  600?: string;
  700?: string;
  800?: string;
  900?: string;
  a700?: string;
  a400?: string;
  a200?: string;
  a100?: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;material-ui에서 제공하는 색상들은 여러가지가 있는데 이를 모두 사용할 필요는 없다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;import { Color } from &quot;./color.types&quot;;

export const yellow: Color = {
  50: &quot;#fffde7&quot;,
  100: &quot;#fff9c4&quot;,
  200: &quot;#fff59d&quot;,
  300: &quot;#fff176&quot;,
  400: &quot;#ffee58&quot;,
  500: &quot;#ffeb3b&quot;,
  600: &quot;#fdd835&quot;,
  700: &quot;#fbc02d&quot;,
  800: &quot;#f9a825&quot;,
  900: &quot;#f57f17&quot;,
  a700: &quot;#ffd600&quot;,
  a400: &quot;#ffea00&quot;,
  a200: &quot;#ffff00&quot;,
  a100: &quot;#ffff8d&quot;,
};

...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;palette 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다크모드에 대응하기 위해 아래와 같이 mode값을 기준으로 palette를 구성해보자.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;import { blue, yellow } from &quot;../color&quot;;
import { PaletteColor } from &quot;../palette.types&quot;;

const light: PaletteColor = {
  main: blue[500] || &quot;&quot;,
  dark: blue[600] || &quot;&quot;,
  light: blue[400] || &quot;&quot;,
  contrast: yellow[50],
};

const dark: PaletteColor = {
  main: blue[300] || &quot;&quot;,
  dark: blue[400] || &quot;&quot;,
  light: blue[200] || &quot;&quot;,
  contrast: yellow[900],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 secondary, error, warning, info, success 등의 palette도 위와 같은 방식으로 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 고민인것은 지금은 &lt;code&gt;@packages/ui&lt;/code&gt; 라이브러리에서 기본적인 시스템을 제공하고, 이를 사용하는 애플리케이션에서 확장성을 고려해서 만들어야하지 않을까? 라는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mui처럼 만들기에는 너무 복잡하기도하고 ... 실무에서는 어떤식으로 제공할까? 분명 다양한 프로젝트에서 사용할수있게 범용성을 고려해서 만들텐데 ... 참고할수있는 좋은 예시가 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한번 생각해보자... 내가 만들고자하는 디자인 시스템은 뭘까? 라이브러리를 제공하고싶은건지, 아니면 여러 프로젝트에서 보고 참조할수있는 문서를 제공하고싶은건지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후자라면 스토리북을 사용해서 문서로 제공하는게 좋을것이고, 전자라면 라이브러리를 제공하는게 좋을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자라면 이전 포스팅에서 스토리북과 &lt;code&gt;@packages/ui&lt;/code&gt;를 나누려고 할 필요도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 고민을 하게된 이유는 디자인시스템을 여러 프로젝트에 일관적으로 사용할수있게 하는것이 목적이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드를 제외하고 다른 프로젝트는 디자인시스템을 사용하지 않을것이다. 따라서 디자인시스템을 여러 프로젝트에서 일관적으로 사용할수있게 하는것이 목적이라면, 라이브러리를 제공하는것이 좋을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 목적은 '공통 UI 컴포넌트'를 제공하는 것이다. 라이브러리로 제공하자. (코드를 짜다보니 이생각 저생각이 들어서 길을 잃었다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;palette를 구성한 결과물은 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;import { action } from &quot;./action&quot;;
import { background } from &quot;./background&quot;;
import { error } from &quot;./error&quot;;
import { info } from &quot;./info&quot;;
import { primary } from &quot;./primary&quot;;
import { secondary } from &quot;./secondary&quot;;
import { success } from &quot;./success&quot;;
import { text } from &quot;./text&quot;;
import { warning } from &quot;./warning&quot;;

export const light = {
  text: text.light,
  primary: primary.light,
  secondary: secondary.light,
  error: error.light,
  warning: warning.light,
  info: info.light,
  success: success.light,
  background: background.light,
  action: action.light,
};

export const dark = {
  text: text.dark,
  primary: primary.dark,
  secondary: secondary.dark,
  error: error.dark,
  warning: warning.dark,
  info: info.dark,
  success: success.dark,
  background: background.dark,
  action: action.dark,
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;typography&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typography는 폰트의 크기, 두께, 간격 등을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typography 역시 material-ui에서 제공하는 디자인을 가져와서 구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 디자인 시스템을 구성하면서 필요한 utils를 구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setFont는 font-family를 설정하고, remToPx와 pxToRem은 후에 반응형 디자인을 구성할 때 사용되지만 미리 구성해두자.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;/**
 * Set font family
 */
export const setFont = (fontName: string) =&amp;gt; {
  return `&quot;${fontName}&quot;,-apple-system,BlinkMacSystemFont,&quot;Segoe UI&quot;,Roboto,&quot;Helvetica Neue&quot;,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;,&quot;Segoe UI Symbol&quot;`;
};

/**
 * Convert rem to px
 */
export const remToPx = (value: string): number =&amp;gt; {
  return Math.round(parseFloat(value) * 16);
};

/**
 * Convert px to rem
 */
export const pxToRem = (value: number): string =&amp;gt; {
  return `${value / 16}rem`;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;import { pxToRem, setFont } from &quot;../style/utills&quot;;

/**
 * Font family
 */
export const defaultFont = &quot;Pretendard&quot;;

export const primaryFont = setFont(defaultFont);

export const secondaryFont = setFont(&quot;Noto Sans KR&quot;);

/* -------------------------------------------------------------------------- */

/**
 * Typography
 */
export const typography = {
  fontFamily: primaryFont,
  secondaryFontFamily: secondaryFont,
  fontWeightLight: &quot;300&quot;,
  fontWeightRegular: &quot;400&quot;,
  fontWeightMedium: &quot;500&quot;,
  fontWeightSemiBold: &quot;600&quot;,
  fontWeightBold: &quot;700&quot;,
  h1: {
    fontWeight: 800,
    lineHeight: 80 / 64,
    fontSize: pxToRem(40),
    fontFamily: secondaryFont,
  },
  h2: {
    fontWeight: 800,
    lineHeight: 64 / 48,
    fontSize: pxToRem(32),
    fontFamily: secondaryFont,
  },
  h3: {
    fontWeight: 700,
    lineHeight: 1.5,
    fontSize: pxToRem(24),
    fontFamily: secondaryFont,
  },
  h4: {
    fontWeight: 700,
    lineHeight: 1.5,
    fontSize: pxToRem(20),
  },
  h5: {
    fontWeight: 700,
    lineHeight: 1.5,
    fontSize: pxToRem(18),
  },
  h6: {
    fontWeight: 600,
    lineHeight: 28 / 18,
    fontSize: pxToRem(17),
  },
  subtitle1: {
    fontWeight: 600,
    lineHeight: 1.5,
    fontSize: pxToRem(16),
  },
  subtitle2: {
    fontWeight: 600,
    lineHeight: 22 / 14,
    fontSize: pxToRem(14),
  },
  body1: {
    lineHeight: 1.5,
    fontSize: pxToRem(16),
  },
  body2: {
    lineHeight: 22 / 14,
    fontSize: pxToRem(14),
  },
  caption: {
    lineHeight: 1.5,
    fontSize: pxToRem(12),
  },
  overline: {
    fontWeight: 700,
    lineHeight: 1.5,
    fontSize: pxToRem(12),
    textTransform: &quot;uppercase&quot;,
  },
  button: {
    fontWeight: 700,
    lineHeight: 24 / 14,
    fontSize: pxToRem(14),
    textTransform: &quot;unset&quot;,
  },
};&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Side Project</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/350</guid>
      <comments>https://wonillism.tistory.com/350#entry350comment</comments>
      <pubDate>Wed, 16 Oct 2024 23:33:10 +0900</pubDate>
    </item>
    <item>
      <title>[Monorepo] 디자인 시스템 (2)</title>
      <link>https://wonillism.tistory.com/349</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 css 라이브러리를 선택해야하는데 나중에 서버사이드 렌더링을 위한 프로젝트가 있을 때, 문제가 생길지도 몰라서 CSS-in-JS 는 피하고싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 선택지는 매우 줄어드는데 사실 가장 유명한건 tailwindCSS이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 tailwindCSS도 방식이 마음에 들지 않는다... className에 css를 작성하는것, 쓰다보면 익숙해지겠지만 기존의 css가 아닌 새로운 키워드들을 학습해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대안으로 Css Modules, Sass 등이 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSS Modules vs Sass&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CSS Modules:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지역 범위 스타일링: 클래스 이름 충돌 방지&lt;/li&gt;
&lt;li&gt;기존 CSS 문법 사용: 학습 곡선 낮음&lt;/li&gt;
&lt;li&gt;컴포넌트 기반 개발에 적합&lt;/li&gt;
&lt;li&gt;빌드 시 최적화: 사용하지 않는 스타일 제거 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동적 스타일링에 제한적&lt;/li&gt;
&lt;li&gt;글로벌 스타일 관리가 복잡할 수 있음&lt;/li&gt;
&lt;li&gt;테마 시스템 구현이 어려울 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용 예:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;.button {
  background-color: blue;
  color: white;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Sass:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수, 중첩, 믹스인 등 강력한 기능&lt;/li&gt;
&lt;li&gt;대규모 프로젝트에 적합&lt;/li&gt;
&lt;li&gt;광범위한 커뮤니티와 생태계&lt;/li&gt;
&lt;li&gt;기존 CSS 지식 활용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전처리기 필요: 빌드 과정 추가&lt;/li&gt;
&lt;li&gt;과도한 사용 시 복잡해질 수 있음&lt;/li&gt;
&lt;li&gt;컴포넌트 단위 스코핑이 기본적으로 제공되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용 예:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;$primary-color: blue;

.button {
  background-color: $primary-color;
  color: white;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 CSS Modules는 동적 스타일링에 제한적이고 테마 시스템 구현에 어려움이 있다는는 점에서 맘에 들지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sass는 빌드 과정이 필요하고 컴포넌트 단위 스코핑이 기본적으로 제공되지 않는다는 점에서 맘에 들지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 무엇을 써야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 styled-components나 emotion과 같은 CSS-in-JS 라이브러리를 피하려는 이유는 서버사이드 렌더링을 위해 라이브러리가 번들링되는 과정에서 문제가 생길 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 대안을 더 찾아봤을 때, vanilla-extract, styleX 등의 라이브러리가 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;styled-components, emotion vs vanilla-extract, styleX&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;런타임 성능:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract/StyleX: 제로 런타임, 빌드 시 정적 CSS 생성&lt;/li&gt;
&lt;li&gt;styled-components/emotion: 런타임에 스타일 생성, 일부 성능 오버헤드 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SSR 호환성:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract/StyleX: 기본적으로 SSR과 완벽 호환&lt;/li&gt;
&lt;li&gt;styled-components/emotion: SSR 지원하나 추가 설정 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;타입 안정성:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract: TypeScript 기반, 강력한 타입 체크&lt;/li&gt;
&lt;li&gt;StyleX: TypeScript 지원, 타입 안정성 제공&lt;/li&gt;
&lt;li&gt;styled-components/emotion: TypeScript 지원하나 덜 강력함&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;학습 곡선:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract: CSS 객체 문법, 비교적 익숙함&lt;/li&gt;
&lt;li&gt;StyleX: 새로운 문법, 학습 필요&lt;/li&gt;
&lt;li&gt;styled-components/emotion: 템플릿 리터럴 문법, 익숙한 CSS 작성 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;동적 스타일링:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract: 별도 런타임 API 필요&lt;/li&gt;
&lt;li&gt;StyleX: 조건부 스타일링 지원&lt;/li&gt;
&lt;li&gt;styled-components/emotion: 동적 스타일링 강점&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;커뮤니티/생태계:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract: 성장 중인 커뮤니티&lt;/li&gt;
&lt;li&gt;StyleX: 비교적 새로움, 작은 커뮤니티&lt;/li&gt;
&lt;li&gt;styled-components/emotion: 큰 커뮤니티, 풍부한 생태계&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;7&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테마 시스템:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract: 강력한 내장 테마 시스템&lt;/li&gt;
&lt;li&gt;StyleX: 글로벌 테마 및 디자인 토큰 지원&lt;/li&gt;
&lt;li&gt;styled-components/emotion: ThemeProvider를 통한 테마 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;8&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개발 도구 지원:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract/StyleX: 정적 분석 가능, 좋은 IDE 지원&lt;/li&gt;
&lt;li&gt;styled-components/emotion: 브라우저 개발 도구 지원, 일부 IDE 플러그인&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;빌드 프로세스:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract/StyleX: 빌드 시 CSS 생성, 추가 빌드 단계 필요&lt;/li&gt;
&lt;li&gt;styled-components/emotion: 일반적인 JS 빌드 프로세스&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;10&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;성숙도:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vanilla-extract/StyleX: 비교적 새로운 기술&lt;/li&gt;
&lt;li&gt;styled-components/emotion: 성숙하고 안정적인 기술&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 vanilla-extract가 가장 좋은 선택이라고 생각이 들었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;vanilla-extract 설정&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트에 vanilla-extract 설치&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;pnpm @packages/ui add -D vanilla-extract
pnpm @packages/ui add -D @vanilla-extract/rollup-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollup.config.js 파일에 플러그인 추가&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;...
import { vanillaExtractPlugin } from &quot;@vanilla-extract/rollup-plugin&quot;;
...

export default defineConfig({
  plugins: [
    ...
    vanillaExtractPlugin()
    ...
    ],
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스토리북 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 현재 packages/ui는 라이브러리를 만들기위해 Rollup으로 구성했었다. 하지만 스토리북을 구성하던중 storybook을 실행하려면 webpack5나 vite를 사용해야한다.&lt;br /&gt;왜냐면 storybook 7에서는 렌더러(예: React, Vue), 빌더(예: Webpack, Vite) 및 기본 설정을 추상화하여 통합을 더 쉽게 만들어주는 프레임워크 개념이 도입되었기 때문이다.&lt;br /&gt;그래서 스토리북을 함께 사용하기 위해서는 번들러를 변경하던지, vite와 rollup을 함께 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 packages/ui를 라이브러리 목적으로 만들것이기 때문에, 별도의 프로젝트를 만들어서 스토리북을 구성해야할 것 같다.&lt;br /&gt;(모노레포... 할게 많네 ...)&lt;br /&gt;우선 스토리북 설정은 조금 뒤로 미루고, 먼저 디자인 시스템 라이브러리를 구성해보자.&lt;/p&gt;</description>
      <category>Side Project</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/349</guid>
      <comments>https://wonillism.tistory.com/349#entry349comment</comments>
      <pubDate>Fri, 11 Oct 2024 13:53:11 +0900</pubDate>
    </item>
    <item>
      <title>[Monorepo] 디자인 시스템(1)</title>
      <link>https://wonillism.tistory.com/348</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 시스템을 만들었으니 이를 사용하는 방법을 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. button 만들기&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export const Button = () =&amp;gt; {
  return &amp;lt;button&amp;gt;Button&amp;lt;/button&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 위처럼 버튼을 만들고 vite 환경으로 만든 프로젝트에서 사용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;apps/test/package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;{
  ...
  &quot;dependencies&quot;: {
    &quot;@packages/ui&quot;: &quot;workspace:*&quot;
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 디펜던시를 추가하고, 빌드를 하게되면 디자인 시스템 패키지가 빌드되어 프로젝트에 적용된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 디자인 시스템 패키지 사용하기&lt;/h2&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ pnpm install
$ pnpm @packages/ui:build&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { Button } from &quot;@packages/ui&quot;;

export const App = () =&amp;gt; {
  return &amp;lt;Button /&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 뭐가 문제인지 버튼이 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 디자인 시스템 패키지가 빌드되어 생성된 버튼 컴포넌트가 프로젝트에 적용되지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 packages/ui/package.json 에 몇가지 문제가 있었다.&lt;br /&gt;rollup 설정에서의 메인 엔트리 경로와 package.json의 main 경로가 일치하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;packages/ui/package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@packages/ui&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;dist/index.js&quot;,
  &quot;types&quot;: &quot;dist/index.d.ts&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;rollup -c rollup.config.mjs&quot;,
    &quot;storybook&quot;: &quot;storybook dev -p 6006&quot;,
    &quot;storybook:build&quot;: &quot;storybook build&quot;,
    &quot;build-storybook&quot;: &quot;storybook build&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;WONILLISM&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;react&quot;: &quot;^18.3.1&quot;,
    &quot;react-dom&quot;: &quot;^18.3.1&quot;,
    &quot;tslib&quot;: &quot;^2.7.0&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@chromatic-com/storybook&quot;: &quot;^1.9.0&quot;,
    &quot;@rollup/plugin-commonjs&quot;: &quot;^28.0.0&quot;,
    &quot;@rollup/plugin-json&quot;: &quot;^6.1.0&quot;,
    &quot;@rollup/plugin-node-resolve&quot;: &quot;^15.3.0&quot;,
    &quot;@storybook/addon-essentials&quot;: &quot;^8.3.4&quot;,
    &quot;@storybook/addon-interactions&quot;: &quot;^8.3.4&quot;,
    &quot;@storybook/addon-links&quot;: &quot;^8.3.4&quot;,
    &quot;@storybook/addon-onboarding&quot;: &quot;^8.3.4&quot;,
    &quot;@storybook/blocks&quot;: &quot;^8.3.4&quot;,
    &quot;@storybook/react&quot;: &quot;^8.3.4&quot;,
    &quot;@storybook/react-vite&quot;: &quot;^8.3.4&quot;,
    &quot;@storybook/test&quot;: &quot;^8.3.4&quot;,
    &quot;@types/react&quot;: &quot;^18.3.11&quot;,
    &quot;@types/react-dom&quot;: &quot;^18.3.0&quot;,
    &quot;prop-types&quot;: &quot;^15.8.1&quot;,
    &quot;rollup&quot;: &quot;^4.24.0&quot;,
    &quot;rollup-plugin-peer-deps-external&quot;: &quot;^2.2.4&quot;,
    &quot;rollup-plugin-terser&quot;: &quot;^7.0.2&quot;,
    &quot;rollup-plugin-typescript2&quot;: &quot;^0.36.0&quot;,
    &quot;storybook&quot;: &quot;^8.3.4&quot;
  },
  &quot;peerDependencies&quot;: {
    &quot;react&quot;: &quot;^18.3.1&quot;,
    &quot;react-dom&quot;: &quot;^18.3.1&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;packages/ui/rollup.config.mjs&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;// javascript/typescript 프로젝트 번들링하기 위한 rollup 설정

import peerDepsExternal from &quot;rollup-plugin-peer-deps-external&quot;;
import resolve from &quot;@rollup/plugin-node-resolve&quot;;
import commonjs from &quot;@rollup/plugin-commonjs&quot;;
import typescript from &quot;rollup-plugin-typescript2&quot;;
import json from &quot;@rollup/plugin-json&quot;;
import { terser } from &quot;rollup-plugin-terser&quot;;

export default {
  input: &quot;src/index.ts&quot;, // 메인 엔트리 경로
  output: [
    // CommonJS 형식으로 dist/index.js 생성
    // 소스맵 생성
    {
      file: &quot;dist/index.js&quot;,
      format: &quot;cjs&quot;,
      sourcemap: true,
    },
    // ES Module 형식으로 dist/index.esm.js 생성
    // 소스맵 생성
    {
      file: &quot;dist/index.esm.js&quot;,
      format: &quot;esm&quot;,
      sourcemap: true,
    },
  ],
  plugins: [
    peerDepsExternal(), // peerDependencies에 있는 모듈을 외부 모듈로 처리 - 번들링에 포함되지 않음
    resolve(), // 모듈 경로 해석
    commonjs(), // CommonJS 모듈을 ES6 Module로 변환
    typescript({
      tsconfigOverride: {
        compilerOptions: {
          declaration: true,
          declarationDir: &quot;./dist&quot;,
        },
      },
      clean: true,
    }), // typescript 파일을 컴파일, 선언 파일( .d.ts ) 생성
    json(), // json 파일을 읽어오는 플러그인
    terser(), // 코드 압축, 난독화
  ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 확인하고 실행해보면 버튼이 보이는 것을 확인할 수 있을 줄 알았지만 ? 역시 인식하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 도구에서 확인해보면 아래와 같은 에러가 발생한다.&lt;br /&gt;Uncaught SyntaxError: The requested module does not provide an export named 'Button'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 에러는 번들된 모듈이 Button을 제대로 내보내지 않아서 발생하는 에러인데...&lt;br /&gt;크게 잘못된점이 없어 보인다. 여러방식으로 시도해보던중 혹시 vite 설정에서 문제가 있는것이 아닐까 싶어서 확인해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색해보니 번들링된 모듈을 모듈 환경에서 인식하지 못하는 것 같아서 아래와 같이 설정을 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;apps/test/vite.config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react&quot;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  optimizeDeps: {
    include: [&quot;@packages/ui&quot;],
  },
});&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Side Project</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/348</guid>
      <comments>https://wonillism.tistory.com/348#entry348comment</comments>
      <pubDate>Fri, 11 Oct 2024 13:51:33 +0900</pubDate>
    </item>
    <item>
      <title>[Monorepo] UI 라이브러리 import</title>
      <link>https://wonillism.tistory.com/347</link>
      <description>&lt;hr&gt;
&lt;p&gt;title: 디자인 시스템 import&lt;br&gt;date created: 2024-10-04&lt;br&gt;date modified: 2024-10-04&lt;br&gt;tags: [pnpm, monorepo, side project, design system]&lt;br&gt;links: []&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;디자인 시스템을 만들었으니 이를 사용하는 방법을 알아보자.&lt;/p&gt;
&lt;h2&gt;1. button 만들기&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export const Button = () =&amp;gt; {
  return &amp;lt;button&amp;gt;Button&amp;lt;/button&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;간단하게 위처럼 버튼을 만들고 vite 환경으로 만든 프로젝트에서 사용해보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apps/test/package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  ...
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;@packages/ui&amp;quot;: &amp;quot;workspace:*&amp;quot;
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위처럼 디펜던시를 추가하고, 빌드를 하게되면 디자인 시스템 패키지가 빌드되어 프로젝트에 적용된다.&lt;/p&gt;
&lt;h2&gt;2. 디자인 시스템 패키지 사용하기&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ pnpm install
$ pnpm @packages/ui:build&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Button } from &amp;quot;@packages/ui&amp;quot;;

export const App = () =&amp;gt; {
  return &amp;lt;Button /&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 뭐가 문제인지 버튼이 보이지 않는다.&lt;/p&gt;
&lt;p&gt;이는 디자인 시스템 패키지가 빌드되어 생성된 버튼 컴포넌트가 프로젝트에 적용되지 않았기 때문이다.&lt;/p&gt;
&lt;p&gt;우선은 packages/ui/package.json 에 몇가지 문제가 있었다.&lt;br&gt;rollup 설정에서의 메인 엔트리 경로와 package.json의 main 경로가 일치하지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;packages/ui/package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;@packages/ui&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;dist/index.js&amp;quot;,
  &amp;quot;types&amp;quot;: &amp;quot;dist/index.d.ts&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;build&amp;quot;: &amp;quot;rollup -c rollup.config.mjs&amp;quot;,
    &amp;quot;storybook&amp;quot;: &amp;quot;storybook dev -p 6006&amp;quot;,
    &amp;quot;storybook:build&amp;quot;: &amp;quot;storybook build&amp;quot;,
    &amp;quot;build-storybook&amp;quot;: &amp;quot;storybook build&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;WONILLISM&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;react&amp;quot;: &amp;quot;^18.3.1&amp;quot;,
    &amp;quot;react-dom&amp;quot;: &amp;quot;^18.3.1&amp;quot;,
    &amp;quot;tslib&amp;quot;: &amp;quot;^2.7.0&amp;quot;
  },
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;@chromatic-com/storybook&amp;quot;: &amp;quot;^1.9.0&amp;quot;,
    &amp;quot;@rollup/plugin-commonjs&amp;quot;: &amp;quot;^28.0.0&amp;quot;,
    &amp;quot;@rollup/plugin-json&amp;quot;: &amp;quot;^6.1.0&amp;quot;,
    &amp;quot;@rollup/plugin-node-resolve&amp;quot;: &amp;quot;^15.3.0&amp;quot;,
    &amp;quot;@storybook/addon-essentials&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@storybook/addon-interactions&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@storybook/addon-links&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@storybook/addon-onboarding&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@storybook/blocks&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@storybook/react&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@storybook/react-vite&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@storybook/test&amp;quot;: &amp;quot;^8.3.4&amp;quot;,
    &amp;quot;@types/react&amp;quot;: &amp;quot;^18.3.11&amp;quot;,
    &amp;quot;@types/react-dom&amp;quot;: &amp;quot;^18.3.0&amp;quot;,
    &amp;quot;prop-types&amp;quot;: &amp;quot;^15.8.1&amp;quot;,
    &amp;quot;rollup&amp;quot;: &amp;quot;^4.24.0&amp;quot;,
    &amp;quot;rollup-plugin-peer-deps-external&amp;quot;: &amp;quot;^2.2.4&amp;quot;,
    &amp;quot;rollup-plugin-terser&amp;quot;: &amp;quot;^7.0.2&amp;quot;,
    &amp;quot;rollup-plugin-typescript2&amp;quot;: &amp;quot;^0.36.0&amp;quot;,
    &amp;quot;storybook&amp;quot;: &amp;quot;^8.3.4&amp;quot;
  },
  &amp;quot;peerDependencies&amp;quot;: {
    &amp;quot;react&amp;quot;: &amp;quot;^18.3.1&amp;quot;,
    &amp;quot;react-dom&amp;quot;: &amp;quot;^18.3.1&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;packages/ui/rollup.config.mjs&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mjs&quot;&gt;// javascript/typescript 프로젝트 번들링하기 위한 rollup 설정

import peerDepsExternal from &amp;quot;rollup-plugin-peer-deps-external&amp;quot;;
import resolve from &amp;quot;@rollup/plugin-node-resolve&amp;quot;;
import commonjs from &amp;quot;@rollup/plugin-commonjs&amp;quot;;
import typescript from &amp;quot;rollup-plugin-typescript2&amp;quot;;
import json from &amp;quot;@rollup/plugin-json&amp;quot;;
import { terser } from &amp;quot;rollup-plugin-terser&amp;quot;;

export default {
  input: &amp;quot;src/index.ts&amp;quot;, // 메인 엔트리 경로
  output: [
    // CommonJS 형식으로 dist/index.js 생성
    // 소스맵 생성
    {
      file: &amp;quot;dist/index.js&amp;quot;,
      format: &amp;quot;cjs&amp;quot;,
      sourcemap: true,
    },
    // ES Module 형식으로 dist/index.esm.js 생성
    // 소스맵 생성
    {
      file: &amp;quot;dist/index.esm.js&amp;quot;,
      format: &amp;quot;esm&amp;quot;,
      sourcemap: true,
    },
  ],
  plugins: [
    peerDepsExternal(), // peerDependencies에 있는 모듈을 외부 모듈로 처리 - 번들링에 포함되지 않음
    resolve(), // 모듈 경로 해석
    commonjs(), // CommonJS 모듈을 ES6 Module로 변환
    typescript({
      tsconfigOverride: {
        compilerOptions: {
          declaration: true,
          declarationDir: &amp;quot;./dist&amp;quot;,
        },
      },
      clean: true,
    }), // typescript 파일을 컴파일, 선언 파일( .d.ts ) 생성
    json(), // json 파일을 읽어오는 플러그인
    terser(), // 코드 압축, 난독화
  ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다시 확인하고 실행해보면 버튼이 보이는 것을 확인할 수 있을 줄 알았지만 ? 역시 인식하지 못했다. &lt;/p&gt;
&lt;p&gt;개발자 도구에서 확인해보면 아래와 같은 에러가 발생한다.&lt;br&gt;Uncaught SyntaxError: The requested module does not provide an export named &amp;#39;Button&amp;#39; &lt;/p&gt;
&lt;p&gt;해당 에러는 번들된 모듈이 Button을 제대로 내보내지 않아서 발생하는 에러인데...&lt;br&gt;크게 잘못된점이 없어 보인다. 여러방식으로 시도해보던중 혹시 vite 설정에서 문제가 있는것이 아닐까 싶어서 확인해보았다.&lt;/p&gt;
&lt;p&gt;검색해보니 번들링된 모듈을 모듈 환경에서 인식하지 못하는 것 같아서 아래와 같이 설정을 추가해주었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apps/test/vite.config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { defineConfig } from &amp;quot;vite&amp;quot;;
import react from &amp;quot;@vitejs/plugin-react&amp;quot;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  optimizeDeps: {
    include: [&amp;quot;@packages/ui&amp;quot;],
  },
});
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Side Project</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/347</guid>
      <comments>https://wonillism.tistory.com/347#entry347comment</comments>
      <pubDate>Fri, 4 Oct 2024 15:36:16 +0900</pubDate>
    </item>
    <item>
      <title>[Monorepo] UI 라이브러리 만들기 (1)</title>
      <link>https://wonillism.tistory.com/346</link>
      <description>&lt;hr&gt;
&lt;p&gt;title: UI 패키지 만들기&lt;br&gt;date created: 2024-10-03&lt;br&gt;date modified: 2024-10-03&lt;br&gt;tags: [pnpm, monorepo, side project]&lt;br&gt;links: []&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;모노레포에서 공통 UI를 담당할 패키지를 만들어보자.&lt;/p&gt;
&lt;h2&gt;1. 패키지 구조 설정&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ mkdir packages
$ cd packages
$ mkdir ui
$ cd ui
$ pnpm init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하위 프로젝트 명령어를 위해 root &lt;code&gt;package.json&lt;/code&gt;에 스크립트를 지정해주자.&lt;br&gt;&lt;code&gt;--filter&lt;/code&gt; 옵션을 사용하면 특정 패키지에 대한 명령어를 실행할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;monorepo&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;ui&amp;quot;: &amp;quot;pnpm --filter @packages/ui&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;WONILLISM&amp;quot;,
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;prettier&amp;quot;: &amp;quot;^3.3.3&amp;quot;,
    &amp;quot;typescript&amp;quot;: &amp;quot;^5.6.2&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2-1. 의존성 설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ pnpm ui add react react-dom tslib
$ pnpm ui add -D @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-json @types/react @types/react-dom rollup rollup-plugin-peer-deps-external rollup-plugin-typescript2 rollup-plugin-terser&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 의존성들은 아래와 같은 역할을 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tslib&lt;/code&gt; : TypeScript 코드를 번들링하고, 타입 정의를 포함시킴, 번들링 과정에서 중복 포함되는 것을 방지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@rollup/plugin-commonjs&lt;/code&gt; : CommonJS 모듈을 ES6로 변환하여 Rollup에서 사용할 수 있게 해줌&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@rollup/plugin-node-resolve&lt;/code&gt; : Node.js의 모듈 해석 알고리즘을 사용하여 외부 모듈을 찾아 번들에 포함시킴&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@rollup/plugin-json&lt;/code&gt; : JSON 파일을 번들에 포함시키고, 모듈 해석 알고리즘을 사용하여 외부 모듈을 찾아 번들에 포함시킴&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@types/react&lt;/code&gt; : React 라이브러리의 타입 정의를 제공&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@types/react-dom&lt;/code&gt; : React DOM 라이브러리의 타입 정의를 제공&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rollup&lt;/code&gt; : 자바스크립트 모듈 번들러&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rollup-plugin-peer-deps-external&lt;/code&gt; : 프로젝트의 종속성을 외부 모듈로 처리하여 번들링 과정에서 중복 포함되는 것을 방지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rollup-plugin-typescript2&lt;/code&gt; : TypeScript 코드를 번들링하고, 타입 정의를 포함시킴&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rollup-plugin-terser&lt;/code&gt; : 번들된 코드를 최적화하여 번들 크기를 줄임&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음으로 peerDependencies를 설정해주자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;peerDependencies&amp;quot;: {
    &amp;quot;react&amp;quot;: &amp;quot;^18.3.1&amp;quot;,
    &amp;quot;react-dom&amp;quot;: &amp;quot;^18.3.1&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 번들링을 위한 설정 파일을 만들어주자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ touch rollup.config.mjs&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-mjs&quot;&gt;// javascript/typescript 프로젝트 번들링하기 위한 rollup 설정

import peerDepsExternal from &amp;quot;rollup-plugin-peer-deps-external&amp;quot;;
import resolve from &amp;quot;@rollup/plugin-node-resolve&amp;quot;;
import commonjs from &amp;quot;@rollup/plugin-commonjs&amp;quot;;
import typescript from &amp;quot;@rollup/plugin-typescript2&amp;quot;;
import json from &amp;quot;@rollup/plugin-json&amp;quot;;
import { terser } from &amp;quot;rollup-plugin-terser&amp;quot;;

export default {
  input: &amp;quot;src/index.ts&amp;quot;, // 메인 엔트리 경로
  output: [
    // CommonJS 형식으로 dist/index.js 생성
    // 소스맵 생성
    {
      file: &amp;quot;dist/index.js&amp;quot;,
      format: &amp;quot;cjs&amp;quot;,
      sourcemap: true,
    },
    // ES Module 형식으로 dist/index.esm.js 생성
    // 소스맵 생성
    {
      file: &amp;quot;dist/index.esm.js&amp;quot;,
      format: &amp;quot;esm&amp;quot;,
      sourcemap: true,
    },
  ],
  plugins: [
    peerDepsExternal(), // peerDependencies에 있는 모듈을 외부 모듈로 처리 - 번들링에 포함되지 않음
    resolve(), // 모듈 경로 해석
    commonjs(), // CommonJS 모듈을 ES6 Module로 변환
    typescript({
      tsconfigOverride: {
        compilerOptions: {
          declaration: true,
          declarationDir: &amp;quot;./dist&amp;quot;,
        },
      },
      rollupCommonJSResolveHack: true,
      clean: true,
    }), // typescript 파일을 컴파일, 선언 파일( .d.ts ) 생성
    json(), // json 파일을 읽어오는 플러그인
    terser(), // 코드 압축, 난독화
  ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2-2. 빌드 스크립트 추가&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  ...
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;build&amp;quot;: &amp;quot;rollup -c rollup.config.mjs&amp;quot;,
    &amp;quot;storybook&amp;quot;: &amp;quot;storybook dev -p 6006&amp;quot;,
    &amp;quot;storybook:build&amp;quot;: &amp;quot;storybook build&amp;quot;
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 스토리북 설정&lt;/h2&gt;
&lt;p&gt;스토리북은 컴포넌트 라이브러리를 시각적으로 테스트하고 문서화하는 데 사용되는 도구이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ pnpm dlx storybook@latest init &lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-1. typescript 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  // 상위 디렉토리의 tsconfig.json 설정을 상속
  &amp;quot;extends&amp;quot;: &amp;quot;../../tsconfig.json&amp;quot;,
  &amp;quot;compilerOptions&amp;quot;: {
    // 컴파일된 파일이 저장될 디렉토리
    &amp;quot;outDir&amp;quot;: &amp;quot;dist&amp;quot;,
    // 사용할 라이브러리 정의
    &amp;quot;lib&amp;quot;: [&amp;quot;ES6&amp;quot;, &amp;quot;dom&amp;quot;],
    // 소스맵 생성 여부
    &amp;quot;sourceMap&amp;quot;: true,
    // JavaScript 파일 컴파일 허용 여부
    &amp;quot;allowJs&amp;quot;: false,
    // React JSX 처리 방식
    &amp;quot;jsx&amp;quot;: &amp;quot;react-jsx&amp;quot;,
    // 모듈 해석 방식
    &amp;quot;moduleResolution&amp;quot;: &amp;quot;node&amp;quot;,
    // 소스 파일의 루트 디렉토리
    &amp;quot;rootDirs&amp;quot;: [&amp;quot;src&amp;quot;],
    // 모듈 해석을 위한 기본 경로
    &amp;quot;baseUrl&amp;quot;: &amp;quot;.&amp;quot;,
    // 엄격한 타입 체크 옵션들
    &amp;quot;noImplicitReturns&amp;quot;: true,
    &amp;quot;noImplicitThis&amp;quot;: true,
    &amp;quot;noImplicitAny&amp;quot;: true,
    &amp;quot;strictNullChecks&amp;quot;: true,
    // 사용하지 않는 지역 변수 체크
    &amp;quot;noUnusedLocals&amp;quot;: true,
    // CommonJS 모듈과 ES 모듈 간의 상호 운용성 개선
    &amp;quot;esModuleInterop&amp;quot;: true,
    // 선언 파일(.d.ts) 생성
    &amp;quot;declaration&amp;quot;: true,
    // 데코레이터 메타데이터 생성
    &amp;quot;emitDecoratorMetadata&amp;quot;: true,
    &amp;quot;experimentalDecorators&amp;quot;: true,
    // JSON 모듈 import 허용
    &amp;quot;resolveJsonModule&amp;quot;: true,
    // 헬퍼 함수 사용 (코드 중복 감소)
    &amp;quot;importHelpers&amp;quot;: true,
    // 모듈 경로 별칭 설정
    &amp;quot;paths&amp;quot;: {
      &amp;quot;tslib&amp;quot;: [&amp;quot;path/to/node_modules/tslib/tslib.d.ts&amp;quot;]
    }
  },
  // 컴파일에 포함할 파일 패턴
  &amp;quot;include&amp;quot;: [&amp;quot;src/**/*&amp;quot;],
  // 컴파일에서 제외할 파일 패턴
  &amp;quot;exclude&amp;quot;: [&amp;quot;node_modules&amp;quot;, &amp;quot;dist&amp;quot;, &amp;quot;**/*.stories.tsx&amp;quot;]
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Side Project</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/346</guid>
      <comments>https://wonillism.tistory.com/346#entry346comment</comments>
      <pubDate>Fri, 4 Oct 2024 00:47:20 +0900</pubDate>
    </item>
    <item>
      <title>[Monorepo]모노레포로 사이드프로젝트 통합하기</title>
      <link>https://wonillism.tistory.com/345</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;예전에도 몇번 다루었던 모노레포를 제대로 구성해보지도 못했어서, 이번에는 앞으로 만들 모든 사이드프로젝트를 하나의 모노레포로 관리해보기 위해서 모노레포를 구성해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익숙한 yarn 대신 pnpm을 사용해서 구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. pnpm 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pnpm을 설치하는 방법은 여러가지가 있는데 그 중 brew와 npm을 통한 설치 방법 둘 중 고민했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;npm을 통한 설치&lt;/b&gt;: Node.js와 긴밀히 연동되며, 주로 Node.js 프로젝트와 작업할 때 편리한 방식이다. 하지만 Node.js 버전을 여러 개 관리할 때는 불편할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Homebrew를 통한 설치&lt;/b&gt;: Node.js 버전과 무관하게 시스템 전역에서 pnpm을 사용할 수 있으며, macOS 시스템과 통합된 패키지 관리가 가능하다. 여러 버전의 Node.js와 함께 사용해야 하는 경우 더 유리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pnpm을 npm에 독립적으로 사용할 것이냐, 의존적으로 사용할 것이냐의 차이인데... 멀리 보면 brew가 좋을것 같지만, 당분간은 node 환경의 프로젝트만 진행하지 않을까싶고, brew를 사용했을때 다른 이슈들이 많이 발생하지 않을까 싶어서, npm을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pnpm.io/ko/installation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pnpm 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727415607828&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install -g pnpm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 프로젝트 구성&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pnpm 프로젝트 초기화&lt;/p&gt;
&lt;pre id=&quot;code_1727416553299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ mkdir projects
$ cd projects
$ pnpm init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-1. 프로젝트 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 구조는 크게 재사용 가능한 패키지나 라이브러리, 여러 프로젝트 형태로 나누자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1727416745853&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;my-monorepo/
├── packages/       # 재사용 가능한 패키지나 라이브러리
│   ├── ui-lib/     # 공통 컴포넌트 라이브러리 (예: 디자인 시스템)
│   └── utils/      # 공통 유틸리티 함수
├── apps/           # 실제 프론트엔드 애플리케이션들이 위치
│   ├── app1/       # 예: React or Next.js 앱
│   └── app2/       # 또 다른 앱
├── node_modules/   # pnpm이 사용하는 전역 노드 모듈 폴더 (Hoisting)
├── package.json    # 루트 패키지 매니저 설정
└── pnpm-workspace.yaml  # 워크스페이스 설정 파일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2. 의존성 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;root에 의존성을 설치하게 되면, 하위 프로젝트에서 의존성을 설치하지 않아도 해당 의존성을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 pnpm-workspace.yaml을 통해 모노레포에서 어떤 디렉토리를 워크스페이스로 인식할지를 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pnpm-workspace.yaml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727417394928&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;packages:
 - &quot;apps/*&quot;
 - &quot;packages/*&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 root package.json에서 공통적으로 사용되는 스크립트나 의존성을 정의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 공통으로 사용될 의존성은 typescript, eslint, prettier 정도일것같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;typescript 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727417907118&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pnpm add -D typescript
$ pnpm tsc --init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r7BmO/btsJPz4MGib/GkgdGgy1YgTinY4yfgHYxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r7BmO/btsJPz4MGib/GkgdGgy1YgTinY4yfgHYxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r7BmO/btsJPz4MGib/GkgdGgy1YgTinY4yfgHYxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr7BmO%2FbtsJPz4MGib%2FGkgdGgy1YgTinY4yfgHYxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1940&quot; height=&quot;716&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;eslint 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727417970551&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pnpm add -D eslint
$ pnpm eslint --init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg2yro/btsJPJ0oM5U/45Tz5p8jfRIY3vjKXAi0OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg2yro/btsJPJ0oM5U/45Tz5p8jfRIY3vjKXAi0OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg2yro/btsJPJ0oM5U/45Tz5p8jfRIY3vjKXAi0OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg2yro%2FbtsJPJ0oM5U%2F45Tz5p8jfRIY3vjKXAi0OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;1020&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prettier 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727418003219&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pnpm add -D prettier
echo {} &amp;gt; .prettierrc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFXUHF/btsJPIHcDcd/ocnq173kxJ3EnxZvQfZnW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFXUHF/btsJPIHcDcd/ocnq173kxJ3EnxZvQfZnW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFXUHF/btsJPIHcDcd/ocnq173kxJ3EnxZvQfZnW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFXUHF%2FbtsJPIHcDcd%2Focnq173kxJ3EnxZvQfZnW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;346&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;eslint, prettier 통합&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727418485424&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pnpm add -D eslint-config-prettier eslint-plugin-prettier&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UGWxr/btsJOIIdaYC/1IZlqWGeDN5BXWc847djBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UGWxr/btsJOIIdaYC/1IZlqWGeDN5BXWc847djBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UGWxr/btsJOIIdaYC/1IZlqWGeDN5BXWc847djBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUGWxr%2FbtsJOIIdaYC%2F1IZlqWGeDN5BXWc847djBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1500&quot; height=&quot;332&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.eslintrc.json에서 prettier 관련 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727418528491&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;extends&quot;: [&quot;eslint:recommended&quot;, &quot;plugin:@typescript-eslint/recommended&quot;, &quot;prettier&quot;],
  &quot;plugins&quot;: [&quot;@typescript-eslint&quot;, &quot;prettier&quot;],
  &quot;rules&quot;: {
    &quot;prettier/prettier&quot;: &quot;error&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 세부 설정은 본인의 스타일에 맞게 설정하면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;script 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1727420381627&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;amp;&amp;amp; exit 1&quot;,
    &quot;build&quot;: &quot;tsc&quot;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 역시 필요한 스크립트를 스타일에 맞게 설정하면 될 것 같다.&lt;/p&gt;</description>
      <category>Side Project</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/345</guid>
      <comments>https://wonillism.tistory.com/345#entry345comment</comments>
      <pubDate>Fri, 27 Sep 2024 15:56:46 +0900</pubDate>
    </item>
    <item>
      <title>[Riverpod] Riverpod 상태관리</title>
      <link>https://wonillism.tistory.com/344</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lwaat/btsCZiXpf2E/NBWZNNODHH2NuZ89Hev1wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lwaat/btsCZiXpf2E/NBWZNNODHH2NuZ89Hev1wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lwaat/btsCZiXpf2E/NBWZNNODHH2NuZ89Hev1wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flwaat%2FbtsCZiXpf2E%2FNBWZNNODHH2NuZ89Hev1wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;222&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에는 많은 상태관리 패키지들이 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 Riverpod에 대해서 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod는 Provider의 anagram(철자 바꾸기)이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod는 Flutter/Dart의 반응형 캐싱 프래임워크로 선언적 프로그래밍과 반응형 프로그래밍을 사용하여 애플리케이션 로직의 상당 부분을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://riverpod.dev/ko/docs/introduction/getting_started&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://riverpod.dev/ko/docs/introduction/getting_started&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704346863067&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Getting started | Riverpod&quot; data-og-description=&quot;Try Riverpod online&quot; data-og-host=&quot;riverpod.dev&quot; data-og-source-url=&quot;https://riverpod.dev/ko/docs/introduction/getting_started&quot; data-og-url=&quot;https://riverpod.dev/ko/docs/introduction/getting_started&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pI9Dp/hyUXPbtnM7/hbnqpxpwOSd7vJuzEXvEnK/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/AuwRa/hyUXTrpfBy/ZKKunn4OKaakqOwv6sxCXK/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/oTncq/hyUXMy4pj7/ajKohIK86WNZVidqThW6I0/img.png?width=2000&amp;amp;height=2000&amp;amp;face=0_0_2000_2000&quot;&gt;&lt;a href=&quot;https://riverpod.dev/ko/docs/introduction/getting_started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://riverpod.dev/ko/docs/introduction/getting_started&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pI9Dp/hyUXPbtnM7/hbnqpxpwOSd7vJuzEXvEnK/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/AuwRa/hyUXTrpfBy/ZKKunn4OKaakqOwv6sxCXK/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/oTncq/hyUXMy4pj7/ajKohIK86WNZVidqThW6I0/img.png?width=2000&amp;amp;height=2000&amp;amp;face=0_0_2000_2000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting started | Riverpod&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Try Riverpod online&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;riverpod.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서 상 아래와 같은 의존성들을 주입해주어야한다는데, 굳이? 다 필요한가 싶긴하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측일뿐 아직 뭐가 뭔지 모르니 우선 다 설치해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1704346316995&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter pub add flutter_riverpod
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner
flutter pub add dev:custom_lint
flutter pub add dev:riverpod_lint&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 Hello World 예제로 어떤 방식으로 Riverpod가 구성되는지 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1704347084389&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

/// 'hello world'라는 값을 store하고 있는 provider
/// Provider를 사용하면 노출된 값을 모의/재정의할 수 있다.
/// Provider가 가지고 있는 매개변수를 store라고 한다.
final helloWorldProvider = Provider((ref) =&amp;gt; 'hello world!');

void main() {
  runApp(
    /// 위젯이 Provider를 읽을 수 있도록
    /// 전체 앱을 ProviderScope 위젯으로 래핑
    /// 여기서 Provider의 상태가 저장된다.
    /// 
    /// 일반적으로 루트 위젯을 ProviderScope로 감싸서 하위 위젯트리에서 Provider객체에 접근할 수 있도록한다.
    /// 물론 루트 위젯을 감싸지 않고 사용할 수도 있다.
    const ProviderScope(
      child: App(),
    ),
  );
}

/// StatelessWidget 대신 Riverpod에 의해 노출되는 ConsumerWidget
///
/// ConsumerWidget은 StatefulWiget이 확장된 추상 클래스이다.
/// abstract class ConsumerStatefulWidget extends StatefulWidget {
/// ...
/// }
/// 
/// ConsumerWidget은 Provider 객체의 상태를 구독하고
/// 상태 변경이 있을 때마다 build를 호출하여 화면을 다시 그린다.
class App extends ConsumerWidget {
  const App({super.key});


  /// WidgetRef는 위젯 트리 내의 Provider 객체에 대한 참조를 제공한다
  /// 이를 통해 하위 위젯에서 Provider 객체를 생성하거나 액세스 할 수 있다.
  /// WidgetRef가 Provider객체에 엑세스하여 상태변경을 감지하면 현재 위젯에 다시 빌드 요청을 한다.
  /// 또한 WidgetRefsms context와 같은 역할을 해서 다른 위젯에서도 상태를 공유하는 역할을 한다.
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(helloWorldProvider);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(&quot;hi&quot;),
        ),
        body: Center(
          child: Text(value),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 주석과 같은식으로 처리된다&lt;/p&gt;</description>
      <category>Dart/Flutter</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/344</guid>
      <comments>https://wonillism.tistory.com/344#entry344comment</comments>
      <pubDate>Thu, 4 Jan 2024 14:46:04 +0900</pubDate>
    </item>
    <item>
      <title>[Firebase] FCM이란 ?</title>
      <link>https://wonillism.tistory.com/342</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekrV60/btsCZ4XMwNJ/FxQvkU8LSboKEGghCL3yxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekrV60/btsCZ4XMwNJ/FxQvkU8LSboKEGghCL3yxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekrV60/btsCZ4XMwNJ/FxQvkU8LSboKEGghCL3yxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekrV60%2FbtsCZ4XMwNJ%2FFxQvkU8LSboKEGghCL3yxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;159&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Firebase란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase는 Google의 모바일 앱 개발 플랫폼이다. 여러가지 개발 시간 단축 및 확장 가능한 인프라가 포함되는 웹 및 모바일 애플리케이션 구축을 위한 백엔드 플랫폼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase는 간편한 애플리케이션 구축, 애플리케이션 릴리즈 및 모니터링, 사용자 참여 및 애플리케이션 프로모션을 쉽게 추적을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 인프라 중 Firebase에서 제공하는 Firebase Cloud Massage(이하 FCM)을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;FCM이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCM은 메시지를 안정적으로 무료 전송할 수 있는 크로스 플랫폼 메시징 솔루션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCM을 사용하면 새 이메일이나 기타 데이터를 동귀화할 수 있음을 클라이언트 앱에 알릴 수 있다. 이런 알림 메시지를 통해 사용자를 유지하고 재참여를 유도할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCM 구현에는 송수신을 위한 두 가지 주요 구성요소가 포함된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Firebase용 Cloud Functions 또는 앱 서버와 같이 메시지를 작성, 타겟팅, 전송할 수 있는 신뢰할 수 있는 환경&lt;/li&gt;
&lt;li&gt;해당 플랫폼별 전송 서비스를 통해 메시지를 수신하는 Apple, Android 또는 웹(자바스크립트) 클라이언트 앱&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FCM 아키텍처 개요&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/95IfU/btsCU2fmLLF/yf7KYuGf5Ypzqll9YHahK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/95IfU/btsCU2fmLLF/yf7KYuGf5Ypzqll9YHahK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/95IfU/btsCU2fmLLF/yf7KYuGf5Ypzqll9YHahK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F95IfU%2FbtsCU2fmLLF%2Fyf7KYuGf5Ypzqll9YHahK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메시지 요청을 작성하거나 구현하는 도구&lt;/li&gt;
&lt;li&gt;FCM 백엔드
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메시지 요청을 수락하는 등 여러 기능을 수행하는 FCM 백엔드는 주제를 통해 메시지 팬아웃을 수행하고 메시지 ID와 같은 메시지 메타데이터를 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;플랫폼 수준 전송 레이어
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기기로 타겟팅된 메시지를 라우팅하고, 메시지 전송을 처리하고, 필요한 경우 플랫폼별 구성을 적용한다. 이 전송 레이어에는 다음이 포함된다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Google Play 서비스를 사용하는 Android 기기용 Android 전송 레이어 (ATL)&lt;/li&gt;
&lt;li&gt;Apple 기기용 Apple 푸시 알림 서비스 (APN)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;사용자 기기의 FCM SDK
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;알림이 표시되거나 앱의 Foreground / Background 상태 및 관련 애플리케이션 로직에 따라 메시지가 처리된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Life Cycle&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FCM에서 메시지를 수신하도록 기기를 등록한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 앱의 인스턴스가 메시지를 수신하도록 등록하여 앱 인스턴스를 고유하게 식별하는 등록 토큰을 받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다운스트림 메시지 전송 및 수신
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 보낸다. 앱 서버가 클라이언트 앱에 메시지를 보낸다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지는 알림 작성기 또는 신뢰할 수 있는 환경에서 작성되며 메시지 요청이 FCM 백엔드로 전송된다.&lt;/li&gt;
&lt;li&gt;FCM 백엔드는 메시지 요청을 수신하고 메시지 ID와 기타 메타데이터를 생성하여 플랫폼별 전송 레이어로 보낸다.&lt;/li&gt;
&lt;li&gt;기기가 온라인 상태이면 메시지가 플랫폼별 전송 레이어를 통해 기기로 전송된다.&lt;/li&gt;
&lt;li&gt;기기에서 클라이언트 앱이 메시지 또는 알림을 수신한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 경로&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FCM SDK 설정
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;플랫폼에 맞는 설정 안내에 따라 앱에서 Firebase 및 FCM을 설정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트 앱 개발
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트 앱에 메시지 처리, 주제 구독 로직 또는 기타 선택사항 기능을 추가한다. 개발 중에는 알림 작성기에서 테스트 메시지를 쉽게 보낼 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;앱 서버 개발
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인증, 보내기 요청 작성, 응답 처리 등을 수행하는 전송 로직을 만들 때 Firebase Admin SDK를 사용할지 아니면 서버 프로토콜 중 하나를 사용할지 결정한다. 그런 다음 신뢰할 수 있는 환경에 로직을 구축한다. 클라이언트 애플리케이션에서 업스트림 메시징을 사용하려면 XMPP를 사용해야 한다. 또한 Cloud Functons는 XMPP에 필요한 영구적 연결을 지원하지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;XMPP(Extensible Messaging and Presence Protocol)는 XML에 기반한 메시지 지향 미들웨어용 통신 프로토콜이다. &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging?authuser=0&amp;amp;_gl=1*dc0ptx*_ga*MTg1Njk1NDA0My4xNzA0MTc0MjU5*_ga_CW55HF8NVT*MTcwNDE3NDI1OS4xLjEuMTcwNDE3NTI4Mi42MC4wLjA.&amp;amp;hl=ko#implementation_paths&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/docs/cloud-messaging?authuser=0&amp;amp;_gl=1*dc0ptx*_ga*MTg1Njk1NDA0My4xNzA0MTc0MjU5*_ga_CW55HF8NVT*MTcwNDE3NDI1OS4xLjEuMTcwNDE3NTI4Mi42MC4wLjA.&amp;amp;hl=ko#implementation_paths&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704180600776&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Firebase 클라우드 메시징&quot; data-og-description=&quot;Firebase 클라우드 메시징(FCM)은 무료로 메시지를 안정적으로 전송할 수 있는 크로스 플랫폼 메시징 솔루션입니다.&quot; data-og-host=&quot;firebase.google.com&quot; data-og-source-url=&quot;https://firebase.google.com/docs/cloud-messaging?authuser=0&amp;amp;_gl=1*dc0ptx*_ga*MTg1Njk1NDA0My4xNzA0MTc0MjU5*_ga_CW55HF8NVT*MTcwNDE3NDI1OS4xLjEuMTcwNDE3NTI4Mi42MC4wLjA.&amp;amp;hl=ko#implementation_paths&quot; data-og-url=&quot;https://firebase.google.com/docs/cloud-messaging?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging?authuser=0&amp;amp;_gl=1*dc0ptx*_ga*MTg1Njk1NDA0My4xNzA0MTc0MjU5*_ga_CW55HF8NVT*MTcwNDE3NDI1OS4xLjEuMTcwNDE3NTI4Mi42MC4wLjA.&amp;amp;hl=ko#implementation_paths&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://firebase.google.com/docs/cloud-messaging?authuser=0&amp;amp;_gl=1*dc0ptx*_ga*MTg1Njk1NDA0My4xNzA0MTc0MjU5*_ga_CW55HF8NVT*MTcwNDE3NDI1OS4xLjEuMTcwNDE3NTI4Mi42MC4wLjA.&amp;amp;hl=ko#implementation_paths&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Firebase 클라우드 메시징&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Firebase 클라우드 메시징(FCM)은 무료로 메시지를 안정적으로 전송할 수 있는 크로스 플랫폼 메시징 솔루션입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;firebase.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Firebase</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/342</guid>
      <comments>https://wonillism.tistory.com/342#entry342comment</comments>
      <pubDate>Tue, 2 Jan 2024 16:30:06 +0900</pubDate>
    </item>
    <item>
      <title>[Dart] Dart Docs - Classes(1)</title>
      <link>https://wonillism.tistory.com/341</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRZz4i/btsBKjISSe5/yp9oup7lP9ByzQxvwGDxD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRZz4i/btsBKjISSe5/yp9oup7lP9ByzQxvwGDxD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRZz4i/btsBKjISSe5/yp9oup7lP9ByzQxvwGDxD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRZz4i%2FbtsBKjISSe5%2Fyp9oup7lP9ByzQxvwGDxD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;180&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart는 Class와 Mixin 기반 상속을 지원하는 객체지향언어다. 모든 객체는 클래스의 인스턴스이고, Null을 제외한 클래스는 모두 Object에서 비롯한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mixin 기반 상속이란 말은, 모든 클래스가 하나의 부모 클래스를 가지고 있지만 (최상위 클래스인 Object 제외) 클래스의 바디는 다양한 클래스 계층에서 재사용 될 수 있음을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart는 &lt;a href=&quot;https://dart.dev/language/extension-methods&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;extension-methods(확장 메소드)&lt;/a&gt;라는 것도 제공하는데, 이는 서브 클래스를 생성하거나, 클래스를 바꾸지 않고 클래스에 기능을 추가하는 방법이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 예시로 클래스에 대해 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1701328377412&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:math';

class Point {
  double x;
  double y;
  double z = 0;
  
  Point(this.x, this.y, [this.z = 0]);
  
  Map&amp;lt;String, double&amp;gt; getPoint()=&amp;gt;{'x':x, 'y':y, 'z':z};
  
  double distanceTo(Point b){
    final dx = x - b.x;
    final dy = y - b.y;
    final dz = z - b.z;
    
    return sqrt(dx * dx + dy * dy + dz * dz);
  }
}

void main() {
  final p = Point(1,1);
  final q = Point(4,5);
  final curr = p.getPoint();
  final dist = p.distanceTo(q);
  
  print(dist);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;클래스 멤버 사용하기&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;객체는 함수와 데이터로 구성된 멤버가 있다. 메소드를 호출 할 때, 객체에서 함수를 호출한다. 메소드는 호출할 때 객체 위에서 실행하므로 메소드는 객체의 함수와 데이터에 접근 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;.&quot;을 사용하면 인스턴스 변수나 메소드를 참조할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;생성자 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자를 사용하여 객체를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 이름은 ClassName 이나 ClassName.identifier 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇몇 클래스는 상수 생성자를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701329505470&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var p = const ImmutablePoint(2, 2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수 문맥에서 생성자나 리터럴 전에 const 키워드를 생략할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701329539946&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다수의 const 키워드가 있습니다.
const pointAndLine = const {
    'point': const [const ImmutablePoint(0, 0)],
    'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 생략 가능하나 첫번째 const 키워드는 사용해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701329564216&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 상수 문맥을 시작하는 한개의 const만 존재
const pointAndLine = {
    'point': [ImmutablePoint(0, 0)],
    'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인스턴스 변수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 초기화되지 않은 인스턴스 변수는 null 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 인스턴스 변수는 암시적인 getter 메소드를 생성한다. 또한 final이 아닌 인스턴스 변수와 초기화없는 late final 인스턴스 변수는 암시적인 setter 메소드를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701330029221&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
    final point = Point();
    point.x = 4; // x의 setter 메소드를 사용
	print(point.x); // x의 getter 메소드를 사용
	print(point.y == null); // 기본값은 null
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추상 클래스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스가 될 수 없는 추상 클래스를 정의하기 위해서 &quot;abstract&quot; 수식어를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상클래스를 사용하는 이유는 주로 자주 구현되는 인터페이스를 정의하는데 있어서 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 클래스를 인스턴스화할 수 있도록 보이려면, 팩토리 생성자를 정의한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701330712949&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이 클래스는 추상으로 선언되며 인스턴스화가 불가합니다.
abstract class AbstractContainer {
    // 생성자, 필드, 메소드를 선언...

    void updateChildren(); // 추상 메소드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;암시적 인터페이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 클래스는 암묵적으로 클래스와 구현된 인터페이스의 모든 인스턴스 멤버를 포함한 인터페이스를 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B의 구현을 상속하지 않고 B클래스의 API를 지원하는 클래스 A를 생성하려면, 클래스 A는 B 인터페이스를 구현해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A클래스는 implements 절에 선언된 한개 이상의 인터페이스를 구현하며 해당 인터페이스에 필요한 API를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1701331826558&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Person, greet()를 포함한 암시적 인터페이스
class Person {
    // 인터페이스에 있지만 이 라이브러리에서만 볼 수 있음
    final String _name;

    // 생성자이기 때문에 인터페이스에 없음
    Person(this._name);

    // 인터페이스
    String greet(String who) =&amp;gt; 'Hello, $who. I am $_name.';
}

// Person 인터페이스 구현체
class Impostor implements Person {
    String get _name =&amp;gt; '';

    String greet(String who) =&amp;gt; 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) =&amp;gt; person.greet('Bob');

void main() {
    print(greetBob(Person('Kathy')));
    print(greetBob(Impostor()));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정적 변수, 정적 함수&lt;/h2&gt;
&lt;pre id=&quot;code_1701332881155&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:math';

class Point {
  double x;
  double y;
  double z = 0;
  static String? name;
  
  // Point(this.x, this.y, this.name [this.z = 0]); -&amp;gt; name은 인스턴스에 존재하지 않음
  Point(this.x, this.y, [this.z = 0]);
  
  Map&amp;lt;String, double&amp;gt; getPoint()=&amp;gt;{'x':x, 'y':y, 'z':z};
  
  double distanceTo(Point b){
    final dx = x - b.x;
    final dy = y - b.y;
    final dz = z - b.z;
    
    return sqrt(dx * dx + dy * dy + dz * dz);
  }
  
  void printInfo() {
    print('x: ${x} y: ${y} z: ${z} name: $name');
  }
}

void main() {
  final p = Point(1,1);
  final q = Point(4,5);
  
  p.printInfo();
  q.printInfo();
  
  Point.name = &quot;점&quot;;
//   p.name = &quot;p점&quot;; line 35 &amp;bull; The static setter 'name' can't be accessed through an instance.
//  q.name = &quot;q점&quot;; 
  
  p.printInfo();
  q.printInfo();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static으로 선언된 변수와 함수는 인스턴스에서 동작하지 않고 사용되기 전까지 초기화 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 전반적인 변수와 메소드를 구현할 때 사용한다.&lt;/p&gt;</description>
      <category>Dart</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/341</guid>
      <comments>https://wonillism.tistory.com/341#entry341comment</comments>
      <pubDate>Thu, 30 Nov 2023 17:29:01 +0900</pubDate>
    </item>
    <item>
      <title>[Dart] Dart Docs - Generics</title>
      <link>https://wonillism.tistory.com/340</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8b7uF/btsBFE8ChZ6/m0pUguTlwss2KIoqVEhaXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8b7uF/btsBFE8ChZ6/m0pUguTlwss2KIoqVEhaXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8b7uF/btsBFE8ChZ6/m0pUguTlwss2KIoqVEhaXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8b7uF%2FbtsBFE8ChZ6%2Fm0pUguTlwss2KIoqVEhaXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;180&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 언어에도(JAVA, Typescript, ...) 존재하는 제네릭이 Dart에도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 제네릭의 정의는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;제네릭은 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있는 기술에 중점을 두어 재사용성을 높일 수 있는 방식이다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Why use generics?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 자주 타입보장이 필요할 때 사용되지만 코드가 실행가능하도록 많은 이점을 가지고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제네릭타입을 적절하게 지정하면 코드가 더 잘 생성된다.&lt;/li&gt;
&lt;li&gt;제네릭을 사용하여 코드 중복을 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 객체에 대한 캐시용 인터페이스를 생성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1701244048075&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하다보니 객체가 아닌 다른 타입용 인터페이스가 필요해지면 아래와 같이 타입에 맞추어 다른 인터페이스를 생성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701244247086&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

abstract class NumberCache {
  Number getByKey(String key);
  void setByKey(String key, Number value);
}

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 제네릭은 이 모든 인터페이스 생성에 대한 문제를 해결해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1701244294928&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class Cache&amp;lt;T&amp;gt; {
  T getByKey(String key);
  void setByKey(String key, T value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Using collection literals&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트, 세트, 맵 리터럴은 매개변수화 되어있다. 무슨 뜻인지 잘 모르겠다. 예시로 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1701245722996&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  /// 타입을 지정하지 않고 선언을 하면 
  /// 런타임 시점에 타입추론을 통해서
  /// Object타입의 배열로 정의된다.
  final coll1 = [&quot;hi&quot;, 1, 2, true]; // JSArray&amp;lt;Object&amp;gt;
  
  /// 아래와 같이 선언시, 문자열 배열이기 때문에
  /// 에러가 발생한다.
  /// The element type 'int' can't be assigned to the list type 'String'.
  final coll2 = &amp;lt;String&amp;gt;[&quot;hi&quot;,&quot;hello&quot;, 1];
  final coll3 = &amp;lt;String&amp;gt;[&quot;say&quot;, &quot;yes&quot;]; // JSArray&amp;lt;String&amp;gt;
  final coll4 = &amp;lt;String&amp;gt;{'say', 'yes'}; // _LinkedHashSet&amp;lt;String&amp;gt;
  final coll5 = &amp;lt;String, String&amp;gt;{       // JsLinkedHashMap&amp;lt;String, String&amp;gt;
    'name': 'womillism',
    'hi': 'hello',
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 생성자에 매개변수화된 타입을 사용할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1701245959359&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var nameSet = Set&amp;lt;String&amp;gt;.from(names);
var views = Map&amp;lt;int, View&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Restricting the parameterized type&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 타입을 구현할 때 인자로 제공되는 타입에 대한 제한을 하고 특정 타입의 하위 타입만 가능하도록 하고 싶을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 extends 키워드를 사용하여 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1701246141954&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Foo&amp;lt;T extends Object&amp;gt; {
  // T에 어느 타입이든 가능하지만 null이 아니어야 합니다.
}

class Foo&amp;lt;T extends SomeBaseClass&amp;gt; {
  // 구현은 여기서...
  String toString() =&amp;gt; &quot;Instance of 'Foo&amp;lt;$T&amp;gt;'&quot;;
}

var someBaseClassFoo = Foo&amp;lt;SomeBaseClass&amp;gt;();
var extenderFoo = Foo&amp;lt;Extender&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드와 함수에서도 마찬가지로 제네릭을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1701246233865&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;T first&amp;lt;T&amp;gt;(List&amp;lt;T&amp;gt; ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #2c3e50; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수의 반환 타입 (T)&lt;/li&gt;
&lt;li&gt;인자의 타입 (List&amp;lt;T&amp;gt;)&lt;/li&gt;
&lt;li&gt;로컬 변수의 타입 (T map)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Dart</category>
      <author>WONILLISM</author>
      <guid isPermaLink="true">https://wonillism.tistory.com/340</guid>
      <comments>https://wonillism.tistory.com/340#entry340comment</comments>
      <pubDate>Wed, 29 Nov 2023 17:24:18 +0900</pubDate>
    </item>
  </channel>
</rss>