<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>딤문의 개발일기</title>
    <link>https://deemmun.tistory.com/</link>
    <description>@Frontend
@Web-developer</description>
    <language>ko</language>
    <pubDate>Wed, 1 Jul 2026 14:01:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>딤문</managingEditor>
    <image>
      <title>딤문의 개발일기</title>
      <url>https://tistory1.daumcdn.net/tistory/4189886/attach/122db18dc1bf4299a3cd4b3f4b491619</url>
      <link>https://deemmun.tistory.com</link>
    </image>
    <item>
      <title>2024-06 블로그 이사 안내</title>
      <link>https://deemmun.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;첫 기술 블로그를 3년 넘게 티스토리에서 운영했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부턴 Medium에서 기술 블로그를 이어가려고 합니다.&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;/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;a href=&quot;https://medium.com/hal-ang&quot;&gt;https://medium.com/hal-ang&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1718552085606&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;medium-com:collection&quot; data-og-title=&quot;hal-ang &amp;ndash; Medium&quot; data-og-description=&quot;FE 기술 일기.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/hal-ang&quot; data-og-url=&quot;https://medium.com/hal-ang&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LAbFi/hyWoEE6lWe/lcGKqkzTJWjNRqaKiiOpjK/img.jpg?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460&quot;&gt;&lt;a href=&quot;https://medium.com/hal-ang&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/hal-ang&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LAbFi/hyWoEE6lWe/lcGKqkzTJWjNRqaKiiOpjK/img.jpg?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460');&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;hal-ang &amp;ndash; Medium&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;FE 기술 일기.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.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>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/90</guid>
      <comments>https://deemmun.tistory.com/90#entry90comment</comments>
      <pubDate>Mon, 17 Jun 2024 00:36:32 +0900</pubDate>
    </item>
    <item>
      <title>[개인 프로젝트] IOS 웹뷰 앱 '비가 오기 전에' 회고 - 무식한 자가 용감하다</title>
      <link>https://deemmun.tistory.com/89</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결과물&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;content_none_scroll.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsApsc/btsF1shVNti/GiEk7LNXBDfYsSv1jb1Kz1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsApsc/btsF1shVNti/GiEk7LNXBDfYsSv1jb1Kz1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsApsc/btsF1shVNti/GiEk7LNXBDfYsSv1jb1Kz1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bsApsc/btsF1shVNti/GiEk7LNXBDfYsSv1jb1Kz1/img.gif&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;342&quot; height=&quot;741&quot; data-filename=&quot;content_none_scroll.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;logo.png&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;1005&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/If735/btsF3lDgIEx/0hsZWToX05Zl4lOjgKRmu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/If735/btsF3lDgIEx/0hsZWToX05Zl4lOjgKRmu0/img.png&quot; data-alt=&quot;비가 오기 전에&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/If735/btsF3lDgIEx/0hsZWToX05Zl4lOjgKRmu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIf735%2FbtsF3lDgIEx%2F0hsZWToX05Zl4lOjgKRmu0%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;163&quot; height=&quot;163&quot; data-filename=&quot;logo.png&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;1005&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비가 오기 전에&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://apps.apple.com/app/%EB%B9%84%EA%B0%80-%EC%98%A4%EA%B8%B0-%EC%A0%84%EC%97%90/id6479691792&quot;&gt;앱스토어 URL&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14일간의 개인 프로젝트가 드디어 끝이 났습니다.&lt;br /&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;퇴사 이후 이사 일정이 있었기에 신경 쓸 것이 많아 그전까지는 사부작사부작 진득하니 하고 싶던 공부를 해야겠다 하며 무직 백수의 기간을 보내고 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날이 좀 풀린 어느 날 커피를 사러 가기 위해 대충 후드집업을 입고 나왔는데 세상에 너무 추운 겁니다.&lt;br /&gt;'아.. 엄청&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;춥네.. 몇&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;도야..'&lt;/span&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;br /&gt;그때 당시에도 팀원들한테 &quot;위젯 기능이 있으면 진짜 좋을텐데 언젠가 만들어 보고 싶어요&quot;라는 말을 했었는데 문득 이 말이 스쳐 지나가면서 이제는 만들 수 있지 않을까? 하는 생각이 들었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 웹 사이트에서 geolocation WEB API를 사용해 기온에 맞는 옷차림 정보만을 제공하는 게 끝이었지만, 이번에는 푸시 알림과 위젯을 지원하는 앱을 만들고자 했고 집에 돌아오자마자 바로 기획을 시작하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별로 어렵지 않았다며 너스레를 떨기엔 개인적으로 굉장히 도전적인.. 프로젝트였기 때문에 오늘은 개인적인 감상을 넣어 주절주절 프로젝트 회고를 한 번 작성해 볼까 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기획&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;만들고자 했던 기능&lt;/u&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 위젯을 통해 유저의 현재 위치 정보를 기반으로 날씨와 옷차림 정보를 제공한다.&lt;br /&gt;- 비가 오기 전에 대비할 수 있도록 푸시 알람을 보내준다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기획 단계에서는 해당 기능들을 구현하기 위해 어떤 작업이 필요한지, 어떤 기술 스택을 사용해야 할지 미리 파악 해야 했습니다.&lt;br /&gt;무식한 자가 용감하다고 하죠. 기획 단계에서 제일 어려웠던 것은 프로젝트의 사이즈를 가늠하는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이사 일정이 잡혀있었기에..가용 기간은 14일이 최대&lt;br /&gt;무조건 14일 내에 구현을 완료하는 것을 목표로 잡고 기획을 세우기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;고민) 하이브리드 앱 ( React-Native, Flutter ) vs IOS 네이티브 vs PWA&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PWA의 경우 푸시 알림도 지원하고, 일부 제한된 환경에서(특정 브라우저) 위젯 기능도 제공한다는 글을 보긴 했는데 기능 명세 상 백그라운드에서 유저의 위치 정보를 받아와야만 했고 동적으로 스케줄러를 통해 유저 설문과 백그라운드 위치 정보를 기반으로 시간을 계산하여 푸시 알람을 전송해야 했기에 PWA로 이를 구현하려면 예상치 못한 이슈 + 정보를 찾기 쉽지 않을 것 같기도 했고 앱스토어에서 앱을 띄우고 싶었기에 PWA는 제외했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React-Native 경험이 있었기에 전체 프로젝트를 RN으로 구성할까 고민을 했었습니다.&lt;br /&gt;다만 위젯 기능을 위해서는 하이브리드 앱으로 구현하더라도 필수 불가결로 네이티브 코드를 따로 만져야 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하이브리드 앱의 존재 자체가 IOS와 안드로이드를 모두 커버하기 위해 탄생한 것인데, 사실 이 앱은 제가 쓰고 싶어서 만드는 것이었기에 최소 충족 조건은 IOS였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 RN을 사용하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;안드로이드 위젯 지원은 추후에 도전해보는 것으로 한 뒤&lt;span&gt;&amp;nbsp;&lt;/span&gt;IOS만 핸들링해볼까도 고민했습니다.&lt;br /&gt;다만 1인 프로젝트인지라 서버도 개발해야 하는 상황이었기 때문에 서버 기능들을 고려해 봤을 때 경험이 적은 서버 쪽에서 예상치 못한 병목이 분명 발생할 것이라 확신했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(가슴에 손을 얹고 정말 나중에 안드로이드 지원을 할 것인지 생각해보면 그렇지 않을 것 같았습니다)&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 웹뷰 앱 서비스의 개발을 담당하며, 네이티브 &amp;lt;-&amp;gt; JS 통신을 위한 브릿지를 구현해 본 적이 있었기에 (&lt;a href=&quot;https://deemmun.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;포스팅 참고&lt;/a&gt;) &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;IOS 스위프트를 통해 네이티브 기능을 처리하고, 웹뷰로 브릿지를 구현하는 게 가장 효율적이겠다는 생각이 들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민 끝에 &lt;b&gt;IOS 웹뷰 앱&lt;/b&gt;으로 결정하고, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;웹뷰를 위한 클라이언트 환경은 SSR 경험이 없었기에 공부할 겸 Next로 구성하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 옛날에 노드 환경에서 NestJS + Mysql 조합을 사용하여 서버를 구현했던 적이 있고, Nest 경험이 좋았던지라 조금 더 익숙할 것이라 생각하여 다시 한번 사용해 보기로 결정하였습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;디자인&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ㅂㅂ.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LJVdK/btsF18bAz4Z/mSiLo0tIvrkvSgzjjgH9T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LJVdK/btsF18bAz4Z/mSiLo0tIvrkvSgzjjgH9T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LJVdK/btsF18bAz4Z/mSiLo0tIvrkvSgzjjgH9T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLJVdK%2FbtsF18bAz4Z%2FmSiLo0tIvrkvSgzjjgH9T0%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;936&quot; height=&quot;840&quot; data-filename=&quot;ㅂㅂ.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1140465552507612&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ㅎㅎ.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHfszx/btsFZKQ3rO1/ovMWUGoxgSt7Avf7iUYAu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHfszx/btsFZKQ3rO1/ovMWUGoxgSt7Avf7iUYAu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHfszx/btsFZKQ3rO1/ovMWUGoxgSt7Avf7iUYAu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHfszx%2FbtsFZKQ3rO1%2FovMWUGoxgSt7Avf7iUYAu0%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;760&quot; height=&quot;934&quot; data-filename=&quot;ㅎㅎ.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&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;서당 개도 3년이면 풍월을 읊는다더니 프론트엔드 3년이기도 하고, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;같이 일했던 디자이너 분 덕분인지 예전에 직접 디자인해서 만들었던 프로젝트랑 비교해 봤을 때&amp;nbsp;&lt;/span&gt;디자인 실력이 많이 늘어난 것 같아 만들고 나서 기분이 매우 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 인터렉션을 좋아하는 편입니다. 귀염 뽀짝하고 부드럽고, 하지만 실무에서는 아무래도 기능 구현에 초점이 맞추어져 있다 보니 인터렉션을 구현할 기회가 별로 없어서 이번 프로젝트에서는 인터렉션을 적용하고자 했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;인터렉션 결과물&lt;/u&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;survey.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckHdrH/btsFZIeOvrf/hM6rejkzW4yxzochjVzvPk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckHdrH/btsFZIeOvrf/hM6rejkzW4yxzochjVzvPk/img.gif&quot; data-alt=&quot;알람 설문&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckHdrH/btsFZIeOvrf/hM6rejkzW4yxzochjVzvPk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ckHdrH/btsFZIeOvrf/hM6rejkzW4yxzochjVzvPk/img.gif&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;410&quot; height=&quot;889&quot; data-filename=&quot;survey.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1734&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;알람 설문&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동영상을 gif로 배속을 설정하여 변환하다 보니 버튼 인터렉션이 보이지 않는 부분이 있어 추가로 첨부합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;RippleButton&lt;/u&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;checkBox.gif&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfqNZ5/btsF0k5y7Z7/OBmrj4K1qPkmzbgvAXGMj1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfqNZ5/btsF0k5y7Z7/OBmrj4K1qPkmzbgvAXGMj1/img.gif&quot; data-alt=&quot;checkBox&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfqNZ5/btsF0k5y7Z7/OBmrj4K1qPkmzbgvAXGMj1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bfqNZ5/btsF0k5y7Z7/OBmrj4K1qPkmzbgvAXGMj1/img.gif&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;210&quot; height=&quot;104&quot; data-filename=&quot;checkBox.gif&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;checkBox&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(컴포넌트는&amp;nbsp;&lt;a href=&quot;https://65ef57f9ccb44b74669d9b0b-unsxvhkncl.chromatic.com/?path=/docs/common-checkbox--docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스토리북 링크&lt;/a&gt;를 통해 확인 가능합니다)&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 존재하는 모든 버튼 요소에서는  ripple 트랜지션이 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React-Native에 Touchableopacity 라는 버튼 컴포넌트가 존재하는데 그와 비슷하게 동작하는 애니메이션을 웹에 존재하는&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;버튼들에도 구현하고자 했고, RippleButton 컴포넌트를 만들어 모든 버튼 컴포넌트에 적용시켰습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Ripple 컴포넌트 구현&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;useRipple.tsx&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711096246322&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useRipple hook
const useRipple = &amp;lt;T extends HTMLElement&amp;gt;(ref: React.RefObject&amp;lt;T&amp;gt;) =&amp;gt; {
  const [ripples, setRipples] = useState&amp;lt;React.CSSProperties[]&amp;gt;([]);

  useEffect(() =&amp;gt; {
    if (ref.current) {
      const $elem = ref.current;

      const clickHandler = (e: MouseEvent) =&amp;gt; {
        const { left, top } = $elem.getBoundingClientRect();
        const _left = e.clientX - left;
        const _top = e.clientY - top;
        const height = $elem.clientHeight;
        const width = $elem.clientWidth;
        const diameter = Math.max(width, height);
        setRipples([
          ...ripples,
          {
            top: _top - diameter / 2,
            left: _left - diameter / 2,
            height: Math.max(width, height),
            width: Math.max(width, height)
          }
        ]);
      };

      $elem.addEventListener(&quot;click&quot;, clickHandler);

      return () =&amp;gt; {
        $elem.removeEventListener(&quot;click&quot;, clickHandler);
      };
    }
  }, [ref, ripples]);

  const _debounced = useDebounce(ripples, 1000);

  useEffect(() =&amp;gt; {
    if (_debounced.length) {
      setRipples([]);
    }
  }, [_debounced.length]);

  return ripples?.map((style, i) =&amp;gt; {
    return (
      &amp;lt;span
        key={i}
        style={{
          ...style,
          position: &quot;absolute&quot;,
          backgroundColor: &quot;#FFFFFF&quot;,
          opacity: &quot;25%&quot;,
          transform: &quot;scale(0)&quot;,
          animation: `ripple ${RIPPLE_DURATION}ms linear`,
          borderRadius: &quot;50%&quot;
        }}
      /&amp;gt;
    );
  });
};

export default useRipple;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;클릭 이벤트를 기반으로 ripple을 위한 hook을 구현했습니다.&lt;/span&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;u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;RippleButton.tsx&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711096416334&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface RippleButtonProps
  extends ButtonHTMLAttributes&amp;lt;HTMLButtonElement&amp;gt; {
  children: ReactNode;
  onClick?: () =&amp;gt; void;
  delay?: number;
  onRippleEndClick?: () =&amp;gt; void;
}
const RippleButton = ({
  // ripple duration default is 600ms
  delay = RIPPLE_DURATION,
  onClick,
  onRippleEndClick,
  children,
  ...props
}: RippleButtonProps) =&amp;gt; {
  const buttonRef = useRef&amp;lt;HTMLButtonElement&amp;gt;(null);
  const ripples = useRipple(buttonRef);

  return (
    &amp;lt;button
      ref={buttonRef}
      {...props}
      style={{ position: &quot;relative&quot;, overflow: &quot;hidden&quot; }}
      onClick={() =&amp;gt; {
        onClick &amp;amp;&amp;amp; onClick();

        if (onRippleEndClick) {
          setTimeout(() =&amp;gt; {
            onRippleEndClick();
          }, delay);
        }
      }}
    &amp;gt;
      {children}
      {ripples}
    &amp;lt;/button&amp;gt;
  );
};

export default RippleButton;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Ripple 동작을 모든 버튼 컴포넌트에 적용하고 싶었기 때문에 기본 button 요소의 어트리뷰트를 포함하고, RippleButton을 위한 인터페이스를 별도로 선언하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onClick은 클릭 즉시 실행, onRippleEndClick은 기본 값으로 useRipple 훅에서 설정한 duration이 지난 후에 실행되도록 타이머 함수를 추가하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클릭 메소드를 두 개로 나눈 것은 목적에 따라 다르게 동작해야 하는 경우가 있었기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ripple2.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3McpB/btsF0GABqOj/GvsUs2RWlKZcwFR1IxnHok/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3McpB/btsF0GABqOj/GvsUs2RWlKZcwFR1IxnHok/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3McpB/btsF0GABqOj/GvsUs2RWlKZcwFR1IxnHok/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c3McpB/btsF0GABqOj/GvsUs2RWlKZcwFR1IxnHok/img.gif&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;332&quot; height=&quot;719&quot; data-filename=&quot;ripple2.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설문의 상단에는 progressBar가 존재합니다. 이는 답변 여부에 따라 transition이 발생하도록 구현되어 있는데 onClick으로만 페이지를 이동할 경우 페이지가 변경되면서 progress bar의 트랜지션이 동작하지 않는 문제가 있었기 때문에, onClick을 통해 답변 여부 상태를 업데이트하고, onRippleEndClick을 통해 페이지 전환이 진행되도록 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구현된 RippleButton 컴포넌트를 통해 모든 버튼 컴포넌트 요소에 Ripple을 적용할 수 있었습니다.&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;u&gt;LargeButton.tsx&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711097234695&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import RippleButton, { RippleButtonProps } from &quot;./RippleButton&quot;;

const LargeButton = ({ children, ...rest }: RippleButtonProps) =&amp;gt; {
  return (
    &amp;lt;RippleButton
      {...rest}
      className=&quot;w-full px-20pxr rounded-10pxr bg-dark-primary text-dark-on-primary text-center button-b-18 py-15pxr&quot;
    &amp;gt;
      {children}
    &amp;lt;/RippleButton&amp;gt;
  );
};

export default LargeButton;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;클라이언트 개발&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 Next, TailwindCSS, Storybook, MSW로 구성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 Next와 MSW는 처음 사용해 보는 것이었는데, Next는 개인적인 SSR 환경 경험을 위해, MSW는 서버 API가 만들어지기 전 API 연결을 진행하기 위해 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next도 난관이 여러 번 있었습니다.&lt;br /&gt;프로젝트를 마친 지금도 Next를 제대로 사용했다고는 생각이 들지 않는데 이는 앞으로도 조금씩 공부를 해서 채워 나가야 할 것 같습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;빌드 환경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next 프로젝트를 구성할 때 Turbopack을 사용해 볼까도 고민했는데, 아직 베타 버전이기도 하고, 프로드에 올리기에는 안정성 부분에서 우려되는 것들이 많아 webpack을 유지하고, 컴파일러는 SWC를 선택해 조금이라도 빌드 속도를 개선해보고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRN 웹팩으로 구성되어있던 서비스를 Vite으로 마이그레이션 하며 개발 경험이 상당히 상승하여 참 좋았는데.. 역체감이라고 하죠.&lt;br /&gt;다시 웹팩 환경으로 돌아오니 웹팩 HMR이 너무 답답하게 느껴져서 새삼 esbuild 성능에 감탄했던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(Turbopack 개발 과정에서 Vite과 협력 중이라는 이야기를 보긴 했지만 아직은 Next가 Vite을 추천하고 있는 것 같진 않아 추후 고려해 보는 것으로 결정했습니다. (&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/upgrading/from-vite&quot;&gt;https://nextjs.org/docs/app/building-your-application/upgrading/from-vite&lt;/a&gt;))&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MSW&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 API 작업이 완료되기 전 Next app router에 MSW를 적용하는 것도 환경에 따라 분기처리를 해주어야 하는 부분이 있어 골치를 조금 먹었던 것 같습니다.&lt;br /&gt;런타임환경에 따라 워커를 분기처리하여 실행될 수 있도록 적용했고, 서버 런타임에서도 동작할 수 있도록 별도의 express 서버를 같이 띄워 express로 전달되는 요청을 가로채 응답할 수 있도록 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Storybook&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이전에 npm 오픈소스를 하나 만들면서 컴포넌트의 확장성을 보여주기 위해 스토리북을 적용해 다양한 사용 예제를 보여준 적이 있었는데, 좀 더 실무 환경에서 사용해보고 싶어 스토리북을 적용해 보았습니다.&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;다만 새로운 기술을 처음 도입하려고 할 때에는 항상 추가적인 리소스가 들어가게 되는데, 그러다 보니 사실 컴포넌트마다 스토리를 새로 만드는 게 생각보다 번거롭게 느껴진 부분이 있었습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;브릿지 통신 ( bridge swift &amp;lt;-&amp;gt; JS )&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음으로 머리가 아팠던 부분은 브릿지 통신이었는데요.....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브릿지는 크게 &lt;u&gt;4가지 기능&lt;/u&gt;이 필요했습니다.&lt;/p&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;유저 권한 요청 (권한 설정 페이지를 통해 별도 수정 가능)&lt;/li&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;IOS에 javascript의 코드를 실행시킬 수 있는 evaluateJavaScript 메서드가 존재하는데, 이 메서드를 통해 window 객체에 데이터를 저장하거나, window 객체에 정의되어 있는 메소드를 호출하여 클라이언트의 상태를 갱신하는 방식으로 구현할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 웹뷰 환경이다 보니 웹뷰 로드가 되기 전에 evaluateJavaScript를 호출하는 경우 에러가 발생하고 값이 전달되지 않는 등의 문제가 있어 권한 요청 함수의 실행 시점 등을 알맞게 변경해 주는 작업을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서는 이 값을 어떻게 관리하는 것이 효율적일까 생각을 했는데, 브릿지 통신을 위한 훅을 별도로 빼고 페이지를 Webview 컴포넌트로 감싸 그 안에서 바로 값이 갱신될 수 있도록 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 ContextAPI를 통해 구현할까 생각했지만, 유저의 위 경도 좌표는 작은 움직임에도 정보가 변경되는 부분이 있어 ContextAPI를 사용하면 불필요한 리렌더링이 상당 부분 발생할 것이라 생각해 Jotai 상태관리 라이브러리를 사용하여 atom store에 저장해 놓은 값을 효율적으로 사용할 수 있도록 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 난항 끝에 생각보다 깔끔하게 브릿지를 구현할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 기술적으로 어려웠다기 보단 Swift, 클라이언트, 서버 프로젝트를 모두 실행시킨 뒤 분리된 환경에서 로그를 보며 디버깅을 진행하고.... 3개의 프로젝트를 왔다 갔다 하며 수정했어야 했는데, 편집기 딜레이가 기본 4초씩 걸리는 현상을 감내하며&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 구현하다 보니 엄청 험난하게 느껴졌던 것 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;19년형 인텔 맥북의 한계를 절실히 느끼는 바람에 하반기에는 새로운 맥북을 사야겠다며 혼자 다짐했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;IOS (Swift) 개발&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOS에선 크게 웹뷰, Firebase, 위젯, 권한 처리, 브릿지 코드를 구현했습니다.&lt;br /&gt;Swift를 이해하고 구현을 시작한 게 아니다 보니 특히 Swift에서 유독 많은 난항이 있던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 필요하다면 얼른 만들어야죠, IOS 작업은 우리의 든든한 파트너, GPT4와 함께 했습니다.&lt;br /&gt;새로운 기술을 습득할 때 GPT가 좋은 이유는 종합적인 인사이트를 빠르게 얻을 수 있어서라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선적으로 특정 기능을 구현하는 것이 기술적으로 가능한 것인지, 어떤 기술들을 사용할 수 있는지 플랜 A ~ N까지 인사이트를 얻고 적당한 방안을 찾아 나가는 식으로 구현을 했습니다.&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;IOS 개발 중 가장 기억에 남는 것은 위젯이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;무제 2.png&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFW1Zd/btsF11qs6Pc/GaiDLuWPsUASL6QjqsVGCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFW1Zd/btsF11qs6Pc/GaiDLuWPsUASL6QjqsVGCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFW1Zd/btsF11qs6Pc/GaiDLuWPsUASL6QjqsVGCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFW1Zd%2FbtsF11qs6Pc%2FGaiDLuWPsUASL6QjqsVGCK%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;659&quot; height=&quot;93&quot; data-filename=&quot;무제 2.png&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯에서도 웹뷰를 혹시 사용할 수 있는 건지 기대했지만 불가능했고, Xcode에서 별도의 Widget Extension을 추가해 새로운 타깃을 만들 고, 직접 UI와 http 통신을 진행해야 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yCIEX/btsF0kkvynL/MQPUkElol69U4JUsYqzZk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yCIEX/btsF0kkvynL/MQPUkElol69U4JUsYqzZk1/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;1279&quot; data-filename=&quot;IMG_0728 2.jpg&quot; width=&quot;568&quot; height=&quot;616&quot; style=&quot;width: 30.9099%; margin-right: 10px;&quot; data-widthpercent=&quot;31.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yCIEX/btsF0kkvynL/MQPUkElol69U4JUsYqzZk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyCIEX%2FbtsF0kkvynL%2FMQPUkElol69U4JUsYqzZk1%2Fimg.jpg&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;1179&quot; height=&quot;1279&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cltbly/btsF1qK3KHS/HiwdtBR8ae3KQktQ86YrfK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cltbly/btsF1qK3KHS/HiwdtBR8ae3KQktQ86YrfK/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;582&quot; data-filename=&quot;IMG_0694 2.jpg&quot; style=&quot;width: 67.9273%;&quot; data-widthpercent=&quot;68.73&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cltbly/btsF1qK3KHS/HiwdtBR8ae3KQktQ86YrfK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcltbly%2FbtsF1qK3KHS%2FHiwdtBR8ae3KQktQ86YrfK%2Fimg.jpg&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;1179&quot; height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;i&gt;사실 그동안 IOS와 싸우며..... 서버와 싸우며..... 아니 이거.... 만들 수 있는 건가..? 무식하면 용감하다더니 정말 너무 용감했던 것 같다는 생각을 하고 있었는데 위젯이 제대로 동작하는 걸 확인한 순간 도파민 대폭발&lt;/i&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;그리고 특이했던 것은 IOS에서 서버로 http 요청을 보낼 때 CORS 설정을 위해 Origin 헤더를 알아보던 중 IOS 앱은 Origin 헤더가 null로 설정되어 있거나 전송되지 않는다는 특징이 있다는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도로 애플 쪽에서 제공하는 Origin이 있지 않을까 생각했는데 아예 존재하지 않는다고 하니, 하긴 이렇게 구현하는 서비스가 얼마나 있겠어라는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이런 경우가 없지는 않을 것 같은데 그럴 때 서버에서 CORS를 어떻게 관리하고 있는지 개인적인 궁금증이 남아있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;서버 개발&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 Node 환경에서 Nest와 Mysql을 사용해 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nest의 경우 공식 문서가 잘 작성되어 있는 편이라 다시 감을 잡는 데에는 어렵지 않았지만, 기능 중 알림 시간대를 설정한 유저에게 미리 알림 형태로 유저에게 푸시 알림을 전송하는 기능이 존재했는데, 설문 기반이다 보니 유저 별로 모두 발송 시간이 다르기 때문에 이걸 어떻게 구현할지에 대해 감이 오지 않는 부분이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 Nest 스케줄러에 있는 Cron 데코레이터를 사용해 알림 타입에 맞게 매 분마다, 매 시간마다 동작하는 두 개의 스케줄러를 만들어 해결할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0730.jpg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zDU5p/btsF2EaD8xC/kfet7R5wCkag0MhBm6ggxK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zDU5p/btsF2EaD8xC/kfet7R5wCkag0MhBm6ggxK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zDU5p/btsF2EaD8xC/kfet7R5wCkag0MhBm6ggxK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzDU5p%2FbtsF2EaD8xC%2Fkfet7R5wCkag0MhBm6ggxK%2Fimg.jpg&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;1179&quot; height=&quot;234&quot; data-filename=&quot;IMG_0730.jpg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 미리 데이터를 푸시 알람에 보여주는 것이 아닌 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;유저가 요청한 period만을 동적으로 변환해&lt;span&gt;&amp;nbsp;푸시 알람을 발송하여&amp;nbsp;&lt;/span&gt;&lt;/span&gt;앱 방문 유도를 진행하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 기획에서는 푸시 알람 시간대가 된 경우, IOS를 통해 유저의 위치 정보를 받아 데이터를 가공해서 보여주고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOS의 remote notification 기능에는 Silent Push 기능이 존재합니다.&lt;br /&gt;유저에게 푸시 알림을 노출하지 않고 별도의 로직을 실행시키기 위해 사용되며, 주로 앱의 업데이트를 위해 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOS 쪽에서는 알림이 전송되면 didReceiveRemoteNotification이라는 메서드가 실행되는데 본래는 이  메소드 내부에서 http HEAD 메서드를 통해 푸시 알림용 서버 API를 호출하고 쿼리 파라미터를 통해 유저 정보를 전달, 서버는 이 요청이 들어오면 데이터를 포함한 푸시 알람을 보낼 수 있도록 설계를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 fcm을 사용하고 있다 보니, firebase-admin을 통해 ios silent push를 실행시켜야 했는데 IOS에서 didReceiveRemoteNotification 메서드 실행이 되지 않아 씨름을 하다가 결국 구현하지 못하고 우선 기획을 변경하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 아닌 웹뷰 앱이다 보니 서버의 버전 하위 호환성 부분에서 상대적으로 자유로운 부분이 있다고 생각해, 우선 추후 피쳐에 수정하는 것으로 변경하고 서버 배포 작업을 진행하는 것으로 결정했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배포&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 클라이언트 배포&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 Vercel을 통해 CI/CD 배포 환경을 구성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 배포 툴을 결정하기 전에 어떤 것을 사용할지 고민을 하다가 amplify를 사용해 본 경험이 있어 이것저것 찾아 보았는데 앰플리파이는 Next의 스트리밍 기능을 지원하지 않는다는 것을 알게 되었습니다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;참고 링크 &lt;a href=&quot;https://github.com/aws-amplify/amplify-hosting/issues/3843&quot;&gt;https://github.com/aws-amplify/amplify-hosting/issues/3843&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next의 streaming은 이미지처럼 HTML을 작게 나누어서 모든 데이터가 로드되기 전에 미리 상호작용을 할 수 있도록 하는 기술인데, Suspense 컴포넌트와 layout.js를 통해 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해 앰플리파이로 Next를 배포할 경우 서스펜스 기능을 사용하지 못하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Suspense가 가져다주는 이점이 크다고 생각해 프론트는&amp;nbsp;&lt;/span&gt;Next에 최적화된 Vercel을 사용하기로 결정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 서버 배포&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 https통신을 위해 가비아에서 도메인을 구입하고, 지갑 보호를 위해 AWS의 프리티어 계정으로 EC2, RDS, loadbalance, Route53, ACM을 통해 배포 환경을 구성했습니다.&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;초반에 EC2, RDS를 통해 서버 배포를 진행해서 우선 http로 EC2 IP에 직접 접근해 사용했었는데, 프론트는 https, 서버는 http 프로토콜을 사용하고 있어 mixed contents 에러가 발생했고, 이를 해결하기 위해 DNS 호스팅을 구성하는 과정에서 갑자기 ssh가 먹통이 되는 문제가 발생했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트를 실행해도 실행 로그만 찍히고 먹통이 되는 바람에 무엇이 문제인지 파악이 안 되어 이것저것 시도해 보다가 결국 ec2 인스턴스, rds, 호스팅을 처음부터 다시 설정했는데도 해결되지 않는 걸 보고 속으로 눈물을 조금 흘렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고 보니 프리티어용 인스턴스를 사용하다 보니 ec2 인스턴스의 CPU 메모리가 금방 부족해지는 바람에 먹통이 되는 것이었고, swap 메모리를 설정해 하드 디스크의 디스크 공간을 메모리처럼 사용할 수 있도록 변경한 뒤 해결할 수 있었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;앱 심사&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14일 차에 앱 심사를 요청할 수 있었습니다.&lt;br /&gt;그리고 앱이 리젝 되어 재심사를 위한 인내의 시간을 보낸 뒤 드디어..&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;앱 배포&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0777.jpg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GIufe/btsF2RvCRUk/9svDkBsB7k7XZqV3jmaAck/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GIufe/btsF2RvCRUk/9svDkBsB7k7XZqV3jmaAck/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GIufe/btsF2RvCRUk/9svDkBsB7k7XZqV3jmaAck/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGIufe%2FbtsF2RvCRUk%2F9svDkBsB7k7XZqV3jmaAck%2Fimg.jpg&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;424&quot; height=&quot;306&quot; data-filename=&quot;IMG_0777.jpg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;/figure&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;날씨 데이터는 OpenWeather API를 사용하고 있는데 하루 1,000 호출까지 무료이기 때문에 사실 딱히 유명해지길 바라지는 않고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주변에 조금 자랑했는데 생각보다 반응이 좋아서 뿌듯하면서도 오히려 약간 당황스러운 상태네요&lt;br /&gt;앱스토어 올라가면 알려 드린다 하고 있는데 갑자기 부담감에 어깨가 무거워지는 바람에 우선 혼자 쓰다가 주변에는 푸시 알람에 데이터를 보여주는 것까지 구현하고 알릴까 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론상 위젯 설치 + 하루 두 번 앱에 접속한다고 가정했을 때 30명 내외가 유효 유저 범위이기 때문에 갑자기 API 응답이 없고 먹통이 되는 상황이 올 수도 있기때문에 약간 조마조마하네요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&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;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다!&amp;nbsp;&lt;/p&gt;</description>
      <category>Project</category>
      <category>Appstore</category>
      <category>AWS</category>
      <category>BeforeTheRain</category>
      <category>Client</category>
      <category>Nest</category>
      <category>Next</category>
      <category>React</category>
      <category>server</category>
      <category>Typescript</category>
      <category>비가 오기 전에</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/89</guid>
      <comments>https://deemmun.tistory.com/89#entry89comment</comments>
      <pubDate>Fri, 22 Mar 2024 21:56:30 +0900</pubDate>
    </item>
    <item>
      <title>번들러 파헤치기 3 - 오픈소스 라이브러리 만들기 (rollup / react / typescript / babel)</title>
      <link>https://deemmun.tistory.com/88</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;vasilis-caravitis-WGOpvIKwq3Y-unsplash.jpg&quot; data-origin-width=&quot;5755&quot; data-origin-height=&quot;4042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u9cbi/btsHEy779ks/P7gnGdJaqVZOdZVkcNhOf1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u9cbi/btsHEy779ks/P7gnGdJaqVZOdZVkcNhOf1/img.jpg&quot; data-alt=&quot;Photo by Vasilis Caravitis on Unsplash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u9cbi/btsHEy779ks/P7gnGdJaqVZOdZVkcNhOf1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu9cbi%2FbtsHEy779ks%2FP7gnGdJaqVZOdZVkcNhOf1%2Fimg.jpg&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;5755&quot; height=&quot;4042&quot; data-filename=&quot;vasilis-caravitis-WGOpvIKwq3Y-unsplash.jpg&quot; data-origin-width=&quot;5755&quot; data-origin-height=&quot;4042&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Photo by Vasilis Caravitis on Unsplash&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 글은 번들러 파헤치기 시리즈의 마지막인 3부로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Rollup을 통해 리액트 컴포넌트 라이브러리를 직접 구성하고 배포하는 과정&lt;/b&gt;에 대해 설명하려고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;번들러 파헤치기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/86&quot;&gt;모듈 시스템의 발전과 역사 (commonJS, AMD, UMD, ESM-esmodule)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/87&quot;&gt;번들러의 발전과 역사 (HTTP/1.1, webpack, rollup, parcel, snowpack, exbuild, vite, turbopack)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/88&quot;&gt;오픈소스 라이브러리 만들기&lt;span&gt;&amp;nbsp;&lt;/span&gt;(rollup / react / typescript / babel)&lt;/a&gt;&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;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3부는 구현에 집중하여 작성할 예정으로 구성과 관련된 기술적 배경에 대한 자세한 설명없이 진행할 예정으로, 본격적인 구성에 들어가기에 앞서 1부, 2부를 읽고오시는 것을 추천드립니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3부 리액트 오픈소스&amp;nbsp;라이브러리&amp;nbsp;만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서문&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요즘 7년 만에 백수의 삶을 살고 있습니다.&lt;/p&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;재직 당시 '추가되면 좋겠다' 이야기가 나왔던 기능이 있었는데 우선순위에 밀려 마음속에만 남아있던 태스크였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;html의 textarea 태그를 사용하는 부분에 하이퍼링크를 자동으로 감지하여 인터렉션을 가능토록 하는 기능인데 기본 textarea 태그는 string 타입 말고는 html 요소를 값이나 자손으로 가질 수 없기 때문에 기존 요소에 없는 기능을 추가했어야 했습니다.&lt;/p&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이런 니즈를 혼자서만 겪을 것 같진 않아&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;확장성 높은 컴포넌트로 만들어 오픈소스를 만들면 좋겠다 생각이 들었습니다.&lt;br /&gt;바로 작업을 시작했는데 이틀정도 걸려&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.npmjs.com/package/react-link-textarea?activeTab=readme&quot;&gt;react-link-textarea&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라이브러리를 만들었네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;305347742-c1a279f1-bb73-425d-a50e-718325c16657.gif&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm7AEe/btsFkVe2DJE/KdqjTdGQIXBLTko5k6VKvK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm7AEe/btsFkVe2DJE/KdqjTdGQIXBLTko5k6VKvK/img.gif&quot; data-alt=&quot;사용 예제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm7AEe/btsFkVe2DJE/KdqjTdGQIXBLTko5k6VKvK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bm7AEe/btsFkVe2DJE/KdqjTdGQIXBLTko5k6VKvK/img.gif&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;473&quot; height=&quot;263&quot; data-filename=&quot;305347742-c1a279f1-bb73-425d-a50e-718325c16657.gif&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사용 예제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&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;저 또한 그랬지만 라이브러리, 오픈 소스 배포가 멀게만 느껴지던 분들이 계실 거라고 생각합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 원인에는 빌드 구성이 많은 비중을 차지할 것이라 생각합니다. 하지만 생각보다 어렵지 않고 누구나 할 수 있다는 걸 알았으면 해서 환경 방법과 관련 경험을 글로 작성해보려고 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 구현할 라이브러리의 방향&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;React 기반&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컴포넌트 라이브러리&lt;/b&gt;&lt;br /&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;typescript&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;환경에서도 동작할 것&lt;br /&gt;- 용량은 최소한으로 가져갈 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴포넌트를 제공하는 리액트 라이브러리를 만들기&lt;/b&gt;&lt;br /&gt;- 번들러는 Rollup을 사용하였습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&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: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2.&amp;nbsp; 환경 구성하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. 프로젝트 생성&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1709305633006&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm init&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709305633007&quot; class=&quot;lasso&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;/* package.json */

{
  &quot;name&quot;: &quot;test&quot;,
	...
    
   // 추가
  &quot;type&quot;: &quot;module&quot;
  
  &quot;main&quot;: &quot;./dist/bundle.js&quot;,
  &quot;module&quot;: &quot;./dist/esm/bundle.js&quot;,
  
  &quot;exports&quot;: {
    &quot;.&quot;: {
      &quot;require&quot;: &quot;./dist/bundle.js&quot;,
      &quot;import&quot;: &quot;./dist/esm/bundle.js&quot;,
    }
  },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CLI를 통해 패키지를 생성하면, 초기 설정만 되어있는 package.json이 생성됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 type 필드에 &quot;module&quot;을 추가하여, ESM 모듈시스템을 사용할 것이라고 지정해 줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리의 라이브러리는 commonJS, ESM을 모두 고려하여 총 두 개의 빌드 파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;dist&lt;/b&gt;라는 폴더에 생성할 예정입니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/86&quot;&gt;commonJS, ESM이 궁금하다면?&lt;/a&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;main : 패키지 사용자가 패키지에 진입할 때 사용하는 진입점&lt;/u&gt;&lt;br /&gt;&lt;u&gt;module : esm 호환 환경에서 진입할 때 사용하는 진입점&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;exports를 통해 엔트리 포인트를 여러 개 두는 것도 가능합니다. require를 사용할 경우 cjs 번들을 참조하도록, import 구문 사용 시에는 esm 번들을 참조하도록 설정해 줄 수 있습니다.&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-2. Rollup 설치&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1709305633010&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm i -D rollup&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709305633010&quot; class=&quot;lasso&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;/* package.json */
{
	...
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;rollup -c&quot;,
    &quot;watch&quot;: &quot;rollup -cw&quot;
  // Option
    &quot;prebuild&quot; : &quot;rm -rf dist&quot;,
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rollup을 설치한 이후에 pacakge.json 폴더에 build 관련 스크립트를 추가해 줍니다.&amp;nbsp;&lt;br /&gt;build를 통해 현재 코드를 번들링 할 수 있습니다. 개발 중일 때는 rollup watch를 사용하는 것이 편리합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #c0d1e7;&quot;&gt;! script 명령 중 'pre' 접두사가 추가된 스크립트는 해당 스크립트가 실행되기 이전에 먼저 작동됩니다.&amp;nbsp;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709305633012&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// rollup.config.js

export default {
    input: './src/index.js', // 진입 경로
    output: [
      {
        file: &quot;./dist/esm/bundle.js&quot;, // 빌드 파일 저장 경로
        format: &quot;es&quot;,                 // 출력 형식
        sourcemap: true               // 디버깅 유용
      },
      {
        file: &quot;./dist/bundle.js&quot;,
        format: &quot;cjs&quot;, 
        sourcemap: true
      }
    ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;루트 디렉터리에 rollup.config.js를 생성하고 위와 같이 설정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빌드 파일의 진입 경로를 input에 명시한 뒤, output 파일을 명시해 줍니다.&lt;br /&gt;output은 복수의 포맷을 원할 경우 위와 같이 배열의 형태로 전달 가능하며, 단일 포맷인 경우 객체를 그대로 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709305633014&quot; class=&quot;json&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;/* package.json */
{
	&quot;peerDependencies&quot;: {
    	&quot;react&quot;: &quot;^18.2.0&quot;,
        &quot;react-dom&quot;: &quot;^18.2.0&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 react 컴포넌트를 사용해야 하기 때문에 peerDependencies에 추가하여 설치해 줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;peerDependencies는 패키지가 작동하기 위해 필요한, 하지만 자동으로 설치되지 않아야 하는 종속성을 명시하는 섹션&lt;/b&gt;입니다.&lt;br /&gt;(^) 틸드를 통해 18.2.0 이상, 19 미만 리액트 환경에서 호환되도록 명시해 줍니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-3. 바벨 플러그인 설정 / peer plugin 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 JSX를 자바스크립트로 변환하기 위해 babel과 번들 플러그인들을 설치해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709305633016&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;npm i -D @babel/core @babel/preset-env @babel/preset-react
npm i -D @rollup/plugin-babel

npm i -D rollup-plugin-peer-deps-external
// peerDependency 패키지 코드가 번들링된 결과에 포함되지 않고, import 구문으로 사용할 수 있도록 함&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709305633016&quot; class=&quot;css&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import babel from &quot;@rollup/plugin-babel&quot;;
import peerDepsExternal from &quot;rollup-plugin-peer-deps-external&quot;;


export default {
    input: &quot;./src/index.js&quot;,
    output: [...],
    plugins: [
      // 바벨 트랜스파일러 설정
      babel({
        babelHelpers: &quot;bundled&quot;,
        presets: [&quot;@babel/preset-env&quot;, &quot;@babel/preset-react&quot;],
        extensions: [&quot;.js&quot;, &quot;.jsx&quot;, &quot;.ts&quot;, &quot;.tsx&quot;]
      }),
      peerDepsExternal()
    ]
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-4. CSS 플러그인 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CSS에 대한 처리가 필요한 경우 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709305633019&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm i -D rollup-plugin-postcss postcss-import autoprefixer&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709305633019&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// rollup.config.js

import autoprefixer from &quot;autoprefixer&quot;;
import cssimport from &quot;postcss-import&quot;;
import postcss from &quot;rollup-plugin-postcss&quot;;

export default {
  input: &quot;./src/index.js&quot;,
  output: [...],
  plugins: [
    ...,
    postcss({
      plugins: [cssimport(), autoprefixer()]
    }),
  ]
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rollup.config.js 파일 plugins에 추가해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709305633021&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// src/index.js

import LinkingTextarea from './LinkingTextarea';

export default LinkingTextarea;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rollup.config.js의 input에 정의되어 있는 파일에는 위와 같이 export default로 내보내고 싶은 파일을 정의할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;watch 스크립트를 실행하여 jsx가 정상적으로 빌드가 되는지 확인합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-5. 타입스크립트 설정&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1709305633022&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;npm i -D @rollup/plugin-typescript
npm i -D typescript tslib
npm i -D @babel/preset-typescript
npm i -D rollup-plugin-dts

npm i -D @types/react @types/react-dom
// # 리액트, 리액트 DOM 타입 패키지 추가&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709305633023&quot; class=&quot;rust&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// tsconfig.json

{
  &quot;compilerOptions&quot;: {
    &quot;declaration&quot;: true, -&amp;gt; 타입 정의 파일 생성 여부
    &quot;outDir&quot;: &quot;dist/dts&quot;, -&amp;gt; 타입 정의 경로
    &quot;emitDeclarationOnly&quot;: true, -&amp;gt; 바벨을 통해 ts를 js로 변환하는 경우 설정
    
    &quot;target&quot;: &quot;es2015&quot;,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;sourceMap&quot;: true,
    &quot;strict&quot;: true,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true
  },
  &quot;include&quot;: [&quot;src/**/*&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;, &quot;dist&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;root 디렉터리에 tsconfig.json을 생성한 뒤 위와 같이 설정해 줍니다.&amp;nbsp;&lt;br /&gt;타입스크립트 환경에 최적화된 라이브러리를 위해선&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;타입 정의 파일 (*. d.ts)을 제공해야 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;이후 js, jsx를 ts, tsx로 바꾼 뒤 아래와 같이 rollup.config.js를 수정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709305633027&quot; class=&quot;gradle&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// rollup.config.js

import autoprefixer from &quot;autoprefixer&quot;;
import babel from &quot;@rollup/plugin-babel&quot;;
import cssimport from &quot;postcss-import&quot;;
import dts from &quot;rollup-plugin-dts&quot;;
import peerDepsExternal from &quot;rollup-plugin-peer-deps-external&quot;;
import postcss from &quot;rollup-plugin-postcss&quot;;
import typescript from &quot;@rollup/plugin-typescript&quot;;

export default [
  {
    input: &quot;./src/index.ts&quot;,   	// ts로 input 확장자 변경
    output: [
      {
        file: &quot;./dist/esm/bundle.js&quot;,
        format: &quot;es&quot;,
        sourcemap: true
      },
      {
        file: &quot;./dist/bundle.js&quot;,
        format: &quot;cjs&quot;,
        sourcemap: true
      }
    ],
    plugins: [
      // 바벨 트랜스파일러 설정
      babel({
        babelHelpers: &quot;bundled&quot;,
        presets: [&quot;@babel/preset-env&quot;, &quot;@babel/preset-react&quot;],
        extensions: [&quot;.js&quot;, &quot;.jsx&quot;, &quot;.ts&quot;, &quot;.tsx&quot;]
      }),
      postcss({
        plugins: [cssimport(), autoprefixer()]
      }),
      typescript(),
      peerDepsExternal()
    ]
  },
  {
    // 타입 declation 위치
    input: &quot;./dist/dts/index.d.ts&quot;,
    output: [{ file: &quot;dist/index.d.ts&quot;, format: &quot;es&quot; }],
    external: [/\.css$/], // css파일이 존재할 경우, 추가
    plugins: [dts()]
  }
];&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빌드 시 자동으로 d.ts 파일을 생성해 줄 수 있도록 타입 정의 부분에 d.ts 핸들링 방식을 위와 같이 설정해 준 뒤, 빌드를 진행하면 dist폴더 내부에 d.ts파일이 생성되어 있는 것을 확인할 수 있습니다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-6. 개발 도중 디버깅하기&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;라이브러리 뼈대는 잡았는데 이제 디버깅을 어떻게 하면서 구현해야 하지?라는 생각이 들어 방법을 찾아보니 npm 배포 후 배포된 것을 다운로드하여 적용하는 방법,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;심링크&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방법이 존재했습니다.&lt;/p&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;로컬에 존재하는 프로젝트를 참조하여 다른 프로젝트에서 사용할 수 있는 후자의 방법을 선택했습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;심링크 방식&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1709305633034&quot; class=&quot;lasso&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 라이브러리 프로젝트에서 입력
npm link

// 라이브러리를 구동하고싶은 프로젝트에서 입력
npm link [package-name]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;package-name은 작동시키고 싶은 라이브러리 프로젝트의 package.json에 존재하는 name을 입력합니다.&lt;/p&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;이후 개발이 완료되면 npm 배포를 진행합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(리액트의 경우 다양한 환경 구성이 존재하기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;Create-React-App / vite-react / Create-React-Next 세 가지 프로젝트를 디버깅 환경으로 구성하여 라이브러리의 호환성, 정상 동작 여부를 확인하시는 것을 추천드립니다.)&lt;/i&gt;&lt;/i&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-7. npm 배포&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1709305633035&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm login

npm publish&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;npm 로그인 상태가 아니라면 CLI를 통해 로그인을 진행한 뒤 publish 명령어를 입력하여 패키지를 배포할 수 있습니다.&lt;br /&gt;(npm 계정이 없다면 회원가입이 필요합니다.)&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;축하합니다. 우리 모두 라이브러리 배포에 성공했습니다~~~~~&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;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&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;원래는 오픈 소스 라이브러리를 제작하면서  회고 느낌으로 번들러를 구성해서 연관 지식을 가볍게 설명하는 글로 작성하려고 했는데, 관련된 걸 설명하려고 하다 보니 너무 방대해져서 결국 3부로 나누어 작성하게 되었습니다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개인적으로 가장 약점이라고 생각했던 부분이 번들러, 빌드 부분이었는데 이번 기회로 많이 친해진 것 같아 뿌듯했습니다.&lt;br /&gt;시간이 많이 걸리긴 했지만 라이브러리도 직접 만들면서 이번엔 유독 기억에 남는 재밌는 경험이었던 것 같네요.&lt;/p&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;오랜 시간 애정을 갖고 담당하던 서비스였는데, 퇴사한 이후에도 내가 작성한 코드가 올라가 동작을 할 수 있다는 게 좀 감회가 새로웠던 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 라이브러리를 만들긴 했지만 textarea 태그 자체가 자주 쓰이는 요소는 아니기도 해서 혼자 만들고 혼자 쓸 수도 있겠다 생각했습니다.&lt;br /&gt;동료분께는 카운트 버그 아니냐면서 침착한 척했으나 사실 속으론 제발 아니어라 아니어라 하고 빌었네요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;TMI이긴 하지만, 개인적으로 개발자 인생에 미션이 하나 있는데 그건 생태계를 뒤흔들어 놓을 정도로 어마무시하게 혁신적인 오픈소스를 직접 만들고 제2의 에반 유가 되는 것입니다. (거창)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;엄청나게 거창하죠? 지금은 터무니없어 보이지만 인생은 끝날 때까지 끝난 게 아닙니다. 아직 모른다! (거창)&lt;br /&gt;거창한 미션에 한 발짝 내디딘 느낌도 들고 재밌는 경험이었던 것 같네요.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;잘못된 정보는 댓글로 알려주시면 빠르게 수정하여 반영토록 하겠습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;다음엔 더 유익한 정보를 들고 포스팅으로 찾아오겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;감사합니다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Code/JavaScript</category>
      <category>Component</category>
      <category>ESbuild</category>
      <category>library</category>
      <category>npm</category>
      <category>React</category>
      <category>rollup</category>
      <category>Vite</category>
      <category>Webpack</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/88</guid>
      <comments>https://deemmun.tistory.com/88#entry88comment</comments>
      <pubDate>Sat, 2 Mar 2024 00:08:41 +0900</pubDate>
    </item>
    <item>
      <title>번들러 파헤치기 2 - 번들러의 발전과 역사 (HTTP/1.1, webpack, rollup, parcel, snowpack, esbuild, vite, turbopack)</title>
      <link>https://deemmun.tistory.com/87</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;randy-fath-ymf4_9Y9S_A-unsplash.jpg&quot; data-origin-width=&quot;2500&quot; data-origin-height=&quot;1667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG1KKT/btsHEX7qv0T/7hMWyIcnosqByh2Lh0ovvk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG1KKT/btsHEX7qv0T/7hMWyIcnosqByh2Lh0ovvk/img.jpg&quot; data-alt=&quot;Photo by Randy Fath on Unsplash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG1KKT/btsHEX7qv0T/7hMWyIcnosqByh2Lh0ovvk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG1KKT%2FbtsHEX7qv0T%2F7hMWyIcnosqByh2Lh0ovvk%2Fimg.jpg&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;2500&quot; height=&quot;1667&quot; data-filename=&quot;randy-fath-ymf4_9Y9S_A-unsplash.jpg&quot; data-origin-width=&quot;2500&quot; data-origin-height=&quot;1667&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Photo by Randy Fath on Unsplash&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 글은 번들러 파헤치기 시리즈의 2부로, 번들러의 발전 및 역사를 주제로 각 번들러에 대한 설명을 진행하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2부에서도 CommonJS(cjs), esmodule, ESM (mjs)에 대한 내용이 등장하기 때문에 이전 포스팅을 가볍게 읽고 오시는 것을 추천드립니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;번들러 파헤치기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/86&quot;&gt;모듈 시스템의 발전과 역사 (commonJS, AMD, UMD, ESM-esmodule)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/87&quot;&gt;번들러의 발전과 역사 (HTTP/1.1, webpack, rollup, parcel, snowpack, exbuild, vite, turbopack)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/88&quot;&gt;오픈소스 라이브러리 만들기&lt;span&gt;&amp;nbsp;&lt;/span&gt;(rollup / react / typescript / babel)&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2부 번들러의 발전과 역사&lt;/b&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;1부에서 서술한 대로, ESCAMScript2015에 자바스크립트 모듈 표준인 esmodule이 등장하기 이전까지는 &lt;b&gt;자바스크립트 모듈 표준이 존재하지 않았습니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하고자 모듈화를 위한 다양한 움직임들이 있었고, CommonJS나 AMD, UMD 등 Module Definition 명세를 토대로 다양한 라이브러리들과 esmodule이 탄생, 등장했습니다.&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. 번들러의 탄생&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 생태계의 발전으로, 웹 애플리케이션의 규모도 점점 커져나가며 다루어야 하는 파일들이 많아지기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브라우저는 필요한 리소스를 네트워크를 통해&lt;/b&gt; 받아옵니다.&lt;br /&gt;필요한 파일들이 많아지면 많은 파일을 네트워크로 요청할 수밖에 없었고, 그로 인한 &lt;b&gt;응답 지연, 동시 전송 문제&lt;/b&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;이 당시 HTTP 표준 스펙은 HTTP/1.1로, 무거운 헤더와 HOLB (Head-Of-Line Blocking)라는 문제를 가지고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HOLB는 다중 요청은 가능하나, 먼저 들어온 요청에 대한 응답이 끝나야 다음에 들어온 요청에 대한 응답을 받을 수 있는 것인데, 앞에 있는 요청 Head에 문제가 생길 경우 뒤에 있는 요청이 블로킹되어있는 것을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 무거운 헤더를 가지고 있었기에 &lt;b&gt;네트워크 요청을 최소화&lt;/b&gt; 해야만 했을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(2015년 스트림을 통해 데이터 교환을 진행하는 HTTP/2가 표준으로 채택되며 HOLB문제는 해결되었으나, HTTP/2 표준의 기반이 되었던 SPDY 프로토콜의 경우에도 2009년도에 나왔기 때문에 이 당시는 지금보다 네트워크 요청 최적화의 중요성이 더욱더 높았을 거라고 생각합니다.)&lt;/i&gt;&lt;/span&gt;&lt;/p&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;u&gt;'파일을 하나로 합치면 되지 않을까?'&lt;/u&gt;&lt;br /&gt;&lt;br /&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 CSS, 폰트, 이미지, JS를 정적인 파일로 변환하고 하나로 합쳐주는 '번들러'가 탄생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어가기에 앞서 각 번들러 관련 주요 용어에 대해 설명하고 넘어가겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;용어 정리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree-Shaking (트리 쉐이킹)&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;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HMR (Hot Module Replacement)&lt;/p&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;코드의 수정이 발생하면 런타임에 이를 알리고 강제로 새로고침을 진행하는 Hot-Reloading 개념을 모듈에 적용시켜 확장한 개념으로 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;code-spliting (코드 스플리팅, 코드 분할, 번들 분할)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;번들을 모두 하나로 통합하는 것이 아닌, lazy-loading을 통해 필요한 시점에 분리된 번들을 요청하는 식으로 번들을 나누어 개별 번들 사이즈를 줄이는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 번들러의 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 번들러 공부를 하면서 가장 어려웠던 게 각 도구들의 의존 관계와 역할을 파악하는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들러의 특징을 검색하면 공통적으로 나오는 내용이 개발 서버 HMR을 제공하고, 정적 파일을 모두 하나로 합칠 수 있으며 (지정한 포맷으로), 이외 다른 빌드 기능도 제공한다는 내용이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 자세히 말하자면 HMR을 제공하지 않는 번들러도 존재하고, 어떤 걸 보고 누군 번들러라고 하고 누군 번들러가 아니라 프론트엔드 빌드 도구라고, 또 누군 컴파일러라고 하기도 하고, 어떤 번들러는 다른 번들러를 또 번들러로 사용하기도 하고 대혼돈을 겪었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 번들러의 공통 특징을 떠올리기 보단&lt;b&gt; 각 도구들이 어떤 부분에 초점을 두고 어떤 문제를 해결하고자 했는지&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 번들러의 발전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (3).jpg&quot; data-origin-width=&quot;2301&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBrmPp/btsFoFPr40n/rfP8ZUq1ID6yK10k734NC1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBrmPp/btsFoFPr40n/rfP8ZUq1ID6yK10k734NC1/img.jpg&quot; data-alt=&quot;빌드 도구의 역사&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBrmPp/btsFoFPr40n/rfP8ZUq1ID6yK10k734NC1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBrmPp%2FbtsFoFPr40n%2FrfP8ZUq1ID6yK10k734NC1%2Fimg.jpg&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;903&quot; height=&quot;106&quot; data-filename=&quot;Untitled (3).jpg&quot; data-origin-width=&quot;2301&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빌드 도구의 역사&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. Webapck, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모듈 번들러의 등장&lt;/span&gt; - 2014&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;webpack&amp;nbsp;is a&amp;nbsp;&lt;b&gt;static module bundler&lt;/b&gt;&amp;nbsp;for modern JavaScript applications&amp;nbsp;- &lt;a href=&quot;https://webpack.js.org/concepts/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;webpack core concepts&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 파일 통합을 넘어서 &lt;b&gt;개발 편의 기능을 제공하는 정적 모듈 번들러&lt;/b&gt;, Webpack이 탄생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹팩은 CSS, 폰트, 이미지, JS를 하나의 파일로 압축하여 관리함으로써 네트워크 요청 수를 줄이고, Tree-Shaking 기법을 통한 빌드 결과물 최적화, HMR (Hot Module Replacement), code-spliting 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹팩은 개발 서버 기능인 webpack-dev-server가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`webpack-dev-server`와 HMR (Hot Module Replacement)은 밀접한 관계가 있습니다.&lt;br /&gt;`webpack-dev-server`는 개발 중인 웹 애플리케이션을 위한 HTTP 서버를 제공하며, HMR 기능을 통해 코드 변경 시 페이지 새로고침 없이 변경된 모듈을 실시간으로 교체할 수 있게 합니다. 이를 통해 개발자는 빠르게 변경 사항을 보고, 애플리케이션의 상태를 유지하면서 개발할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Webpack은 인터프리터 언어인 자바스크립트&lt;/b&gt;로 구현되어 있으며, &lt;b&gt;규모가 크고 복잡한 애플리케이션&lt;/b&gt;을 관리하는 것에 중점을 두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다 보니 초기 설정이 복잡하여 러닝 커브가 높은 편입니다.&lt;br /&gt;많은 플러그인과 로더를 구성해야 하며, 큰 프로젝트에서는 구성 파일이 매우 방대해질 수 있습니다.&lt;br /&gt;그로인해 빌드 시간이 길어질 수 있으며, 특히 프로젝트가 커질수록 이 문제는 더욱 심화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 프로젝트를 생성할 때 사용하던 CRA(create-react-app)의 경우에도 webpack 번들러가 적용되어 있습니다.&lt;br /&gt;React 프로젝트를 실행시키기 위해 필요한 복잡한 번들러 설정을 미리 템플릿으로 만들어 두고 CLI를 통해 만들어내는 것이죠.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;react-scripts eject&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;( CRA 프로젝트에서 script에 존재하는 'eject'를 실행시키거나, 위 CLI명령어를 입력하게 되면 숨겨져 있던 파일들이 모두 노출되며 그곳에서 웹팩의 자세한 설정을 확인할 수 있습니다. - webpack.config.js )&lt;/i&gt;&lt;/span&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;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(궁금해서 브랜치를 따로 만들어서 실행해 본 적이 있는데 굉장히 많은 파일과 복잡한 설정들을 보고 놀랐던 기억이 납니다)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webpack은 이처럼 단점도 존재하지만, 번들러의 개념을 처음 탄생시킨 도구로서 최적화, 강력한 모듈 번들링 기능과 함께 광범위한 생태계를 제공하며, 많은 개발자들에게 선택되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;초기 웹팩이 릴리즈 되는 시점은 esmodule 표준이 채택되기 전으로 commonjs인 cjs 포맷만을 지원&lt;/b&gt;했다는 특징이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(2020.10 릴리즈 된 v5에 esmodule 포맷 지원이 추가되었습니다.)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. Rollup.JS, ESM (esmodule) 지원 번들러의 등장 - 2015&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The Javascript module bundler - &lt;a href=&quot;https://rollupjs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Rollup.JS&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rollup은 경량화와 번들 최적화를 중점에 둔&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;esm 지원 모듈 번들러입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECMAScript2015에 자바스크립트 내부 스펙으로 esmodule이 추가되었습니다.&lt;br /&gt;(&lt;a href=&quot;https://deemmun.tistory.com/86&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;모듈 시스템 관련 이전 포스팅 참고)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존&lt;b&gt; cjs와 esmodule의 가장 큰 차이점이라면 모듈의 정의, 로딩 방식&lt;/b&gt;을 꼽을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;commonJS (cjs)&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709218919166&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* test.js */
module.exports = 'hello'

/* index.js */
const hello = require('./test.js')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;esmodule (mjs)&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709218956520&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* test.js */
const hello = 'hello'
export default hello;

/* index.js */
import hello from './test.js'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cjs의 경우 &lt;b&gt;module.exports 객체를&lt;/b&gt; 통해 모듈을 정의하고,&lt;b&gt; require 함수&lt;/b&gt;를 통해 모듈 로더가 동기적으로 불러옵니다.&lt;br /&gt;&lt;b&gt;esmodule의 경우 import, export 구문&lt;/b&gt;을 통해 모듈을 정의하고,&lt;b&gt; import&lt;/b&gt;를 통해 비동기적으로 모듈을 불러옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;module.exports 구문은 객체를 통해 관리됩니다. 객체가 갖는 특성이 있기 때문에 모듈 내에서 동적으로 module.exports를 변경하며 할당할 수 있습니다. 그렇기에 모듈을 실행한 뒤에야 반환 값을 알 수 있으며, &lt;b&gt;모듈 간 의존성을 파악하는 게 구조적으로 어렵다는 문제가&lt;/b&gt; 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면,&lt;b&gt; esm은&lt;/b&gt; import, export 구문을 통해 내보낼 모듈, 불러올 모듈을 명확히 파악할 수 있다 보니 &lt;b&gt;모듈 간 의존성 파악을 명확하게 할 수 있다는 특징&lt;/b&gt;을 갖고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rollup은 공식문서에서도 강력한 Tree-shaking을 제공한다고 소개하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;웹팩 이후에 등장한 모듈 번들러로서 &lt;b&gt;당시 웹팩에는 없던 esm을 지원&lt;/b&gt;하며 의존성 파악이 명확하여 사용하지 않는 코드를 제거하는 Tree-shaking을 더 강력하게 지원하게 되었다고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(esm이 아닌 cjs포맷으로도 번들링 가능하나, esm 최적화에 특화된 도구)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롤업은 다양한 번들 포맷과 Tree-shaking, code-spliting을 제공합니다.&lt;br /&gt;자체적으로 HMR 기능이 구현되어 있지는 않지만, 플러그인 rollup-plugin-hot을 통해 HMR을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;롤업은 번들 사이즈 경량화와 최적화에 중점을 둔 번들러&lt;/b&gt;로서, &lt;b&gt;라이브러리&lt;/b&gt;를 구현하는 데에 많이 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(라이브러리는 기존 패키지에 설치되는 모듈로서, 패키지의 빌드 결과물에 함께 포함되기 때문에 더더욱 경량화에 신경 써야 하기 때문)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 현재 제일 각광받고 있는 프론트엔드 빌드 도구인 Vite 번들러의 내장 번들러로서도 사용되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-3. Parcel, zero-configuration 번들러의 등장 - 2017&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The zero configuration tool - &lt;a href=&quot;https://parceljs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Parcel&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Parcel은 webpack과 Rollup와 같은 복잡한 설정 없이 바로 사용할 수 있는 번들러&lt;/b&gt;를 목표로 탄생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zero configuration의 특징을 갖고 있다 보니 러닝커브 또한 낮으며, Parcel은 멀티 코어 처리를 활용하여 빠른 빌드 시간을 제공하고 파일 시스템 캐시를 사용하여 재빌드 시간을 단축시킬 수 있으며, HMR 기능을 제공한다는 특징이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;k.png&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3nRco/btsFqXPxJwE/ADtsDuH69X5Z5plhXACBeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3nRco/btsFqXPxJwE/ADtsDuH69X5Z5plhXACBeK/img.png&quot; data-alt=&quot;Parcel 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3nRco/btsFqXPxJwE/ADtsDuH69X5Z5plhXACBeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3nRco%2FbtsFqXPxJwE%2FADtsDuH69X5Z5plhXACBeK%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;1598&quot; height=&quot;472&quot; data-filename=&quot;k.png&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Parcel 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇듯 Parcel은 사용법이 간단하고, 빠른 성능을 가지고 있으나 단점 또한 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;zero-configuration 방식은 초기 설정이 쉽지만, 좀 더 복잡한 프로젝트에서 필요한 &lt;b&gt;세밀한 최적화나 커스터마이징에 한계&lt;/b&gt;가 존재하며, 프로젝트의 규모가 커질수록 파일 수와 의존성이 급격히 증가하여 &lt;b&gt;캐시 관리와 멀티 코어 리소스를 효율적으로 사용하는 데에 어려움&lt;/b&gt;이 발생할 수 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다른 번들러에 비해 사용자 커뮤니티가 좁은 편에 속하며, &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;때문에 세밀한 최적화와 다양한 설정이 필요한 큰 프로젝트보다는 중소 프로젝트에 적합한 번들러라고 볼 수 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3-4. esbuild, 100배 빠른 번들러의 등장 - 2020&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;An extremely fast bundler for the web - &lt;a href=&quot;https://esbuild.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;esbuild doc&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;esbuild 번들러는 빌드 도구 성능에 새로운 시대를 열고, 사용하기 쉬운 번들러&lt;/b&gt;를 만들기 위해 탄생했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;- The main goal of the esbuild bundler project is to bring about a new era of build toll performance, and create an easy-to-use modern bundler along the way&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;esbuild의 &lt;b&gt;주요 기능&lt;/b&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;a style=&quot;color: #000000;&quot; href=&quot;https://esbuild.github.io/content-types/#javascript&quot;&gt;JavaScript&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://esbuild.github.io/content-types/#css&quot;&gt;CSS&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://esbuild.github.io/content-types/#typescript&quot;&gt;TypeScript&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://esbuild.github.io/content-types/#jsx&quot;&gt;JSX&lt;/a&gt;&lt;span&gt;&amp;nbsp;내장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;ESM과 CommonJS 모듈 번들 지원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;CSS모듈을 포함한 CSS번들 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Tree shaking, 번들 minification, source map 지원&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;code spliting (예정)&lt;/span&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;b&gt;기존의 번들러와 약간은 다른 특징&lt;/b&gt;을 띄고 있었기 때문입니다. 그러다 공식 문서를 통해 해답을 찾을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;esbuild의 제작자 &lt;a href=&quot;https://github.com/evanw&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Evan Wallace&lt;/a&gt;는 &lt;a href=&quot;https://esbuild.github.io/faq/#upcoming-roadmap&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서 FAQ&lt;/a&gt;를 통해 esbuild가 프론트엔드의 올인원 도구가 되어야 한다고 생각하지 않는다고 기술하였습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;I want to avoid the pain and problems of the &quot;webpack config&quot; model where the underlying tool is too flexible and usability suffers&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무나 유연하기에 오히려 유용성이 저하되는 webpack의 구성을 예로 들며 웹팩과 같은 모델을 지양하고 있음을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지원할 예정이 없는 기능들&lt;/b&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;a style=&quot;color: #000000;&quot; href=&quot;https://esbuild.github.io/content-types/#javascript&quot;&gt;JavaScript&lt;/a&gt;,&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://esbuild.github.io/content-types/#css&quot;&gt;CSS&lt;/a&gt;,&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://esbuild.github.io/content-types/#typescript&quot;&gt;TypeScript, JSX가 아닌 다른 프론트엔드 언어 지원 (e.g Elm, Svelte, Vue, Angular)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;타입스크립트 타입 체킹 (tsc 별도 실행)&lt;/li&gt;
&lt;li&gt;AST 트리 조정을 위한 커스텀 API&lt;/li&gt;
&lt;li&gt;Hot-module reloading (&lt;b&gt;HMR&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;Module federation&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;esbuild는 그럼 왜 이렇게 빠른 것인가?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;esbuild.png&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3qsR0/btsFoDRFoMY/exc18RrV1g5eYVlnMrdPAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3qsR0/btsFoDRFoMY/exc18RrV1g5eYVlnMrdPAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3qsR0/btsFoDRFoMY/exc18RrV1g5eYVlnMrdPAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3qsR0%2FbtsFoDRFoMY%2Fexc18RrV1g5eYVlnMrdPAK%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;1670&quot; height=&quot;400&quot; data-filename=&quot;esbuild.png&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;인터프리터 vs 컴파일&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;웹팩은 인터프리터 언어인 javascript&lt;/b&gt;로 구현되었습니다.&lt;br /&gt;반면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;esbuild는 컴파일 언어인 Go&lt;/b&gt;로 구현되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사람이 작성한 프로그래밍 언어가 실제로 실행이 되기 위해선 컴퓨터가 알아들을 수 있는 기계어로 변환되어야 합니다.&lt;br /&gt;이 방식으로 인터프리터, 컴파일, JIT 컴파일이 존재합니다. 설명을 위해 인터프리터 언어와 컴파일 언어만 비교군으로 두겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터프리터 언어&lt;/b&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;li&gt;전체 명력 속도는 컴파일 언어에 비해 느리다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴파일 언어&lt;/b&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;'번역서',&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;소스코드를 한 번에 다른 목적 코드로 변환한 후 한 번에 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;컴파일 과정 : 고급언어 -&amp;gt; 변환 -&amp;gt; 로우 레벨 언어 -&amp;gt; 실행 파일 생성 및 실행&lt;/li&gt;
&lt;li&gt;규모가 큰 프로젝트의 경우 빠르게 동작&lt;/li&gt;
&lt;li&gt;컴파일 시 시간 소요&lt;/li&gt;
&lt;li&gt;메모리 차지&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정보를 찾아보다가 이해가 빠르게 되는 문장을 보아 인용하자면, &lt;b&gt;(인터프리터) 동시통역 vs (컴파일) 번역서&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동시통역을 하는 것보다&lt;b&gt; 번역서를 읽는 것이 훨씬 빠를 것&lt;/b&gt;입니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 자바스크립트는 싱글 스레드로 작동하기에 한 번에 한 가지 일만 처리할 수 있어 처리 속도가 제한적인 반면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Go 언어&lt;/b&gt;는 core자체가 병렬 처리를 위해 설계되었기 때문에 필요한 스레드를 만들고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;여러 작업을 동시에 처리&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Go언어로 구현된 esbuild는 CPU를 최대한 많이 사용하는 방향으로 설계되었으며, &lt;b&gt;CPU캐시를 적극 사용&lt;/b&gt;토록 하여 JS를 파싱해 만드는 구문 분석 트리 AST를 캐시에 최대한 오래 머물도록 하여 속도를 높였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단점이라면 &lt;b&gt;아직 1.0.0 버전에 도달하지 못했다는 것, es5를 완벽히 지원하지 않는다는 것&lt;/b&gt;을 이야기할 수 있을 것 같습니다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;버전&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여전히 esbuild는 활발하게 개발되고 있으며, Vite의 pre-bundling의 도구로 사용되어 빠른 개발 서버를 제공하고, snowpack v3.0.0에서도 번들링 도구로 채택되었지만 현재 날짜 기준 (2024.02.26) 가장 최신 버전은 v0.20.1입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;제작자는 esbuild가 안정된 상태에 들어왔다고 설명하였으나, 아직 v1.0.0 미만 버전이라는 점에서 다수의 개발자들에게 충분히 고민될 요소라는 생각이 듭니다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ES5 지원&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 esbuild는 es5를 지원하지 않는 것은 아니지만, 100% 지원은 하지 않고 있습니다.&lt;br /&gt;IE가 서비스를 종료하긴 했지만, 모든 개발자들이 es5지원을 포기할 수는 없는 노릇입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 대한 내용을 찾다가 깃허브에서&lt;a href=&quot;https://github.com/evanw/esbuild/issues/297&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; 2020년에 작성된 이슈&lt;/a&gt;를 발견했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;활발한 논의들과 es5로 변환하기 위한 대체 방안이 남겨져있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2023년 제작자 Evan Wallace는 es5문제를 해결하기 위한 다른 방안들이 존재하며 (SWC), 작업의 우선순위 상 소수의 사용자를 위한 es5에 대한 지원 작업은 합리적이지 않다고 생각한다는 의견을 남기며 이슈를 닫았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;역시 기가 막힌 것을 만들어내기 위해선 우선순위 선정 능력과 약간의 카리스마가 필요하다고 봅니다.&lt;/span&gt;&lt;/i&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;Rollup.js : rollup-plugin-esbuild&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webpack : esbuild-loader&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;esbuild의 빠른 번들링과 축소화를 rollup과 webpack 번들러에서도 플러그인과 로더를 통해 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3-5.&amp;nbsp; Snowpack, 빠른 웹 개발을 위한 프론트엔드 빌드 툴의 등장, - 2019&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Snowpack is a lightning-fast frontend build tool - &lt;a href=&quot;https://www.snowpack.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;snowpack doc&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;snowpack은 ESM을 활용하고 &lt;b&gt;개발 서버에 중점&lt;/b&gt;을 둔 빌드 툴로, &lt;b&gt;번들링 하지 않는 개발(Unbundled Development)&lt;/b&gt;을 핵심 개념으로 갖고 있습니다.&lt;/p&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;u&gt;Unbundled Development?&lt;/u&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;snowpack.png&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mldwy/btsFm8YYZJo/fp8SNzhw6U2id4JoEllfEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mldwy/btsFm8YYZJo/fp8SNzhw6U2id4JoEllfEk/img.png&quot; data-alt=&quot;snowpack - How Snowpack Works&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mldwy/btsFm8YYZJo/fp8SNzhw6U2id4JoEllfEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmldwy%2FbtsFm8YYZJo%2Ffp8SNzhw6U2id4JoEllfEk%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;752&quot; height=&quot;396&quot; data-filename=&quot;snowpack.png&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;snowpack - How Snowpack Works&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스노우팩은 빠른 개발 서버를 구축하는 데에 중점&lt;/b&gt;을 두었고, 웹팩과 비교하여 설명하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹팩 롤업과 같은 기존 번들러&lt;/b&gt;들은 코드의 수정이 발생하면 &lt;b&gt;변경된 파일을 다시 빌드하고, 전체 파일에 대한 번들링&lt;/b&gt;을 진행합니다.&lt;br /&gt;이 경우 O(n)의 복잡도를 갖게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스노우팩은 ESM이 널리 사용되며 파일 간 의존성 파악이 명확해졌기에 이는 더욱 불필요한 과정이라고 생각했고, 번들링은 필요한 때에 선택적으로 진행할 수 있어야 한다라는 컨셉 하에 &lt;b&gt;코드의 변경이 발생할 경우 해당 파일만 리빌드하고 캐시로 갖고 있도록 구현&lt;/b&gt;하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(번들링을 아예 하지 말아야 한다는 것이 아니라, 개발할 땐 번들링이 불필요하다는 주장입니다. 실제로 스노우팩은 웹팩, 롤업, Parcel과 같은 번들러를 플러그인으로 지원하였습니다)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Single-file 빌드는 빠르고, 디버깅이 쉬우며, 프로젝트의 사이즈가 개발에 영향을 미치지 않으며, 캐시가 간단하다는 특징이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;snowpack-benchmarks.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c88ewY/btsFmjfj4WM/qjBbs9murDr7qCgOfxbJyK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c88ewY/btsFmjfj4WM/qjBbs9murDr7qCgOfxbJyK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c88ewY/btsFmjfj4WM/qjBbs9murDr7qCgOfxbJyK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc88ewY%2FbtsFmjfj4WM%2FqjBbs9murDr7qCgOfxbJyK%2Fimg.jpg&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;300&quot; height=&quot;282&quot; data-filename=&quot;snowpack-benchmarks.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 50ms 내 개발 서버 구동을 진행할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 내장 빌드 도구로는 번들러인 esbuild를 사용했습니다.&lt;br /&gt;esbuild는 빠른 빌드가 가능했으나 모든 기능을 하나로 통합하는 올인원 번들러가 아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스노우팩은 실제 프로덕션을 위한 빌드 결과물은 웹팩, 롤업과 같은 번들러를 사용할 수 있도록 하고, 개발 중일 때에는 빠른 빌드가 가능한 esbuild를 채택하여 개발 경험을 개선했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명을 위해 snowpack보다 esbuild에 대한 설명을 먼저 진행했었는데, 사실 시기적으로는 snowpack이 esbuild보다 먼저 출시되었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;u&gt;먼저 나왔는데 어떻게 esbuild를 사용할 수 있었던 것일까요?&lt;/u&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2021.01.13 작성된 &lt;a href=&quot;https://www.snowpack.dev/posts/2021-01-13-snowpack-3-0#built-in-optimizations-powered-by-esbuild&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;v3.0.0 릴리즈 노트&lt;/a&gt;를 통해 그 내용을 알 수 있었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Snowpack already uses esbuild internally as our default single-file builder for JavaScript, TypeScript and JSX files. Snowpack v3.0 takes this integration one step further, with a new built-in build optimization pipeline.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292e; text-align: start;&quot;&gt;스노우팩은 이미 JSX, js, ts파일의 싱글 파일 빌더로 esbuild를 사용 중이었고, v3.0에 추가적으로 esbuild를 사용한 내장 빌드 최적화 파이프라인을 구축하였다고 설명하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 릴리즈 노트에선 esbuild와 관련된 내용을 찾을 수 없었지만, 이를 통해 최초 릴리즈 이후 esbuild를 채택했다고 추측할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스노우팩은 2020 자바스크립트 오픈 소스 어워즈에서 개발 생산 부스터로 1위를 차지했었으나, 현재 사실상 지원 종료를 알리며 Vite을 대안으로 추천하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ldtJa/btsFmlYreJu/Mt1cwW2IvQsHAJLZMJK52k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ldtJa/btsFmlYreJu/Mt1cwW2IvQsHAJLZMJK52k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ldtJa/btsFmlYreJu/Mt1cwW2IvQsHAJLZMJK52k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FldtJa%2FbtsFmlYreJu%2FMt1cwW2IvQsHAJLZMJK52k%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;1088&quot; height=&quot;156&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;156&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&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;3-6. Vite, 빛처럼 빠른 프론트엔드 빌드 툴 등장 - 2020&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Next Generation frontend build tooling - &lt;a href=&quot;https://vitejs.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Vite doc&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite은 &lt;b&gt;ESM을 이용한 개발서버와 Rollup 최적화 빌드 커맨드를 제공하는 프론트엔드 빌드 툴&lt;/b&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;번들링 도구들이 다수 등장했으나 다루어야 하는 자바스크립트 모듈이 많아질수록 개발서버의 구동 시간과 코드의 수정이 브라우저에 반영되는 시간이 점점 느려지는 문제가 있었고, Vite은 이를 해결하고자 느린 개발서버와, 느린 업데이트 문제를 해결하고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 의존 관계를 명확히 파악할 수 있도록 해주는 ESM의 특징, 빠른 개발 서버 구동을 위해 Unbundled Development에 초점을 맞춘 Snowpack의 배경을 보고 왔습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://www.snowpack.dev/&quot;&gt;Snowpack&lt;/a&gt;&amp;nbsp;is also a no-bundle native ESM dev server that is very similar in scope to Vite.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #2c3e50; text-align: start;&quot;&gt;Vite은 번들하지 않는 ESM개발 서버를 제공했던 snowpack과 코어 컨셉이 일치하고, 많은 영역에 대해 유사함을 가지고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;u&gt;느린 개발 서버&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느린 개발 서버 문제를 해결하기 위해 Vite은 소스 코드를 두 가지 영역으로 나누어 처리하도록 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존성 ( Dependencies )&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지의 디펜던시들은 개발 도중 변경이 일어나는 부분은 아닙니다. 이를 Plain javascript라고 볼 수 있으며, 이런 의존성 모듈을 효과적으로 관리하기 위해&lt;b&gt; Dependency pre-bundling&lt;/b&gt; 개념을 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite은 &lt;b&gt;esbuild를 통해 의존성을 단일 모듈로 번들링합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성이 필요로 하는 모듈에 대한 HTTP요청을 단 한 개의 요청으로 사용할 수 있도록 하나로 합친 뒤, 모든 의존성을 캐시상태로 &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;/node_modules/.vite/&lt;/span&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;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;소스 코드 ( Source code )&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;소스코드는 ESM을 통해 제공하고, 라우팅 베이스 import를 사용하여 Vite은 브라우저가 요청하는 대로 코드 변환 후 해당 페이지에 필요한 유효 범위를 갖는 모듈들을 제공할 수 있도록 구현했습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 코드의 수정이 발생하면 의존성을 포함한 모든 모듈을 다시 번들링 하여 재구성하여 발생하던 Bundle based bundler 문제를 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;u&gt;느린 업데이트&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느린 업데이트의 문제는 &lt;b&gt;HMR을 번들러가 아닌 ESM을 통해 진행할 수 있도록 HTTP 헤더를 적극 활용&lt;/b&gt;하여 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite은 파일의 수정이 일어날 경우 변경된 부분만 수정하여 파일을 교체하고, 브라우저 요청이 발생하면 교체된 파일을 보내주는 역할만 진행함으로써 ESM과 캐싱을 적극 활용했다고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(캐싱 관련 내용은&lt;/i&gt;&lt;/span&gt;&lt;i&gt; &lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://deemmun.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HTTP cache 파헤치기&lt;/a&gt;&lt;/i&gt;&lt;i&gt; &lt;/i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;포스팅에서 확인 가능합니다)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite은 제공되는 chunk 파일의 모듈은&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt; &amp;lt;link rel=&quot;modulepreload&quot;/&amp;gt;&lt;/span&gt;를 통해 html에 주입하여 브라우저가&lt;b&gt; javascript 모듈을 미리 로드하고 캐시&lt;/b&gt; 할 수 있도록 하여 모듈을 병렬적으로 불러올 수 있도록 하였으며, 또 캐싱을 적극 활용하여 성능 최적화에 많은 노력을 기울였음을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Vite은&lt;b&gt; pre-bundling은 esbuild&lt;/b&gt;를, &lt;b&gt;production을 위한 build 번들러로는 Rollup&lt;/b&gt;을 채택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;esbuild도 번들 기능이 있는데 왜 Rollup을 사용했는가?&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;esbuild는 롤업과 비교했을 때, 아직 플러그인의 유연성이 부족한 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;esbuild는 rollup보다 빠르지만, 메인 번들러로 사용하기에는 플러그인 지원이 부족하여, Vite은 Rollup을 번들러로 구성하여 자체 빌드 커맨드와 CLI를 통해 유연성을 확보하고자 하였고, 개발 서버는 esbuild를 통해 빠르게, 빌드는 Rollup을 사용하여 유연성 있게 설계를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;왜 Snowpack보다 Vite였나?&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 의문은 &lt;a href=&quot;https://v2.vitejs.dev/guide/comparisons.html#snowpack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 통해 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Snowpack은 production을 위한 번들링을 다른 번들러를 직접 선택하여 진행할 수 있었습니다. (webpack, rollup, parcel) 번들러에 따라 최종 결과물의 출력이 다르기 때문에 Vite은 이 빌드 과정을 더 밀도 높은 통합을 통해 간소화된 경험을 제공하기 위해 Rollup을 단일 번들러로 구성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 MPA (multi-page app), 자동 css spliting, 최적화된 비동기 청크 로딩, auto polifill import 지원을 통해 snowpack 보다 넓은 범위의 기능을 지원할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite 점유율이 궁금해서 찾아보다가 재밌는 리서치를 발견했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;result.png&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZYdG7/btsFoISZbHj/zu2VplpGdPeQLRO0BK5fLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZYdG7/btsFoISZbHj/zu2VplpGdPeQLRO0BK5fLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZYdG7/btsFoISZbHj/zu2VplpGdPeQLRO0BK5fLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZYdG7%2FbtsFoISZbHj%2Fzu2VplpGdPeQLRO0BK5fLk%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;1513&quot; height=&quot;894&quot; data-filename=&quot;result.png&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;894&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://2022.stateofjs.com/en-US/libraries/build-tools/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;참고 : https://2022.stateofjs.com/en-US/libraries/build-tools/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite은 Javascript 빌드 도구 중, Retention 수치 ( would use again / (would use again + would not use again) ) 98%로 1등을 차지했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Usage 항목((would use again + would not use again) / total)을 보니 첫 모듈 번들러로서 가장 오랜 기간 자리를 지켜 온 webpack이 85%로 1등을, Vite이 49%로 3위 차지하고 있는 걸 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;2022년 자료로, 현재 2023년 설문이 진행 중인데 1년 사이 Vite의 Usage가 많이 오르지 않았을까 합니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 또한 담당하던 CRA 프로젝트가 점점 거대해지며, 코드 스플리팅도 적용하고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;번들 최적화를 수시로 진행했었는데도 모듈이 하도 커져서 개발 서버를 한 번 키는 것부터 상당한 시간이 걸렸던 적이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;HMR 반영이 느려져 강제로 새로고침하며 사용하던 게 습관이었는데 그것마저 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;CSR 환경이다 보니 날이 갈수록 개발 경험이 안 좋아지던 문제가 있어 Vite으로 마이그레이션을 진행한 적이 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마이그레이션이 어렵지 않아 빠르게 수정하여 배포했고, esm파일을 통한 빠른 디버깅 + 갑자기 비약적으로 상승한 개발 서버의 속도 덕에 동료와 함께 '와 대박이다' 했던 기억이 납니다. (매트로 잘 가!!)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;(이후로 CRA 대신 무조건 create-vite을 쓰고 있습니다)&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Vite은 여전히 활발히 개발이 이루어지고 있고, 개인적으로 정말 차세대 프론트엔드 빌드 툴이라는 타이틀이 잘 어울리는 도구라고 생각합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3-7. Turbopack, Vite보다 5배 빠른 Rust 기반 Webpack 후속 번들러 등장 - 2022&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Turbopack is an incremental bundler optimized for Javascript and Typescript, written in Rust. - &lt;a href=&quot;https://turbo.build/pack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Turbopack doc&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터보팩은 Next.js, SWC 등을 만든 Vercel에서 제작한&lt;b&gt; 공식 webpack 후속 번들러&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터보팩은 Next.js 13 버전 번들러로 추가되었고, 공식 문서를 통해 기존의 번들러로 &lt;b&gt;SSR 기능을 사용하는 것은 각 런타임 환경에 대해 컴파일러를 생성하고 번들을 연결하여 통신을 관리하는 것에 대한 유지 보수 비용의 부담&lt;/b&gt;이 존재했고, 이를 해결하고자 자체 번들 시스템을 구현하였다고 설명했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Turbopack은 함수 수준의 캐싱을 제공하는 터보 엔진을 구현&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증분 계산을 가능케 하는 Rust의 turbo 라이브러리 기반으로 동작하며, 호출된 내용과반환된 내용을 메모리 내 캐시에 저장하여 증분 업데이트 계산 속도를 올렸다고 주장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 터보팩은 요청한 부분에 대한 컴파일만 최소한으로 진행하여,&lt;b&gt; lazy bundling&lt;/b&gt;을 설명했습니다.&lt;br /&gt;esm은 필요한 모듈을 전부 받아와야 하기 때문에 필요한 모듈이 많아질수록 네트워크 요청 수가 많아지는 부담이 있었고, esbuild의 경우 lazy loading 기능이 존재하지 않아 Turbopack은 수&lt;b&gt;신된 요청을 기반으로 모듈 그래프를 작성하여 최소한으로 필요한 코드만을 번들로 묶어 제공&lt;/b&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; Turbo 엔진은 최대 속도에서 최소한의 작업만 수행&lt;/b&gt;하는 것을 목표로 하는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터보팩은 초기에 &lt;u&gt;공식 문서의 밴치마크를 통해 Vite보다 10배 빠르고, webpack보다 700배 빠른 번들러임을 주장&lt;/u&gt;했습니다.&lt;br /&gt;궁금해서 정말 10배나 빠른 건지 찾아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite 제작자 에반 유는 직접 Turbopack이 정말로 Vite보다 10배 빠른지 벤치마킹을 진행하였고 Turbopack이 주장한 내용과 일치하지 않는 부분이 있다며 벤치마크 정정 요청을 진행했던 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/yyx990803/vite-vs-next-turbo-hmr/discussions/8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Is Turbopack really 10x Faster than Vite? - Evan you&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Turbopack은 초기 벤치마킹 환경에 대한 상세한 설명이 존재하지 않았고, 이후  정정한 벤츠마크 내용을&amp;nbsp;&lt;a href=&quot;https://turbo.build/blog/turbopack-benchmarks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Turbopack 블로그&lt;/a&gt;에 작성했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;We are now measuring Turbopack to be consistently 5x faster than Vite for HMR, over all application sizes.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Turbopack은 HMR 환경에서 Vite보다 5배 빠른 것으로 내용을 정정했습니다.&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;벤츠마크 레포 이슈 스레드에&amp;nbsp;&lt;/span&gt;아래와 같은 의견을 남겼습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Vercel의 벤치마크(다시 말하지만 Vite는 SWC를 사용하지 않음)의 수치를 보면 최대 10,000개 모듈까지 ~15ms 대 ~100ms 범위에 있습니다.&amp;nbsp;숫자는 20,000 범위 위에서만 기울어지기 시작합니다.&amp;nbsp;Vite의 경우 종속성은 사전 번들로 제공되므로 사용자 소스 모듈만 HMR을 설명하며 앱이 그렇게 클 가능성은 거의 없 습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;그렇다면 Vite이 아닌 Turbopack을 쓰는 게 좋지 않나?&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Turbopack은 로드맵을 통해 SvelteKit와의 밀도 높은 통합, 다른 프레임워크의 지원, 웹팩 사용자를 위한 마이그레이션에 대한 계획을 가지고 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 &lt;b&gt;Turbopack은 2024.03 현재 베타 상태로, Next.js 개발서버에서 선택 기능&lt;/b&gt;으로 사용되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Turbopack만이 갖는 이점이 존재하고는 있으나, 아직 베타 버전이며 Vite과 비교했을 때 플러그인 생태계와 안정성 부분에 대한 차이 또한 명확히 존재하는 것이 사실이기에 production에 사용하기 위한 번들러로는 아직 고려해야할 것이 많은 도구라는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;p data-ke-size=&quot;size16&quot;&gt;그 어떤 도구도 장, 단점은 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금이야 웹팩보단 다른 번들러를 선호하지만 그럼에도 불구하고 웹팩의 점유율이 왜 높은 건지, rollup은 왜 나왔는지, esbuild는 왜 기존 번들러와 다른 개념을 추구하는지, 왜 도구들이 esbuild를 같이 사용하는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 것들은 그 당시 각 도구들이 해결하고자 했던 문제를 떠올리면 실마리를 찾을 수 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅은 Rollup을 통해 직접 번들러를 구성하여, 라이브러리 배포를 진행하는 글로 찾아오겠습니다.&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;br /&gt;감사합니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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://esbuild.github.io/faq/#why-is-esbuild-fast&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://esbuild.github.io/faq/#why-is-esbuild-fast&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.snowpack.dev/concepts/how-snowpack-works&quot;&gt;https://www.snowpack.dev/concepts/how-snowpack-works&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://heropy.blog/2020/10/31/snowpack/&quot;&gt;https://heropy.blog/2020/10/31/snowpack/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.snowpack.dev/guides/optimize-and-bundle&quot;&gt;https://www.snowpack.dev/guides/optimize-and-bundle&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://fe-developers.kakaoent.com/2022/220623-webpack-module-federation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fe-developers.kakaoent.com/2022/220623-webpack-module-federation/&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #222529; color: #d1d2d3; text-align: start;&quot;&gt;
&lt;div data-qa=&quot;message-text&quot;&gt;
&lt;div data-qa=&quot;block-kit-renderer&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://muhammad-fiaz.medium.com/turbopack-the-rust-powered-successor-to-webpack-91f85f3ed73c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://muhammad-fiaz.medium.com/turbopack-the-rust-powered-successor-to-webpack-91f85f3ed73c&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://muhammad-fiaz.medium.com/turbopack-the-rust-powered-successor-to-webpack-91f85f3ed73c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://muhammad-fiaz.medium.com/turbopack-the-rust-powered-successor-to-webpack-91f85f3ed73c&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://ui.toast.com/posts/ko_20220127&quot;&gt;https://ui.toast.com/posts/ko_20220127&lt;/a&gt;&lt;/p&gt;</description>
      <category>Code/JavaScript</category>
      <category>Bundler</category>
      <category>ESbuild</category>
      <category>javascript</category>
      <category>Parcel</category>
      <category>rollup</category>
      <category>turbopack</category>
      <category>Vite</category>
      <category>Webpack</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/87</guid>
      <comments>https://deemmun.tistory.com/87#entry87comment</comments>
      <pubDate>Fri, 1 Mar 2024 23:36:37 +0900</pubDate>
    </item>
    <item>
      <title>번들러 파헤치기 1 - 모듈 시스템의 발전과 역사 (commonJS, AMD, UMD, ESM-esmodule)</title>
      <link>https://deemmun.tistory.com/86</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;maarten-van-den-heuvel-8EzNkvLQosk-unsplash.jpg&quot; data-origin-width=&quot;4898&quot; data-origin-height=&quot;3265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U1Mvm/btsHDF7ZxfR/PgIexgVvjlhdK08Rb9oqNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U1Mvm/btsHDF7ZxfR/PgIexgVvjlhdK08Rb9oqNk/img.jpg&quot; data-alt=&quot;Photo by Maarten van den Heuvel on Unsplash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U1Mvm/btsHDF7ZxfR/PgIexgVvjlhdK08Rb9oqNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU1Mvm%2FbtsHDF7ZxfR%2FPgIexgVvjlhdK08Rb9oqNk%2Fimg.jpg&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;4898&quot; height=&quot;3265&quot; data-filename=&quot;maarten-van-den-heuvel-8EzNkvLQosk-unsplash.jpg&quot; data-origin-width=&quot;4898&quot; data-origin-height=&quot;3265&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Photo by Maarten van den Heuvel on Unsplash&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;개인적으로 클라이언트 환경에서 가장 진입장벽이 높게 느껴지는 부분은 빌드 환경인 것 같습니다.&lt;br /&gt;다른 부분은 사실 실무에서도 자주 다루고 접하다 보니 금방 익숙해지는 반면, 프론트엔드의 경우 한 번 구성해 놓으면 직접 수정 할 일이 적다 보니 유난히 어렵게 느껴지기 쉬운 듯 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;번들러 파헤치기 포스팅은 모듈 시스템의 역사, 번들러의 역사, 빌드 환경 구성&lt;/b&gt;으로 나누어 포스팅을 하려고 합니다.&lt;br /&gt;각 시리즈는 이전 내용들에 의존하고 있는 부분이 있으니 길더라도 환경 구성 전 모든 시리즈를 훑어보시는 걸 추천드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;빌드 환경 자체가 모듈 시스템, 번들러, 컴파일러 등 많은 부분이 서로 의존성이 높고, 공통점, 차이점, 특징도 워낙 다양한 부분이다 보니 선수 이해가 있어야 번들러를 이해하고 알맞은 구성을 할 수 있기에 포스팅도 최대한 흐름이 자연스러울 수 있도록 작성할 생각입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;번들러 파헤치기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/86&quot;&gt;모듈 시스템의 발전과 역사 (commonJS, AMD, UMD, ESM-esmodule)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2부 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deemmun.tistory.com/87&quot;&gt;번들러의 발전과 역사 (HTTP/1.1, webpack, rollup, parcel, snowpack, exbuild, vite, turbopack)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3부 - &lt;a href=&quot;https://deemmun.tistory.com/88&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;오픈소스 라이브러리 만들기 (rollup / react / typescript / babel)&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1부 모듈 시스템의 역사&lt;/b&gt;&lt;/h2&gt;
&lt;p 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;구조적인 특징만을 가지고 있던 정적인 '문서'에 동적인 처리를 추가해서 좀 더 문서를 잘 보여줄 수 있기 위해 탄생했기에 자바스크립트가 현대 웹 생태계의 거대한 주축이 될 것이라고 아무도 생각하지 못했고, 그렇다 보니 빠르게 발전하는 웹 생태계의 속도를 자바스크립트로만은 따라가기 어려운 문제들이 많았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트 생태계는 대부분 이러한 문제를 해결하기 위해 다양한 라이브러리와 프레임워크 등이 나오고 또 지속적으로 ECMAScript의 표준 명세가 업데이트 되고 있는 것이라고 볼 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1140465552507612&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. '모듈'의 의미&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어가기에 앞서 우선 '모듈'의 의미에 대해 설명을 해야할 것 같습니다.&lt;br /&gt;'모듈'은 일반적으로 코드와 데이터의 묶음을 의미합니다. Node 환경에서 모듈은 분리된 개별 파일 1개를 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 모듈 시스템 탄생 전&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;javascript 파일은 html의 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt; 태그로부터 불러와집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저가 문서를 보여주기 위해서는 html을 파싱하는 단계를 거치게 됩니다.&lt;br /&gt;html에서 외부 리소스를 요청하는 태그가 여럿 존재하지만 그 중 html의 파싱과, 렌더링에 직접적인 영향을 주는 요소로 CSS와 JS파일이 존재합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 중 javascript를 불러오는 &lt;b&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt; 태그는 parser-blocking 요소&lt;/b&gt;로서, html 파싱 도중 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;script&lt;/span&gt; 태그를 만나게되면 파싱을 잠시 멈춘 뒤 javascript의 파일에 대한 파싱과 AST를 생성한 뒤 실행하고 그 뒤에 다시 html 파싱을 중단점으로부터 이어서 진행한다는 특징이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(지금의 script태그는 async, defer 등의 어트리뷰트를 통해 js파일 병렬 로드 처리, 실행 시점 제어가 가능하나, 여기선 아무 속성이 없는 경우로 설명합니다)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 당시 자바스크립트는 모듈 시스템이 존재하지 않았습니다.&lt;/b&gt;&lt;br /&gt;script 태그를 통해 &lt;b&gt;각각의 분리된 모듈을 불러오는 경우에도 모두 하나의 파일에서 동작하는 것처럼 같은 스코프를 공유&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모듈 모두 같은 스코프에서 같은 전역객체를 보기 때문에, &lt;b&gt;전역 상태의 오버라이딩&lt;/b&gt; 등 예상치 못한 오류가 발생할 수 있는 문제들이 존재했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하고자 즉시 실행 함수 IIFE를 통해 불필요한 전역 변수를 선언하지 않도록 하거나, namespace를 통해 관리하는 등의 방법이 나왔지만, 여전히 전역 객체의 모듈을 관리하는 것은 까다로운 문제였습니다.&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 모듈 시스템의 발전 및 역사&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (1).jpg&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbmJIX/btsFoaaMSsL/SnVXCZk7WGCDtzHRo2ORUk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbmJIX/btsFoaaMSsL/SnVXCZk7WGCDtzHRo2ORUk/img.jpg&quot; data-alt=&quot;모듈시스템의 탄생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbmJIX/btsFoaaMSsL/SnVXCZk7WGCDtzHRo2ORUk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbmJIX%2FbtsFoaaMSsL%2FSnVXCZk7WGCDtzHRo2ORUk%2Fimg.jpg&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;961&quot; height=&quot;176&quot; data-filename=&quot;Untitled (1).jpg&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모듈시스템의 탄생&lt;/figcaption&gt;
&lt;/figure&gt;
&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. CommonJS의 탄생 (2009) - 구) ServerJS&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서버를 위한 모듈 표준을 만들자, 첫 모듈 시스템&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2005 ~ 2008년, J-Query의 탄생, AJAX의 보편화로 인한 사실상 프론트엔드의 탄생이었던 시기, 웹 생태계가 엄청난 발전을 하게 되면서 &lt;b&gt;자바스크립트가 클라이언트 사이드에서만 작동하지 않고, 서버 사이드에서도 동작해야 한다&lt;/b&gt;라는 목소리가 나오기 시작했습니다.&lt;/p&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;하지만 클라이언트와 서버를 같은 언어로 통합한다면 리소스 측면에서도 많은 이점이 있었기에 수요는 늘어갔으며,&lt;br /&gt;서버 사이드에서도 사용하는 데에 문제가 없도록 골칫덩어리인 모듈 문제를 해결하고자 모듈 표준을 만들자는 움직임이 일어났고 Kevin Dangoor을 주축으로 모인 개발자들에 의해 CommonJS가 탄생하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CommonJS는 초기에 ServerJS였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후에는 환경 제약없이 범용적으로 사용하는 것을 목표로 하여 commonJS로 이름을 변경하였으나, 초기 이름을 통해 &lt;b&gt;서버 사이드 수요로 인해 탄생&lt;/b&gt;했다는 것을 명확히 알 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709132500993&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// test.js
module.exports = 'hi'

// index.js
const Hi = require('./test.js')&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;commonJS는 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;module.exports&lt;/span&gt;, 숏컷인 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;exports&lt;/span&gt; 객체를 통해 모듈을 내보내고,&lt;span style=&quot;background-color: #ffc9af;&quot;&gt; require&lt;/span&gt; 함수를 통해 모듈을 불러올 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다시 말해, &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;module.exports&lt;/span&gt;라는 전역 객체에 값을 할당한 뒤,&lt;span style=&quot;background-color: #ffc9af;&quot;&gt; require&lt;/span&gt; 함수를 실행시켜 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;module.exports&lt;/span&gt; 전역 객체에 접근하여 값을 가져오는 방법을 사용할 수 있게 된 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;commonJS의 경우 모듈 로더가 동기적으로 작동&lt;/b&gt;한다는 특징이 있습니다. &lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(명확히는 require() 함수가 동기적으로 작동하기 때문)&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모듈을 순서대로 하나씩 불러오고 처리하는 것인데, 서버사이드 작동을 위해 고안되었기때문에 &lt;b&gt;비동기로 동작하는 브라우저 환경에 사용하기에는 무리&lt;/b&gt;가 있었습니다. &lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(commonJS만 존재하던 시기에는 클라이언트 사이드에서도 commonJS를 사용하는 던 시기가 존재했습니다)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;왜 서버는 동기적으로 작동해도 괜찮은가?&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버 어플리케이션은 &lt;b&gt;파일 시스템에 직접 접근&lt;/b&gt;할 수 있으며, &lt;b&gt;필요한 모듈이나 데이터를 로컬에서 빠르게 로드&lt;/b&gt;할 수 있습니다. 이러한 방식은 네트워크 지연 시간이 거의 없기 때문에 동기적 로딩이 사용자 경험에 미치는 영향이 적다고 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;왜 브라우저는 동기적으로 작동하면 안되는가?&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저의 경우 서버 환경과 달리 &lt;b&gt;네트워크를 통해 모듈이나 라이브러리를 로드&lt;/b&gt;해야 하기에, 브라우저 환경에서의 동기적 로딩은 웹 페이지의 로딩 시간을 크게 증가시킬 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;규모가 큰 어플리케이션의 경우에는 더 많은 영향을 받을 수 밖에 없었고, &lt;b&gt;브라우저 환경을 고려한 비동기로 동작하는 모듈 시스템 AMD가 탄생&lt;/b&gt;하게 됩니다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. AMD (Asynchronous Module Definition) - 2009&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;비동기 로드 모듈 시스템을 위한 표준&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(CommonJS 그룹에서 브라우저 환경을 위해 비동기 동작도 지원해야 한다는 의견이 있던 구성원들이 독립하며 만들어진 그룹)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AMD 그룹은 &lt;b&gt;브라우저 환경을 위한 브라우저 모듈의 표준을 만들고자&lt;/b&gt; 했으며,&lt;br /&gt;AMD 그룹은 자바스크립트 &lt;b&gt;모듈과 의존성을 비동기적으로 로드하는 방법을 정의하는 개방형 표준&lt;/b&gt;을 공개했습니다. &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/amdjs/amdjs-api/blob/master/AMD.md&quot;&gt;AMD 표준 명세&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 표준 명세를 기반으로 모듈 시스템을 위한 다양한 라이브러리가 탄생했습니다.&amp;nbsp;&lt;br /&gt;표준이 알려지며 가장 널리 채택되기 시작한 라이브러리로는 &lt;b&gt;RequireJS&lt;/b&gt;가 존재합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(AMD로는 curl.js, SystemJS 등 다양한 라이브러리도 존재했습니다.)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709184053385&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* RequireJS */

// messages.js
define(function () {
    return {
        getHello: function () {
            return 'Hello World';
        }
    };
});


// main.js
define(function (require) {
    // Load any app-specific modules
    // with a relative require call,
    // like:
    var messages = require('./messages');

    // Load library/vendor modules using
    // full IDs, like:
    var print = require('print');

    print(messages.getHello());
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RequireJS는 AMD명세를 따르며, &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;define&lt;/span&gt;을 통해 모듈을 정의하고, &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;require&lt;/span&gt;를 통해 모듈을 비동기적으로 불러와 사용할 수 있게되었습니다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-3. UMD (Universal Module Difinition) - 2009 ~ 2010&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;다양한 모듈 방식을 분기처리를 통해 관리하는 프로그래밍 패턴&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트 생태계가 점점 넓어지며, &lt;b&gt;CommonJS / AMD 모듈 시스템을 모두 지원해야 하는 상황&lt;/b&gt;이 생기게 되었고 &lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;(브라우저/서버 런타임을 모두 지원하는 라이브러리)&lt;/i&gt;&lt;/span&gt;, 이 두 모듈 방식을 좀 더 효율적으로 구성하기 위해 &lt;b&gt;모듈 관리 패턴 UMD&lt;/b&gt;가 탄생했습니다. &lt;a href=&quot;https://github.com/umdjs/umd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UMD API 명세&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709185177165&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(function(root, factory) {
  if (typeof define === 'function' &amp;amp;&amp;amp; define.amd) {
    // AMD
    define([], factory);
  } else if (typeof module === 'object' &amp;amp;&amp;amp; module.exports) {
    // CommonJS
    module.exports = factory();
  } else {
    // browser
    root.isDev = factory();
  }
})(this, function() {
  return process.env.NODE_ENV === 'development';
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-4. ESM (esmodule) - 2015&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;자바스크립트 모듈 표준의 등장,&amp;nbsp;ECMAScript2015&amp;nbsp;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모듈화를 위한 다양한 움직임 끝에 드디어 ECMAScript2015 표준 명세에 ECMA modules이 등장하며, &lt;b&gt;자바스크립트 자체 모듈 시스템&lt;/b&gt;을 사용할 수 있게 되었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709185861873&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* package.json */
{
	&quot;type&quot; : &quot;module&quot;
}

/* html */
&amp;lt;script type=&quot;module&quot; src=&quot;./test.js&quot; /&amp;gt;
&amp;lt;script type=&quot;module&quot; src=&quot;./test.mjs&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;package.json 내부에 모듈 타입을 정의하여&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt; (기본값 commonJS)&lt;/i&gt;&lt;/span&gt; ESM 사용을 명시하며, script 태그의 type을 module로 설정하여 mjs, js포맷 파일에 대해 esmodule을 사용할 수 있게되었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709186167018&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* test.js */
const hello = 'hello'
export default helo;

/* index.js */
import test from './test.js';
console.log(test) // 'hello'&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709186249522&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* test.js */
export const hello = 'hello'
export const test = 'test'

/* index.js */
import {hello, test} from './test.js';
console.log(test) // 'hello'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;export&lt;/span&gt;, &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;import&lt;/span&gt; 구문을 통해 내보낼 모듈과 가져올 모듈을 정의&lt;/b&gt;할 수 있습니다.&lt;br /&gt;(단일 모듈은 export default 를 통해 정의)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;commonJS&lt;/b&gt;는 module.exports 객체를 통해 모듈을 정의하다 보니, 객&lt;b&gt;체 특성상 재할당 등을 통해 동적으로 값이 변경될 여지가 많아 명확히 의존성을 파악하기 어려운&lt;/b&gt; &lt;b&gt;구조&lt;/b&gt;를 가지고 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면, &lt;b&gt;ESM의 경우 import, export 구문을 사용해 모듈을 명확히 정의&lt;/b&gt;할 수 있게 되었고, 이로 인해 &lt;b&gt;각 모듈 간 명확한 의존성 파악&lt;/b&gt;을 가능하게 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 큰 특징을 다시 짚고 넘어가겠습니다.&lt;br /&gt;&lt;b&gt;명확한 모듈 정의 방식을 사용함으로서 모듈 간 의존성 파악이 명확해졌다.&lt;/b&gt;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 정리&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;흐름도 : CJS -&amp;gt; AMD -&amp;gt; UMD -&amp;gt; ESM&lt;br /&gt;자바스크립트의 모듈 표준이 존재하지 않던 시절, 모듈화를 위한 움직임이 모듈 시스템을 탄생시켰다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CommonJS&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;li&gt;동기적 모듈 로더 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AMD&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;li&gt;비동기적 모듈 로더 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;UMD&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;commonJS, AMD, 브라우저 등 다양한 모듈을 환경에 맞춰 제공하기 위해 고안된 프로그래밍 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;ESM&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ECMAScript2015 채택된 자바스크립트 모듈 시스템&lt;/li&gt;
&lt;li&gt;import, export 구문을 통해 사용하는 모듈을 명확히 정의하여 사용할 수 있음&lt;/li&gt;
&lt;li&gt;명확한 의존성 파악 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 특징들은 이후 번들러의 발전, 역사 등과 또 아주 깊은 관련이 있기때문에 1부로 먼저 포스팅을 작성했습니다.&lt;br /&gt;다음 포스팅은 번들러의 발전, 역사로 찾아오겠습니다!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음 포스팅 :&lt;a href=&quot;https://deemmun.tistory.com/87&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; 번들러의 발전과 역사&lt;/a&gt;&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;size14&quot;&gt;- &lt;a href=&quot;https://redfin.engineering/node-modules-at-war-why-commonjs-and-es-modules-cant-get-along-9617135eeca1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://redfin.engineering/node-modules-at-war-why-commonjs-and-es-modules-cant-get-along-9617135eeca1&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://youthfulhps.dev/javascript/nodejs-module-system/&quot;&gt;https://youthfulhps.dev/javascript/nodejs-module-system/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://yceffort.kr/2023/05/what-is-commonjs&quot;&gt;https://yceffort.kr/2023/05/what-is-commonjs&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://d2.naver.com/helloworld/591319&quot;&gt;https://d2.naver.com/helloworld/591319&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://d2.naver.com/helloworld/12864&quot;&gt;https://d2.naver.com/helloworld/12864&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://blog.rhostem.com/posts/2019-06-23-universal-module-definition-pattern&quot;&gt;https://blog.rhostem.com/posts/2019-06-23-universal-module-definition-pattern&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://github.com/volojs/create-template/blob/master/www/app/main.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/volojs/create-template/blob/master/www/app/main.js&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://ko.javascript.info/modules-intro&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.javascript.info/modules-intro&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://github.com/amdjs/amdjs-api/blob/master/AMD.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/amdjs/amdjs-api/blob/master/AMD.md&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://velog.io/@yesbb/%EB%AA%A8%EB%93%88-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EC%97%AD%EC%82%AC-%EA%B7%B8%EB%A6%AC%EA%B3%A0-ESM&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@yesbb/%EB%AA%A8%EB%93%88-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EC%97%AD%EC%82%AC-%EA%B7%B8%EB%A6%AC%EA%B3%A0-ESM&lt;/a&gt;&lt;/p&gt;</description>
      <category>Code/JavaScript</category>
      <category>AMD</category>
      <category>cjs</category>
      <category>CommonJS</category>
      <category>javascript</category>
      <category>js</category>
      <category>mjs</category>
      <category>Module System</category>
      <category>UMD</category>
      <category>모듈시스템</category>
      <category>자바스크립트</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/86</guid>
      <comments>https://deemmun.tistory.com/86#entry86comment</comments>
      <pubDate>Thu, 29 Feb 2024 15:47:55 +0900</pubDate>
    </item>
    <item>
      <title>[HTML-그래서] 브라우저 동작 원리와 함께 보는 HTML5</title>
      <link>https://deemmun.tistory.com/84</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jackson-sophat-wUbNvDTsOIc-unsplash.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnnLUE/btsHDzNx2Pd/D94CiTiA0giFaWSpHFZcQ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnnLUE/btsHDzNx2Pd/D94CiTiA0giFaWSpHFZcQ0/img.jpg&quot; data-alt=&quot;Photo by Jackson Sophat on Unsplash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnnLUE/btsHDzNx2Pd/D94CiTiA0giFaWSpHFZcQ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnnLUE%2FbtsHDzNx2Pd%2FD94CiTiA0giFaWSpHFZcQ0%2Fimg.jpg&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;3840&quot; height=&quot;2160&quot; data-filename=&quot;jackson-sophat-wUbNvDTsOIc-unsplash.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Photo by Jackson Sophat on Unsplash&lt;/figcaption&gt;
&lt;/figure&gt;
&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹, 특히 프론트엔드 환경에서 html은 뗄래야 뗄 수 없는 존재입니다.&lt;br /&gt;&lt;br /&gt;react / vue 등을 사용했을 때도 html을 항상 같이 사용하고 있으니 더더욱 간과하기 쉬운 부분이라는 생각이 듭니다.&lt;br /&gt;저 또한 그랬던 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 html을 공부하면서 마음 먹은 게 있습니다.&lt;br /&gt;'알고 있다고 생각하지 말기', '재미없으면 더 깊게 보기'&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 내용 정리도 할 겸 html에서 놓치기 쉬운 부분들에 대해 최대한 쉽게 흘러가듯 작성해보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HTML5 템플릿 예제&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1707746532610&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;// IDE에 html:5를 입력하면 나오는 자동완성 템플릿
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
	&amp;lt;head&amp;gt;
  		&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
		&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
		&amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
	&amp;lt;/head&amp;gt;
	&amp;lt;body&amp;gt;
    ...
    		&amp;lt;script type=&quot;module&quot; src=&quot;/src/index.tsx&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. DTD (Document Type Definition) 선언부&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1707746532615&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;

&amp;lt;!doctype html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브라우저는 html 파일을 받으면 이 선언부를 통해 document의 형식을 판별합니다.&lt;/b&gt; (대소문자 상관없이 사용 가능)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 예시는 웹표준인 html5 문서임을 나타내고 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;과거 브라우저들은 점유율 경쟁에서 이기기 위해 &lt;b&gt;웹 표준 개발 기구 W3C&lt;/b&gt; (world wide web consortium)의 명세를 무시한 채 서로 뽐내듯이 각자만의 기능을 넣었습니다.&lt;br /&gt;현대에는 이를 '브라우저 전쟁'이라 부릅니다.&lt;br /&gt;&lt;br /&gt;브라우저마다 html 요소를 렌더링하는 방식 등이 상이했기때문에, 개발자는 눈물 흘리며 브라우저마다 핸들링하고, 당연히 개발자 머리가 많이 아팠을 겁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;html5는 결국 궁극적으로 웹 접근성, 사용자 경험 증대를 위해 탄생했다고 생각하면 좀 더 편할 것 같습니다.&lt;br /&gt;브라우저마다 html을 처리하는 방식이 달라 발생하는 불편은 유저의 몫일테니까요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹 표준 개발 기구 W3C는&amp;nbsp;HTML4.01 표준 이후 html에 xml문법이 적용된 XHTML1.0을 표준으로 발표했습니다.&amp;nbsp;&lt;br /&gt;하지만 XHTML만으로는 빠른 속도로 성장하는 웹 생태계를 포용하는 데에 한계가 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;제한된 환경에서 추가적인 기능을 제공하기 위해 active-X등과 같은 플러그인들이 우후죽순 생겨나기 시작했고, PC가 아닌 환경에서는 플러그인을 사용하지 못하는 등의 웹 접근성 문제 또한 생겨나게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 XHTML의 구조적인 문제에 지친 애플, 모질라, 오페라는 &lt;span style=&quot;background-color: #ffffff; color: #202122; text-align: start;&quot;&gt;WHATWG (Web Hypertext Application Technology Working Group)를 설립하고,&amp;nbsp; HTML4.0의 후속 버전인 HTML5를 출시하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202122; text-align: start;&quot;&gt;&lt;/span&gt;이 당시 웹 표준이던 XHTML은 개발자들에게 외면 받으며, 개발자들은 보다 유연한 문법과 멀티미디어 기능, 하위 버전 호환성까지 갖춘 HTML5를 사용하기 시작했습니다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #202122; text-align: start;&quot;&gt;사용하는 사람이 없으니 사실상 표준이 무의미하겠죠.&lt;br /&gt;그래서 XHTML2.0을 개발하던 W3C&lt;span style=&quot;background-color: #ffffff; color: #202122; text-align: start;&quot;&gt;는 개발을 멈추고, 2014년 HTML5를 표준으로 채택했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이와 같이 웹 문서에도 여러 형식과 버전이 존재하고 있기 때문에, 브라우저는 html에서 DTD 선언부를 통해 문서의 형식과 버전을 판별하여 그에 맞는 렌더링 모드를 설정합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DTD 선언부가 존재하지 않는다면&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;브라우저는 비표준 렌더링 모드인 quirks mode로 인식하기 때문에, 개발자가 의도한대로 렌더링되지 않고 또&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;브라우저마다 각기 다른 형식으로 렌더링 될 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. html Tag&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1707746532618&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;html lang='en'&amp;gt;
...
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;html태그는 최상위 요소인 root를 뜻하며, 모든 요소는 html 하위에 존재해야 합니다.&lt;br /&gt;&lt;i&gt;(DTD 선언부의 html 어트리뷰트는 root 엘리먼트를 뜻합니다. 그래서 'html'이 설정되어 있습니다)&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;html 태그에서 하나 더 눈여겨 볼 것은&lt;b&gt; lang 어트리뷰트&lt;/b&gt;입니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 스크린리더 접근성 향상&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- 크롬 언어 인터페이스 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스크린리더는 lang 속성을 통해 언어를 설정 하기 때문에, lang이 올바르지 않다면 한국어 컨텐츠가 영어 발음으로 읽히는 등의 문제가 발생할 수 있습니다.&lt;/p&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;그렇기 때문에 lang 어트리뷰트를 통해 &lt;b&gt;문서의 주 언어&lt;/b&gt;를 올바르게 설정해야 합니다.&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. head Tag&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1707746532619&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
	&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
	&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
	&amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
	&amp;lt;meta name=&quot;description&quot; content=&quot;...&quot;&amp;gt;
	&amp;lt;meta name=&quot;keywords&quot; content=&quot;...&quot;&amp;gt;
    
	&amp;lt;link href=&quot;/media/examples/link-element-example.css&quot; rel=&quot;stylesheet&quot; /&amp;gt;

    ...
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;head 태그에는 렌더링되지 않는 요소들, 메타 데이터가 포함되어 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메타 데이터는 데이터를 설명하기 위한 데이터&lt;/b&gt;입니다.&lt;br /&gt;쉽게 말하자면, html은 데이터이고 이 html문서를 설명하기 위한 정보 데이터들을 메타 데이터라 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문서의 제목, 설명, 인코딩 방식, 디바이스에서의 viewport 정의, 웹 어플리케이션 정보, 키워드 등을 정의할 수 있고 검색엔진 봇이 문서의 메타 데이터를 참고하여 사이트에 대한 정보를 이해하고 인덱싱하기 때문에 SEO 최적화에 중요한 요소입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;더 많은 메타 태그 속성이 궁금하다면 (&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://blog.munilive.com/posts/meta-tag-property-and-use-method.html&quot;&gt;메타 태그 속성 정리&lt;/a&gt;)&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예시를 다시 보면 약간의 의문이 들 수 있습니다.&lt;br /&gt;외부 리소스를 불러오는 link태그가 head 내부에 존재합니다. 아 이것도 메타 데이터로 봐야하나..? 생각할 수 있을 것 같습니다.&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. link Tag&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;link태그는 head내부에서 사용하길 권장하고 있습니다.&amp;nbsp;&lt;br /&gt;link태그는 주로 아이콘, 파비콘, 폰트, 스타일시트 등 외부 리소스를 불러오는 데에 사용되는 태그입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;link 태그에 대해 좀 더 자세히 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;브라우저 렌더링 방식과 함께 보기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저는 렌더링을 위해 html문서를 한 줄 한 줄 순서대로 파싱합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 html 요소를 만나면 DOM 트리를 만들어 나가고, link태그를 통해 CSS 파일이나 style 태그를 만나게 되면 CSS를 파싱하며 CSSOM트리를 구축합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSS : style sheet / 렌더 블로킹 요소 (Render-blocking) / 스크립트 블로킹 요소 (script-blocking)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;CSS 파싱이 진행될 때 script 태그와는 달리 &lt;b&gt;DOM의 파싱이 멈추지는 않습니다&lt;/b&gt;.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Dom 파싱을 멈추는 요소는&lt;b&gt; Parser-blocking&lt;/b&gt;이라 합니다. Parser-blocking 요소로는 script 태그가 존재합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;CSS&lt;/b&gt;의 경우 임베디드 스타일이나 인라인 스타일에서 일부 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Parser-blocking&lt;/span&gt;&amp;nbsp;요소로 작동할 수 있다고하지만, 일반적으로 &lt;b&gt;Render-blocking&lt;/b&gt; 요소로서 렌더 트리 구축만을 멈추게 합니다. (렌더 트리 구축 후 레이아웃 -&amp;gt; 페인트 -&amp;gt; 렌더링 진행)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, CSS는 script-blocking 요소로서 C&lt;b&gt;SSOM구축이 완료되기 전&lt;/b&gt;이라면 &lt;b&gt;다운로드한 스크립트에 대한 파싱을 블로킹&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;JS로 DOM 트리 요소의 스타일을 수집한 후에 CSSOM이 업데이트 되어 DOM 트리가 변경된다면 JS는 잘못된 스타일을 가질 수 있기 때문입니다.&lt;/i&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;link rel 어트리뷰트 : preload / prefetch / preconnect&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;link 태그는 rel 어트리뷰트를 통해 파서에게 &lt;b&gt;리소스의 우선 순위를 알려줄 수 있습니다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;웹 최적화를 위해 preload / prefetch / preconnect를 사용할 수 있으며, 아래에서 간단하게 설명해보겠습니다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;preload&lt;/u&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1707746532625&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;preload&quot; as=&quot;font&quot; crossorigin=&quot;crossorigin&quot; type=&quot;font/woff2&quot; href=&quot;myfont.woff2&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;preload는 폰트 등과 같이 필수로 불러와야 하는 리소스에 대해 사용하는 것을 추천합니다.&amp;nbsp;&lt;br /&gt;브라우저는 해당 리소스의 priority를 highest로 처리하며 리소스를 받아옵니다.&amp;nbsp;&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;prefetch&lt;/u&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1707746532627&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;prefetch&quot; href=&quot;page-2.html&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;prefetch는 &lt;b&gt;미래에 사용될 것이라고 생각되는 리소스를 미리 요청, 캐싱하여 사용할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예로 들자면 다음 페이지에 대한 문서를 요청할 수 있습니다.&lt;br /&gt;다만 해당 파일만을 받아오는 것이지, 파일이 &lt;b&gt;참조하고 있는 외부 리소스들을 받아오지는 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이미 클라이언트에 캐싱된 데이터가 존재하더라도 prefetch를 통해 받아오게 되기때문에 prefetch를 적용할 때에는 효용 가치가 있는지 파악한 후에 사용하는 게 좋습니다.&amp;nbsp;&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;preconnect&lt;/u&gt;&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1707746532629&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://어쩌고저쩌고.cloudfront.net&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;preconnect는 &lt;b&gt;현재 도메인에서 외부 도메인의 리소스를 참고할 것이라 브라우저에게 알려주는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저는 DNS lookup을 통해 도메인의 호스트 정보를 받아오고, TCP왕복을 진행한 뒤 https 프로토콜을 위한 SSL/TLS 핸드셰이크 과정을 거치게됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;preconnect를 사용할 경우 &lt;b&gt;DNS, TCP/IP등과 같은 외부 도메인에 대한 연결을 미리 진행할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;HTTP/1.1 이상부터는 TCP에 대한 연결을 유지하고 있기때문에 preconnect를 잘 사용한다면 최적화에 큰 도움을 줄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트에서 사용 예를 들자면, 도메인과 연결된 CDN링크가 될 수 있을 것 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. script Tag&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1707746532630&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
 	...
	&amp;lt;script type=&quot;module&quot; src=&quot;/src/index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;js파일을 불러올 수 있는 script 태그입니다.&amp;nbsp;&lt;br /&gt;script태그는 body태그 하단에 위치해야 한다는 이야기를 들어보셨을 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크게 한 가지의 이유가 존재한다고 생각합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- Parse-Blocking 요소&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저 런타임 환경에서 작동하는 자바스크립트에선 DOM API를 통해 &lt;b&gt;자유롭게 DOM에 접근&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;브라우저 동작원리를 한 번 더 살펴보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저는 html파일을 상단에서부터 한 줄 한 줄 파싱하며 DOM트리를 생성합니다.&lt;br /&gt;이 과정에서 html 파서가 script 태그를 만날 경우, 엔진의 제어권은 렌더링 엔진에서 자바스크립트 엔진으로 옮겨가게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;script는 Parse-blocking 요소로서 &lt;b&gt;script 태그를 만나게 되면 html의 '파싱'을 멈추고&lt;/b&gt;, 자바스크립트 파일에 대한 다운로드를 진행한 뒤, 다운로드가 완료되면 파싱을 진행합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;자바스크립트 엔진은 추상 구문 트리(abstract syntax tree, AST)를 생성한 후 자바스크립트를 실행한 뒤, html 파싱이 중단되었던 지점으로 돌아와 &lt;b&gt;다시 html 파싱을 진행&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 자바스크립트가 실행되는 시점에 js내부에 아직 DOM트리에 존재하지 않는 요소에 대한 로직이 존재한다면, 해당 DOM요소를 감지하지 못할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;살펴보니, html을 파싱하다 말고 script 파일의 다운로드를 기다린다는 게 비효율적이라는 생각이 들 것입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HTML5 : async / defer 어트리뷰트의 등장&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;HTML5에서 script 태그의 특성이 가진 문제를 해결하기 위해 어트리뷰트로 async, defer가 추가되었습니다.&lt;br /&gt;두 어트리뷰트가 해결하고자 하는 &lt;b&gt;공통 관심사는 스크립트 파일 처리에 대한 최적화&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;async&lt;/u&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1707746532633&quot; class=&quot;html xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;script async type=&quot;text/javascript&quot; src=&quot;/src/index.js&quot; /&amp;gt;


&amp;lt;script&amp;gt;
	document.getElementById(&quot;test&quot;).innerHTML = &quot;Hello test!&quot;;
&amp;lt;/script&amp;gt;
--&amp;gt; import없이 script 파일 작성 시, async와 동일하게 작동&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;async의 경우 html 파싱 과정에서 미리 리소스를 다운로드합니다. (&lt;b&gt;html 파싱과 스크립트 파일 다운로드를 병렬적으로&lt;/b&gt;)&lt;br /&gt;&lt;b&gt;다운로드가 완료되는 시점에 html 파싱을 멈추고 js파일에 대한 파싱을 진행&lt;/b&gt;합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;미리 js파일을 다운로드하고, 다운로드가 완료되는 시점에 자바스크립트 엔진이 분석을 시작하는 것이기때문에 js파일에 대한 실행 시점은&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명확히 할 수 없습니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;그렇기때문에 광고 스크립트나, DOM 의존성이 낮은 스크립트에 적용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;defer&lt;/span&gt;&lt;/span&gt;&lt;/u&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1707746532635&quot; class=&quot;html xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;script defer type=&quot;text/javascript&quot; src=&quot;/src/index.js&quot; /&amp;gt;

&amp;lt;script type=&quot;module&quot; src=&quot;/src/index.js&quot; /&amp;gt;
-&amp;gt; type이 'module'인 경우, defer와 동일하게 작동&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;defer의 경우 async와 동일하게 &lt;b&gt;script파일에 대한 다운로드를 html 파싱과 병렬로 처리&lt;/b&gt;합니다.&lt;br /&gt;&lt;b&gt;다만 자바스크립트 파일의 실행 시점에 차이&lt;/b&gt;가 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&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;size18&quot;&gt;&lt;u&gt;DOMContentLoaded&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ㅇㅇ.jpeg&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uhyKc/btsEIPrmdJm/85SzwDXKq86rWfhTertmz1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uhyKc/btsEIPrmdJm/85SzwDXKq86rWfhTertmz1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uhyKc/btsEIPrmdJm/85SzwDXKq86rWfhTertmz1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuhyKc%2FbtsEIPrmdJm%2F85SzwDXKq86rWfhTertmz1%2Fimg.jpg&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;711&quot; height=&quot;272&quot; data-filename=&quot;ㅇㅇ.jpeg&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 html 파싱을 진행하며 DOM트리를 구축하고, CSSOM 트리를 구축한 뒤 두 트리를 결합하여 렌더 트리를 생성합니다. (레이아웃 이후는 이번에 설명하지 않겠습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html문서의 생명주기와 관련된 이벤트 중 &lt;b&gt;DOMContentLoaded&lt;/b&gt;가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOMContentLoaded는 브라우저가 HTML을 전부 읽고, DOM트리를 완성하는 즉시 (CSSOM 트리는 완성되어 있는 상태) 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 렌더링 과정 그림과 함께 보면 &lt;b&gt;Render Tree로 결합되기 직전에 실행되는 이벤트&lt;/b&gt;라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;defer 속성을 갖고 있는 script 태그의 파일의 경우 DOMContentLoaded가 실행되기 직전에 js파일을 실행&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고로 DOM 의존성이 존재하는 스크립트 파일에 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에선 HTML 태그의 역할과 놓칠 수 있는 부분들에 대해 설명하는 글을 생각했었는데, 작성하다 보니 양이 좀 많아진 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML자체가 웹 문서를 보여주기 위해 탄생 했지만, 이후 웹 생태계가 넓어지며 어플리케이션 형태로 진화하다 보니 html에서도 브라우저 위에서 효율적으로 작동하기 위한 여러 방법들이 고안되었습니다. 그래서 브라우저 동작 원리를 빼놓고 설명하지 않을 수가 없었네요.&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;/p&gt;</description>
      <category>Code/그래서</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/84</guid>
      <comments>https://deemmun.tistory.com/84#entry84comment</comments>
      <pubDate>Tue, 13 Feb 2024 00:27:37 +0900</pubDate>
    </item>
    <item>
      <title>[Network] HTTP cache (browser cache, proxy cache) 파헤치기</title>
      <link>https://deemmun.tistory.com/83</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;charles-chen-GKVtl_Q7ZmA-unsplash.jpg&quot; data-origin-width=&quot;6720&quot; data-origin-height=&quot;4480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPdDcZ/btsHDikRNwl/sPuWHKTvZMlsX0InSXA7i1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPdDcZ/btsHDikRNwl/sPuWHKTvZMlsX0InSXA7i1/img.jpg&quot; data-alt=&quot;Photo by Charles Chen on Unsplash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPdDcZ/btsHDikRNwl/sPuWHKTvZMlsX0InSXA7i1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPdDcZ%2FbtsHDikRNwl%2FsPuWHKTvZMlsX0InSXA7i1%2Fimg.jpg&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;6720&quot; height=&quot;4480&quot; data-filename=&quot;charles-chen-GKVtl_Q7ZmA-unsplash.jpg&quot; data-origin-width=&quot;6720&quot; data-origin-height=&quot;4480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Photo by Charles Chen on Unsplash&lt;/figcaption&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;/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;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1140465552507612&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 cache / 캐싱 의미에 대해 설명해보겠습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;Cache ?&lt;/b&gt;&lt;/u&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;i&gt;밥을 먹기 위한 과정&lt;/i&gt;&lt;br /&gt;&lt;/span&gt;- 밥솥을 열고 숟가락에 얹은 뒤 다시 자리에 앉아서 한 숟갈을 먹고 다시 밥 한 숟갈을 뜨기위해 밥솥을 열러 간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;이 밥 데이터에 대해 캐싱을 해놓는다면 아래와 같습니다.&lt;/i&gt;&lt;br /&gt;- 밥솥을 열고 밥을 가져와 식탁에 올려 놓는다.&lt;br /&gt;- 먹는다. 또 먹는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱은 데이터를 근접한 장소에 가져다놓고 필요한 때에 효율적으로 사용하기 위해 사용되는 기법입니다.&lt;br /&gt;그렇기 때문에 브라우저 캐시, 웹 캐시, proxy 캐시, react query cache, apollo client cache 등등 다양한 곳에서 불리우고 있는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐시의 이점은 크게 아래와 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저 로딩 속도가 빠르다&lt;br /&gt;- 불필요한 네트워크 요청 / 리소스를 줄일 수 있다.&lt;br /&gt;- 사용자 경험 증대&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 우선적으로 HTTP 캐시에 대해 작성해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;캐싱 매커니즘&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시는 크게 fresh / stale의 상태로 구분할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fresh&lt;/b&gt; - 신선한 데이터&lt;br /&gt;&lt;b&gt;stale&lt;/b&gt; - 상한 데이터, 쉽게 말해 &lt;b&gt;데이터의 정합성을 보장할 수 없는 상태&lt;/b&gt;&lt;/p&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;http 캐싱 매커니즘에 대해 먼저 가볍게 이해하고 들어가면 좋을 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 10.03.24.png&quot; data-origin-width=&quot;2212&quot; data-origin-height=&quot;1332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Evdl/btsDRpmoQ0f/MwmBrJH5El3PBDGFz2UIX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Evdl/btsDRpmoQ0f/MwmBrJH5El3PBDGFz2UIX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Evdl/btsDRpmoQ0f/MwmBrJH5El3PBDGFz2UIX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Evdl%2FbtsDRpmoQ0f%2FMwmBrJH5El3PBDGFz2UIX0%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;2212&quot; height=&quot;1332&quot; data-filename=&quot;스크린샷 2024-01-22 오후 10.03.24.png&quot; data-origin-width=&quot;2212&quot; data-origin-height=&quot;1332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;프론트엔드답지 않게 색조합이 좀 그렇긴 한데 정보 전달 목적이니 이해해주실 거라 믿습니다.&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저에서 GET요청이 발생하면 uri를 통해 브라우저 &lt;b&gt;캐시 데이터를 먼저 탐색&lt;/b&gt;합니다.&lt;br /&gt;캐시 데이터가 존재하지 않는다면 &lt;b&gt;origin 서버로 요청을 보내고 응답&lt;/b&gt;을 받습니다.&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;세부 설정에 따라 달라질 수 있겠지만, 캐시를 허용한 경우&lt;span&gt; 해당 &lt;b&gt;응답을 캐시에 저장&lt;/b&gt;합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;캐시 데이터가 존재한다면 해당 &lt;b&gt;캐시 데이터가 신선한지 판별&lt;/b&gt;을 해야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;판별이 필요한 이유&lt;/b&gt;는 간단합니다.&lt;br /&gt;캐싱은 데이터를 서버가 아닌 곳에 저장해놓고 사용하기 때문에 서버의 실제 데이터와 캐싱된 데이터간의 정합성 문제가 발생할 수 있기때문입니다. (이 캐시 헤더는 개발자가 지정하여 사용할 수 있습니다. )&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;캐시에는 두 가지 상태가 존재한다고 말씀드렸습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;1. 캐시가 fresh 상태인 경우&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;캐시가 fresh상태인 경우 실제 origin 서버까지 요청을 보내지 않고 브라우저에 데이터 응답을 보냅니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 9.38.49.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf2bPt/btsDQcOBSUR/TSTGP94OHNtorB354CilW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf2bPt/btsDQcOBSUR/TSTGP94OHNtorB354CilW0/img.png&quot; data-alt=&quot;chrome network&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf2bPt/btsDQcOBSUR/TSTGP94OHNtorB354CilW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf2bPt%2FbtsDQcOBSUR%2FTSTGP94OHNtorB354CilW0%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;661&quot; height=&quot;127&quot; data-filename=&quot;스크린샷 2024-01-22 오후 9.38.49.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;chrome network&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 200 응답을 보면 브라우저 캐시를 통해 가져온 데이터임을 확인할 수 있습니다.&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 캐시가 stale 상태인 경우&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;캐시 헤더를 통해 개발자가 fresh 기간에 대해 정의 하긴 하지만, 실제로는 &lt;b&gt;데이터가 변경되지 않아 같은 데이터를 제공하여도 괜찮은 경우&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 &lt;b&gt;실제로 데이터가 변경되었는지를 확인할 수 있어야 할 것&lt;/b&gt;입니다.&lt;br /&gt;이 경우 origin 서버에서 새로운 데이터를 받아오면 데이터 정합성은 보장되겠지만 변하지 않았을 수도 있는 데이터를 요청하는 게 효율적이라고 보기는 어렵습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;받은 응답을 저장하는 것이 캐시 데이터이기 때문에 캐시는 &lt;b&gt;캐시가 저장된 시점의 http 응답 헤더 데이터를 소유&lt;/b&gt;하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저는 데이터의 변경 여부를 검증하기 위해 저장되어 있던 캐시의 http 헤더 데이터를 함께 origin 서버로 전달합니다.&lt;br /&gt;서버는 헤더에 존재하는 캐시 &lt;b&gt;검증 헤더를 통해 데이터가 변경되었는지를 판별&lt;/b&gt;하고&lt;b&gt; 데이터가 존재하는 경우 304 Not Modified&lt;/b&gt; 응답을 반환합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이 경우 http 요청은 진행되나 응답에 Body 데이터가 존재하지 않고 헤더 정보만을 반환합니다.&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 10.13.49.png&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cglBqI/btsDJiv1ia5/6gU6402H13gF6znkSS1JG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cglBqI/btsDJiv1ia5/6gU6402H13gF6znkSS1JG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cglBqI/btsDJiv1ia5/6gU6402H13gF6znkSS1JG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcglBqI%2FbtsDJiv1ia5%2F6gU6402H13gF6znkSS1JG0%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;732&quot; height=&quot;143&quot; data-filename=&quot;스크린샷 2024-01-22 오후 10.13.49.png&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첨부 스샷은 같은 문서를 처음 진입했을 때와 다시 새로고침하여 캐싱된 데이터를 받아온 경우입니다.&lt;br /&gt;용량 차이를 확인할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;304응답이 오면 브라우저는 캐시 데이터의 헤더를 반환받은 응답의 http 헤더로 교체하여 원본 데이터가 아닌 캐시 정보에 대한 헤더만 변경합니다.&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;검증 헤더를 통해 확인한 결과 데이터가 변경된 경우엔 304가 아닌 200 응답을 새롭게 내려주고, 이 데이터를 다시 캐싱할 수 있게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지가 HTTP 캐싱의 큰 흐름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 캐시에는 private cache / public cache가 존재합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;1. private cache (웹 브라우저 캐시)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 캐시가 저장된 IP에서만 사용되는 개인 캐시&lt;br /&gt;- 웹사이트의 개인 정보나 브라우저 네비게이션을 캐싱하여 뒤로 가기/앞으로 가기 빠른 처리 등이 가능함&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. public cache / ex) Proxy cache, CDN cache&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 둘 이상의 클라이언트에서 사용되는 캐시&lt;br /&gt;- 자주 요청되며, 자주 변경되지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.01.50.png&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;1286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SKnS6/btsDQaJ2sUU/d5xg7tgk7dEkRdRhiCdHH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SKnS6/btsDQaJ2sUU/d5xg7tgk7dEkRdRhiCdHH1/img.png&quot; data-alt=&quot;proxy cache&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SKnS6/btsDQaJ2sUU/d5xg7tgk7dEkRdRhiCdHH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSKnS6%2FbtsDQaJ2sUU%2Fd5xg7tgk7dEkRdRhiCdHH1%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;1928&quot; height=&quot;1286&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.01.50.png&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;1286&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;proxy cache&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프록시 캐시는 본 서버로 요청이 들어가기 전 위치&lt;/b&gt;하며, AWS의 CloudFront과 같은 CDN (Content Delivery Network)를 예로 들 수 있습니다.&amp;nbsp;&lt;br /&gt;위에서 보았던 플로우와 비슷한 방식으로 작동합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CDN&lt;/b&gt;은 근접한 곳에 자원을 배치하여 네트워크 사용량을 최적화하고 보다 빠른 사용자 경험을 위해 존재한다는 캐시의 역할에서 추가적으로 개인이 아닌 다수의 ip가 보다 효율적으로 자원을 이용할 수 있도록 근거리에 존재하는 저장소(엣지 로케이션)에 데이터를 저장하고 좀 더 빠르게 받아볼 수 있는 기술입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;origin 서버가 미국인데 한국에서 네트워크 요청을 보낸다면 상당히 오래 걸릴 것입니다.&lt;br /&gt;이를 보완하기 위해 CDN이 존재하며, CDN은 해당 리소스가 근접한 저장소에 캐싱되어있는지 확인하고 캐싱 데이터가 존재한다면 해당 데이터를 응답으로 반환하며 origin 서버까지 전달되는 요청을 줄일 수 있다는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공유 가능한 자원이라는 의미에서 프록시 캐시는 public으로 분류되는 것입니다.&lt;/b&gt;&lt;br /&gt;(물론 접속한 ip에 배정된 가장 근접한 저장소에 캐싱 데이터가 존재하지 않는다면 최초 요청 시 origin 서버 요청과 같은 지연이 발생할 수 있습니다)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;캐시를 관리하기 위한 헤더&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;http 헤더에는 Cache를 관리하기 위한 필드가 몇 개 존재하고 있습니다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;HTTP header : Expires&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705930181731&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expires: Wed, 21 Oct 2015 07:28:00 GMT&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;캐시의 만료일을 직접 설정할 수 있습니다.&lt;br /&gt;다만 날짜를 명시해주어야 하니 Cache-Control의 'max-age', 's-max-age'에 비해 유연성이 떨어진다는 단점이 있습니다.&lt;br /&gt;- (Cache-Control 헤더에 'max-age', 's-max-age'가 존재할 경우 Expires 헤더는 무시됩니다)&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;HTTP header : Pragma (http/1.0)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;http/1.0에서 존재하던 Pragma입니다. http/1.1부터 좀 더 강화된 버전의 cache-control 헤더가 나타나면서 대체 가능하지만, 하위 호환성을 위해 사용되곤 합니다. (캐싱 무효화)&lt;/p&gt;
&lt;pre id=&quot;code_1705930076949&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Pragma : no-cache&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;u&gt;&lt;b&gt;HTTP header : Cache-Control (http/1.1)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http 헤더의 cache-control 필드에는 '디렉티브'를 통해 캐시의 생명주기, 타입, 캐시 관리 방법을 정의할 수 있습니다.&lt;br /&gt;cache가 얼만큼 어떤 방식으로 관리되면 좋을지 설정할 수 있는 헤더입니다.&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;b&gt;디렉티브&lt;/b&gt; 몇 개를 설명해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;public&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705930881285&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control : public&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;private&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705930915814&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control : private&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;max-age&amp;lt;second&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705930925977&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control : max-age=400&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;- 캐시한 데이터를 몇 초동안 fresh한 데이터로 볼 것인지 정의&lt;br /&gt;- Expires헤더와 함께 사용될 경우 Expires 헤더는 무시&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;s-max-age&amp;lt;second&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705930957011&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control : s-max-age=400
Cache-Control : max-age=400, s-max-age=100000&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;b&gt;- public&lt;/b&gt; cache에서만 유효하며, max-age와 동일한 역할 (public cache에서 'max-age=30, s-max-age=2000'과 같이 사용 가능하나 max-age, Expires 보다 우선순위가 높다&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;no-store&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705931049945&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control : no-store&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;캐싱하지 않는다.&lt;/b&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;no-cache&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705930977406&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control : no-cache&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;데이터는 캐싱&lt;/b&gt;하지만, 항상 origin 서버에&lt;b&gt; 검증 후 사용&lt;/b&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;must-revalidate&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705932968091&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control : must-revalidate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프록시 캐시에서 origin 서버로 요청을 보내는 도중 네트워크 연결 문제 등이 발생할 수 있습니다.&lt;br /&gt;&amp;nbsp;이 경우 &lt;b&gt;504에러를 응답&lt;/b&gt;하기때문에 오래된 캐싱 데이터를 보여주면 안되는 경우 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 캐시의 관리 방식을 정의할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이제 캐시가 유효한지 검증하는 검증 헤더에 대해 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;검증 헤더&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Last-Modified (If-Modified-Since)&lt;br /&gt;- Etag (If-None-Match)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증 헤더의 목적은&lt;b&gt; stale한 캐시 데이터가 실제 서버 데이터에서도 변경되었는지 판별하기 위해 존재합니다.&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Last-Modified (조건부 요청 헤더 : if-Modified-Since)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;(마지막 수정일자 기준으로 변경 여부를 판별)&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱 데이터는 첫 요청의 응답을 저장해놓는다 설명한 부분이 있었습니다.&lt;br /&gt;단순히 body 데이터만을 저장하는 것이 아닌 데이터를 설명하는 메타 데이터인 헤더도 저장하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.23.12.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7hwnv/btsDJXx9LYp/lW0Oc9CGi2mvV0RAIhLoR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7hwnv/btsDJXx9LYp/lW0Oc9CGi2mvV0RAIhLoR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7hwnv/btsDJXx9LYp/lW0Oc9CGi2mvV0RAIhLoR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7hwnv%2FbtsDJXx9LYp%2FlW0Oc9CGi2mvV0RAIhLoR0%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;752&quot; height=&quot;44&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.23.12.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시가 stale하여 서버쪽으로 실제 변경이 필요한 데이터인지 확인하기 위해 요청을 보낼 때 캐시 데이터에 존재하던&lt;b&gt; Last-Modified 값&lt;/b&gt;을&amp;nbsp; &lt;b&gt;요청 헤더 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;If-Modified-Since&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;에 실어 아래와 같이 GET 요청을 전송&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.23.22.png&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dinGwy/btsDQcOCUIl/N1soKQ3kogqW1kJU3V8Fu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dinGwy/btsDQcOCUIl/N1soKQ3kogqW1kJU3V8Fu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dinGwy/btsDQcOCUIl/N1soKQ3kogqW1kJU3V8Fu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdinGwy%2FbtsDQcOCUIl%2FN1soKQ3kogqW1kJU3V8Fu0%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;756&quot; height=&quot;58&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.23.22.png&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 If-Modified-Since를 통해 해당 자원의 마지막 수정일자를 확인하고 동일하다면 304응답을 반환할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Last-Modified, If-Modified-Since는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1초 미만 단위로 캐시 조정이 불가능하며, 날짜 형식이기때문에 &lt;b&gt;실제 데이터가 변경되었는지 정확히 판별하기 어려운 부분이 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;A데이터를 수정하여 B데이터로 가공했다가 다시 A데이터로 수정하여 원복하는 경우를 예로 들어보겠습니다.&lt;br /&gt;결과적으로 데이터는 동일하나 Last-Modified는 변경되기 때문에 캐싱 로직을 별도로 서버에서 관리하고싶을 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이를 보완하기 위해 ETag 헤더를 사용할 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2. Etag (조건부 요청 헤더 : If-None-match)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(Etag 값 기준으로 변경 여부를 판별)&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Etag (Entity tag) 값은 개발자가 지정하여 사용할 수 있습니다. 버전명, 컨텐츠의 해시값 등으로 정할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.40.44.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP3LDj/btsDQ6HhlpT/lSz8eQWQoP9jmE9M3Lqc40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP3LDj/btsDQ6HhlpT/lSz8eQWQoP9jmE9M3Lqc40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP3LDj/btsDQ6HhlpT/lSz8eQWQoP9jmE9M3Lqc40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP3LDj%2FbtsDQ6HhlpT%2FlSz8eQWQoP9jmE9M3Lqc40%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;878&quot; height=&quot;40&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.40.44.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증 로직과 동일하게 stale한 캐시의 변경 여부를 감지하기 위해 응답을 통해 받아 놓았던&lt;b&gt; Etag의 값을 요청 헤더의 If-None-Match에 실어 요청을 보냅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.40.58.png&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZXxuJ/btsDNKx1O9H/46KCjxF8jKzMGjNOd6jpd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZXxuJ/btsDNKx1O9H/46KCjxF8jKzMGjNOd6jpd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZXxuJ/btsDNKx1O9H/46KCjxF8jKzMGjNOd6jpd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZXxuJ%2FbtsDNKx1O9H%2F46KCjxF8jKzMGjNOd6jpd1%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;876&quot; height=&quot;42&quot; data-filename=&quot;스크린샷 2024-01-22 오후 11.40.58.png&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;간단하게 리소스의&lt;b&gt; Etag값과 If-None-Match의 값이 일치하는지 확인&lt;/b&gt; 후 처리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;웹 성능 최적화와&amp;nbsp;&lt;/span&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;멀게만 느껴지던 CloudFront도 직접 만져보며 CDN을 이해하게 되고, 어쩌다 보니 AWS lambda@Edge까지 흘러가서 AWS cloud9, cloudWatch까지 사부작 사부작 만져보았는데 머리가 얼마나 뜨거웠는지 모릅니다.&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;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Code/Network</category>
      <category>cache</category>
      <category>HTTP/1.0</category>
      <category>http/1.1</category>
      <category>network</category>
      <category>최적화</category>
      <category>캐싱</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/83</guid>
      <comments>https://deemmun.tistory.com/83#entry83comment</comments>
      <pubDate>Tue, 23 Jan 2024 00:13:44 +0900</pubDate>
    </item>
    <item>
      <title>2024.01.06 (토요일) - 좋은 결과물</title>
      <link>https://deemmun.tistory.com/82</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sam-moghadam-khamseh-63VIBT5dtKA-unsplash.jpg&quot; data-origin-width=&quot;6000&quot; data-origin-height=&quot;4000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxjYzz/btsHE90YlQZ/d54kBNMmJP5mvhdC3FkaKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxjYzz/btsHE90YlQZ/d54kBNMmJP5mvhdC3FkaKk/img.jpg&quot; data-alt=&quot;Photo by Sam Moghadam Khamseh on Unsplash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxjYzz/btsHE90YlQZ/d54kBNMmJP5mvhdC3FkaKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxjYzz%2FbtsHE90YlQZ%2Fd54kBNMmJP5mvhdC3FkaKk%2Fimg.jpg&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;6000&quot; height=&quot;4000&quot; data-filename=&quot;sam-moghadam-khamseh-63VIBT5dtKA-unsplash.jpg&quot; data-origin-width=&quot;6000&quot; data-origin-height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Photo by Sam Moghadam Khamseh on Unsplash&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 새해가 밝았습니다.&lt;br /&gt;주말 아침, 일어나서 환기를 시키고 발이 시려운 탓에 양반 다리를 하고 공부를 하다가 새삼 새해가 밝았다는 사실이 놀라워 멍을 좀 때리다가 블로그에 들어왔습니다.&lt;br /&gt;매체를 가리지 않고 글 쓰는 걸 좋아하는 편입니다.&lt;br /&gt;기술 블로그는 지식 공유의 목적이 뚜렷하기에 주제와 구성을 생각하며 나름대로 신경 써 작성하곤 하는데 다른 곳에선 그냥 생각나는대로 주제 없이 쓰다가 제목을 정하고 발행을 합니다. 이 곳에선 구어체를 사용한다면, 다른 곳에선 편하게 문어체로 글을 쓰고 더 솔직하게 또 진정성있게 내용을 풀어낸다는 차이가 있을 것 같네요.&lt;br /&gt;요즘엔 커리어에 대한 고민이 8할을 차지하고 있어 이곳에 편하게 글을 작성해볼까 합니다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년엔 참 많은 일이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최고의 복지는 동료라는 말이 있지 않나, 내게 첫 회사가 그랬다.&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어떻게 다 이런 사람들만 있지 싶을 정도로&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;하나같이 유능하고, 배울 점 많고, 열정적이었다.&amp;nbsp;&lt;br /&gt;다들 개성이 강한데 모두가 같은 목표를 이루기 위한 배려심과 존중이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 서비스 매각으로 인한 헤어짐이 크게 아쉬웠고 또 슬펐다.&lt;br /&gt;그러다 인수한 회사로의 합류에 대해 고민하는 시점이 있었다.&lt;br /&gt;크게 두 가지 고민이 있었고, 고민 끝에 합류를 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 서비스, 동료에 대한 애정 &lt;br /&gt;2. 커리어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 회사에 대한 정보가 부족했었고, 시니어 없이 혼자 FE를 맡아 신규 서비스를 런칭한다는 것에 대한 장단이 있는 듯 하여 걱정이 있었다.&lt;br /&gt;많은 고민을 하던 찰나 팀장님의 '와주세요'를 듣고 바로 그래 한 번만 더 해보자. 1년만 더 해보자.&amp;nbsp;&lt;br /&gt;원체 존경하던 분이었고, 항상 개인의 선택을 존중하시던 분이었기에 '와주세요'라는 말이 더 크게 다가왔던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 1년이 다 되어간다.&lt;br /&gt;신규 서비스로 업계 1위를 만들겠다는 목표를 가지고 달려왔다.&lt;br /&gt;런칭과 동시에 공격적 마케팅을 통해 유저를 확보할 예정이었기에 런칭까지 7개월 가량을 정말 몰입해 일했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저녁 11시 반에 퇴근해서 택시타고 헬스장에 내려 운동하고.. 그렇게 집에 오면 새벽 두시..3~4시간 자고 두시간 반 출근해서 일하다가 점심에 눈을 붙이고의 반복이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직무 별 1명, 총 인원 5명의 소규모 팀이었지만 모두가 다인분을 해냈던 팀이었다.&lt;br /&gt;프론트엔드 개발자는 결과물의 최말단에서 동료들의 작업물을 완성도 있게 보여주는 직업이다.&lt;br /&gt;또, 다양한 직군과 가장 긴밀하게 엮여있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 프론트엔드 개발자가 나와 같은 생각을 갖고 있지는 않을 것이다.&lt;br /&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;1. 내가 무얼하고 있는지 모두가 알게 하고, 동료가 궁금해지기 전에 말한다.&lt;br /&gt;2. 의문은 넘어가지 않고 확인한다.&lt;br /&gt;3. 숨기지 않는다.&lt;br /&gt;4. 이유없는 코드를 작성하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;동료들이&amp;nbsp;&lt;/span&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;나의 요청에 기분이 상하진 않을지, 내 질문이 무례하거나 재촉하는 것처럼 전달되진 않을지, 이 질문이나 의견을 말하면 혹시 내 이미지에 영향이 가지 않을지.. 사회인이라면 상기의 고민들을 피할 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 상대가 감정적으로 받아들이지 않고 결국 같은 목표를 보며 더 좋은 방향을 고민할 수 있는 동료란 확신이 있다면 부드러운 분위기 속에서 원활한 의견 교류가 가능하게 되며, 불필요한 에너지 낭비를 줄일 수 있다고 생각한다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;또 코드적인 측면에선 항상 이유없는 코드를 작성한 적은 없었던 것 같다.&lt;br /&gt;그 당시 생각했던 최선의 결과물을 구현하기 위해 항상 노력했었다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;런칭 이후 입사한 개발 동료가 있는데, 이야기를 나누다가 어떻게 코드의 이유를 다 기억하고 있는 거냐 물었던 적이 있는데 웃어 넘겼던 적이 있다.&lt;br /&gt;나중에 생각해보니 그냥 썼던 코드는 없었기 때문이 아닐까싶었다.&lt;br /&gt;시간이 없어 급히 마무리를 해야하는 때도 분명 있다.&lt;br /&gt;그래도 마음 속 백로그에 항상 담아두고 수정 가능한 때가 오면 개선해 나갔던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 길게 쓰긴 했지만 사실 내가 진짜 '좋은 결과물'을 만들었는지는 잘 모르겠다.&lt;br /&gt;서비스는 런칭되어 웹, 앱 모두 출시되었지만 출시 후 6개월인 현재, 유저 200명 중 100명이 본인이고, 나머지 100명은 동료들이다.&lt;br /&gt;&lt;br /&gt;회사의 사업 방향이 바뀌어 1년 간 유저가 없는 상태로 많은 기능이 추가되었고, 변경과 삭제가 있었다.&lt;br /&gt;서비스는 현 시점 기준, 206번째의 배포가 진행되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWxefK/btsDbfxKBXB/vRLkzE5ayKOJR8OboQnsWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWxefK/btsDbfxKBXB/vRLkzE5ayKOJR8OboQnsWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWxefK/btsDbfxKBXB/vRLkzE5ayKOJR8OboQnsWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWxefK%2FbtsDbfxKBXB%2FvRLkzE5ayKOJR8OboQnsWk%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;410&quot; height=&quot;113&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무는 애자일하게 했지만 그냥 일을 많이 빠르게 했지 정작 애자일 업무를 위한 피드백 창구가 없었다.&lt;br /&gt;알파 테스트, 베타 테스트, 전사 대상 테스트, 만족도 테스트 등등 런칭과 마케팅 도입을 위한 여러 번의 전사 테스트를 진행하였고, 평가 결과와 데이터로 보았을 때도 모두 좋은 결과가 있었다.&lt;br /&gt;유저와 마주하는 날만을 그렇게 염원하며 기다려왔던 것 같은데 서비스는 곧 앱스토어에서 내려갈 예정이다.&lt;br /&gt;&lt;br /&gt;서비스가 종료되는 것은 아니고 웹 플랫폼만 유지하는 것이지만 괜히 씁쓸하긴 하다.&lt;br /&gt;하지만 후회없이 열심히했고 또 성장했으며 과정에 떳떳해 부끄럽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 회사에 걸쳐 합을 맞춰 온 팀원들이 있어 참 좋았고,&lt;br /&gt;여러 난관을 같이 겪어내며 성장하는 기분을 느낄 수 있어 좋은 경험이었다.&lt;br /&gt;참 기억에 남는 1년이었다.&lt;br /&gt;좋은 동료들 덕에 참 재밌게 일했고, 좋은 지인들 덕에 참 도움도 위로도 많이 받았고,&lt;br /&gt;참 감사한 사람도 많은 1년이었다.&lt;br /&gt;정든 팀원들을 하나 둘 보내며 7~10년 후 다시 한 번 모여서 일했으면 좋겠다는 말을 했다.&lt;br /&gt;그때쯤이면 멋진 시니어 개발자가 되어있지 않을까,&lt;br /&gt;그랬으면 좋겠다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;상반기엔 부족한 부분을 공부하고 또 이런 저런 인생 고민도 떠올려 보면서 새로운 시작을 준비 해나가려 한다.&lt;br /&gt;추운 겨울이었지만 머지않아 봄은 올 것이다.&lt;br /&gt;&lt;br /&gt;그러다 또 다시 뜨거운 환경에서 데이기도, 뿜어내기도 하면서 그렇게 일할 것 같다.&lt;br /&gt;아마도 유능한 동료들 사이에 작게만 느껴지는 나를 보게될 것이고,&lt;br /&gt;부족한 나를 다시 마주하는 과정에선 또 성장통이 있을테다&lt;br /&gt;그럼에도 불구하고 나는 그런 상황을 제일 좋아한다.&lt;br /&gt;사실 일부러 그런 상황을 만들기 위해 목표를 높게 잡는 편이기도 하다.&lt;br /&gt;만족감을 잘 느끼는 편은 아니다 보니 참 피곤하게 산다싶은 것도 사실이지만, 욕심이 많아 대충하기는 싫고, 항상 잘 해내고 싶다. 그래서 좋아하나 보다.&lt;br /&gt;어차피 한 번 사는 인생, 열심히 잘 살아서 불타는 청춘을 보냈다며 나중에 자서전이나 한 권 써 봐야겠다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 일기</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/82</guid>
      <comments>https://deemmun.tistory.com/82#entry82comment</comments>
      <pubDate>Sat, 6 Jan 2024 17:52:20 +0900</pubDate>
    </item>
    <item>
      <title>[React/typescript] 재사용 가능한 동적 타입 리스트 컴포넌트 만들기 (generic type/ render props pattern)</title>
      <link>https://deemmun.tistory.com/81</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;nick-fewings-8MGUft1c5bs-unsplash.jpg&quot; data-origin-width=&quot;3032&quot; data-origin-height=&quot;2021&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etrUTU/btsHCRBchlM/rrC3yz6mDEsFryblcLmun0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etrUTU/btsHCRBchlM/rrC3yz6mDEsFryblcLmun0/img.jpg&quot; data-alt=&quot;Photo by Nick Fewings on Unsplash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etrUTU/btsHCRBchlM/rrC3yz6mDEsFryblcLmun0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetrUTU%2FbtsHCRBchlM%2FrrC3yz6mDEsFryblcLmun0%2Fimg.jpg&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;3032&quot; height=&quot;2021&quot; data-filename=&quot;nick-fewings-8MGUft1c5bs-unsplash.jpg&quot; data-origin-width=&quot;3032&quot; data-origin-height=&quot;2021&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Photo by Nick Fewings on Unsplash&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;담당하고 있는 서비스가 만들어진지 곧 1년이 다 되어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MAU 5천만의 업계 최고 플랫폼을 만들어 내고야 말겠다는 꿈을 꾸며 항상 코드를 작성할 때 먼 미래를 생각해 작성했던 것 같은데 각오가 무색하게 3개월만 지나도 '왜 이렇게 했지'라는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그땐 최선이라 생각했던 것들이 지금 와서는 아쉬운 게 투성이인지라 개발 범위가 겹치는 것이 있다면 본 일정에 무리 없는 선에서, 또는 개선해야만 하는 것들은 공유하며 마감 기한을 늘려보는 식으로 거슬리는 것들을 조금이라도 개선해나가며 작업하는 습관을 가지게 된 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1140465552507612&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘엔 리액트 디자인 패턴을 공부하며 체화 시키기 위해 노력 중인데 신기한 게 공부하다 보면 '어 이거 DynamicInput 컴포넌트에 쓰면 딱이겠다', '이거 온보딩 컴포넌트에 쓰면 너무 좋겠다' 이런 부분이 딱 떠오르는 게 재밌는 것 같습니다.&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;프로젝트 사이즈가 많이 커진 상태라 이런 저런 고민이 많은 상태인데, 오늘은 render props 컴포넌트를 구현하며 배열 렌더링을 위한 컴포넌트 재사용성을 좀 더 확장 시킨 과정에 대해 작성해보려고 합니다.&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;1&lt;/p&gt;
&lt;pre id=&quot;code_1704006789128&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        &amp;lt;div className='overflow-y-scroll w-full pt-16pxr'&amp;gt;
          {resultPois.map((poi, index) =&amp;gt; (
            &amp;lt;SearchResultItem
              key={poi.id}
              onClick={() =&amp;gt; {
                setDeparturePoint(poi);
              }}
              text={poi.name}
              subText={poi.address}
            /&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2&lt;/p&gt;
&lt;pre id=&quot;code_1704006851936&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; &amp;lt;&amp;gt;
    &amp;lt;ResultTitle text='찜한 장소' /&amp;gt;
     {savedPois.map((poi, index) =&amp;gt; (
       &amp;lt;SearchResultItem
          key={`savedPoi_${index}`}
          text={poi.name}
          subText={poi.address}
          leftIconType='heart'
          onClick={() =&amp;gt; onClickPoi(poi)}
       /&amp;gt;
	))}
 &amp;lt;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 코드 모두 배열을 맵핑하여 SearchResultItem을 반환하는 JSX코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Item을 묶는 리스트를 하나 만들어서 가둬 놓자니 순회한 배열의 요소가 갖는 타입이 모두 상이했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SearchResultItem에 전달해야 하는 props값도 페이지마다, 데이터마다 상이하여 섣불리 묶었다간 결합도만 높아지겠다 싶어 List를 만들지 않고 SearchResultItem을 필요할 때 사용하도록 구현했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 큰 문제는 한 가지라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. list에 무엇이 들어올지 모르는데 어떻게 수정할 수 있지? : typescript의 제네릭 타입과 renderProps로 확장성을 높일 수 있겠다 생각했습니다.&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;SearchResult.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1704007373745&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...

interface SearchResultListProps&amp;lt;T&amp;gt; {
  list: T[];
  render: (item: T, index: number) =&amp;gt; JSX.Element;
}

const SearchResultList = &amp;lt;T,&amp;gt;({list, render}: SearchResultListProps&amp;lt;T&amp;gt;) =&amp;gt; {
  return list.map((item, index) =&amp;gt; render(item, index));
};

SearchResult.List = SearchResultList;

export default SearchResult&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SearchResultList를 구현하고, typescript 제네릭을 통해 list에 들어오는 배열의 요소 타입을 갖는 props 인터페이스를 선언하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 타입스크립트는 자동으로 render함수의 첫 번째 인지는 list 요소의 타입인 것으로 정의되어 개발 편의성을 보장하며, render props패턴의 컴포넌트를 사용할 수 있게 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 사용하면 아래와 같이 수정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-31 오후 4.26.29.png&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAOwup/btsCMq2Pk4Y/c4tP62jAYcxcAfA6Mo8if0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAOwup/btsCMq2Pk4Y/c4tP62jAYcxcAfA6Mo8if0/img.png&quot; data-alt=&quot;수정 부분 (SearchResultItem의 리팩토링은 별도로 이루어졌습니다)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAOwup/btsCMq2Pk4Y/c4tP62jAYcxcAfA6Mo8if0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAOwup%2FbtsCMq2Pk4Y%2Fc4tP62jAYcxcAfA6Mo8if0%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;548&quot; height=&quot;448&quot; data-filename=&quot;스크린샷 2023-12-31 오후 4.26.29.png&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수정 부분 (SearchResultItem의 리팩토링은 별도로 이루어졌습니다)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SearchResult.List로 SearchResult.tsx에 정의한 리스트 컴포넌트를 불러오고, list prop에 배열을 넣은 뒤 render prop을 통해 렌더링부분을 정의해줍니다.&amp;nbsp;&lt;br /&gt;기존 map으로 사용되던 콜백 함수 부분을 그대로 모델링 하였다고 생각하면 이해가 빠를 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(SearchResultItem 컴포넌트 또한 새로이 리팩토링되어 list와 render prop부분만 봐주시면 좋을 듯 합니다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-12-31 오후 4.26.20.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VzY9W/btsCReUyv4g/QGMLOTkYsSM9TGHGgfVNOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VzY9W/btsCReUyv4g/QGMLOTkYsSM9TGHGgfVNOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VzY9W/btsCReUyv4g/QGMLOTkYsSM9TGHGgfVNOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVzY9W%2FbtsCReUyv4g%2FQGMLOTkYsSM9TGHGgfVNOk%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;544&quot; height=&quot;355&quot; data-filename=&quot;edited_스크린샷 2023-12-31 오후 4.26.20.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;render prop의 첫 번째 인자의 타입을 확인해보면 togetherZones 요소의 타입이 그대로 정의되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 자유롭게 list를 통해 렌더링되던 SearchResult 관련 코드의 상당 부분을 수정할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음엔 다른 디자인 패턴으로 찾아오겠습니다&lt;/p&gt;</description>
      <category>Code/TypeScript</category>
      <category>compound</category>
      <category>design-pattern</category>
      <category>Generic</category>
      <category>React</category>
      <category>Render Props</category>
      <category>Typescript</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/81</guid>
      <comments>https://deemmun.tistory.com/81#entry81comment</comments>
      <pubDate>Sun, 31 Dec 2023 16:35:41 +0900</pubDate>
    </item>
    <item>
      <title>[React - 그래서] key prop은 렌더링에 어떻게 쓰이는가 (Fiber / 재조정 reconciliation)</title>
      <link>https://deemmun.tistory.com/80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Fiber.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjGMKU/btsB7k8RBka/cLf5k9GMgUXZfNekGQhXpk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjGMKU/btsB7k8RBka/cLf5k9GMgUXZfNekGQhXpk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjGMKU/btsB7k8RBka/cLf5k9GMgUXZfNekGQhXpk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjGMKU%2FbtsB7k8RBka%2FcLf5k9GMgUXZfNekGQhXpk%2Fimg.webp&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;1024&quot; height=&quot;536&quot; data-filename=&quot;Fiber.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;`${stack} - 그래서` 카테고리 탄생 배경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 카테고리를 개설했습니다.&lt;br /&gt;기술 공부를 할 때 그래서..? 이게 뭐..?라는 느낌이 들며 지식 간 연결이 끊어져있는 느낌이 들 때가 잦습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때 다른 분들은 '왜?'를 떠올리던데 저는 대부분 그것보다 이전 단계에서 이해를 못하는 편이라 '그래서? 이게 무슨 상관인데?'로 인덱싱 문제를 해결하곤 합니다. why? 보다 then.....what? 느낌이랄까요&lt;br /&gt;&lt;br /&gt;노력형 개발자가 저뿐만은 아니겠죠, 계속 성장 하고 있는 걸 보면 사실 큰 문제는 아닌 것 같습니다.&amp;nbsp;&lt;br /&gt;하나씩 모으다 보면 어느 순간에 뭔가 연결되는 느낌이 드는데 그때 적지않은 도파민이 나오기에 그 쾌감이 퍽 재밌기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;개인적으로는 새로운 개념을 받아들일 때 얕게 우선 정의부터 대략적으로 받아들이고 시간을 갖고 조금씩 깊게 들어가야 이해하는 편이라 이번 글도 그러한 방식으로 편하게 작성해볼까 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1140465552507612&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;아마 알고있을&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;React key prop&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;- 배열을 통해 JSX 요소를 렌더링할 때 불필요한 리렌더링 방지를 위해 주로 사용된다.&lt;br /&gt;- key 프롭을 설정하지 않으면, &amp;lt;Component key={index} /&amp;gt; 와 같이 index를 사용한 것처럼 작동되며 이건 최악이다!&lt;br /&gt;&amp;nbsp; :&amp;nbsp; 근데 Index가 괜찮은 경우가 있기도 하다?&lt;br /&gt;- key prop을 안 넣으면 콘솔에 에러가 뜬다.&lt;br /&gt;- key prop에는 유니크한 id를 넣어야 한다. 하지만 그렇다고 Math.random() 같은 걸 넣으면 안된다.&lt;br /&gt;&lt;br /&gt;react의 key prop에 대해 찾아본 적이 있다면 아마 얕게라도 위의 내용에 대해 인지하고 있을 것 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;여기서 베이스를 조금씩 확장시켜 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 컴포넌트는 props와 state가 변경되면 리렌더링됩니다.&lt;br /&gt;&lt;br /&gt;virtual dom을 통해 마지막으로 렌더링 된 vdom(current Tree)와 props와 state가 변경되어 바뀔 vdom (Work In Progress Tree)을 비교하고, 변경된 부분만 실제 리얼 dom을 업데이트하여 수정 후 렌더링합니다. (vdom은 js 객체 형태로 메모리에 저장)&lt;br /&gt;&lt;br /&gt;이러한 과정을 리액트에선 재조정, reconciliation이라 합니다.&lt;br /&gt;&lt;br /&gt;두 virtual dom 트리를 비교하는 데에는 리액트에서 구현한 diffing 알고리즘을 통해 진행됩니다.&lt;br /&gt;&lt;br /&gt;diffing 알고리즘에선 조정자를 사용해 현재 트리와 진행 중인 작업 트리간의 차이점을 찾아 계산된 변경 사항을 렌더러에 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;조정자&lt;/u&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React 16 이전 : Stack Reconciler&lt;/li&gt;
&lt;li&gt;React 16 이후 : Fiber Reconciler&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 비교 알고리즘에 사용되는 모델이 16이전에는 stack, 이후에는 Fiber로 변경되었습니다.&lt;br /&gt;Stack Reconciler의 경우 렌더링을 진행할 때 재귀 호출 방식 + 모두 동기적인 방식으로 작동되었습니다.&lt;br /&gt;&lt;br /&gt;요즘 장치는 대부분 60FPS로 환경을 재생합니다. 계산해보면&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot; data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;16ms마다 새 프레임이 나타나야하는 것인데&lt;br /&gt;Stack Reconciler의 경우 모든 업데이트에 대해 재귀 호출 방식으로 모든 virtual dom 노드에 대해 탐색한 뒤 렌더링까지 진행되기에 UI의 덜커덕거림이나 뒤틀림이 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;u&gt;그래서..진짜 Fiber Reconciler여야 했는가?&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fiber는 동시성 렌더링을 위해 좀 더 효율적인 방법으로 실제 dom에 React element를 반영하기 위해 도입되었습니다.&lt;br /&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;&lt;br /&gt;&lt;b&gt;우선순위 선정 / 재사용&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;저는 크게 두 가지 키워드로 Fiber의 역할을 이해할 수 있었습니다.&lt;br /&gt;Fiber 이전까지는 재귀 호출 방식으로 탐색을 해내가야 했고, render phase -&amp;gt; commit phase가 진행되는 도중에 작업을 중지할 수 없었습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;키워드를 조금 더 해석해보자면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;1. 우선순위 선정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;- 컴포넌트를 렌더링하고 있는데 유저의 클릭 이벤트가 발생되었다. 유저는 전자보다 후자의 응답이 빠르게 올 것을 기대할 것이다.&lt;br /&gt;&lt;/span&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;&amp;nbsp; &amp;nbsp;: 더 우선 순위가 높은 작업이 있다면, 작업을 잠시 멈추고 나중에 다시 작업한다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;동시성 참고 링크 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://goidle.github.io/react/in-depth-react18-concurrent_render/&quot;&gt;https://goidle.github.io/react/in-depth-react18-concurrent_render/&lt;/a&gt;&lt;/blockquote&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;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;&lt;b&gt;2. 재사용&lt;/b&gt;&lt;br /&gt;-  재활용 할 수 있는 걸 굳이 다시 만들지 말자.&lt;br /&gt;&amp;nbsp; : 불필요한 virtual dom 탐색 / 추가 / 삭제를 줄일 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;그래서.. Fiber는 대체 뭔데?&lt;/span&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fiber는 하나의 작업 단위이자, React element와 대응하는 인스턴스&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;우선 하나의 작업 단위라는 뜻은 '&lt;b&gt;우선순위&lt;/b&gt;'와 함께 생각하면 좋을 것 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 한 번에 한 가지 일만 실행할 수 있는 싱글스레드 언어입니다.&lt;br /&gt;&lt;br /&gt;자바스크립트 코드를 실행시키면 유효 범위를 갖는 코드들에 대해 실행 컨텍스트를 생성하고, 이를 콜 스택에 푸시하여 작업을 진행합니다.&lt;br /&gt;Promise, 비동기 요청, 이벤트 처리 등은 콜스택이 아닌 마이크로 태스크큐, 태스크 큐에 보관됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜스택이 비어 있어야만 브라우저의 이벤트 루프가 이를 감지하여 큐에 보관되어 있는 작업을 콜스택으로 푸시해주며, 싱글스레드 언어인 자바스크립트를 보완해주고 있습니다.&lt;br /&gt;&lt;br /&gt;Fiber는 이 콜스택을 재구현한 것입니다.&lt;br /&gt;더 높은 우선순위의 작업이 들어왔다면 작업을 멈추고 (포인터로 기억) 다른 작업을 먼저 할 수 있도록 콜스택을 조정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;그럼 React element와 대응하는 인스턴스&lt;/b&gt;라는 말에 대해 조금 더 설명해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React element의 확장 -&amp;gt; Fiber 노드&lt;br /&gt;리액트는 재조정 과정에 앞서 render 과정을 먼저 진행합니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;리액트 재조정에서의 render 의미는 dom이 페인트 되는 것을 뜻하는 것이 아닌 '준비'정도로 받아들이면 쉬울 것 같습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;재조정 - Render 단계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에선 JSX를 바벨을 통해 React element로 변환하는 작업만을 진행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1702804513828&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;React.createElement( 
  type, // 태그 이름 문자열 'div' | 개발자 정의 컴포넌트함수 Custom .. | 리액트 호스트컴포넌트 Suspense..
  [props], // 컴포넌트 프롭스
  [...children] 
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 React.createElement()를 호출하는 js코드로 변환합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;재조정 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: left;&quot;&gt;Reconcile 단계&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Reconcile 단계에서는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React element를&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Fiber 노드로 변환&lt;/b&gt;하는 작업을 진행하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실제 Dom 트리와 새 React 엘리먼트를 비교하여 변경점을 적용&lt;/b&gt;합니다.&lt;br /&gt;Fiber 노드에는 엘리먼트와 관련된 정보들을 포함하고 있으며, 재조정은 깊이 우선 탐색으로 작동됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;key를 설명하는 데에 가장 필요할 것으로 보이는 Fiber 구현체의 key를 몇 개 뽑아 보았습니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;- sibling : 다음 형제 Fiber&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- child : 자식 FIber&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- return : 부모 Fiber&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;각 노드들은 single linked list로 연결되어 있고, 객체로 존재하고 있는 &lt;b&gt;child&lt;/b&gt; 요소들에 대해서도 Fiber 노드로 변환하는 작업을 거칩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그럼 이제 간단한 예제 코드와 함께 진짜 key에 대해서 파악을 해보겠습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;첫 마운트가 끝나, React element들에 대한 Fiber노드가 존재하는 상황이라 가정&lt;/blockquote&gt;
&lt;pre id=&quot;code_1702804513829&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const Test = () =&amp;gt; {
return (
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;안&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;녕&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;하&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;쇼&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Fiber (객체 일부)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- type : 태그&amp;nbsp;이름&amp;nbsp;문자열&amp;nbsp;'div'&amp;nbsp;|&amp;nbsp;개발자&amp;nbsp;정의&amp;nbsp;컴포넌트함수&amp;nbsp;Custom&amp;nbsp;..&amp;nbsp;|&amp;nbsp;리액트&amp;nbsp;호스트컴포넌트&amp;nbsp;Suspense..&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- key : element의 key 프롭&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- sibling : 다음 형제 Fiber&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- &lt;b&gt;child&lt;/b&gt; : 자식 FIber&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- return : 부모 Fiber&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702804513829&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;Fiber 해체

ul
// -&amp;gt; type : 'ul' / 형제 sibling : null / return 부모 Fiber / child : li Fiber (첫 번째 자식 요소 -&amp;gt; &amp;lt;li&amp;gt;안&amp;lt;/li))

li ('안')
// -&amp;gt; type : 'li' / sibling : li Fiber (&amp;lt;li&amp;gt;녕&amp;lt;/&amp;gt;) / return ulFiber / child null

li ('녕')
// -&amp;gt; type : 'li' / sibling : liFiber (&amp;lt;li&amp;gt;하&amp;lt;/&amp;gt;) / return ulFiber / child null

...


li ('쇼')
// -&amp;gt; type : 'li' / sibling : null / return ulFiber / child null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;예시코드는 현재 key에 대해 지정을 하지 않아, 이 경우 fiber의 key는 null을 갖지만 리액트 내부에서 이를 인덱스로 가정하여 처리합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;리액트&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Fiber는 재사용할 수 있는 것은 최대한 재사용&lt;/b&gt;할 수 있도록 구현되어 있습니다.&lt;br /&gt;이 포인트에서 key의 존재 목적을 다시 생각할 수 있습니다.&lt;br /&gt;&lt;br /&gt;Fiber 노드에서는 부모의 자식 관계를 sibling을 통해 효율적으로 관리합니다.&lt;br /&gt;상태나 props의 변경으로 인해 업데이트가 진행되는 단계에서 Reconciler는 Fiber를 재사용할지, 교체 삭제 또는 생성을 할지 판단하는데 이때 엘리먼트의&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;type이나 key가 이전과 다르다면&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;해당 Fiber에 대한  작업을 다시 진행하게 됩니다.&lt;br /&gt;&lt;/span&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;&lt;br /&gt;React에서는 key prop에 유니크한 값을 넣도록 강조하고 있는데 그 이유를 여기서 찾을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;- 배열의 index를 사용하지 마라&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 배열의 마지막에 요소가 추가되는 것이라면 큰 문제가 없을 수도 있습니다. 하지만 같은 type을 갖고 Fiber에서 index를 키로 갖고 있는 상태일 경우, 배열 사이사이 요소가 재정렬 되는 로직이 추가된다면 예기치 못한 문제가 발생할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;- key에 유니크한 값을 지정해야 한다. 그렇다고 Math.random()등을 사용해선 안된다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMjE2bXMlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU1RCUyQyUyMnNlbGVjdGlvbnMlMjIlM0ElNUIlNUQlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI4ZTNjZGEyNGZjOGE0ZGM1YjFlZWExZDBhMjM5MTMwNyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjIxOTdlZDBiZWFmZTRhNjU4M2EzNmU4OTlkNmMxYTcwJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDg4NGZmYjJlODU3NDExOTk1Y2ZmZjFiZWE0NTJmMzQlMjIlN0Q=&quot;&gt;key의 존재 목적은 '효율성'에 있기에 변경이 필요한 부분에 대해서만 작업하기 위함이며,&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key는 부모의 자식 요소간 관계도를 나타낸다는 것을 알 수 있습니다.&lt;br /&gt;렌더 부분에 Math.random()을 사용한다면 렌더링 될 때마다 key가 변경될테니 효용이 없을 것입니다.&lt;br /&gt;- (key가 다르다면 새로운 Fiber노드를 생성하니 하위 노드도 모두 다시 생성하게 됩니다)&lt;br /&gt;형제 요소 간 동일한 key를 사용하는 것 또한 목적과 동작을 생각해 보았을 때 key를 제대로 활용하지 못하게 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;서두에 정의했던 일반적인 key prop 내용에 대해 다시 한 번 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열을 통해 JSX 요소를 렌더링할 때 불필요한 리렌더링 방지를 위해 주로 사용된다.&lt;br /&gt;- key 프롭을 설정하지 않으면, key에 index를 사용한 것처럼 작동되며 이건 최악이다!&lt;br /&gt;&amp;nbsp; :&amp;nbsp; 근데 Index가 괜찮은 경우가 있기도 하다?&lt;br /&gt;- key prop을 안 넣으면 콘솔에 에러가 뜬다.&lt;br /&gt;- key prop에는 유니크한 id를 넣어야 한다. 하지만 그렇다고 Math.random() 같은 걸 넣으면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&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;그로인해 단순히 unique한 값만 부여하는 것이 아닌 세부 값을 추가해주어야 할 때가 있습니다.&lt;br /&gt;배열 데이터에서 분명 데이터 자체의 id를 사용해 key를 설정했으나 하위 컴포넌트의 일부가 이전 값으로 보이는 경우가 종종 있는데 이 경우는 변경을 시도했으나 이전 key와 현재 key가 같고 형제 요소 간 Fiber타입이 같을 때 발생할 수 있어 key에 보다 명확한 유니크 값을 설정할 필요가 있습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;이런 문제를 방지하기 위해 고유 id 값으로만 판별이 어렵다 판단되는 아이템에 대해서는 id는 고유하되 변경될 수 있고 렌더링에 반영되어야 하는 특정 값에 대해서도 추가로 결합해주어 사용하고 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글이 key prop의&lt;span&gt;&amp;nbsp;&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;글을 마무리 지으려다가 간만에 주니어 개발자의 이런 저런 사견을 조금 적어볼까 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘에는  기술에 대해 깊게 파고들기 위해 노력하고 있는데 여간 어려운 게 아닙니다.&lt;br /&gt;와!!! 하다가도 음.....하고, 아하! 하다가도 엄...? 하고, 왜 이걸 지금 알았지 성찰도 하고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;더닝크루거.jpeg&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn8LzA/btsB7c3VvY8/hej1vmnkoAGur9zXkKRiM1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn8LzA/btsB7c3VvY8/hej1vmnkoAGur9zXkKRiM1/img.webp&quot; data-alt=&quot;더닝 크루거 효과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn8LzA/btsB7c3VvY8/hej1vmnkoAGur9zXkKRiM1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn8LzA%2FbtsB7c3VvY8%2Fhej1vmnkoAGur9zXkKRiM1%2Fimg.webp&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;480&quot; height=&quot;378&quot; data-filename=&quot;더닝크루거.jpeg&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;더닝 크루거 효과&lt;/figcaption&gt;
&lt;/figure&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;어느덧 경력 만 3년이 되기까지 한 달이란 시간이 남아있네요.&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;&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;p data-ke-size=&quot;size16&quot;&gt;구현 능력, 문제 해결 능력을 키우는 데에 많은 도움이 되었기에 후회한다는 말은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 방식을 바꾸어 개인적으로 좀 더 넓은 범위의 공부를 하다보니 시야가 더 빨리 확장되며 실무 능력 또한 같이 상승하고 있다는 느낌을 받고 있는 것 같습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 들자면 https를 공부하다가 암호화를 알게 되고, 또 대칭키 비대칭키를 이해하려고 머리 쥐어 뜯으며 그림 그려가며 이해하고 그러다가 실무에서 SSO 기능 구현을 위해  url 서치 파라미터로 token등과 같은 정보를 주고 받으며 통신을 해야 했는데 공부를 하고 나니 token을 생으로 전달하는 건 안되겠다 싶어 암복호화 플로우를 설계해서 공유하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 플로우 설계를 하면서 비대칭키 rsa로 jwt 전달하려니 바이트 초과로 사용을 못해서 res 대칭키 방식과 혼합해서 사용하는 방식으로도 생각을 해보고..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 암 복호화는 프론트가 아닌 서버 api를 통해 해결하긴 했지만, 만약 https 공부를 따로 안 했다면 아이디어를 떠올리지도 못했을텐데 하는 생각과 함께 보다 넓은 범위의 개인 공부에 대한 중요성을 크게 느낀 재밌는 경험이지않았나&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;추상화를 어디까지 해야 선언적인 코드인가를 고민하다가 의견을 찾아보니 기존 컨벤션에 맞추면 된다는데 그 컨벤션을 맞춘 사람이 내 자신이라면......? 커스텀 훅으로 묶을 때도 co-locate 패턴도 좋은데 ui랑 분리하겠다고 비즈니스 로직을 묶는 게 진짜 괜찮은가?에 대한 뭔가 근본적인 물음을 계속 던지고 있는 것 같습니다.&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;/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;</description>
      <category>Code/그래서</category>
      <category>Fiber</category>
      <category>Frontend</category>
      <category>javascript</category>
      <category>React</category>
      <category>reconciler</category>
      <category>reconilitation</category>
      <category>virtual DOM</category>
      <author>딤문</author>
      <guid isPermaLink="true">https://deemmun.tistory.com/80</guid>
      <comments>https://deemmun.tistory.com/80#entry80comment</comments>
      <pubDate>Sun, 17 Dec 2023 19:20:55 +0900</pubDate>
    </item>
  </channel>
</rss>