PageSourceSearch

https://www.kimcoder.io/blog

html kimcoder.io collected 2026-09-24 15:03:32 UTC 227,691 bytes, 16 lines download raw bytes

1<!DOCTYPE html><html lang="ko"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"/><meta name="google-site-verification" content="vtaBV0ETmVVgtWBi6a2TUBROjDjHCT5hILk7MxdF7B0"/><style>
2        @media (max-width: 390px) {
3            .formkit-slide-in {
4              display: none;
5            }
6          }
7          @media (max-height: 740px) {
8            .formkit-slide-in {
9              display: none;
10            }
11          }
12          </style><link rel="preconnect" href="https://KB3ECWAG70-dsn.algolia.net" crossorigin="true"/><title>Blog | kimcoder</title><meta name="description" content="흘러가는 지식, 학습의 과정, 경험 정리를 위주로 작성되는 글들입니다."/><link rel="icon" type="image/x-icon" href="/favicon.png"/><link rel="apple-touch-icon" href="/favicon.png"/><meta property="og:type" content="website"/><meta property="og:url" content="https://kimcoder.io"/><meta property="og:title" content="Blog"/><meta property="og:description" content="흘러가는 지식, 학습의 과정, 경험 정리를 위주로 작성되는 글들입니다."/><meta property="og:image" content="/kimcoder-og.png"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:site" content="@kimcoder"/><meta name="twitter:creator" content="@kimcoder"/><meta name="twitter:title" content="Blog"/><meta name="twitter:description" content="흘러가는 지식, 학습의 과정, 경험 정리를 위주로 작성되는 글들입니다."/><meta name="twitter:image" content="/kimcoder-og.png"/><meta name="next-head-count" content="21"/>
12<script>
vendor: 327 bytes, lines 12-16
12(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
13new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
14j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
15'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
16})(window,document,'script','dataLayer','
16GTM-MKLQHRB
vendor: 3 bytes, line 16
16');
16</script>
16<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /><link rel="preload" href="/_next/static/css/5ca929a8400de023.css" as="style" crossorigin=""/><link rel="stylesheet" href="/_next/static/css/5ca929a8400de023.css" crossorigin="" data-n-g=""/><link rel="preload" href="/_next/static/css/d1b9f621a8797bcb.css" as="style" crossorigin=""/><link rel="stylesheet" href="/_next/static/css/d1b9f621a8797bcb.css" crossorigin="" data-n-p=""/><noscript data-n-css=""></noscript>
16<script defer="" crossorigin="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script>
16<script src="/_next/static/chunks/webpack-df1f9c351e43cde0.js" defer="" crossorigin=""></script>
16<script src="/_next/static/chunks/framework-fda0a023b274c574.js" defer="" crossorigin=""></script>
16<script src="/_next/static/chunks/main-c69e4a6afd49bbe5.js" defer="" crossorigin=""></script>
16<script src="/_next/static/chunks/pages/_app-37680da0538876f5.js" defer="" crossorigin=""></script>
16<script src="/_next/static/chunks/336-1f3c353cd534fe20.js" defer="" crossorigin=""></script>
16<script src="/_next/static/chunks/pages/blog-470c084b010d54b4.js" defer="" crossorigin=""></script>
16<script src="/_next/static/mYpFDr1DZDEyR2tzQItwY/_buildManifest.js" defer="" crossorigin=""></script>
16<script src="/_next/static/mYpFDr1DZDEyR2tzQItwY/_ssgManifest.js" defer="" crossorigin=""></script>
16<style id="__jsx-6e762080cf24aab">div.jsx-6e762080cf24aab{background:#fff;position:-webkit-sticky;position:sticky}div.shadow.jsx-6e762080cf24aab{-webkit-box-shadow:rgba(0,0,0,.06)0px 6px 20px;-moz-box-shadow:rgba(0,0,0,.06)0px 6px 20px;box-shadow:rgba(0,0,0,.06)0px 6px 20px}</style><style data-href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/l/font?kit=UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZs&skey=c491285d6722e4fa&v=v20) format('woff')}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/l/font?kit=UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuI6fMZs&skey=c491285d6722e4fa&v=v20) format('woff')}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/l/font?kit=UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuGKYMZs&skey=c491285d6722e4fa&v=v20) format('woff')}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/l/font?kit=UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuFuYMZs&skey=c491285d6722e4fa&v=v20) format('woff')}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7W0Q5nw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7W0Q5n-wU.woff2) format('woff2');
16unicode-range:U+1F00-1FFF}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7W0Q5nw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7W0Q5nw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;
16src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7W0Q5n-wU.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7W0Q5nw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style></head><body class="font-sans antialiased text-gray-900"><div id="__next"><div class="h-full min-h-full"><div style="top:0" class="jsx-6e762080cf24aab z-10"><div class="border-b border-gray-200 bg-white"><div class="container mx-auto"><div class="flex flex-wrap items-center"><div class="nav-height flex w-60 items-center"><a href="/"><h1 id="page-title">@kimcoder</h1></a></div><div class="flex-grow hidden lg:block ml-8"><div><button type="button" class="group py-2 px-3 border-gray-200 border hover:text-gray-600 hover:border-gray-300 transition duration-150 ease-in-out pointer flex items-center bg-gray-50 text-left w-full text-gray-500 rounded-lg text-sm align-middle"><svg width="1em" height="1em" class="mr-3 align-middle text-gray-600 flex-shrink-0 group-hover:text-gray-700" style="margin-bottom:2px" viewBox="0 0 20 20"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" stroke-width="2" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg>Search words<span class="ml-auto invisible lg:visible"><kbd class="border border-gray-300 mr-1 bg-gray-100 align-middle p-0 inline-flex justify-center items-center  text-xs text-center mr-0 rounded group-hover:border-gray-300 transition duration-150 ease-in-out " style="min-width:1.8em">⌘</kbd><kbd class="border border-gray-300 bg-gray-100 align-middle p-0 inline-flex justify-center items-center  text-xs text-center ml-auto mr-0 rounded group-hover:border-gray-300 transition duration-150 ease-in-out " style="min-width:1.8em">K</kbd></span></button></div></div><div class="flex-grow nav-height flex w-3/4 items-center justify-between space-x-4 md:w-auto md:justify-end md:space-x-8"><div class="flex space-x-4 text-sm md:space-x-8 md:text-base"><div><a class="font-medium leading-6" href="/blog">Blog</a></div><div><a class="font-medium leading-6" href="/books">Books</a></div></div><div class="flex"><a href="https://github.com/kimcoder" target="_blank" rel="noopener noreferrer"><span class="sr-only">GitHub</span><svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path></svg></a><a class="ml-2.5" href="https://www.facebook.com/kimcoder" target="_blank" rel="noopener noreferrer"><span class="sr-only">LinkedIn</span><svg width="24" height="24" viewBox="0 0 30 30" fill="#1877f2"><path d="M30 15.091C30 6.756 23.285 0 15 0S0 6.756 0 15.091C0 22.625 5.484 28.868 12.656 30V19.454H8.848V15.09h3.808v-3.324c0-3.782 2.239-5.872 5.666-5.872 1.64 0 3.358.295 3.358.295v3.714h-1.893c-1.863 0-2.443 1.164-2.443 2.358v2.83h4.16l-.665 4.362h-3.495V30C24.516 28.868 30 22.625 30 15.091z" fill="#1877f2"></path></svg></a><a class="ml-2.5" href="https://www.linkedin.com/in/daehyun-kim-b9a26716b/" target="_blank" rel="noopener noreferrer"><span class="sr-only">Facebook</span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#0073b1"><path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"></path></svg></a></div></div></div></div></div></div><div class="container mx-auto bg-white px-4 pt-16 pb-20 lg:pt-24 lg:pb-28"><div class="relative"><div><h1 class="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 lg:text-5xl ">Blog 📝</h1><div class="mt-3 sm:mt-4 lg:grid lg:items-center lg:gap-5"><p class="text-xl leading-7 text-gray-500">흘러가는 지식, 학습의 과정, 경험 정리를 위주로 작성되는 글들입니다.</p></div></div><div class="markdown_markdown__wTkaE">
16<div class="border-t mt-6 grid gap-16 border-gray-100 pt-10 lg:grid-cols-2 lg:gap-x-5 lg:gap-y-12"><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2025-10-1">2025.10.1</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/about-tanstack-start"><span class="cursor-pointer hover:underline">TanStack Start 알아보기</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">TanStack의 신규 풀 스택 프레임워크인 TanStack Start에 대해 알아봅니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/about-tanstack-start">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2024-02-04">2024.02.04</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/ts-pattern"><span class="cursor-pointer hover:underline">ts-pattern을 활용하여 해결했던 것</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">패턴 매칭 그리고 ts-pattern에 대해 알아보고, 이를 활용하여 문제들을 해결했던 사례들을 소개합니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/ts-pattern">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2022-07-01">2022.07.01</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/micro-frontend-module-federation"><span class="cursor-pointer hover:underline">Micro Frontend, 그리고 Webpack Module Federation</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">마이크로 프론트엔드에 관한 고찰과 웹팩 5의 Module Federation 일부 기능에 대해 알아보고, 적당한 예시 코드 작성을 통해 얻은 생각을 기록하는 글입니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/micro-frontend-module-federation">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2022-05-19">2022.05.19</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/clean-frontend-architecture"><span class="cursor-pointer hover:underline">클린한 프론트엔드 아키텍처를 향한 첫 걸음</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">변경에 유연한 프론트엔드 아키텍처를 구성하는 하나의 방법과 방법의 근거, 그리고 이를 제어해 줄 수 있는 ESLint의 규칙까지 소개하고자 합니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/clean-frontend-architecture">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2022-02-19">2022.02.19</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/javascript-data-types"><span class="cursor-pointer hover:underline">자바스크립트의 데이터 타입과 메모리</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">자바스크립트에서 사용할 수 있는 데이터 타입의 종류와 몇가지 특징들을 알아봅니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/javascript-data-types">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2022-02-07">2022.02.07</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/eslint-import-order"><span class="cursor-pointer hover:underline">eslint import rule 설정의 이점</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">import구문의 정렬 규칙 적용의 이점에 대해 소개합니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/eslint-import-order">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2022-01-21">2022.01.21</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/enum"><span class="cursor-pointer hover:underline">Enum에 대한 고찰</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">Typescript의 Enum에 대해 알아봅니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/enum">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2022-01-18">2022.01.18</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/javascript-pass-by-value-function-parameters"><span class="cursor-pointer hover:underline">JavaScript Pass By Value Function Parameters</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">Kent C. Dodds의 JavaScript Pass By Value Function Parameters 글을 번역하였습니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/javascript-pass-by-value-function-parameters">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2022-01-03">2022.01.03</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/interface-vs-type"><span class="cursor-pointer hover:underline">Interfaces vs Type Aliases</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">Typescript의 Interface와 Type Aliasing에 대해 알아봅니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/interface-vs-type">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2021-12-12">2021.12.12</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/image-optimize"><span class="cursor-pointer hover:underline">이미지 파일 최적화를 통한 웹 성능 향상</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">웹에서 자주 사용되어지는 이미지에 대해 알아보고, 최적화 원리와 방식에 대해 알아봅니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/image-optimize">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2018-10-11">2018.10.11</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/react-simple-image-slider-3"><span class="cursor-pointer hover:underline">React 컴포넌트 제작기 3 / 3</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/react-simple-image-slider-3">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2018-10-08">2018.10.08</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/react-simple-image-slider-2"><span class="cursor-pointer hover:underline">React 컴포넌트 제작기 2 / 3</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/react-simple-image-slider-2">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2018-09-21">2018.09.21</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/react-simple-image-slider-1"><span class="cursor-pointer hover:underline">React 컴포넌트 제작기 1 / 3</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/react-simple-image-slider-1">읽으러 가기<span aria-hidden="true">→</span></a></div></div><div class="space-y-2 pb-6"><div class="posted text-sm leading-5 text-gray-500"><time dateTime="2018-09-17">2018.09.17</time></div><h3 class="mt-2 text-2xl font-semibold leading-7 text-gray-900"><a class="block" href="/blog/tdz"><span class="cursor-pointer hover:underline">TDZ(Temporal Dead Zone)</span> </a></h3><div class="mt-3 leading-6 text-gray-500 "><div class="markdown_markdown__wTkaE text-gray-500">TDZ(Temporal Dead Zone)에 대해 알아봅니다.</div></div><div class="mt-3"><a class="transition text-base font-semibold leading-6 text-blue-600 duration-150 ease-in-out hover:text-blue-500" href="/blog/tdz">읽으러 가기<span aria-hidden="true">→</span></a></div></div></div></div></div></div></div><!--$--><!--/$--></div>
16<script id="__NEXT_DATA__" type="application/json" crossorigin="">{"props":{"pageProps":{"posts":[{"content":"\n최근에 회사에서 공유했던 TanStack Start에 대한 내용을 가지고 블로그에도 글을 써보고자 한다.\n\n클라이언트 사이드 렌더링을 주로 활용했었던 과거와 달리, 많은 회사에서 풀 스택 프레임워크를 도입하여 페이지 렌더링을 다양한 방식으로 처리하여 서비스들을 운영하고 있다.\n\n내가 경험했던 회사와 서비스들 그리고 현재에도 운영하고 있는 서비스들은 Next.js를 주로 활용하고 있지만, TanStack에서 새롭게 발표한 TanStack Start라는 풀 스택 프레임워크가 흥미로워서 간단히 살펴보았다.\n\n## TanStack Start?\n\nTanStack Start는 TanStack Router를 만든 Tanner Linsley가 주도하는 새로운 풀스택 프레임워크이다.\n\n\u003cdiv className='my-4 block justify-start gap-4 sm:flex'\u003e\n  \u003cimg src='/assets/images/about-tanstack-start-1.jpg' className='w-full sm:w-1/3' /\u003e\n  \u003cdiv\u003e\n    \u003cblockquote className='text-lg font-bold mb-6 flex flex-col gap-4 italic'\u003e\n      \u003cp\u003eTanStack Start는\u003c/p\u003e\n      \u003cp\u003e95%의 TanStack Router와\u003c/p\u003e\n      \u003cp\u003e5%의 서버사이드 작업을 위한 작은 레이어로 이루어져있다.\u003c/p\u003e\n    \u003c/blockquote\u003e\n    \u003cp\u003e\n      Tanner Linsley는 TanStack Start에 대해{' '}\n      \u003ca className='custom-anchor youtube' href='https://www.youtube.com/watch?v=4PymccvinIo\u0026t=179s'\u003e\n        Next Gen Fullstack React with TanStack\n      \u003c/a\u003e{' '}\n      유투브 영상에서 위와 같이 간략하게 소개하였다.\n    \u003c/p\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n5%의 서버사이드 레이어는 **Vite**, **Nitro** 두 가지로 구성되었다고 소개를 하였다. 영상에서는 Vinxi라는 프레임워크도 같이 언급하였지만, Vinxi는 RC 버전에서 제거되었다.\n\n- **Vite** - 기본 개발 환경 구성 및 빌드, 개발 서버 제공 프레임워크\n- **Nitro** - 서버리스 우선(Serverless-first)적인 웹 애플리케이션 프레임워크 ( Nuxt3의 핵심 엔진 )\n\n따라서, TanStack Start를 사용한다면 TanStack Router에서 제공하는 기능 외에 추가적으로 아래와 같은 기능들을 이용할 수 있다.\n\n- **전체 문서 SSR 및 하이드레이션**\n- **스트리밍 SSR**\n- **서버 함수 / RPC**\n- **지원되는 다양한 런타임 ( Node, Bun, Deno, Edge )**\n\n공식 문서에서도 Tanstack Router로 해결할 수 있는 부분 외에 위에 나열된 기능들이 필요한 경우 사용을 권장하고 있다.\n\nReact Server Components(이하 RSC) 같은 경우는 현재 지원하지 않고 있으며, 곧 지원할 예정이라고 밝힌 상태이다.\n\n## TanStack Router?\n\nTanStack Start를 사용하려면 먼저 TanStack Router에 대해 알아두면 매우 쉽게 접근할 수 있는데, 일반적으론 CSR 프레임워크로 분류되어 아래와 같은 기능들을 이용할 수 있다.\n\n- **모든 라우팅 관련 요소(경로, 파라미터, 검색 매개변수 등)들에 대해 완벽한 타입스크립트 지원을 통해 타입 안전성 제공**\n\n- **파일 기반 라우팅 시스템과 중첩, 레이아웃 구조를 통한 유연한 라우팅 설계**\n\n- **내장된 데이터 로더 \u0026 캐싱**\n\n- **라우트 매칭/로깅 미들웨어 제공**\n\nReact Router를 사용해 본 경험이 있다면 어렵지 않게 사용할 수 있는 기능들이고, 훌륭한 타입스크립트 지원 덕분에 좋은 DX까지 느낄 수 있기도 하다.\n\n조금 더 자세히 알아보면 재미나게도 **TanStack Router도 실험적 기능을 사용하여 SSR을 할 수 있다는 점**이다.\n\n### TanStack Router의 SSR\n\n기능적으로는 전체 문서 SSR 및 하이드레이션, Streaming SSR이 지원되는데 CSR으로만 설정한 TanStack Router과는 다르게 아래와 같이 **서버, 클라이언트 엔트리 모듈이 필요하다.**\n\n```tsx title=\"src/entry-server.tsx\"\nimport { createRequestHandler, defaultStreamHandler } from '@tanstack/react-router/ssr/server';\nimport { createRouter } from './router';
16\n\nexport async function render({ request }: { request: Request }) {\n  const handler = createRequestHandler({ request, createRouter });\n\n  return await handler(defaultStreamHandler);\n}\n```\n\n```tsx title=\"src/entry-client.tsx\"\nimport { hydrateRoot } from 'react-dom/client';\nimport { RouterClient } from '@tanstack/react-router/ssr/client';\nimport { createRouter } from './router';\n\nconst router = createRouter();\n\nhydrateRoot(document, \u003cRouterClient router={router} /\u003e);\n```\n\n위와 같이 엔트리들을 구성한 후, 서버 런타임에서 아래와 같이 서버 모듈을 실행하여 SSR을 처리할 수 있다.\n\n```json title=\"package.json\"\n  ...\n  \"scripts\": {\n    \"serve\": \"NODE_ENV=production node server.js\",\n  }\n\n// server.js 내부에는 server, client 엔트리를 불러와서 SSR을 처리하는 코드가 들어간다.\n```\n\n더 자세한 예제는 [공식 문서의 예제](https://tanstack.com/router/latest/docs/framework/react/examples/basic-ssr-streaming-file-based)에서 확인할 수 있다.\n\n## TanStack Start와 Tanstack Router의 관계\n\n**내부적으로 @tanstack/router-core 모듈을 통해 SSR이 지원**되며, 이는 **TanStack Start에서도 동일하게 구성**되어 있는 상황이다.\n\nTanStack Router의 SSR 관련 주요 기능들, 그리고 TanStack Start과 얽혀있는 내용들을 정리하면 아래과 같이 볼 수 있다.\n\n| 시점          | 내용                                                                                                                                                                                                                                                           |\n| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 2023년        | @tanstack/router 초기 개발 시작.\u003cbr/\u003e핵심 경로 매칭, history 관리, loader 구조 설계에 집중. SSR은 아직 없음.                                                                                                                                                   |\n| 2024년 상반기 | **@tanstack/router-core v1.0 베타 단계에서 createServerRouter, dehydrate, hydrate API가 실험적으로 도입됨**\u003cbr/\u003e서버에서 라우터를 실행하고 loader를 프리패치하는 SSR 유틸리티가 추가됨.                                                                        |\n| 2024년 하반기 | **@tanstack/start 알파 공개.**\u003cbr/\u003e@tanstack/router-core SSR 유틸리티를 실제 Vite + React 환경에서 DX 좋게 래핑한 파일 기반 풀스택 프레임워크로 등장.                                                                                                          |\n| 2025년 7월    | @tanstack/router-core 의 SSR 기능은 여전히 experimental로 관리 중.\u003cbr/\u003e**@tanstack/start는** router-core SSR을 기본으로 사용하여 createServerRouter + Vite SSR + React Query hydration을 자동으로 처리.\u003cbr/\u003e다양한 실제 사용 피드백을 받아 **안정화 진행 중.** |\n| 2025년 9월    | **@tanstack/start RC 릴리즈.**                                                                                                                                                                                                                                 |\n\n추측컨데 TanStack Router를 제공할 당시부터 SSR 기능을 염두에 두고 설계가 진행되지 않았을까 생각된다.\n\nTanStack Start가 RC 버전으로 릴리즈된 지금도 여전히 @tanstack/router-core에 대한 의존성을 가지고 있는 상황이다.\n\n```mermaid\ngraph TD\n  Start[\"\u003cb\u003e@tanstack/start\u003c/b\u003e\"]\n\n  %% start -\u003e (direct deps)\n  Start --\u003e|\"Start의 Client/Server 엔트리(런타임 연결)\"| RSC[\"@tanstack/react-start-client\\n@tanstack/react-start-server\"]\n  Start --\u003e|라우터 공통 유틸/헬퍼 레이어| RU[\"@tanstack/router-utils\"]\n  Start --\u003e|\"React 라우터 통합(라우팅 API)\"| RR[\"\u003cb\u003e@tanstack/react-router\u003c/b\u003e\"]\n  Start --\u003e|Start 플러그인 시스템 코어| SPC[\"@tanstack/start-plugin-core\"]\n  Start --\u003e|\"클라/서버 코어 런타임(기반 모듈)\"| SCC[\"@tanstack/start-client-core\\n@tanstack/start-server-core\"]\n\n  %% each -\u003e router-core (shared foundation)\n  RSC --\u003e RC[\"\u003cb\u003e@tanstack/router-core\u003c/b\u003e\"]\n  RU  --\u003e RC\n  RR  --\u003e RC\n  SPC --\u003e RC\n  SCC --\u003e RC\n```\n\n## TanStack Start의 주요 기능들\n\nTanStack Start의 주요 기능들을 하나씩 살펴보자.\n\n- **전체 문서 SSR 및 하이드레이션**\n- **스트리밍 SSR**\n- **서버 함수 / RPC**\n- **지원되는 다양한 런타임 ( Node, Bu
16n, Deno, Edge )**\n\n### 전체 문서 SSR 및 하이드레이션\n\nTanStack Start는 **기본적으로 모든 라우트에 대해 SSR이 활성화**되어 있다.\n이는 루트 라우트에서 전역으로 설정을 할 수 있고, 하위 라우트 개별적으로 설정이 가능하다.\n\nSSR 동작은 서버로 요청이 들어오면 서버가 해당 라우트의 `beforeLoad`, `loader`를 실행하고, 컴포넌트를 HTML로 렌더링한 뒤 내려준다.\n그리고 브라우저에서는 `\u003cScripts /\u003e`를 통해 로드된 번들이 hydration을 수행하면서 \"이미 그려진 HTML\"을 \"동작하는 React 앱\"으로 바꾼다. ( 공식 문서에서는 이 기본 동작을 ssr: true로 설명)\n\n**TanStack Start가 전체 문서(full-document) SSR이라고 말하는 이유는, 루트 라우트에서 `\u003chtml\u003e`, `\u003chead\u003e`, `\u003cbody\u003e`까지 포함한 문서 골격을 직접 구성하도록 가이드하기 때문이다.**\n예를 들어 Start 기본 예제에서는 루트 라우트에서 shellComponent를 정의하고, 그 안에 `\u003cHeadContent /\u003e`와 `\u003cScripts /\u003e`를 배치한다.\n\n```tsx title=\"src/routes/__root.tsx\"\nimport { HeadContent, Scripts, Outlet, createRootRoute } from '@tanstack/react-router';
16\n\nexport const Route = createRootRoute({\n  shellComponent: RootDocument,\n});\n\nfunction RootDocument({ children }: { children: React.ReactNode }) {\n  return (\n    \u003chtml\u003e\n      \u003chead\u003e\n        \u003cHeadContent /\u003e\n      \u003c/head\u003e\n      \u003cbody\u003e\n        {children}\n        \u003cScripts /\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  );\n}\n```\n\n```tsx title=\"src/routes/demo/start/ssr/full-ssr.tsx ( 하위 라우트의 전체 문서 SSR 예제 )\" {4-6}\nimport { createFileRoute } from '@tanstack/react-router';\nimport { createServerFn } from '@tanstack/react-start';\n\nexport const getPunkSongs = createServerFn({\n  method: 'GET',\n}).handler(async () =\u003e [\n  { id: 1, name: 'Teenage Dirtbag', artist: 'Wheatus' },\n  { id: 2, name: 'Smells Like Teen Spirit', artist: 'Nirvana' },\n  { id: 3, name: 'The Middle', artist: 'Jimmy Eat World' },\n  { id: 4, name: 'My Own Worst Enemy', artist: 'Lit' },\n  { id: 5, name: 'Fat Lip', artist: 'Sum 41' },\n  { id: 6, name: 'All the Small Things', artist: 'blink-182' },\n  { id: 7, name: 'Beverly Hills', artist: 'Weezer' },\n]);\n\nexport const Route = createFileRoute('/demo/start/ssr/full-ssr')({\n  component: RouteComponent,\n  loader: async () =\u003e await getPunkSongs(), // 서버에서 데이터 페칭\n});\n// loader는 \"초기 요청 때는 서버에서\", \"이후 클라이언트 내비게이션에서는 브라우저에서\"도 실행될 수 있다.\n// 그래서 process.env 같은 값을 loader 안에서 바로 읽으면, 클라이언트 번들에 섞이거나(혹은 런타임에서 터지거나) 문제가 된다.\n// 이런 경우에는 서버 함수(createServerFn)나 서버 전용 유틸(createServerOnlyFn)로 경계를 명확히 나누는 게 핵심이다.\n\nfunction RouteComponent() {\n  const punkSongs = Route.useLoaderData();\n\n  return (\n    \u003cdiv\n      className='flex items-center justify-center min-h-screen bg-gradient-to-br from-zinc-800 to-black p-4 text-white'\n      style={{\n        backgroundImage: 'radial-gradient(50% 50% at 20% 60%, #1a1a1a 0%, #0a0a0a 50%, #000000 100%)',\n      }}\u003e\n      \u003cdiv className='w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10'\u003e\n        \u003ch1 className='text-3xl font-bold mb-6 text-purple-400'\u003eFull SSR - Punk Songs\u003c/h1\u003e\n        \u003cul className='space-y-3'\u003e\n          {punkSongs.map((song) =\u003e (\n            \u003cli key={song.id} className='bg-white/10 border border-white/20 rounded-lg p-4 backdrop-blur-sm shadow-md'\u003e\n              \u003cspan className='text-lg text-white font-medium'\u003e{song.name}\u003c/span\u003e\n              \u003cspan className='text-white/60'\u003e - {song.artist}\u003c/span\u003e\n            \u003c/li\u003e\n          ))}\n        \u003c/ul\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n**앞서 언급한대로 라우트 단위로 SSR 동작을 조절할 수도 있다.** ( **Selective SSR** )\n\n기본은 `ssr: true`지만, 라우트에 `ssr: false`(완전 CSR) 또는 `ssr: 'data-only'`(데이터는 서버에서 준비, UI는 클라이언트에서 렌더)처럼 모드를 줄 수 있다.\n\n### 스트리밍 SSR\n\n스트리밍 SSR(Streaming Server-Side Rendering)은 **\"모든 데이터가 준비된 뒤에 한 번에 HTML을 내려주는 방식\"** 대신, **먼저 그릴 수 있는 UI를 즉시 내려주고** **나머지 UI는 준비되는 순서대로 채워 넣는** 렌더링 전략이다.\n\n특히 아래와 같은 화면에서 효과가 크다.\n\n- **레이아웃/상단 핵심 콘텐츠/내비게이션**처럼 초기 가시성이 중요한 영역은 빠르게 노출하는 경우\n- **댓글/추천/통계·집계/외부 연동 데이터**처럼 지연될 수 있는 데이터는 준비되는 즉시 해당 영역만 이어서 렌더링하는 경우\n\n이 방식은 사용자 입장에서는 **빈 화면 대기 시간**을 줄여주고, 개발자 입장에서는 **UI를 Suspense boundary 단위로 의도적으로 쪼개서,** 느린 영역만 독립적으로 처리할 수 있게 해준다.\n\nloader에서는 **즉시 필요한 데이터는 `await` 해서 먼저 확정**하고, 오래 걸려도 되는 데이터는 **Promise를 그대로 반환하는 deferred 패턴**으로 미뤄둘 수 있다.\n\nTanStack Start는 이걸 **[TanStack Router의 Deferred Data Loading](https://tanstack.com/router/v1/docs/framework/react/guide/deferred-data-loading) + `\u003cAwait /\u003e`** 조합으로 손쉽게 구현할 수 있다.\n그리고 이 패턴은 **서버에서만**이 아니라, 이후 **클라이언트 내비게이션에서도 동일하게 동작**한다는 게 포인트다.\n\n```tsx title=\"src/routes/home.tsx\"\nimport { Await, createFileRoute } from '@tanstack/react-router';\nimport { createServerFn } from '@tanstack/react-start';\nimport * as React from 'react';\n\n// 빠른 데이터\nconst getHero = createServerFn().handler(async () =\u003e {\n  return { title: 'Hello Start', subtitle: 'TTFB는 빠르게, 완성도는 점진적으로' };\n});\n\n// 느린 데이터 (데모용)\nconst getFeed = createServerFn().handler(async () =\u003e {\n  await new Promise((r) =\u003e setTimeout(r, 1_500));\n  return Array.from({ length: 5 }).map((_, i) =\u003e ({\n    id: i + 1,\n    text: `feed item #${i + 1}`,\n  }));\n});\n\nexport const Route = createFileRoute('/home')({\n  ssr: true, // 기본값이긴 한데 명시해도 OK\n  loader: async () =\u003e {\n    return {\n      hero: await getHero(),\n      feed: getFeed(), // await 하지 않음 (deferred)\n    };\n  },\n  component: HomePage,\n});\n\nfunction HomePage() {\n  const data = Route.useLoaderData();\n\n  return (\n    \u003cmain style={{ padding: 24 }}\u003e\n      \u003ch1\u003e{data.hero.title}\u003c/h1\u003e\n      \u003cp\u003e{data.hero.subtitle}\u003c/p\u003e\n\n      \u003csection style={{ marginTop: 24 }}\u003e\n        \u003ch2\u003eFeed\u003c/h2\u003e\n\n        \u003cAwait\n          promise={data.feed}\n          fallback={\u003cdiv style={{ opacity: 0.6 }}\u003e피드 불러오는 중...\u003c/div\u003e}\n          children={(items) =\u003e (\n            \u003cul\u003e\n              {items.map((it) =\u003e (\n                \u003cli key={it.id}\u003e{it.text}\u003c/li\u003e\n              ))}\n            \u003c/ul\u003e\n          )}\n        /\u003e\n      \u003c/section\u003e\n    \u003c/main\u003e\n  );\n}\n```\n\n내부적으로 **TanStack Start의 스트리밍 SSR은 Web Streams 기반 렌더링을 우선**한다.\n\n`react-dom/server`에서 `renderToReadableStream`을 쓸 수 있는 환경이면 이 메서드를 호출하고, 환경이 안 맞으면 Node용 `renderToPipeableStream`으로 폴백하는 식이다.\n덕분에 **Node뿐 아니라 Bun/Deno/Edge 같은 런타임에서도 \"스트림으로 응답을 흘려보내는\" 큰 구조는 동일**하게 가져갈 수 있다. ( [내부 구현 코드](https://github.com/TanStack/router/blob/main/packages/react-router/src/ssr/renderRouterToStream.tsx) )\n\n결론적으로 Next.js의 스트리밍 SSR과 같은 결이다. 먼저 **shell에서 TTFB/초기 가시성을 확보**하고, 느린 섹션은 데이터가 준비되는 대로 **스트림에서 순차적으로 합류**하면서 화면이 점점 완성된다.\n\n이 구현을 손쉽게 처리해 주는 **`Deferred Data Loading`** + **`\u003cAwait /\u003e`** API를 통해, React 18이 제공하는 스트리밍 SSR + 
16Suspense를 잘 녹여 낸것으로 보인다.\n\n개인적으론 TanStack만의 좋은 DX가 이 지점에서 나온다고 느꼈다.\n스트리밍 SSR 자체는 React 18이 제공하는 기본 기능이고 Next.js도 같은 결로 풀어가지만, TanStack은 그걸 라우터, 로더 레이어에서 하나의 \"패턴\"으로 정리해둔 느낌이다.\n\n### Selective SSR\n\n기본적으로 TanStack Start는 **모든 라우트가 SSR 활성화**로 설정되어 있다.\n\n다만 라우트 단위로 `ssr` 옵션을 줄 수 있어서, 화면 성격에 맞게 렌더링 전략을 바꿀 수 있다.\n\n| 옵션               | 컴포넌트(서버) | 컴포넌트(클라) | loader(서버) | loader(클라) | beforeLoad(서버) | beforeLoad(클라) |\n| ------------------ | -------------- | -------------- | ------------ | ------------ | ---------------- | ---------------- |\n| `ssr: true` (기본) | ✅             | ✅             | ✅           | ✅           | ✅               | ✅               |\n| `ssr: \"data-only\"` | ❌             | ✅             | ✅           | ✅           | ✅               | ✅               |\n| `ssr: false`       | ❌             | ✅             | ❌           | ✅           | ❌               | ✅               |\n\n- **`true` (기본)** : SSR. 스트리밍이 걸리면 shell이 먼저 나오고 나머지가 합류한다.\n- **`\"data-only\"`** : 데이터만 서버에서 미리 가져오고 렌더링은 클라이언트에서만 진행한다.\n- **`false`** : 완전 CSR. HTML은 비어 있고 클라이언트에서 전부 시작한다.\n\n지도/3D/캔버스처럼 브라우저에서만 렌더링되는 화면을 구성한다면 SSR의 이점이 제한적이므로 손쉽게 비활성화 처리를 할 수 있고, SEO/TTFB가 중요한 화면은 SSR로 제공하여 라우트별로 다양한 전략을 손쉽게 구현할 수 있다는 것이 매력적이다.\n\n### 서버 함수(Server Functions)\n\nTanStack Start의 서버 함수는 **\"클라이언트에서 서버 로직을 함수처럼 호출\"할 수 있게 만드는 RPC 레이어**다.\n\n**사용 경험(호출 방식)은 함수 호출처럼 유지**하면서, **실제로는 네트워크 경계를 안전하게 넘는 호출로 컴파일**되는 형태이다.\n\n**애플리케이션 어디에서든 서버 로직을 함수 형태로 호출**할 수 있고, 클라이언트에서 서버 함수를 호출하는 경우에는 호출이 서버로의 fetch 요청으로 바뀐다.\n따라서, **클라이언트 컴포넌트를 다루는 코드에서 서버 함수를 import해도 안전**하고, 빌드 과정 중 클라이언트 번들에서 서버 로직이 RPC stub으로 교체되기 때문에 실제 서버 코드는 브라우저로 전달되지 않는다.\n\n#### 기본 예시\n\n서버 함수는 보통 아래 체인으로 구성한다.\n\n`createServerFn({ method })` : 이 함수가 어떤 HTTP 메서드 성격인지(조회/변경)를 명시\u003cbr /\u003e\n\u0026nbsp;\u0026nbsp; `.inputValidator(...)`: 네트워크 경계를 넘는 입력을 검증/정규화\u003cbr/\u003e\n\u0026nbsp;\u0026nbsp;`.handler(...)` : 서버에서만 실행될 실제 로직\n\n이 구조 덕분에 **입력 타입 → 검증 → 실행 로직**이 한 곳에 모이고, 호출자는 동일한 인터페이스로만 사용하게 된다.\n\n```tsx\nimport { createServerFn } from '@tanstack/react-start';\nimport { z } from 'zod';\n\nconst UserSchema = z.object({\n  name: z.string().min(1),\n  age: z.number().min(0),\n});\n\nexport const getUser = createServerFn({ method: 'GET' })\n  .inputValidator(UserSchema)\n  .handler(async ({ data }) =\u003e {\n    return findUserById(data.id);\n  });\n```\n\n또한, 서버 함수는 **단일 data 파라미터를 받으며**, 네트워크 경계이기 때문에** validation이 타입 안정성과 런타임 정확성을 보장**하며\n\n라우터의 **`loader` 같은 서버 수명주기뿐 아니라, 컴포넌트에서도 `useServerFn()` 형태로 호출**할 수 있다.\n\n```tsx\n// In a route loader\nexport const Route = createFileRoute('/posts')({\n  loader: () =\u003e getServerPosts(),\n});\n\n// In a component\nfunction PostList() {\n  const getPosts = useServerFn(getServerPosts);\n\n  const { data } = useQuery({\n    queryKey: ['posts'],\n    queryFn: () =\u003e getPosts(),\n  });\n}\n```\n\n#### 에러 처리\n\n서버 함수는 성공 값뿐 아니라 에러/리다이렉트/notFound 같은 제어 흐름도 지원한다.\u003cbr/\u003e\n그리고 이런 결과는 `useServerFn()`이나 라우트 수명주기에서 \"자연스럽게\" 처리되도록 설계되어 있다.\n\n```tsx title=\"일반 에러는 클라이언트로 serialize되어 전달\"\nimport { createServerFn } from '@tanstack/react-start';
16\n\nexport const riskyFunction = createServerFn().handler(async () =\u003e {\n  if (Math.random() \u003e 0.5) {\n    throw new Error('Something went wrong!');\n  }\n  return { success: true };\n});\n\n// Errors are serialized to the client\ntry {\n  await riskyFunction();\n} catch (error) {\n  console.log(error.message); // \"Something went wrong!\"\n}\n```\n\n```tsx title=\"인증 필요 시 redirect()를 throw\"\nimport { createServerFn } from '@tanstack/react-start';\nimport { redirect } from '@tanstack/react-router';\n\nexport const requireAuth = createServerFn().handler(async () =\u003e {\n  const user = await getCurrentUser();\n\n  if (!user) {\n    throw redirect({ to: '/login' });\n  }\n\n  return user;\n});\n```\n\n```tsx title=\"리소스가 없으면 notFound()를 throw\"\nimport { createServerFn } from '@tanstack/react-start';\nimport { notFound } from '@tanstack/react-router';\n\nexport const getPost = createServerFn()\n  .inputValidator((data: { id: string }) =\u003e data)\n  .handler(async ({ data }) =\u003e {\n    const post = await db.findPost(data.id);\n\n    if (!post) {\n      throw notFound();\n    }\n\n    return post;\n  });\n```\n\n이러한 구조는 \"API 응답을 status code로 분기하고, 프론트에서 다시 해석하는\" 패턴보다 라우팅/렌더링 수명주기와 더 직접적으로 결합된다.\n\n#### 다양한 기능과 형태로 사용\n\n서버 함수는 \"그냥 RPC\"를 넘어서, 요청/응답 컨텍스트를 다룰 수도 있다.\u003cbr/\u003e\n아래 예시처럼 `getRequest()`, `getRequestHeader()`로 요청을 읽고, `setResponseHeaders()`, `setResponseStatus()`로 응답을 조정할 수 있다.\n\n```tsx\nimport { createServerFn } from '@tanstack/react-start';\nimport { getRequest, getRequestHeader } from '@tanstack/react-start/server';\nimport { setResponseHeaders, setResponseStatus } from '@tanstack/react-start/server';\n\nexport const getCachedData = createServerFn({ method: 'GET' }).handler(async () =\u003e {\n  // Access the incoming request\n  const request = getRequest();\n  const authHeader = getRequestHeader('Authorization');\n\n  // Set response headers (e.g., for caching)\n  setResponseHeaders(\n    new Headers({\n      'Cache-Control': 'public, max-age=300',\n      'CDN-Cache-Control': 'max-age=3600, stale-while-revalidate=600',\n    }),\n  );\n\n  // Optionally set status code\n  setResponseStatus(200);\n\n  return fetchData();\n});\n```\n\n또, **서버 함수 미들웨어**와 함께 사용한다면 입력 데이터를 검증하거나 서버 함수 실행 전후에 클라이언트 측 로직을 수행하는 등의 처리를 할 수 있다.\n\n- 인증/인가: \"서버 함수 실행 전에\" 강제하기\n- 로깅/트레이싱: \"RPC 호출 단위로\" 일관된 관측성 부여하기\n- 공통 컨텍스트 주입: request 단위 데이터(예: workspaceId) 전파하기\n\n```tsx\nimport { createMiddleware, createServerFn } from '@tanstack/react-start';\n\nconst loggingMiddleware = createMiddleware({ type: 'function' })\n  .client(() =\u003e {\n    // 서버 함수 호출 전후에 클라이언트 측에서 실행할 로직\n  })\n  .server(() =\u003e {\n    // 서버 함수가 실행되기 전, 실행된 후에 서버에서 실행할 서버 측 로직\n  });\n\nconst fn = createServerFn()\n  .middleware([loggingMiddleware])\n  .handler(async () =\u003e {\n    //...\n  });\n```\n\n이외에도 **비동기 제너레이터와 함께 스트리밍 데이터**를 전달할 수 있고, **빌드 타임에 정적으로 서버 함수를 구성하여 결과를 캐싱**할 수도 있으며, 서버 함수의 `.url` 속성을 활용해 JS없이 HTML form으로도 호출할 수 있다고 한다.\n\n서버 함수는 TanStack Start의 핵심 기능 중 하나로, 타입 안전성, 실행 경계(클라이언트/서버) 분리, 그리고 호출 일관성을 하나의 추상화로 묶는다. 이러한 접근은 별도의 API 레이어 없이 서버 로직을 구성할 수 있게 하며, 클라이언트와 서버 사이의 경계를 보다 명확하고 일관된 방식으로 다룰 수 있게 만든다.\n\n## 마치며\n\nTanStack Start를 살펴보면서 느낀 점은, 이 프레임워크가 단순히 새로운 기능을 추가한 것이 아니라 **기존 풀스택 개발 방식 자체를 다른 시선으로 바라보게 만든다**는 점이었다.\n\n특히 **라우터 중심 구조 위에 SSR, 스트리밍, 서버 함수가 자연스럽게 얹히는 방식**은 기존에 API 레이어를 별도로 설계하던 흐름과는 다른 방향성을 보여준다.\n\nTanStack Router를 사용해 본 경험이 있다면 **비교적 빠르게 적응할 수 있을 것 같다는 인상**도 받았고, 개인적으로는 Next.js를 사용해 온 경험과 비교했을 때 **데이터 흐름과 서버 로직이 더 일관되고 단순**하게 느껴지는 지점도 있었다.\n\n물론 아직은 RC 단계인 만큼 실제 서비스 적용에는 고려할 요소들이 남아있지만, 이러한 접근이 **앞으로 어떤 형태로 자리 잡을지는 충분히 지켜볼 만한 가치가 있어 보인다.**\n\n---\n\n## 참고\n\n- [TanStack Start 문ì
16„œ](https://tanstack.com/start/latest)\n- [TanStack Start가 Nitro를 사용한 이유](https://tanstack.com/blog/why-tanstack-start-is-ditching-adapters#tanstack-start--nitro--vite--tanstack-router)\n- [TanStack Router 소스 및 SSR](https://github.com/TanStack/router)\n- [React DOM Server — renderToReadableStream / renderToPipeableStream](https://react.dev/reference/react-dom/server/renderToReadableStream)\n- [Sentry × TanStack Start 가이드](https://docs.sentry.io/platforms/javascript/guides/tanstackstart-react/)\n- [Tanner Linsley 인터뷰 — Next Gen Fullstack React with TanStack (YouTube)](https://www.youtube.com/watch?v=XXXXX)\n","data":{"id":"about-tanstack-start","title":"TanStack Start 알아보기","description":"TanStack의 신규 풀 스택 프레임워크인 TanStack Start에 대해 알아봅니다.","preview":"TanStack의 신규 풀 스택 프레임워크인 TanStack Start에 대해 알아봅니다.","keywords":"tanstack, tanstack start, full stack framework, react, next.js, ssr, streaming ssr, server functions, nitro, vite, selective ssr, node, bun, deno","createdAt":"2025.10.1","published":true},"filePath":"about-tanstack-start.mdx"},{"content":"\n현재 운영 중인 서비스에서 도입한 **`ts-pattern`** 이라는 라이브러리를 통해 여러 문제들을 해결했던 내용들에 대해 작성해 ë³¼ 예정이다.\n\n패턴 매칭은 자바스크립트도 [제안 단계](https://github.com/tc39/proposal-pattern-matching)이기 때문에 알고 있는 사람들도 있겠지만, 일반적인 경우에 자바스크립트로만 개발을 해왔던 개발자라면 패턴 매칭이라는 개념 자체도 생소할 수 있다.\n\n따라서, 글의 주제인 **`ts-pattern`** 을 활용하여 해결했던 것을 이야기하기 전에, 패턴 매칭이라는 것부터 친숙해질 수 있는 시간을 가져보자.\n\n## 패턴 매칭?\n\n**패턴 매칭(pattern matching)**은 **데이터를 검색할 때 특정 패턴이 출현하는지, 또한 어디에 출현하는지 등을 특정하는 방법의 일종**이다.\n\n패턴 매칭은 검색, 분석, 정규화, 추출 등 다양한 응용 분야에서 사용하고 있으며, 자바스크립트에서도 정규표현식을 활용하여 문자열에서 특정 패턴을 찾아 검색, 추출, 대체 등을 수행할 때 사용되는 개념이다.\n\n현재 많은 함수형 언어(Rust, Swift, Elixir, Haskell, Erlang, F#, Scala 등)에서 직접적인 패턴 매칭이 지원되며, Python도 3.10 버전에서 [구조적 패턴 매칭(Structural Pattern Matching)](https://peps.python.org/pep-0634/)이라는 이름으로 도입되었다.\n\n직접 패턴 매칭 기능을 활용하면 데이터의 **값, 자료구조, 타입 등 여러가지 유형의 패턴으로 검색**을 수행할 수 있고, 특정 데이터 구조에서 **원하는 값을 추출**할 수도 있다. 이는 적은 양의 코드로 많은 양의 데이터를 읽고 다루기에 아주 좋은 표현 방법이며, 이로 인해 코드의 가독성과 유지보수성도 향상시킬 수 있다.\n\n### 자바스크립트에서의 사용\n\n아직까지 자바스크립트에서는 사용할 수 없다.\n\n앞서 말한 듯이 ECMA 표준이 되기 위한 **[제안 단계(TC39 process, Stage 1)](https://tc39.es/proposal-pattern-matching/)일뿐이다.** 아래와 같은 문제들을 가지고 제안이 시작되었으니 간단히 맥락 정도만 짚고 넘어가 보자.\n\n- 자바스크립트에 **값을 일치시키는 방법은 많지만**, 문자열을 위한 **정규표현식 외에 패턴을 일치시키는 방법은 없다.**\n- `switch`에는 많은 제약이 있다.\n  - 유일한 비교는 일치 연산자(`===`)만이 사용 가능\n  - 우발적인 실패를 방지하기 위해선 각 `case`별로 `break` 문이 필요\n  - `case` 의 애매한 scope ( 중괄호로 감싸지 않는다면 블록 범위 변수는 다른 `case` 에서도 접근 가능 )\n  - 기타 등등\n\n오래전 자바스크립트 생태계와 달리 다룰 수 있는 것들 것들이 넓고 더 깊어지면서 크고 다양한 값들을 처리하기 위한 방법이 필요해진 것으로 맥락을 해석할 수도 있을 것 같다.\n\n매년 연말에 실시하는 **[State of JavaScript](https://stateofjs.com/en-US)**에서도 패턴 매칭 기능에 대한 니즈를 보여주고 있다.\n\n![top_currently_missing_from_js](/assets/images/pattern-matching-0.png)\n\n자바스크립트 생태계는 아니ì
16§€ë§Œ [파이썬의 패턴 매칭 도입에 얽힌 이야기](https://ryanking13.github.io/2021/03/04/python-patma.html)도 같이 읽어보면 재미있을 것 같다.\n\n**ê²°êµ­ 현시점에서 자바스크립트에서 사용은 별도 라이브러리의 도움을 받을 수밖에 없는 상황이고,** 이를 도와주는 **`ts-pattern`** ê³¼ 몇 가지 사례를 소개하겠다.\n\n## ts-pattern\n\n[TC39로의 제안 깃허브](https://github.com/tc39/proposal-pattern-matching?tab=readme-ov-file#userland-matching)에도 소개된 **`ts-pattern`**은 타입스크립트 환경에서 패턴 매칭을 쉽게 표현할 수 있게 해주는 라이브러리이다.\n\n더 안전하고 나은 조건을 작성할 수 있고, 복잡함을 간결하게 표현할 수 있으며 그에 따라 코드 가독성도 좋아지고 철저한 타입 체크를 통해 케이스 누락도 방지할 수 있다.\n\n지난 2년간의 npm trends만 보더라도 라이브러리의 꾸준한 인기와 엄청난 기세를 확인할 수 있다.\n\n![npm-trends-of-ts-pattern](/assets/images/pattern-matching-1.png)\n\n**`ts-pattern`**의 주요 기능과 API들을 간단히 둘러보고 이것들을 바탕으로 어떤 문제들을 해결했는지 확인해 보자.\n\n### 주요 기능\n\n- 중첩된 객체, 배열, 튜플, 셋, ë§µ 및 모든 기본 유형 등 **모든 데이터 구조에 대해 패턴 매치 가능**\n- **타입 추론을 통한 타입 안전성 확보**\n- 철저한 타입 체크를 통해 **가능한 모든 경우를 `.exhaustive()`와 일치하도록 강제**\n- 패턴을 사용하여 **`isMatching`**으로 데이터 형태를 검증 가능\n- **catch-all**, **type-specific wildcards**를 제공하여 **표현력 좋은 API**\n  - catch-all: `P._`\n  - type-specific wildcards: `P.string`, `P.number`, `P.array`, etc..\n- 복잡한 경우를 위해 **예측, 결합, 교차 및 제외 패턴을 지원**\n- `P.select(name?)` 함수를 통해 **속성 선택을 지원**\n- ~2kB 밖에 되지 않은 **작은 번들 크기**\n\n### 주요 API\n\n위에서 나열했던 기능들 대비하여, **`ts-pattern`**은 사용하기 위해 사전에 알아두어야 할 API가 많지는 않다.\n\n다른 언어에서 패턴 매칭을 사용하는 문법들이 익숙하다면 아주 쉽게 접근할 수 있을 것이다. 그렇지 않더라도 어려운 것은 없으니 크게 걱정할 것은 없다. 천천히 예시 코드들과 API들에 대해 확인해 보자.\n\n기본적인 코드의 형태는 아래와 같은 모습이다.\n\n```ts {1,5-9}\nimport { match } from 'ts-pattern';\n\nconst value = [1, 2, 3];\n\nconst result = match(value)\n  .with([1, 2, 3, 4], () =\u003e 'it is [1, 2, 3, 4]')\n  .with([1, 2, 3], () =\u003e 'it is [1, 2, 3]')\n  .with([1, 2], () =\u003e 'it is [1, 2]')\n  .run();\n\nconsole.log(result); // it is [1, 2, 3]\n```\n\n#### match(value)\n\n**`ts-pattern`**은 내부적으로 [빌더 패턴](https://en.wikipedia.org/wiki/Builder_pattern)으로 디자인된 `MatchExpression`이라는 클래스를 사용하여 매칭을 처리한다.\n\n**`match(value)`**는 이 클래스의 인스턴스를 생성하여 반환해 주는 아주 간단한 함수이다. 따라서, 이 함수의 실행은 항상 선행되어야 한다.\n\n#### .with(pattern, [...patterns], handler)\n\n**입력 값을 패턴별로 매칭을 시켜주는 메서드**이다.\n\n**패턴 인자의 타입은 일반적으로 입력 값의 타입에 의해 결정된다.** (물론, 아래처럼 입력의 타입 추론이 힘든 경우도 있다. )\n\n```ts {1,5-8}\nimport { match, P } from 'ts-pattern';\n\nconst toString = (value: unknown): string =\u003e\n  match(value) // -\u003e value가 unknown이기 때문에 아래 with 함수의 패턴 인자로 여러 타입을 넣을 수 있다.\n    .with(P.string, (str) =\u003e str)\n    .with(P.number, (num) =\u003e num.toFixed(2))\n    .with(P.boolean, (bool) =\u003e `${bool}`)\n    .with({ kimcoder: '😊' }, (obj) =\u003e JSON.stringify(obj))\n    .otherwise(() =\u003e 'Unknown');\n\nconsole.log(toString('aa')); // 'aa'\nconsole.log(toString(123)); // 123.00\nconsole.log(toString({ kimcoder: '😊' })); // {\"kimcoder\":\"😊\"}\n```\n\n타입스크립트를 사용하면서 많은 타입들에 대한 정의가 되어 있어 추론이 잘 되는 상황이라면, 위의 예ì‹
16œëŠ” 예외적인 상황이라고 ë³¼ 수 있다.\n\n또, **`.with`** 메서드는 오버로딩된 함수로, 아래처럼 여러 패턴들을 나열한 형태로 호출할 수도 있다.\n\n```ts {8-11,15-16}\nimport { match } from 'ts-pattern';\n\nconst upperCase: string = 'A';\nconst printUpper = () =\u003e 'it is upper';\nconst printLower = () =\u003e 'it is lower';\n\nconst first = match(upperCase)\n  .with('a', printLower)\n  .with('b', printLower)\n  .with('A', printUpper)\n  .with('B', printUpper)\n  .otherwise(() =\u003e 'where is it?');\n\nconst second = match(upperCase)\n  .with('a,', 'b', printLower)\n  .with('A', 'B', printUpper)\n  .otherwise(() =\u003e 'where is it?');\n\nconsole.log(first); // 'it is upper'\nconsole.log(second); // 'it is upper'\n```\n\n**`.with`**가 체이닝되는 모습처럼 **호출 순서가 위에서 아래로 흐른다는 점은 ê¼­ 기억해두어야 한다.**\n\n중첩 객체와 같이 복잡한 구조를 가진 값의 패턴들을 검사하고자 한다면 **항상 세부적인 형태의 패턴은 먼저 검사를 해야 한다.** ( 이는 다른 언어에서도 마찬가지이다. )\n\n#### .exhaustive(), .otherwise(), .run()\n\n위 함수들은 **패턴 매칭 표현식을 실행하고, 결과를 반환**하는 `MatchExpression`의 메서드이다.\n\n| 메서드                                                    | 설명                                                                                                                                                                                                   |\n| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `exhaustive(): TOutput`                                   | - **입력 값과 일치하는 패턴이 없으면 타입 체크가 실패한다.**\u003cbr/\u003e(철저한 타입 체크, Exhaustiveness Type Check)\u003cbr/\u003e- **일치하는 패턴이 런타임에 ê¼­ 있어야 한다.**\u003cbr/\u003e(그렇지 않으면 오류가 발생한다.) |\n| `otherwise(handler: (value: TInput) =\u003e TOutput): TOutput` | - **일치하는 패턴이 없을 경우에 실행된다.**\u003cbr/\u003e- `switch`의 `default`로 생각하면 편하다.                                                                                                              |\n| `run(): TOutput`                                          | - **일치하는 패턴이 런타임에 ê¼­ 있어야 한다.**\u003cbr/\u003e(그렇지 않으면 오류가 발생한다.)                                                                                                                    |\n\n```ts {1}\nmatch('2024 years!').exhaustive();\n\n/*\n  위 코드는 NonExhaustiveError 명목으로 타입 체크가 실패한다.\n  만약, 타입 불일치 경고를 무시하고 코드를 컴파일하고 실행하면, 런타임에서 아래와 같은 ì˜
16¤ë¥˜ê°€ 발생한다.\n  Error: Pattern matching error: no pattern matches value \"2024 years!\"\n  \n  아래와 같이 수정하면 정상적으로 타입 체크를 수행할 수 있다.\n\n  match(\"2024 years!\")\n    .with(\"2024 years!\", () =\u003e \"okay\")\n    .exhaustive();\n*/\n```\n\n```ts {1}\nmatch('2024 years!').run();\n\n/*\n  위 코드를 컴파일하고 실행하면, 런타임에서 아래와 같은 오류가 발생한다.\n  Error: Pattern matching error: no pattern matches value \"2024 years!\"\n*/\n```\n\n위 메서드들은 결과를 반환하기 위해서는 필히 써야 하니, 위와 같은 특징들을 잘 인지하고 있어야 한다.\n\n#### isMatching(pattern, value?)\n\n**`isMatching`**는 타입 가드 함수이다. 커링이 된 버전과 그렇지 않은 버전, 2가지 버전으로 사용할 수 있다.\n\n- `isMatching(pattern, value): boolean`: 패턴과 값의 매치가 성공하면 결과를 `boolean` 값으로 알려준다.\n- `isMatching(pattern): (value) =\u003e boolean` : 패턴만 넣고 함수를 실행하면 해당 패턴에 대한 타입 가드 함수가 반환된다.\n\n커링이 된 버전은 아래와 같이 좀 더 의미 있는 이름으로 표현하여 사용할 수도 있다.\n\n```ts {1,3}\nimport { isMatching } from 'ts-pattern';\n\nconst isDiscountProduct = isMatching({\n  salePrice: P.number.gt(0),\n  discountedSalePrice: P.number.gt(0),\n});\n\nif (isDiscountProduct(res.data)) {\n  // do something!\n}\n```\n\n#### Patterns\n\n**`.with`** 메서드와 **`isMatching`** 함수의 인자로 들어가는 패턴들에 대해서도 알아야 한다.\n\n패턴으로 사용할 수 있는 형태는 아주 많고, 표현력도 좋은 편이라 코드를 작성하는데도 쉽고 읽는데도 어려움이 없다.\n\n아래와 같이 많은 패턴들이 존재하지만, 이 글에서는 몇 가지만 다룰 예정이다. 자세한 ê±´ [ts-pattern 문서](https://github.com/gvergnaud/ts-pattern/tree/main?tab=readme-ov-file#patterns)에서도 확인할 수 있다.\n\n- **`Literals`**\n- **`Wildcards`**\n- **`Objects`**\n- **`Tuples (arrays)`**\n- **`Sets`**\n- **`Maps`**\n- **`P.array patterns`**\n- **`P.when patterns`**\n- **`P.not patterns`**\n- **`P.select patterns`**\n- **`P.optional patterns`**\n- **`P.instanceOf patterns`**\n- **`P.union patterns`**\n- **`P.intersection patterns`**\n- **`P.string predicates`**\n- **`P.number and P.bigint predicates`**\n\n#### P.select(name?)\n\n**`P.select`**는 입력한 데이터 구조에서 특정 값을 추출하고, 핸들러 함수에 값을 주입시킬 수 있다.\n\n이는 복잡한 구조에서 특정 값만 추출하여 반환하고자 할 때 유용하게 쓰일 수 있다.\n\n```ts {4}\nimport { match, P } from 'ts-pattern';\n\nconst message = match(data)\n  .with({ order: { showOrderCount: true, orderCount: P.select() } }, (count) =\u003e {\n    return match(count)\n      .with(P.number.gt(0), () =\u003e `${count}번이나 주문했던 고객입니다!`)\n      .with(0, () =\u003e '첫 주문 고객입니다!')\n      .with(null, () =\u003e undefined)\n      .exhaustive();\n  })\n  .otherwise(() =\u003e undefined);\n```\n\n또, **`P.select`**를 호출 시 인자로 이름을 지정할 수도 있다. 입력받은 이름으로 핸들러 함수에서 값을 참조할 수 있다.\n\n```ts {17}\ntype Data = {\n  product: {\n    name: string;\n    price: number;\n    isSale: boolean;\n    relateProducts: Product[];\n  };\n  ad: {\n    url: string;\n    bannersImages: string[];\n  };\n};\n\nmatch(res)\n  .with(\n    {\n      product: { name: P.select('name'), price: P.select('price') },\n      ad: { url: P.select('adUrl') },\n    },\n    ({ name, price, adUrl }) =\u003e {\n      return { name, price: price.toLocaleString(), adUrl };\n    },\n  )\n  .otherwise(() =\u003e '');\n```\n\n#### P.union()\n\n**`P.union(...subpatterns)`**는 인자로 주어진 패턴들 중 하나라도 일치하면 매칭이 성공하는 것으로 처리시킬 수 있는 패턴이다.\n\n```ts {10}\nimport { match, P } from 'ts-pattern';\n\ntype Input =\n  | { type: 'user'; name: string }\n  | { type: 'org'; name: string }\n  | { type: 'text'; content: string }\n  | { type: 'img'; src: string };\n\nconst output = match(input)\n  .with({ type: P.union('user', 'org') }, (userOrOrg) =\u003e {\n    // userOrOrg: User | Org\n    return userOrOrg.name;\n  })\n  .otherwise(() =\u003e '');\n```\n\n주어진 패턴들이 모두 일치해야지만 매칭이 되는 **`P.intersection(...subpatterns)`**와 같은 패턴도 있다.\n\n#### Wildcards\n\n자바스크립트의 원시 타입들에 대한 와일드카드로도 검사를 수행할 수 있다.\n\n와일드카드의 종류는 다음과 같이 있고, 설명은 이름 ê·¸ 자체로도 충분하다.\n\n**`P._(=P.any)`** **`P.string`** **`P.number`** **`P.bigint`** **`P.boolean`** **`P.symbol`** **`P.nullish`**\n\n```ts title=\"wildcards\" {4-10}\nimport { match, P } from 'ts-pattern';\n\nconst output = match\u003cunknown\u003e(input)\n  .with(P.string, () =\u003e 'it is a string!')\n  .with(P.number, () =\u003e 'it is a number!')\n  .with(P.bigint, () =\u003e 'it is a bigint!')\n  .with(P.boolean, () =\u003e 'it is a boolean!')\n  .with(P.symbol, () =\u003e 'it is a symbol!')\n  .with(P.nullish, () =\u003e 'it is either null or undefined!')\n  .with(P._, () =\u003e 'it is any!')\n  .run();\n```\n\n#### Predicates\n\n숫자와 문자 유형의 값들에 대한 검사를 수행 시, `predicates`들을 사용하여 좀 더 표현력 좋고 간결한 코드를 작성할 수 있다.\n\n```ts {4-7,13-15}\nimport { match, P } from 'ts-pattern';\n\nconst output = match('kimcoder')\n  .with(P.string.startsWith('c'), () =\u003e '😊')\n  .with(P.string.regex(/sparkle/), () =\u003e '⭐')\n  .with(P.string.minLength(100), () =\u003e 'it is so long')\n  .with(P.string.includes('coder'), () =\u003e 'coder')\n  .otherwise(() =\u003e 'it is default handler');\n\nconsole.log(output); // coder\n\nconst output2 = match(2024)\n  .with(P.number.between(1, 10), () =\u003e '1 ~ 10')\n  .with(P.number.negative(), () =\u003e 'it is negative number')\n  .with(P.number.gte(2024), () =\u003e '2024! 🎉')\n  .otherwise(() =\u003e 'it is default handler');\n\nconsole.log(output2); // 2024! 🎉\n```\n\n## 해결했던 문제\n\n현재 운영 중인 [배민사장님앱](
16https://apps.apple.com/kr/app/%EB%B0%B0%EB%AF%BC%EC%82%AC%EC%9E%A5%EB%8B%98/id1042003297)의 웹뷰를 개발하면서 **`ts-pattern`**을 적용하여 해결했던 문제들을 공유하고자 한다.\n\n배민사장님앱은 사용자가 배달의민족의 여러 유형의 주문들을 접수하고 상태를 ë³¼ 수 있는 기능을 제공하고 있다.\n주문 접수 처리 및 주문의 상태를 화면에 보여주기 위해선 API를 호출하여 데이터를 받아오고 있고, 이 API의 응답 객체는 사용되는 내용들에 비해 아주 크고, 깊은 뎁스를 가진 속성들도 있다.\n\n사실 위의 내용이 큰 문제는 아니지만, **가장 까다롭고 어려운 문제는 이 하나의 데이터 구조에서 너무 많은 분기가 일어난다는 것이다.** 그리고 앞으로 서비스가 고도화될수록 더 복잡해질 가능성이 크다는 것이다.\n\n위의 내용을 단순화하여 도식으로 그려보면 아래처럼 표현할 수 있겠다.\n\n```mermaid\nstateDiagram-v2\n    state A \u003c\u003cfork\u003e\u003e\n      사용자 --\u003e A: 주문 지면을 접속하여 호출된 API로부터 전달된 데이터가 유형별로 처리된다.\n      A --\u003e 배민1주문\n      A --\u003e 배민배달주문\n      A --\u003e 배민포장주문\n      A --\u003e 배민매장주문\n      A --\u003e 배민배달예약주문\n      A --\u003e 배민포장예약주문\n      A --\u003e 배민매장예약주문\n      A --\u003e 스토어배달주문\n      A --\u003e 스토어픽업주문\n\n    state B \u003c\u003cjoin\u003e\u003e\n      배민1주문 --\u003e B\n      배민배달주문 --\u003e B\n      배민포장주문 --\u003e B\n      배민매장주문 --\u003e B\n      배민배달예약주문 --\u003e B\n      배민포장예약주문 --\u003e B\n      배민매장예약주문 --\u003e B\n      스토어배달주문 --\u003e B\n      스토어픽업주문 --\u003e B\n\n      B --\u003e 렌더링: 위에서 처리된 값들이 화면에 전달되어 유형에 맞게 렌더링된다.\n```\n\n단순하게 주문의 유형만 표시했지만 내부적으로는 렌더링을 하지 않도록 처리가 필요한 곳이 있고, 주문의 상태에 따라 분기가 또 필요하고, 배달 주문의 경우 배달 현황 상태 노출 등의 처리도 필요하다.\n\n주문의 배달 현황을 노출하는 로직을 단순화하여 패턴 매칭 사용 전/후를 가볍게 비교해 보자.\n\n```ts title=\"useOrderRiderStatus.ts\" showLineNumbers\nimport { match, P } from 'ts-pattern';\n\n/*\n  라이더 상태에 대한 임의의 정책을 구성하여 [라이더 현황 텍스트]를 처리한다.\n\n    1. 신규, 취소 주문 상태에서는 화면에 노출하지 않는다.\n    2. 배달 주문에서만 화면에 노출한다.\n    3. 음식양이 많아 라이더가 여러 명인 경우 현황 텍스트에 설명 문구를 추가한다.\n    4. 라이더의 상태에 따른 문구를 노출한다.\n    5. 배차가 완료된 예약 주문인 경우엔 현황 텍스트를 다르게 노출한다.\n*/\ntype Order = {\n  status: '신규' | '접수중' | '취소' | '완료';\n  type: '배달' | '포장' | '매장';\n  reservation: boolean;\n  rider: null | {\n    riderCompany: string;\n    status: 'ë°°ì°¨ 전' | 'ë°°ì°¨ 완료' | 'ë°°ì°¨ 취소' | '오는 중' | '배달 중' | '배달 완료';\n    riderAssignTime: string;\n    riderCount: number;\n  };\n}; // 임의의 정책에 맞게 필요한 부분만 타이핑\n\nconst getStatusText = (data: Order) =\u003e {\n  if (data.status === '신규' || data.status === '취소') return '';\n  if (data.type !== '배달') return '';\n  if (!data.rider) return '';\n\n  if (data.rider.status === 'ë°°ì°¨ 전') return '라이더 배차를 기다리고 있어요.';\n  if (data.rider.status === 'ë°°ì°¨ 완료') {\n    if (data.reservation) {\n      return '주문의 예약 시간을 확인하셨나요? 배차가 방금 막 되었어요.';\n    }\n    return '라이더 배차가 완료되었어요.';\n  }\n  // 일부 로직 생략\n};\n\nconst getStatusTextWithPatterMatching = (data: Order) =\u003e {\n  const getEmpty = () =\u003e '';\n\n  return match(data)\n    .with({ status: P.union('신규', '취소') }, { type: P.not('배달') }, getEmpty)\n    .with({ rider: { status: 'ë°°ì°¨ 전' } }, () =\u003e '라이더 배차를 기다리고 있어요.')\n    .with(\n      { reservation: true, rider: { status: 'ë°°ì°¨ 완료' } },\n      () =\u003e '주문의 예약 시간을 확인하셨나요? 배차가 방금 막 되었어요.',\n    )\n    .with({ reservation: false, rider: { status: 'ë°°ì°¨ 완료' } }, () =\u003e '라이더 배차가 완료되었어요.')\n    .otherwise(getEmpty);\n  // 일부 로직 생략\n};\n```\n\n패턴 매칭을 사용한 코드에서는 많은 조건식과 블럭들이 사라졌다. 연산자 또한 사라진 모습이다.\n\n**단지 요구사항 구현에 부합하는 패턴들을 나열하고 구현 처리를 해주는 함수들을 선언해두었을 뿐이다.**\n\n인라인으로 넣은 객체 형식의 패턴들이 많아 가독성 측면에서 조금 불편할 수 있다면, 이 패턴들을 변수로 선언하여 조금 더 의미 있는 값으로 표현하고 재사용성도 높일 수 있다.\n주문 관련 영역에서 비슷한 요구사항들을 처리하는 곳이 많아서 `{ status: P.union('신규', '취소') }` 패턴을 상수로 선언하고 ê·¸ 상수를 참조하는 것으로도 코드의 재사용성과 가독성을 높일 수 있을 것이다.\n\n`.with({ status: P.union('신규', '취소') }, handler)` -\u003e `.with(NEW_ORDER, CANCELED_ORDER, handler)`\n\n결과적으로 ë³´ë©´ 동일한 동작을 수행하는 코드는 아래와 같은 모습으로 바뀌게 된다.\n\n```ts title=\"ASIS-TOBE\"\nif (data.status === '신규' || data.status === '취소') return ''; // ASIS\n\n.with(NEW_ORDER, CANCELED_ORDER, handler); // TOBE\n```\n\n## 마치며\n\n앞서 이야기했듯이 아직은 자바스크립트가 패턴 매칭을 직접적으로 사용할 수 있는 환경은 아니다.\n\n따라서, **서비스에 외부 의존성을 하나 추가하고 새로운 유형의 코드를 읽어야 한다는 것이 가장 큰 허들이 될 것이다.**\n\n그럼에도 불구하고 다루고 있는 문제들의 복잡도가 높고 안정적으로 서비스를 제공하는데 조금 더 포커스를 맞추고자 한다면, 사용을 고민하고 동료와 논의해 보는 것만으로도 좋은 시간이 될 것이라 확신한다.\n이러한 시간을 가지는 것만으로도 패턴 매칭이란 것에 대해 인지할 수 있고 복잡한 문제 해결을 위한 옵션들이 늘어나는 것이라고 생각한다.\n\n**만약 현재 마주하고 있는 문제들의 복잡도가 높고 처리하는 데 많은 시간을 할애하고 있다면 `ts-pattern`이 우리를 도와줄 것이다.**\n\n## 참고\n\n- [패턴 매칭 TC39 슬라이드](https://hackmd.io/@mpcsh/HkZ712ig_#/)\n- [파이썬의 패턴 매칭 도입에 얽힌 이야기](https://ryanking13.github.io/2021/03/04/python-patma.html)\n- [ts-pattern 깃허브](https://github.com/gvergnaud/ts-pattern)\n- [패턴 매칭 위키피디아](https://ko.wikipedia.org/wiki/%ED%8C%A8%ED%84%B4_%EB%A7%A4%EC%B9%AD)\n","data":{"id":"ts-pattern","title":"ts-pattern을 활용하여 해결했던 것","description":"패턴 매칭 그리고 ts-pattern에 대해 알아보고, 이를 활용하여 문제들을 해결했던 사례들을 소개합니다.","preview":"패턴 매칭 그리고 ts-pattern에 대해 알아보고, 이를 활용하여 문제들을 해결했던 사례들을 소개합니다.","keywords":"javascript, typescript, pattern, Pattern matching, ts-pattern","slug":"ts-pattern","createdAt":"2024.02.04","published":true},"filePath":"ts-pattern.mdx"},{"content":"\n이 글에서는 마이크로 프론트엔드에 관한 ê³ ì°°ê³¼ [웹팩 5의 Module Federation](https://webpack.kr/concepts/module-federation/) 일부 기능에 대해 알아보고, 적당한 예ì‹
16œ 코드 작성을 통해 얻은 생각을 나눠 ë³¼ 생각이다.\n\n내가 마이크로 프론트엔드 아키텍처에 관심을 가지고 고찰을 하게 된 계기는 현재 개발/운영 중인 [배민상회](https://mart.baemin.com/)라는 서비스의 환경과 관련이 있다.\n( 배민상회는 요식업 사장님들을 대상으로 필요한 상품들을 판매하는 이커머스 서비스 )\n\n여느 쇼핑몰들과 마찬가지로 많은 페이지가 존재하고, 지난 수년 간 많은 개발자들의 열정과 노력으로 생산된 코드의 양도 풍족한 상황이다. `Webpack Bundle Analyzer` 를 통해 분석한 결과를 잠깐 확인을 해보면,\n\n![https://www.kimcoder.io/assets/images/baeminmart-158.png](https://www.kimcoder.io/assets/images/baeminmart-158.png)\n\n쇼핑몰에서 제공하는 **약 120개의 페이지는 14M에 육박하는 번들 코드가 158개의 청크로 분리**되어 있다.\n\n( 서버사이드 코드와 프로젝트 외부에 있는 디자인 시스템과 같은 코드들까지 고려하면 조금 더 많을 것 같다. )\n과거 개발 초기 단계에서 선택된 모노리스한 Single-Page-Application 방식의 개발은 현재까지 고수되어 왔고,\n\n비대해진 애플리케이션의 빌드 퍼포먼스, 코드 탐사, 의존 관리 등의 비용은 점차 증가하고 있다.\n\n개발 편의성과 생산성을 위해 아키텍처 개선이 필요한 시간이 된 것이라고 생각이 들었다.\n\n여러가지 방식을 통해 개선이 가능하겠지만,\n\n나는 **마이크로 프론트엔드에 대한 ê³ ì°°**ê³¼ **게임 체인저라고 불리우는 [웹팩 5의 Module Federation](https://webpack.kr/concepts/module-federation/)**의 일부 기능에 대해 알아 ë³¼ 예정이고, 예시 코드 작성 경험을 통해 리팩터링의 밑거름을 만들 생각이다.\n\n## Micro Frontend?\n\n마이크로 프론트엔드의 개념은 수많은 개발자, 그리고 조직들이 모노리스 아키텍처에서 마이크로 서비스 아키텍처로 전환하면서 얻은 이점들을 웹 애플리케이션 개발 환경과 생태계에도 적용하고자 했던 접근으로부터 나온 것이다.\n\n마이크로 프론트엔드에 대한 설명과 세부적인 구현 예는 [마틴파울러 글](https://martinfowler.com/articles/micro-frontends.html#TheMicroFrontends)에도 소개된 [Cam Jackson](https://camjackson.net/)의 글에 잘 설명이 되어있다. 이 글에서는 마이크로 프론트엔드의 구현으로 여러 가지 방식의 예를 들었지만 결과적으로 채택한 방식은 **컨테이너 애플리케이션와 마이크로 애플리케이션을 분리하고** **런타임에서 컨테이너 애플리케이션이 마이크로 애플리케이션들을 통합**하는 방식이었다. 애플리케이션들은 모두 독립된 환경에서 개발할 수 있으며, 필요에 의해 공통 UI 구성 요소들을 사용할 수도 있다. 컨테이너 애플리케이션 통합하는 역할을 맡아 라우팅 경로에 따라 각 마이크로 애플리케이션을 제공할 수 있도록 처리된다.\n\n![https://www.kimcoder.io/assets/images/micro-frontend-1.png](https://www.kimcoder.io/assets/images/micro-frontend-1.png)\n\n마이크로 프론트엔드의 주요한 장단점은 아래와 같이 요약할 수 있다.\n\n**장점**\n\n- 마이크로 애플리케이션들의 **기술 스택, 코드 베이스, 외부 패키지 버전 관리는 개별적으로 처리**할 수 있다.\n- 마이크로 애플리케이션들의 **개발, 테스트, 배포는 독립적**이다.\n  - 코드베이스는 모두 각 애플리케이션의 명세에 맞게 단순하게 설계 가능.\n- **사용자에게는 단일 애플리케이션처럼 동작**한다.\n  - 각 기능에서 개별로 구현된 UI/UX는 동일한 룩앤필과 경험을 제공.\n\n**단점**\n\n- **사용자로부터의 외부 패키지 번들 중복 요청**.\n  - ex) react와 같은 패키지가 애플리케이션 A와 B의 번들링 코드에 중복으로 들어갈 수 있음.\n- **개발 환경.**\n  - 독립적인 개발 환경을 보장받을 수 있지만, 통합된 애플리케이션의 환경과 차이가 있으므로 발생할 수 있는 문제점.\n- **운영 복잡도**\n  - 기술 스택, 개발 관습의 파편화 가능성.\n  - 코드 저장소, 배포 파이프라인, 테스트 및 릴리즈 프로세스 증가 가능성.\n\n아키텍처에는 항상 다양한 관점과 상황에서의 해법이 여럿 있고 마이크로 프론트엔드에 대해 생각하는 것ë
16„ 마찬가지일 것이다. 나는 [Cam Jackson 글](https://martinfowler.com/articles/micro-frontends.html#TheMicroFrontends)에 소개된 마이크로 프론트엔드에 대한 해법에 전적으로 동의한다.\n\n애플리케이션들은 런타임 환경에서 통합이 되어야 하고, 각자의 개발 생명 주기를 가질 수 있어야 한다. 이는 CI/CD도 모두 독립적으로 구성이 될 수 있다는 뜻이며, 일부 변경된 기능을 위해 애플리케이션 전체가 다시 빌드, 배포가 되지 않아도 된다는 것이다.\n\n이제, 마이크로 프론트엔드 구성을 손쉽게 해줄 [웹팩 5의 Module Federation](https://webpack.kr/concepts/module-federation/)에 대해서 알아보도록 하자.\n\n## Module Federation?\n\n웹팩 5 버전의 출시와 함께 나온 기능 중 하나인 **모듈 페더레이션은 특정 애플리케이션(모듈)에서 동적으로 다른 빌드의 모듈 코드를 불러와 실행을 할 수 있게 해준다.**\n\n즉, 여러 개의 개별 빌드 코드가 하나의 어플리케이션 내에서 구동되는 것이다. 또한, 개별 빌드는 서로 의존성이 없어 개별적으로 빌드, 배포가 가능하다.\n\n모듈 페더레이션의 몇 가지 용어와 특징을 ( _고수준 관점으로_ )요약해 보겠다.\n( 상세한 내용은 [공식 문서](https://webpack.kr/concepts/module-federation/)에서 확인할 수 있다. )\n\n- **각각의 빌드는 컨테이너 역할을 한다.**\n- **각각의 빌드는 호스트와 리모트 모두 될 수 있다.**\n  - **host :** 호스트는 페이지 로드 시 가장 먼저 초기화되는 빌드.\n  - **remote :** 리모트는 호스트에 의해 소비되는 빌드.\n  - 빌드 된 **모듈은 런타임에서 모두 양방향(Bidirectional)으로 소비될 수 있다.**\n- 각 컨테이너들에서 공통적인 외부 패키지의 디펜던시는 설정으로 청크의 중복을 막을 수 있다.\n  - 특정 버전을 명시한다면, 해당 버전으로 사용할 수 있다.\n  - ex) react, react-dom, etc..\n- 위의 **설정들은 웹팩의 ModuleFederationPlugin을 통해 할 수 있다.**\n- 환경에 의존하지 않는다.\n  - ex) browser, node.js 등에서 사용 가능.\n\n### 유즈케이스\n\n- **공용 컴포넌트 라이브러리의 컨테이너로 사용**\n  여러 애플리케이션에서 사용하는 디자인 시스템과 같은 공용 컴포넌트 혹은 추상화된 코드들을 라이브러리 컨테이너로 만들어 공유하는 방법이다.\n  이는 애플리케이션과 공용 코드들의 독립적인 개발, 배포가 가능하다.\n  공용 컴포넌트의 업데이트가 일어날 경우, 애플리케이션들은 배포 없이 런타임에서 최신 버전의 컴포넌트 라이브러리를 자동으로 사용할 수 있다.\n\n\u003cbr /\u003e\n\n![https://www.kimcoder.io/assets/images/federation-component-libarary-container.png](https://www.kimcoder.io/assets/images/federation-component-libarary-container.png)\n\n\u003cbr /\u003e\n\n- **페이지별 독립적 빌드 시 사용**\n  단일 페이지 애플리케이션에서 각 페이지를 분리하여 독립적인 개발과 배포 프로세스를 가질 수 있다.\n  애플리케이션 쉘을 상위 레벨에 두고, 페이지 라우팅에 맞는 각 애플리케이션을 원격 모듈로 참조하는 방식이다. 애플리케이션 쉘은 일반적으로 사용되는 라이브러리를 공유 모듈로 정의할 수 있고, 라우팅 경로의 변경이 있어날 경우에 배포를 하게 된다. 각 페이지 애플리케이션은 다른 페이지와 상관없이 독립적인 배포가 가능하다.\n\n\u003cbr /\u003e\n\n![https://www.kimcoder.io/assets/images/federation-page-seperation.png](https://www.kimcoder.io/assets/images/federation-page-seperation.png)\n\n\u003cbr /\u003e\n\n리모트와 호스트 사이의 화살표는 **모듈들이 런타임에서 모두 양방향(Bidirectional)으로 소비가 가능한 것을 보여준다.** 이는 예제에서 조금 더 자세히 다루겠다.\n\n위의 두 가지의 사례를 적절하게 조합하여 다음 예제를 구성해 보자.\n\n## 예제\n\n![https://www.kimcoder.io/assets/images/federation-example.png](https://www.kimcoder.io/assets/images/federation-example.png)\n\n위와 같은 구조로 설계를 한다고 가정하여, 몇 가지 설정에 대해 구체적인 코드를 작성해 보자\n\n## 디렉토리 구조\n\n```tsx\n- packages\n\t- core\n\t\t- ...\n\t\t- webpack.config.js\n\t\t- package.json\n\t- order\n\t\t- core와 유사\n\t- shell\n\t\t- core와 유사\n\t- products\n\t\t- core와 유사\n\t- ...\n- ...\n- package.json // 패키지 스크립트 병렬 실행을 위한 스크립트\n```\n\n- 각 모듈은 packages 디렉토리 하위에 위치한다.\n- 각 모듈은 webpack을 사용하며, 독립적으로 외부 패키지 의존성을 가진다.\n- 루트 디렉토리의 package.json은 각 모듈의 스크립트를 병렬로 실행할 수 있게 설정한다.\n\n## Shell Module\n\n### webpack.config.js\n\n- **shell**의 webpack 설정은 아래와 같은 내용을 따르도록 한다.\n  - 호스트 애플리케이션으로써, 모든 리모트 애플리케이션 모듈 소비.\n  - order와 같은 다른 애플리케이션들에서 페더레이트가 가능하도록 `Shell App` 을 리모트로 노출.\n    - 만약, 페이지 전역에서 사용될 
16Context 등과 같은 코드가 있다면 노출.\n  - 코어 라이브러리 컨테이너 모듈 소비.\n  - 로컬에서 3000번 포트로 동작.\n  - 공용 외부 패키지는 중복된 청크가 발생되지 않도록 선언.\n\n```tsx\nplugins: [\n    new ModuleFederationPlugin({\n      name: 'shell',\n      filename: 'shell.remoteEntry.js',   // shell의 리모트 엔트리\n      remotes: {\n\t\t\t\tcore: 'core@http://localhost:2000/core.remoteEntry.js',\n\t\t\t\tmain: \"main@http://localhost:3001/main.remoteEntry.js\",\n        products: \"products@http://localhost:3002/products.remoteEntry.js\",\n        order: \"order@http://localhost:3003/order.remoteEntry.js\",\n      },\n      exposes: {\n        './Shell': './src/Shell'\n      },\n      shared: [\n        {\n          react: {\n            singleton: true,\n            requiredVersion: deps.react,\n          },\n          'react-dom': {\n            singleton: true,\n            requiredVersion: deps['react-dom'],\n          },\n        },\n      ],\n    }),\n]\n```\n\n### Entry of webpack\n\n- 모듈 페더레이션 시, 일반적으로 비동기 경계를 사용하는 것이 추천되기 때문에 이 내용을 따른다.\n  - 비동기 경계가 추천되는 이유는 성능 향상을 위해 청크의 초기화 코드를 분할하기 위함.\n\n```tsx\n// index.js\nimport('bootstrap');\n\n// bootstrap.js\nimport ShellApp from \"./ShellApp\";\nimport React from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\u003cShellApp /\u003e);\n```\n\n### React App\n\n- 코어 컴포넌트, 혹은 디자인 시스템 컴포넌트는 **코어 컴포넌트 라이브러리 컨테이너 모듈에서** **가져온다**.\n- 각 **페이지들은 개별 페이지 애플리케이션 모듈에서 가져온다**.\n\n```tsx\nconst Tabs = React.lazy(() =\u003e import('core/Tabs'));\n\nconst MainPage = React.lazy(() =\u003e import('main/MainPage'));\nconst ProductsPage = React.lazy(() =\u003e import('products/ProductsPage'));\n....\n\nconst ShellApp = () =\u003e {\n\treturn (\n\t  \u003cProvider\u003e\n\t\t\t\u003cReact.Suspense fallback={'Loading'}\u003e\n\t\t\t\t\u003cGNB\u003e\n\t\t\t\t\t\u003cTabs\u003e\n\t          \u003cLink to=\"/\"\u003e...\u003c/Link\u003e\n\t          \u003cLink to=\"/products\"\u003eProducts\u003c/Link\u003e\n\t\t\t\t\t\t\t....\n\t        \u003c/Tabs\u003e\n\t\t\t\t\u003c/GNB\u003e\n\t\t\t\u003c/React.Suspense\u003e\n\t\t\t\u003cReact.Suspense fallback={'Loading'}\u003e\n\t\t\t\t\u003cRoutes\u003e\n          \u003cRoute path=\"/\" element={\u003cMainPage /\u003e} /\u003e\n          \u003cRoute path=\"products/*\" element={\u003cProductsPage /\u003e} /\u003e\n\t\t\t\t\t\t....\n\t\t\t\t\u003c/Routes\u003e\n\t\t\t\u003c/React.Suspense\u003e\n\t\t\u003c/Provider\u003e\n\t)\n};\n\nexport default ShellApp;\n```\n\n## Core Component Library Module\n\n### webpack.config.js\n\n- **Core Component Library**의 webpack 설정은 아래와 같은 내용을 따르도록 한다.\n  - 리모트 모듈로만 사용.\n  - 로컬에서 2000번 포트로 동작.\n  - 공용 외부 패키지는 중복된 청크가 발생되지 않도록 선언.\n\n```tsx\nplugins: [\n    new ModuleFederationPlugin({\n      name: 'core',\n      filename: 'core.remoteEntry.js',\n      exposes: {\n        './Button': './src/Button'\n        './Tabs': './src/Tabs'\n      },\n      shared: {\n        react: {\n          singleton: true,\n          requiredVersion: deps.react,\n        },\n        'react-dom': {\n          singleton: true,\n          requiredVersion: deps['react-dom'],\n        },\n      },\n    }),\n  ],\n```\n\n**Components**\n\n- 컴포넌트들은 일반적인 코어 컴포넌트 혹은 디자인 시스템의 설계 패턴으로 구현한다.\n\n## Page Application Module\n\n### webpack.config.js\n\n- **page application**들**의** webpack 설정은 아래와 같은 내용을 따르도록 한다.\n  - 리모트 애플리케이션이지만, 로컬에서는 호스트가 될 수 있다.\n  - 페이지를 리모트로 노출.\n  - 코어 라이브러리 컨테이너 모듈 소비.\n  - 로컬에서 3000번 이상의 포트에서 호스트로 동작.\n  - 공용 외부 패키지는 중복된 청크가 발생되지 않도록 선언.\n\n```tsx\nplugins: [\n    new ModuleFederationPlugin({\n      name: 'main',\n      filename: 'main.remoteEntry.js',\n      remotes: {\n        shell: 'shell@http://localhost:3000/shell.remoteEntry.js',\n        core: 'core@http://localhost:2000/core.remoteEntry.js',\n      },\n      exposes: {\n        \"./MainApplication\": \"./src/MainApplication\",\n      },\n      shared: {\n        react: {\n          singleton: true,\n          requiredVersion: deps.react,\n        },\n        'react-dom': {\n          singleton: true,\n          requiredVersion: deps['react-dom'],\n        }\n      },\n    }),\n    new HtmlWebpackPlugin({\n      template: './public/index.html',\n    }),\n  ],\n```\n\n### Entry of webpack\n\n- `Shell` 과 마찬가지로 엔트리는 비동기로 구현되도록 처리한다.\n- 로컬 개발 시, Shell을 소비하여 단일 애플리케이션처럼 동작할 수 있도록 한다.\n\n```tsx\n// index.js\nimport('bootstrap');\n\n// bootstrap.js\nimport ShellApp from \"./ShellApp\";\nimport React from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst root = createRoot(document.getElementById(\"root\"));\n\nroot.render(\u003cShellApp /\u003e);\n\n// app.js\nimport React from \"react\";\n\nconst Shell = React.lazy(() =\u003e import(\"shell/Shell\"));\n\nfunction App() {\n  return (\n    \u003cReact.Suspense fallback={\"Loading Shell\"}\u003e\n      \u003cShell /\u003e\n    \u003c/React.Suspense\u003e\n  );\n}\n\nexport default App;\n```\n\n### Consumed React App\n\n- 코어 컴포넌트, 혹은 디자인 시스템 컴포넌트는 **코어 컴포넌트 라이브러리 컨테이너 모듈에서** **가져온다**.\n\n```tsx\n\nimport React from \"react\";\n\nconst Button = React.lazy(() =\u003e import(\"core/Button\"));\n\nconst MainApplication = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003csection\u003e\n        \u003ch1\u003eMAIN PAGE\u003c/h1\u003e\n      \u003c/section\u003e\n      \u003csection\u003e\n        \u003cReact.Suspense fallback=\"fallback\"\u003e\n          \u003cButton\u003eButton of MAIN\u003c/Button\u003e\n        \u003c/React.Suspense\u003e\n      \u003c/section\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default MainApplication;\n```\n\n위와 같이 구현 후 웹 페이지의 네트워크 로그는 아래와 같은 모습으로 나타난다.\n\n![https://www.kimcoder.io/assets/images/federation-example-network.png](https://www.kimcoder.io/assets/images/federation-example-network.png)\n\n공용 외부 패키지로 선언한 리액트는 하나의 청크로 구성되어 로드되고, 각 모듈들은 원격 엔트리를 통해 로드된다.\n네트워크 로그만 보았을 때는, 단순히 코드 스플리팅이 된 것처럼 보이지만 **가장 중요한 것은 각 모듈들이 독립적으로 개발, 운영, 배포가 되었다는 것**이다.\n\n**단일 애플리케이션으로써의 통합은 런타임에서 일어나게 된다.**\n\n이 예제의 코드들은 모두 아래에서 확인이 가능하다.\n\n- Github : [https://github.com/kimcoder/module-federation-example](https://github.com/kimcoder/module-federation-example)\n- CodeSandbox : [https://codesandbox.io/s/stupefied-kapitsa-gd5255](https://codesandbox.io/s/stupefied-kapitsa-gd5255)\n- CodeSandbox Page : [https://gd5255.sse.codesandbox.io/order](https://gd5255.sse.codesandbox.io/order)\n\n\u003ciframe\n  src='https://codesandbox.io/embed/stupefied-kapitsa-gd5255?fontsize=14\u0026hidenavigation=1\u0026theme=dark'\n  className='w-full overflow-hidden rounded-md border-none'\n  style={{ height: 500 }}\n  title='stupefied-kapitsa-gd5255'\n  allow='accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr;
16 xr-spatial-tracking'\n  sandbox='allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts'\n/\u003e\n\n## 마치며\n\n이 장문의 글에서 소개한 **Micro Frontend Architecture**의 지향점과 **Webpack 5의 Module Federation**의 주요한 포인트는 아래 2가지라고 생각된다.\n\n- **각 모듈은 독립적으로 개발, 운영, 배포**를 할 수 있다.\n- **런타임에서 여러 모듈들이 통합되고, 사용자에게는 모듈들이 단일 애플리케이션처럼 동작**한다.\n\n단일 모듈의 독립적이고 유연한 개발 프로세스를 가질 수 있고, 배포와 롤백도 보다 더 민첩하게 할 수 있다는 추가적인 장점이 있을 수 있다. 하지만 분명한 것은 좋은 방식의 설계에 대한 그만한 비용이 발생할 것이며 거버넌스 측면에서도 많은 고민과 협의, 그리고 합리적인 정책들이 필요하다는 것이다.\n\n추가적으로 Angular, Vue, Server Side Rendering, React \u0026 Vue 등 이 [링크](https://github.com/module-federation/module-federation-examples)에서 다양한 Module Federation의 예제들도 있으니 확인해 ë³´ë©´ 좋을 것 같다.\n\n## 참고\n\n- [https://www.thoughtworks.com/radar/techniques/micro-frontends](https://www.thoughtworks.com/radar/techniques/micro-frontends)\n- [https://microfrontends.com/](https://microfrontends.com/)\n- [https://martinfowler.com/articles/micro-frontends.html](https://martinfowler.com/articles/micro-frontends.html)\n- [https://webpack.kr/concepts/module-federation/](https://webpack.kr/concepts/module-federation/)\n- [https://github.com/module-federation/module-federation-examples](https://github.com/module-federation/module-federation-examples)\n- [https://github.com/kimcoder/module-federation-example](https://github.com/kimcoder/module-federation-example)\n- [https://codesandbox.io/s/stupefied-kapitsa-gd5255](https://codesandbox.io/s/stupefied-kapitsa-gd5255)\n- [https://gd5255.sse.codesandbox.io/order](https://gd5255.sse.codesandbox.io/order)\n","data":{"id":"micro frontend and module federation","title":"Micro Frontend, 그리고 Webpack Module Federation","description":"마이크로 프론트엔드에 관한 ê³ ì°°, 그리고 웹팩의 모듈 페더레이션","preview":"마이크로 프론트엔드에 관한 ê³ ì°°ê³¼ 웹팩 5의 Module Federation 일부 기능에 대해 알아보고, 적당한 예시 코드 작성을 통해 얻은 생각을 기록하는 글입니다.","keywords":"react, next, create-react-app, clean architecture, architecture","slug":"micro-frontend-module-federation","createdAt":"2022.07.01","published":true},"filePath":"micro-frontend-module-federation.mdx"},{"content":"\n**_소프트웨어는 항상 부드러워야 한다_**\n\n이 글에서는 위의 인용구처럼 **변경에 유연한 프론트엔드 아키텍처를 구성하는 하나의 방법과 방법의 근거, 그리고 이를 제어해 줄 수 있는 ESLint의 규칙까지 소개하고자 한다.**\u003cbr/\u003e\n\n나는 지난 몇 년간 잦은 이직으로 꽤나 많고 다양한 프로젝트를 경험한 바가 있다.\u003cbr/\u003e\n대부분은 `React`로 구성이 되어있었고, 이를 개발하고 유지 보수하는 많은 개발자분들도 알게 되었다.\u003cbr/\u003e\n웹 애플리케이션의 규모, 운영 기간, 플랫폼, 인프라, 개발 환경, 투입 인력, 컨벤션과 같은 것은 모두 달랐지만,\u003cbr/\u003e\n`React` 와 동반되는 라이브러리, 코드 패턴, 그리고 이를 유지 보수하기 위한 개발자들의 열정과 노력들은 비슷한 점이 많았다.\u003cbr/\u003e\n\n많은 개발자들이 항상 옳은 선택을 하려고 노력하지만,\u003cbr/\u003e\n크고 작은 피쳐들, 유지 보수, 버그/ 장애 대응, 필수적인 플랫폼 버전 대응 등 다양한 업무들과 타이트한 일정 속에 편의적인 선택들을 하는 경우도 많이 보았다.\u003cbr/\u003e\n이러한 편의적인 선택은 언젠가 다시 우리들의 발목을 잡게 될 것이다.\u003cbr/\u003e\n\n자유로운 발목과 함께 훨훨 날아가기 좋은 아키텍처를 구성해 보자.\n\n## 좋은 아키텍처? 프론트엔드에서는?\n\n`클린 아키텍처 (로버트 C.마틴 )`에서 좋은 아키텍처에 대해 설명한 것 중 몇 가지를 추려보았다.\n\n- **좋은 아키텍처는 세부사항을 정책으로부터 신중하게 가려내고, 둘이 결합되지 않도록 엄격하게 분리한다.**\n- **좋은 아키텍처는 의존성의 방향이 컴포넌트 수준을 기반으로 연결되도록 만들어야 한다.**\n- **좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다.**\n  - ( 향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다. )\n\n위의 내용과 나의 이해를 바탕으로 ì
16–´ë– í•œ 목적과 원칙을 가지고 프론트엔드 아키텍처에 적용을 하였는지 간략히 정리를 해보자면..\n\n### 목적\n\n\u003e **좋은 아키텍처**의 궁극적인 목적은 소프트웨어를 개발하고 유지 보수하는 데 있어, **최소한의 노력으로 최대의 효율을 얻게 해주는 것**이다.\n\n이는 개발자가 시스템을 쉽게 이해하도록 해주고, 쉽게 개발하게 해주며, 유지 보수 및 배포까지 쉽게 해줄 수 있게 한다는 것으로 풀어서 표현이 가능하다.\u003cbr/\u003e\n\n아키텍처 관점에서 쉽게 이해하고, 개발, 유지 보수한다는 것은 어떤 의미일까?\u003cbr/\u003e\n적어도 나에게는 애플리케이션의 구조가 한 눈에 드러나 누구나 쉽게 파악할 수 있으며, 기능별 모듈화가 잘 되어 있고, 독립적이며 쉽게 배포까지 할 수 있어야 한다는 것으로 이해하고 있다.\u003cbr/\u003e\n\n웹 애플리케이션의 규모에 따라 차이가 있을 수 있고 좋은 아키텍처를 구성하는 여러 선택지가 있겠지만,\u003cbr/\u003e\n여기서는 **단일 소스 수준으로 모노리틱한 구조에서 모듈 간 의존성을 제어하는 방식을 통해 구성**해 ë³¼ 것이다.\u003cbr/\u003e\n\n변화가 많고 기술의 발전 속도도 빠른 생태계 속에서 유연한 아키텍처를 구성하여 최소한의 노력으로 최대의 효율을 가질 수 있도록 해보자.\n\n### 규칙\n\n- 웹 애플리케이션 로직과 세부적인 도메인 로직을 분리한다.\n- 분리된 로직간의 의존 방향을 명확하게 한다.\n- 변화가 많은 외부 모듈을 감추고, 추상화된 인터페이스를 사용할 수 있게한다.\n\n## 디렉토리 구조\n\n이 글에서는 `Next.js` 프로젝트를 대상으로 위의 규칙을 준수하여 구성해 보겠다.\u003cbr/\u003e\n[Next.js](https://github.com/vercel/next.js/tree/canary/examples)의 많은 예제들의 수준과 비슷하게 한다면, 아래와 같은 계층을 가진 구조의 모양이 나올 수 있다.\n\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-1.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-1.png)\n\n### Core\n\n입력과 출력으로부터 거리가 멀기 때문에 고수준이라고 ë³¼ 수 있는 계층이다.\u003cbr/\u003e\n앞서 말한 원칙에서 세부적인 도메인 로직이 아닌, 웹 애플리케이션 로직과 추상화된 코드들이 들어간다.\u003cbr/\u003e\n예를 들면 아래와 같은 성격의 코드들이 들어갈 수 있다.\n\n- 애플리케이션이 외부와의 통신을 위해 필요한 구현체\n- 도메인과 연관이 없는 고수준의 유틸리티\n- 재사용가능한 컴포넌트\n  - ex) Button, Input, Select\n- 중요도가 높은 외부 모듈(라이브러리)의 어댑터\n\n**Core 계층에 있는 코드들은 절대 Core 원의 외부에 있는 계층**(lib/components, pages)**을 참조하면 안 된다.**\n\n### Components / Lib\n\n이 계층은 도메인에 종속적이며, Core 계층의 코드들을 참조할 수 있고, 아래 예시와 같은 코드들이 들어간다.\u003cbr/\u003e\n( 주문이라는 도메인이 속한 프로젝트라고 가정. )\n\n```tsx\n- src\n\t- lib\n\t\t- order\n\t\t\t- constatns\n\t\t\t- hooks\n\t\t\t- mutations\n\t\t\t- queries\n\t\t\t- utils\n\t\t\t- ....\n\n- src\n\t- components\n\t\t- order\n\t\t\t- ItemList.tsx\n\t\t\t- Price.tsx\n\t\t\t- Payment.tsx\n\t\t\t- ...\n```\n\n- **Lib**\n  - 도메인에 관련되어 있는 로직들.\n  - components 계층과 pages 계층에서 참조되어진다.\n- **Components**\n  - 도메인에 관련되어 있는 컴포넌트.\n  - 도메인에 국한되어 재사용성은 낮다.\n  - pages 계층에서 참조되어진다.\n\n### **Pages**\n\n도메인에 의존도가 높은 계층. 입력/출력과 밀접해있으며, 가장 저수준이라고 ë³¼ 수 있다.\u003cbr/\u003e\n`Next.js`의 기본 설정 값인 파일 시스템 기반으로 라우팅 처리를 하며, **Components**와 **Lib** 계층의 코드들을 참조하여 구성할 수 있다.\n\n```tsx\n- src\n  - pages\n    - api\n      - order\n        - [...slug].tsx  // src/lib/order/.. 참조\n        - ...\n    - order\n      - [id].tsx // src/components/order/.. 참조\n      - _middleware.tsx // src/lib/order/.. 참조\n      - ...\n```\n\n### Dependency diagram\n\n위의 구조와 규칙을 가지고 간략히 의존성 그래프로 표현하면 아래와 같은 모양이 될 것이다.\n\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-2.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-2.png)\n\n의존성은 모두 단방향으로만 흘러가고, 역으로 참조해서는 안 된다.\u003cbr/\u003e\n\n예를 들어, core 계층에 API 통신을 위한 구현체가 있다고 가정해 보자.\u003cbr/\u003e\n이 구현체는 UI의 형태 혹은 상태, 세부적인 도메인 로직를 알아서는 안되며, 알아도 좋을 게 없다.\u003cbr/\u003e\n마찬가지로 도메인에 종속적인 코드들 또한, 이 구현체가 비동기 통신을 위하여 어떤 객체를 사용하여 구현이 되었는지, 어떻게 API 서버와 통신을 하는지 전혀 알 필요가 없다.\u003cbr/\u003e\n\n이러한 **관심사의 분리로 인해 각 모듈은 여러 책임
16에서 벗어나기 쉽고, 테스트하기도 더 쉬워지며, 유지 보수 비용도 줄어들 것**이다.\n\n## ESLint 플러그인 활용\n\n앞서 소개했던 아키텍처의 계층과 경계를 효과적으로 다루기 위해 ESLint를 활용하고, 이것이 개발 주기에 자연스레 녹아들 수 있게 해보자.\nESLint 기본 규칙에도 참조를 제한할 수 있는 `no-restricted-imports`가 있지만 이것으로는 위의 의존성 규칙을 해결할 수는 없다.\u003cbr/\u003e\n\n따라서, `eslint-plugin-import`라는 플러그인을 사용하여 **계층 간의 의존성 제어**를 다뤄보고,\u003cbr/\u003e\n추가적으로 **외부 모듈의 의존성 제어**도 같이 다뤄보도록 하겠다.\n\n### 소개\n\n[eslint-plugin-import](https://github.com/import-js/eslint-plugin-import)는 [creact-react-app](https://www.npmjs.com/package/eslint-config-react-app)과 [next.js](https://www.npmjs.com/package/next)에서도 사용하고 있는 플러그인이며,\u003cbr/\u003e\n이것으로 ESLint의 import 관련한 기본 규칙들보다 조금 더 확장된 기능들로 린팅을 할 수 있다.\u003cbr/\u003e\n플러그인 하위에 여러 룰이 있지만, 이 글에서는 [no-restricted-paths](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-restricted-paths.md)만 다루도록 하겠다.\n\n### 계층 간 의존성 제어\n\n위의 의존성 규칙을 린트 룰로 표현하면 아래와 같다.\n\n```jsx\n\"rules\": {\n    \"import/no-restricted-paths\": [\n      \"error\",\n      {\n        \"zones\": [\n          {\n            \"target\": \"src/core\",\n            \"from\": \"src/components\"\n          },\n          {\n            \"target\": \"src/core\",\n            \"from\": \"src/lib\"\n          },\n          {\n            \"target\": \"src/core\",\n            \"from\": \"src/pages\"\n          },\n          {\n            \"target\": \"src/lib\",\n            \"from\": \"src/pages\"\n          },\n          {\n            \"target\": \"src/components\",\n            \"from\": \"src/pages\"\n          }\n        ]\n      }\n    ]\n  },\n  \"settings\": {\n    \"import/resolver\": {\n      \"typescript\": {\n        \"project\": \".\"\n      }\n    }\n  }\n```\n\n타입스크립트를 사용하는 프로젝트라면 아래 [import/resolver](https://github.com/import-js/eslint-plugin-import#typescript)의 설정이 추가로 필요하다.\n\n### 오류 화면\n\n룰을 지키지 않았을 경우 IDE에서 아래와 같은 모습들을 볼 수 있다.\u003cbr/\u003e\n또한, 절대 경로, 상대 경로, 별칭 경로 등 모두 인식이 가능하다.\n\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-3.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-3.png)\u003cbr/\u003e\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-5.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-5.png)\n\n코드 편집기에서 보여주는 오류를 예시 화면으로 들었지만, 당연하게도 린트 명령어로도 규칙에 대한 검증을 할 수 있다.\n\n### 오류 메시지 추가\n\n린트 규칙에 아래와 같은 메시지를 추가하여 동료에게 조금 더 개발 친화적인 오류를 안내해 줄 수도 있다.\n\n```rust\n\"rules\": {\n    \"import/no-restricted-paths\": [\n      \"error\",\n      {\n        \"zones\": [\n          ....\n          {\n            \"target\": \"src/core\",\n            \"from\": \"src/lib\",\n\t\t\t\t\t\t\"message\": \"\\n의존성 규칙에 어긋나는 참조입니다.\"\n          },\n          ....\n        ]\n      }\n    ]\n  },\n```\n\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-4.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-4.png)\n\n### 외부 모듈 의존성 제어\n\n앞서 설명한 것처럼, core 계층에 API 통신을 위한 구현체가 있으며 `axios`를 사용한다고 가정한 뒤, 아래의 원칙을 바탕으로 린트 규칙 설정을 해 보자.\n\n1. `axios`는 core 계층의 특정한 API 모듈 구현체에서만 존재를 알고 있다.\n2. 특정 구현체는 추상화된 인터페이스를 구현하고 이를 다른 모듈들이 사용할 수 있게 한다.\n3. 다른 모듈들은 `axios`를 알지도 못하고 참조할 수 없으며, 오직 특정 구현체가 제공하는 인터페이스만 알 수있다.\n\n```tsx\n\"rules\": {\n\t\"no-restricted-imports\": [\n\t\t\"error\",\n\t\t{\n\t    \"paths\": [{\n\t      \"name\": \"axios\",\n\t\t    \"message\"
16: \"\\naxios는 @core/utils/ApiUtil.ts에서만 참조가 가능합니다.\"\n\t\t\t}]\n\t\t}\n\t],\n\t\"import/no-restricted-paths\": [\n\t\t\"error\",\n\t\t{\n\t    \"zones\": [\n\t\t\t\t....\n\t\t\t]\n\t\t}\n\t],\n\n\t....\n\n\"overrides\": [\n\t{\n\t\t\"files\": [\"src/core/utils/ApiUtil.ts\"],\n\t\t\"rules\": {\n\t\t\t\"no-restricted-imports\": \"off\"\n\t\t}\n\t}\n],\n\n....\n```\n\n### 오류 화면과 추가된 메시지\n\n특정 구현체 외에서 axios 참조 시 아래와 같은 오류가 발생한다.\u003cbr/\u003e\n또한, 친화적인 오류 메시지를 추가할 수 있다.\n\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-6.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-6.png)\u003cbr/\u003e\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-7.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-7.png)\u003cbr/\u003e\n![https://www.kimcoder.io/assets/images/clean-architecture-frontend-8.png](https://www.kimcoder.io/assets/images/clean-architecture-frontend-8.png)\n\n예시와 같이 외부 모듈의 의존성을 제어하고 코드를 유지해 나갈 수 있다면,\u003cbr/\u003e\n외부 모듈의 버저닝 대응, 모듈 교체와 같은 변화가 생길 시 **최소의 노력으로 최대의 효율**을 낼 수 있을 것이다.\n\n## 마치며\n\n모든 문제 해결에 대한 정답이 정해져있지 않은 것처럼, 아키텍처에 관한 접근도 마찬가지이다.\u003cbr/\u003e\n**이 글에서 말하는 내용은 모든 환경과 상황을 만족시키는 정답이 아니다.**\u003cbr/\u003e\n\n하지만, 아래와 같은 상황에 놓여있다면 한번 즈음은 고려해 볼 만한 내용이 될 수 있을 것 같다.\n\n- _단일 소스 수준의 모노리스 아키텍처로 프로젝트가 관리되고 있을 경우._\n- _엔터프라이즈급 혹은 어느 정도 규모 있는 웹 애플리케이션._\n- _테스트하기 쉬운 애플리케이션을 만들고 싶은 경우._\n- _계층 간의 의존성 규칙을 개발주기에 자연스레 포함시키고 싶은 경우._\n\n또, **이 글의 내용을 통해 계층 간의 경계는 명확해졌지만, 이 외에도 신경 써야 할 것이 많다.**\u003cbr/\u003e\n**도메인 간의 경계를 어떻게 다뤄야 할지, 어떤 외부 모듈을 추상화시키고 기준을 어떻게 세울 것인지, 테스트 경계는 어떻게 다룰지 등.**\u003cbr/\u003e\n글의 제목에서 볼 수 있듯이, 여기서는 단지 **첫걸음**을 내딛였을 뿐이다.\u003cbr/\u003e\n\n중요한 사실은 **아키텍처는 지속적으로 상황에 맞게 성장해야 하며, 이를 위해 개발자들의 부단한 노력이 필요하다는 것이다.**\n\n## 참고\n\n- [http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html](http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)\n- [https://github.com/vercel/next.js/tree/canary/examples](https://github.com/vercel/next.js/tree/canary/examples)\n- [https://eslint.org/docs/rules/no-restricted-imports](https://eslint.org/docs/rules/no-restricted-imports)\n- [https://github.com/import-js/eslint-plugin-import](https://github.com/import-js/eslint-plugin-import)\n- [https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-restricted-paths.md](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-restricted-paths.md)\n- [https://github.com/import-js/eslint-plugin-import#typescript](https://github.com/import-js/eslint-plugin-import#typescript)\n- [https://www.npmjs.com/package/eslint-config-react-app](https://www.npmjs.com/package/eslint-config-react-app)\n- [https://www.npmjs.com/package/next](https://www.npmjs.com/package/next)\n","data":{"id":"clean frontend architecture","title":"클린한 프론트엔드 아키텍처를 향한 첫 걸음","description":"클린한 프론트엔드 아키텍처를 향한 첫 걸음","preview":"변경에 유연한 프론트엔드 아키텍처를 구성하는 하나의 방법과 방법의 근거, 그리고 이를 제어해 줄 수 있는 ESLint의 규칙까지 소개하고자 합니다.","keywords":"react, next, create-react-app, clean architecture, architecture","slug":"clean-frontend-architecture","createdAt":"2022.05.19","published":true},"filePath":"clean-frontend-architecture.mdx"},{"content":"\n자바스크립트는 **느슨한 타입(loosely typed)의 동적(dynamic) 언어**
16이다.\u003cbr/\u003e\n언어에서 사용할 수 있는 데이터 타입의 종류와 몇가지 특징들을 알아보자.\n\n## 데이터 타입 종류\n\n자바스크립트의 데이터 타입은 크게 2가지로 나눌 수 있다.\n\n- **원시형 타입(Primitive type)**\n- **참조형 타입(Reference type)**\n\n위의 2가지를 나누는 기준은 **할당이나 연산시 값을 복제하냐 혹은 참조하냐**라고 볼 수 있겠다.\u003cbr/\u003e\n엄격하게 말하면 사실 둘 다 복제를 하지만, 복제를 하는 대상이 다르다.\u003cbr/\u003e\n이는 아래에서 조금씩 더 다뤄보도록 하겠다.\n\n### 원시형\n\n원시형으로 다뤄지는 타입들은 아래와 같다.\u003cbr/\u003e\n`Number`, `String`, `Boolean`, `null`, `undefined`, `Symbol`, `BigInt`\u003cbr/\u003e\n이들은 일반적으로 **불변성**을 가지는 데이터 타입이라고 표현된다.\u003cbr/\u003e\n\n원시형 타입의 몇 가지의 특징을 살펴보자.\n\n#### 특징\n\n- 값 자체는 **불변**하며 변형이 불가능 하다.\n- 값은 고정된 크기로 메모리에 저장된다. (ex) 숫자의 경우 64비트)\n- 같은 값을 사용한다면 하나의 메모리를 사용한다.\n\n#### 원시형 타입과 메모리\n\n원시형 타입 변수의 할당과 연산시, 메모리의 변화를 파악해보자.\n\n```js\nlet name = '김대현'; // 변수 선언과 값 할당\nname = 'kimcoder'; // 변수 값 재할당\n```\n\n위의 코드에서 `name`이라는 변수 선언시에는 메모리에서 2개의 주소를 할당한다.\u003cbr/\u003e\n하나는 **변수 식별자(변수명)**을 위한 것이고, 하나는 **변수 값(데이터)**를 위한 것이다.\n\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        변수 영역\n      \u003c/th\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1002\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1003\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1004\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름 : name\n        \u003cbr /\u003e 값: @5003\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        데이터 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300 '\u003e5004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e'김대현'\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n1003의 식별자는 `name`이고, 데이터는 `김대현`이라는 값의 메모리 상 주소(`@5003`)이다.\u003cbr/\u003e\u003cbr/\u003e\n이제, 변수 값을 `kimcoder`로 재할당시 변화를 확인해 보자.\n\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}
16\u003e\n        변수 영역\n      \u003c/th\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1002\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1003\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1004\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300 bg-yellow-50'\u003e\n        이름 : name\n        \u003cbr /\u003e 값: @5004\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        데이터 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300 bg-yellow-50'\u003e5004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e'김대현'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300 bg-yellow-50'\u003e'kimcoder'\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n`console.log(name)`으로 확인해보면 `kimcoder`가 출력되므로 의도한대로 변수 값의 변화가 되었다고 보인다!\u003cbr/\u003e\n하지만 실제 `@5003`에 저장된 문자열 값의 변화는 일어나지 않았다. 단지 변수 `name`의 값(메모리 주소)이 `@5003` 에서 `@5004`으로 변한 것이다.\n`name`에, `kimcoder`라는 값을 할당하기 전에 이미 `kimcoder`라는 값을 사용하여서 메모리에 저장이 되어있는 상황이라면 그 주소로 변하게 된다.\n\n만약, `김대현`이란 값을 더 이상 사용하지 않게되면, 가바지 콜렉션의 대상이 된다.\n\n### 참조형\n\n참조형은 포괄적으로 보자면 모든 `Object`라고 볼 수 있는데,\u003cbr/\u003e\n여기에는 `Array`, `Function`, `Date`, `RegExp`, `Map`, `WeakMap`, `Set`, `WeakSet` 등이 있다.\u003cbr/\u003e\n원시형이 아닌 타입들은 모두 참조형이라고 생각하여도 좋다.\u003cbr/\u003e\n\n#### 특징\n\n- 참조형(객체)는 키와 값으로 구성된 프로퍼티들의 집합이다.\n  - 프로퍼티 값으로는 자바스크립트의 모든 값을 사용할 수 있다.\n- 변수에 값을 할당할 때, 값이 아닌 데이터 객체의 주소를 저장한다.\n- 고정된 크기의 메모리를 사용하지 않는다.\n\n#### 참조형 타입 변수의 메모리 구조\n\n참조형도 데이터를 변수에 할당하는 과정이 메모리상에서 어떻게 처리되는지 확인해보자.\n\n```js\nlet data = {\n  name: 'kimcoder',\n  nation: 'korea',\n};\n```\n\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}
16\u003e\n        변수 영역\n      \u003c/th\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1002\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1003\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1004\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1005\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름 : data\n        \u003cbr /\u003e 값: @5002\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        데이터 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5005\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e@7002 ~ ?\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e'kimcoder'\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e'korea'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        객체 @5002의 변수 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7005\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름: name\n        \u003cbr /\u003e\n        값: @5004\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름: nation\n        \u003cbr /\u003e\n        값: @5005\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n원시형 데이터와의 차이는 `객체의 변수(프로퍼티) 영역`이 별도로 존재한다는 것이다. 객체가 별도로 할애한 영역은 변수 영역일 뿐 `데이터 영역`은 기존의 메모리 공간을 그대로 활용 하고 있다.\n또한, 데이터 영역에 저장된 값은 모두 불변값이다(=원시형).\u003cbr/\u003e\n그러나 변수에는 다른 값을 ì
16–¼ë§ˆë“ ì§€ 대입할 수 있으며, 이러한 성질 때문에 흔히 참조형은 불변하지 않다라고 표현하는 것이다.\n\n객체 내 요소의 값 변경시 어떤 변화가 일어나는지도 확인해 보자.\n\n```js\nlet data = {\n  name: 'kimcoder',\n  nation: 'korea',\n};\n\ndata.name = '김대현';\n```\n\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        변수 영역\n      \u003c/th\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1002\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1003\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1004\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1005\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름 : data\n        \u003cbr /\u003e 값: @5002\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        데이터 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300 bg-yellow-50'\u003e5003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5005\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e@7002 ~ ?\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300 bg-yellow-50'\u003e'김대현'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e'kimcoder'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e'korea'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        객체 @5002의 변수 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7005\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300 bg-yellow-50'\u003e\n        이름: name\n        \u003cbr /\u003e\n        값: @5003\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름: nation\n        \u003cbr /\u003e\n        값: @5005\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n변화된 영역을 살펴보자. 변수 `data`가 바라보고 있는 주소는 `@5002`로 변하지 않았다. 다만, 객체 내부 요소의 값만 변경이 되었다.\u003cbr/\u003e\n**객체 내부의 요소는 추가 및 삭제, 수정이 가능하기 때문에 참조형 타입은 가변값이라고 표현**한다.\n\n#### 참조형 객체 복사\n\n```js\nlet otherData = data;\n```\n\n위와 같은 코드가 있다고 생각해보자\n\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}
16\u003e\n        변수 영역\n      \u003c/th\u003e\n      \u003cth className='w-28 p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1002\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1003\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1004\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e1005\u003c/td\u003e\n      \u003ctd className='w-28 p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름 : data\n        \u003cbr /\u003e 값: @5002\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300 bg-yellow-50'\u003e\n        이름 : otherData\n        \u003cbr /\u003e 값: @5002\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        데이터 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e5005\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e@7002 ~ ?\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e'김대현'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e'kimcoder'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e'korea'\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 text-center border border-gray-500 bg-gray-300' rowSpan={2}\u003e\n        객체 @5002의 변수 영역\n      \u003c/th\u003e\n      \u003cth className='p-1.5 text-center border border-gray-300 bg-gray-100'\u003e주소\u003c/th\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7002\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7003\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7004\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e7005\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e...\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth className='p-1.5 border border-gray-300 bg-gray-100'\u003e데이터\u003c/th\u003e\n      \u003ctd className='p-1.5 border border-gray-300 '\u003e\n        이름: name\n        \u003cbr /\u003e\n        값: @5003\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 border border-gray-300'\u003e\n        이름: nation\n        \u003cbr /\u003e\n        값: @5005\n      \u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n      \u003ctd className='p-1.5 text-center border border-gray-300'\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n`otherData`를 위해 변수 영역에 추가로 공간이 할당되었고, 데이터는 그대로 `@5002 객체`를 바라보고 있다.\u003cbr/\u003e\n따라서 원본 `data`의 `name` 속성 값을 변경하면 `otherData`의 `name` 속성 값도 같이 변하게 된다.\n\n위와 같은 성질로 인해 참조형 데이터를 사용할 때, 불변성을 유지하고자 추가적인 처리를 해주는 것이다.\n\n\u003e 대표적인 예시의 상황으로 함수 인자로 전달하고 참조시, 원본 데이터의 값 변경을 막기 위해 추가 처리를 한다.\n\n```js\nlet data = {\n  name: 'kimcoder',\n};\n\ndata = {\n  age: '12',\n};\n```\n\n위와 같이 객체의 프로퍼티가 변경되는 것이 아니라 변수의 데이터 자체가 변경되는 경우에는 해당되지 않는다.\n\n## 마치며\n\n**원시형 타입은 값을 변경할 때 값 자체가 변경되는게 아닌 변수영역의 메모리가 변경되는 것**이다.\u003cbr/\u003e\n**참조형 타입**은 원시형 타입과 달리 변수의 **데이터가 프로퍼티의 집합으로 구성**되어 있다.\u003cbr/\u003e\n내부 프로퍼티의 값의 변경이 일어날 데이터 객체는 변경되지 않은 상태로 내부 프로퍼티만 변경된다.\u003cbr/\u003e\n사용할 때는 별도의 불변성 처리를 위한 코드를 작성해야 하는 것이 좋다.\u003cbr/\u003e\n\n## References\n\n- https://developer.mozilla.org/ko/docs/Web/JavaScript/Data_structures\n","data":{"id":"자바스크립트의 데이터 타입과 메모리","title":"자바스크립트의 데이터 타입과 메모리","description":"자바스크립트에서 사용할 수 있는 데이터 타입의 종류와 몇가지 특징들을 알아봅니다.","preview":"자바스크립트에서 사용할 수 있는 데이터 타입의 종류와 몇가지 특징들을 알아봅니다.","keywords":"javascript, data type, primitive type, reference type, memory","slug":"javascript-data-types","createdAt":"2022.02.19","published":true},"filePath":"javascript-data-types.mdx"},{"content":"\n자바스크립트 개발을 하는 많은 조직과 팀들이 코드 정적 분석 도구로 `eslint`를 사용 중에 있을 것이다.\u003cbr/\u003e\n또한, 여러 코드 컨벤션을 만들고 이를 준수하고자 많은 노력들을 하고 있을거라고 생각한다.\u003cbr/\u003e\n코드 정적 분석이 CI/CD 단계에 자동화되어 자연스럽게 녹아있으면 더할 나위 없이 좋을 것이다.\u003cbr/\u003e\n여러 컨벤션 중에서도 **`import`구문의 정렬** 규칙 적용의 이점에 대해 소개해보고자 한다.\n\n## import rule?\n\n`import` 구문의 **정렬 규칙**에 대한 조건들은 아래 내용들을 제어한다.\n\n- 임포트 구문들에 대해 순서 적용을 할 것이냐.\n- 임포트 구문의 멤버 변수들에 대해 순서 적용을 할 것이냐.\n- 임포트 구문 그룹에 대한 순서와 이를 공백을 통해 분리할 것이냐.\n  - 그룹화에 설정은 `eslint`의 기본 규칙, 플러그인마다 다르다.\
16n\n여기서 순서는 `alphabetize` 되는 것을 말하며, [eslint의 기본 규칙인 sort-imports](https://eslint.org/docs/rules/sort-imports) 외에 정렬에 관련된 여러 플러그인들의 제어 조건들도 유사하다.\n\n## 컨벤션과 적용\n\n팀내에 아래와 같은 컨벤션이 있다고 가정해 보자.\n\n### 컨벤션\n\n| 우선순위 | 임포트 그룹                                   | 비고                                                          |\n| -------- | --------------------------------------------- | ------------------------------------------------------------- |\n| 1        | react, next.js와 같은 메인 프레임워크 모듈    | react-router와 같은 react에 의존이 있는 외부 모듈도 포함한다. |\n| 2        | node_modules에 있는 외부 모듈                 |                                                               |\n| 3        | node_modules에 있지만 팀에서 개발한 외부 모듈 | ex) 디자인시스템, 플랫폼 관련 모듈                            |\n| 4        | 절대 경로로 임포트한 프로젝트 내 모듈         |                                                               |\n| 5        | 상대 경로로 임포트한 프로젝트 내 모듈         | ex) 이미지, CSS                                               |\n\n- 위와 같이 임포트 구문들을 그룹화하고 우선 순위대로 정렬한다.\n- 각 그룹 사이에는 개행 문자를 넣는다.\u003cbr/\u003e\n\n코드로 표현된 모습은 아래와 같을 것이다.\n\n```js\nimport { useEffect } from 'react';\nimport { Link } from 'react-router-dom';\n\nimport A from 'moduleA';\nimport B from 'moduleB';\n\nimport { Button, ButtonProps } from '@MyDesignSystem/Button';\n\nimport Thumbnail from 'src/components/Thumbnail';\n\nimport image from './a.png';\nimport style from './style.module.css';\n```\n\n위와 같은 컨벤션은 그룹화를 커스터마이즈할 수 있는 플러그인을 추가로 설치해야 한다.\u003cbr/\u003e\n이 글에서는 [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)를 예시로 하겠다.\n\n### 적용\n\n아래와 같이 `import` 컨벤션에 대한 내용을 `eslint` 설정에 추가한다.\u003cbr/\u003e\n설정에 대한 자세한 내용은 [import/order의 문서](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md)에서 참조할 수 있다.\n\n```js title=\".eslintrc\"\n{\n  // ...\n  \"plugins\": [ /*... ,*/ \"import\"],\n  \"rules\": {\n    \"sort-imports\": [\n      \"error\",\n      {\n        \"ignoreCase\": true,\n        \"ignoreDeclarationSort\": true,\n        \"ignoreMemberSort\": false,\n        \"allowSeparatedGroups\": true\n      }\n    ],\n    \"import/order\": [\n      \"error\",\n      {\n        \"newlines-between\": \"always\",\n        \"groups\": [[\"builtin\", \"external\"], \"internal\", \"parent\", \"sibling\", \"index\"],\n        \"pathGroups\": [\n          {\n            \"pattern\": \"next\",\n            \"group\": \"builtin\"\n          },\n          {\n            \"pattern\": \"react\",\n            \"group\": \"builtin\"\n          },\n          {\n            \"pattern\": \"@MyDesignSystem/**\",\n            \"group\": \"internal\"\n          },\n          {\n            \"pattern\": \"src/**\",\n            \"group\": \"internal\"\n          },\n        ],\n        \"pathGroupsExcludedImportTypes\": [\"src/**\", \"@MyDesignSystem/**\"],\n        \"alphabetize\": {\n          \"order\": \"asc\",\n          \"caseInsensitive\": true\n        }\n      }\n    ]\n  }\n}\n```\n\n### 적용의 이점\n\n필자가 생각하는 컨벤션 적용의 이점은 **코드 리뷰시 diff가 발생하지 않는다**는 점이다.\u003cbr/\u003e\n물론, `import 구문`이 정말로 변경된 것이라면 합당한 diff가 발생하는 것이 맞겠지만,\u003cbr/\u003e\n여러명과 협업하는 경우에는 개인마다 사용하는 IDE도 다르고, 설정 값들이 다 다를 수 있다.\u003cbr/\u003e\n**이로인해 코드리뷰시 의도치 않은 diff를 보여주고 또 봐야할 수 있는 상황이 발생할 수 있다.**\u003cbr/\u003e\n\n아래 코드리뷰 이미지를 확인해보자.\n\n\u003cimg src='/assets/images/eslint-import-example.png' alt='컨벤션 룰이 적용 안 된 코드 리뷰 예제' /\u003e\n\u003cbr /\u003e\n개발자 A가 아래와 같은 순서로 작업을 하였다고 가정해 보자.\n\u003cbr /\u003e\n\u003cbr /\u003e\n\n1. 커밋 1에서 `useCallback`을 사용을 하지 않게 되어, 해당 import member 삭제.\n2. 커밋 3에서 다시 `useCallback`를 사용하게 됨.\n3. 정렬되지 않은 채 import member가 추가 됨.\n4. import한 멤버들은 변함이 없는 상태이지만 정렬이 바뀌었다는 이유로 **불필요한 diff 발생**.\n5. ( ImagePreLoader도 비슷한 맥락에서 순서가 변경 됨. )\n\n\u003cbr /\u003e\n예시를 위해 극단적인 상황을 만든 것으로 보일 수도 있으나,\n\u003cbr /\u003e\n단순히 개인 IDE 설정 값이 실행되어 정렬 ìˆ
16œì„œê°€ 언제나 바뀔 수 있는 상황도 있다.\n\u003cbr /\u003e\n\n## 마치며\n\n사실, `import` 구문 순서가 비지니스 로직에 영향을 끼친다거나 예기치 못한 상황을 발생시킬 일은 없다고 생각한다.\u003cbr/\u003e\n순서는 중요하지 않다.\u003cbr /\u003e\n생산성이 좋은 개발 문화를 지향한다면, `import` 구문 정렬에 대한 규칙도 lint할 수 있도록 하자!\n\n## References\n\n- https://eslint.org/docs/rules/sort-imports\n- https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md\n","data":{"id":"eslint import/order","title":"eslint import rule 설정의 이점","description":"import구문의 정렬 규칙 적용의 이점에 대해 소개합니다.","preview":"import구문의 정렬 규칙 적용의 이점에 대해 소개합니다.","keywords":"javascript, eslint, import rule, eslint-plugin-import","slug":"eslint-import-order","createdAt":"2022.02.07","published":true},"filePath":"eslint-import-order.mdx"},{"content":"\n`enum`은 자바스크립트의 타입 레벨에서는 존재하지 않지만 타입스크립트에서는 존재하는 몇 안되는 기능 중 하나이다.\n흔히 프로그래밍 중 연관된 상수들을 묶을때 열거형이라고 불리우는 `enum`을 통해 표현하고 한다.\n\n```ts\nenum Direction {\n  Up,\n  Down,\n  Left,\n  Right,\n}\n\nfunction moveTo(direction: Direciton) {\n  if (direction === Direciton.UP) {\n    // Do something..\n  }\n\n  // Do something..\n}\n```\n\n타입스크립트로 개발을 하다보면 위와 같은 코드를 흔히 작성하게 된다.\u003cbr/\u003e\n앞서 말한것 처럼, `enum`은 자바스크립트에서는 존재하지 않기 때문에 타입스크립트로 `enum`을 사용했을 때, 이 코드가 컴파일이 된 후 자바스크립트에서는 어떤식으로 표현이 되는지 고찰을 해보고 사용하는 것이 좋겠다.\n\n## Enum의 특징과 분류\n\n- Enum은 값으로 아래와 같은 값들을 가질 수 있다.\n  - 문자열 리터럴 ( e.g. `'hello'`, `'a'`, `'bye'` )\n  - 숫자 리터럴 ( e.g. `0`, `1`, `0.2`, `1000` )\n  - 부호가 있는 숫자( 음수 ) 리터럴 ( e.g. `-1`, `-0.2`, `-1000` )\n- 이 값들은 또한, 모두 계산되어진 값들이어야 한다.\n\n### Numeric enums\n\n- `numeric enum`의 멤버는 초기값을 할당해 주지 않으면, 기본적으로 **ê·¸ 전의 멤버로부터 1씩 증가되는 숫자가 값으로 할당**된다.\n- 첫 멤버가 값을 할당받지 않았다면 `0`을 값으로 가지게 된다.\n\n아래 코드를 ë³´ë©´ 확실하게 이해할 수 있다.\n\n```ts\nenum Direction {\n  Up,    // 0\n  Down,  // 1\n  Left,  // 2\n  Right, // 3\n}\n\nenum Speed {\n  VeryLow = -1,     // -1\n  Low,              // 0\n  Normal = -0.2,    // -0.2\n  Fast,             // 0.8\n  VeryFast          // 1.8\n  SuperFast = 100,  // 100\n  UltraFast         // 101\n}\n```\n\n**`numeric enum`은 컴파일 시, `reverse mapping`이 된다.**\n\n#### after compile\n\n```ts\n// before compile\nenum Direction {\n  Up,\n  Down,\n  Left,\n  Right,\n}\n\nlet up = Direction.Up; // 0\nlet nameOfUp = Direction[up]; // 'Up'\n\n// after compile\nvar Direction;\n(function (Direction) {\n  Direction[(Direction['Up'] = 0)] = 'Up';\n  Direction[(Direction['Down'] = 1)] = 'Down';\n  Direction[(Direction['Left'] = 2)] = 'Left';\n  Direction[(Direction['Right'] = 3)] = 'Right';\n})(Direction || (Direction = {}));\n```\n\n```ts\nconsole.log(Direciton);\n\n{0: 'Up', 1: 'Down', 2: 'Left', 3: 'Right', Up: 0, Down: 1, Left: 2, Right: 3}\n```\n\n- reverse mapping이 된 후의 실제 `Direction`은 위와 같은 형태를 이룬다.\n- **숫자를 값으로 가지는 enum의 멤버들은 모두 `reverse mapping`이 된다.**\n\n### String enums\n\n- `string enum`의 멤버들은 모두 초기 값을 할당 받아야 한다.\n- `numeric enum`처럼 자동 증가되는 값을 가지지 않지만, 직렬화가 잘 된다는 장점이 있다.\n  - 멤버의 이름에 관계 없이, **코드 동작시 의미 있고 읽을 수 있는 값을 제공할 수 있다.**\n\n```ts\nenum Direction {\n  Up = 'UP',\n  Down = 'Down',\n  Left = 'Left',\n  Right = 'Right',\n}\n```\n\n#### after compile\n\n```ts\nvar Direction;\n(function (Direction) {\n  Direction['Up'] = 'UP';\n  Direction['Down'] = 'Down';\n  Direction['Left'] = 'Left';\n  Direction['Right'] = 'Right';\n})(Direction || (Direction = {}));\n```\n\n### Heterogeneous enums\n\n- 기술적으로 `enum`은 `string enum`ê³¼ `numeric enum`의 멤버가 혼합이 될 수 있지만, 목적이 불분명한 코드가 될 수 있다.\n- 자바스크립트 런타임의 동작을 영리한 방법으로 활용하는 이유가 아니라면 **사용하지 않는 것을 권장한다.**\n- 혼합하여 사용할때도 마찬가지로 `numeric enum`의 자동 증가되는 값 할당, `reverse mapping` 등의 특징은 동일하다.\n\n```ts\nenum Direction {\n  Up,\n  Down = 'Down',\n  Left = 1,\n  Right,\n}\n```\n\n#### after compile\n\n```ts\nvar Direction;\n(function (Direction) {\n  Direction[(Direction['Up'] = 0)] = 'Up';\n  Direction['Down'] = 'Down';\n  Direction[(Direction['Left'] = 1)] = 'Left';\n  Direction[(Direction['Right'] = 2)] = 'Right';\n})(Direction || (Direction = {}));\n```\n\n### Declaration Merging\n\n- `enum`도 **declaration merging(선언 병합)**의 대상이 된다.\n- `numeric enum`의 선언 병합시, 2번째 enum은 초기 값을 항상 가져야 한다.\n\n```ts\nenum Direction {\n  Up,\n  Down,\n  Left,\n}\n\nenum Direciton {\n  Right = 4,\n}\n```\n\n#### after compile\n\n```ts\nvar Direction;\n(function (Direction) {\n  Direction[(Direction['Up'] = 0)] = 'Up';\n  Direction[(Direction['Down'] = 1)] = 'Down';\n  Direction[(Direction['Left'] = 2)] = 'Left';\n})(Direction || (Direction = {}));\n(function (Direction) {\n  Direction[(Direction['Right'] = 4)] = 'Right';\n})(Direction || (Direction = {}));\n\nconsole.log(Direciton);\n\n{0: 'Up', 1: 'Down', 2: 'Left', 3: 'Right', Up: 0, Down: 1, Left: 2, Right: 3}\n```\n\n### Immediately invoked function expression\n\n- 모든 `enum`은 컴파일 후 **즉시실행 함수 표현식(IIFE)**의 형식으로 코드가 변환된다.\n- rollupê³¼ 같은 번들러는 IIFE의 사용 여부를 판단할 수 없기 때문에, Tree-shaking 되지 않는다.\n  - `enum`이 죽은 코드가 되더라도 컴파일된 코드의 번들에는 존재하게 된다.\n  - [여기서](https://rollupjs.org/repl/?version=2.26.11\u0026shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMCU3QkRpcmVjdGlvbiU3RCUyMGZyb20lMjAnLiUyRmVudW0uanMnJTVDbmltcG9ydCUyMCU3QmhlbGxvJTdEJTIwZnJvbSUyMCcuJTJGdHJlZXNoYWtlZCclNUNuaW1wb3J0JTIwJTdCaGklN0QlMjBmcm9tJTIwJy4lMkZub3RUcmVlc2hha2VkJyU1Q24lNUNuY29uc29sZS5sb2coaGVsbG8pJTVDbiUyMiUyQyUyMmlzRW50cnklMjIlM0F0cnVlJTdEJTJDJTdCJTIybmFtZSUyMiUzQSUyMmVudW0uanMlMjIlMkMlMjJjb2RlJTIyJTNBJTIyJ3VzZSUyMHN0cmljdCclNUNuZXhwb3J0JTIwdmFyJTIwRGlyZWN0aW9uJTVDbiUzQi
16hmdW5jdGlvbiUyMChEaXJlY3Rpb24pJTIwJTdCJTVDbiUyMCUyMERpcmVjdGlvbiU1QidVcCclNUQlMjAlM0QlMjAnVVAnJTVDbiUyMCUyMERpcmVjdGlvbiU1QidEb3duJyU1RCUyMCUzRCUyMCdET1dOJyU1Q24lMjAlMjBEaXJlY3Rpb24lNUInTGVmdCclNUQlMjAlM0QlMjAnTEVGVCclNUNuJTIwJTIwRGlyZWN0aW9uJTVCJ1JpZ2h0JyU1RCUyMCUzRCUyMCdSSUdIVCclNUNuJTdEKShEaXJlY3Rpb24lMjAlN0MlN0MlMjAoRGlyZWN0aW9uJTIwJTNEJTIwJTdCJTdEKSklMjIlN0QlMkMlN0IlMjJuYW1lJTIyJTNBJTIydHJlZXNoYWtlZC5qcyUyMiUyQyUyMmNvZGUlMjIlM0ElMjJleHBvcnQlMjB2YXIlMjBoZWxsbyUyMCUzRCUyMCdoZWxsbyclMjIlN0QlMkMlN0IlMjJuYW1lJTIyJTNBJTIybm90VHJlZXNoYWtlZC5qcyUyMiUyQyUyMmNvZGUlMjIlM0ElMjJleHBvcnQlMjB2YXIlMjBoaSUyMCUzRCUyMCdoaSclMjIlN0QlNUQlMkMlMjJvcHRpb25zJTIyJTNBJTdCJTIyZm9ybWF0JTIyJTNBJTIyZXMlMjIlMkMlMjJuYW1lJTIyJTNBJTIybXlCdW5kbGUlMjIlMkMlMjJhbWQlMjIlM0ElN0IlMjJpZCUyMiUzQSUyMiUyMiU3RCUyQyUyMmdsb2JhbHMlMjIlM0ElN0IlN0QlN0QlMkMlMjJleGFtcGxlJTIyJTNBbnVsbCU3RA==) 직접 확인할 수 있다.\n\n## const enum\n\n- `enum`의 추가 코드 생성과 간접 참조 코드를 피하고 싶으면 `const enum`을 사용하는게 방안이 될 수 있다.\n- `const enum`은 컴파일 중에 완전히 제거되고, 참조하는 코드영역에서 값만 인라인되어진다.\n\n```ts\nconst enum Direction {\n  Up,\n  Down,\n  Left,\n  Right,\n}\n\nconsole.log(Direction.Up);\n```\n\n위와 같은 코드가 컴파일이 되면, 아래와 같이 인라인된 코드만 남게 된다.\n\n```ts\nconsole.log(0 /* Up */);\n```\n\n기본 `enum`ê³¼ 비교했을 때, 컴파일 후에는 많은 차이가 생기는 것으로 ë³¼ 수 있다.\u003cbr/\u003e\n불필요한 코드들이 생성되지 않아, 메모리와 번들링 된 코드의 사이즈가 조금 더 줄어든다는 이점이 있다.\u003cbr/\u003e\n하지만 `d.ts` 파일을 생성해야하거나, 라이브러리로써 제공해야 될 경우에는 타입추론이 불가하다는 단점도 있다.\u003cbr/\u003e\n이럴 경우엔, `tsconfig`의 [preserveConstEnums](https://www.typescriptlang.org/tsconfig#preserveConstEnums) 옵션을 `true`로 설정하여 해결할 수 있다.\u003cbr/\u003e\u003cbr/\u003e\n\n또, `tsconfig`의 [isolatedModules](https://www.typescriptlang.org/tsconfig#isolatedModules) 옵션을 `true`로 설정하고\n`cosnt enum`을 사용하면 컴파일시 오류가 나게 되니, 사용 전에 이러한 부분을 ê¼­ 숙지하여야 한다.\n\n## Object Literal const assertions\n\n타입스크립트 3.4 버전에 나온 **const assertions**을 사용하면 `enum`을 사용하지 않아도 동일하게 상수들을 열거하는 표현을 쉽게 할 수 있다.\n\n```ts\nconst Direction = {\n  Up: 'Up',\n  Down: 'Down',\n  Left: 'Left',\n  Right: 'Right',\n} as const;\n\ntype EnumerateDirection = typeof Direction[keyof typeof Direction];\n// EnumerateDirection = 'Up' | 'Down' | 'Left' | 'Right'\n\nfunction moveTo(direction: EnumerateDirection) {\n  if (direction === 'Up') {\n    // Do something..\n  }\n}\n```\n\n위의 코드와 같이 타입을 추론하기 위해 type aliasing을 한 번 해주어야하는 번거로움이 있지만, `enum`으로부터 얻을 수 없는 몇 가지의 장점이 존재한다.\n\n1. 컴파일ì
16„ 하면 타입스크립트에서 작성한 형식이 유지된 채로 자바스크립트로 변환된다.\n2. Tree-shaking이 적용된다.\n3. (의도하지 않은)선언 병합의 대상이 되지 않는다.\n\n#### after compile\n\n```ts\nconst Direction = {\n  Up: 'Up',\n  Down: 'Down',\n  Left: 'Left',\n  Right: 'Right',\n};\n\n// EnumerateDirection = 'Up' | 'Down' | 'Left' | 'Right'\nfunction moveTo(direction) {\n  if (direction === 'Up') {\n    // Do something..\n  }\n}\n```\n\n## 마치며\n\n위의 내용을 요약하자면,\n\n1. `enum` 멤버들의 값은 계산된 문자열 혹은 숫자만 할당될 수 있다.\n2. `enum`도 선언 병합이 가능하다.\n3. `numeric enum`은 컴파일 후 `reverse mapping`이 된다.\n4. `enum`은 IIFE로 변환되기 때문에 Tree-shaking이 되지 않는다.\n5. `const enum`은 해당 값을 참조하는 부분의 코드만 인라인된다.\n6. TS 3.4 이상의 버전은 객체 리터럴을 `const assertions` 해주는 것으로 상수의 열거를 표현할 수 있다.\n\n## References\n\n- https://www.typescriptlang.org/docs/handbook/enums.html\n- https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions\n","data":{"id":"enum","title":"Enum에 대한 ê³ ì°°","description":"Typescript의 Enum에 대해 알아봅니다.","preview":"Typescript의 Enum에 대해 알아봅니다.","keywords":"typescript, javascript, enum, reverse mapping","slug":"enum","createdAt":"2022.01.21","published":true},"filePath":"enum.mdx"},{"content":"\n\u003e **Kent C. Dodds**의 [JavaScript Pass By Value Function Parameters](https://kentcdodds.com/blog/javascript-pass-by-value-function-parameters) 글을 번역하였습니다.\n\n왜 아래 코드가 동작하지 않을까요?\n\n````js\nfunction getLogger(arg) {\n  function logger() {\n    console.log(arg);\n  }\n  return logger;\n}\n\nlet fruit = 'raspberry';\nconst logFruit = getLogger(fruit);\n\nlogFruit(); // \"raspberry\"\nfruit = 'peach';\nlogFruit(); // \"raspberry\" Wait what!? Why is this not \"peach\"?```\n````\n\n여기서 일어나고 있는 일을 이야기하기 위해, 나는 fruit이라는 변수를 생성했고 문자열 raspberry를 할당했다.\n그런 다음, 나는 호출되었을 때 fruit를 출력하는 logger라고 불리는 함수를 생성하고 반환해주는 함수에 fruit를 전달했다.\n함수를 호출했을 때, 예상대로 raspberry를 console.log 출력 값으로 받는다.\u003cbr/\u003e\n\n하지만, 나는 fruit에 peach를 재할당했고 logger를 다시 호출했다. fruit의 새로운 값을 console.log의 출력으로 얻는 것 대신에, fruit의 이전 값을 얻었다!\ngetLogger를 다시 호출하여 새로운 logger를 얻는 것으로 이것을 피할 수 있다.\n\n```js\nconst logFruit2 = getLogger(fruit);\nlogFruit2(); // \"peach\" what a relief...\n```\n\n하지만 왜 단순히 변수의 값을 바꾸고, logger가 마지막 값을 가져올 수 없는 이유가 뭘까?\u003cbr/\u003e\n\n정답은 자바스크립트 안에 있다. 함수를 인자와 함께 호출할 때, 인자는 참조가 아닌 값으로 전달된다는 것이다. 여기서 무슨 일이 일어나는지 간단하게 설명하겠다.\n\n````js\nfunction getLogger(arg) {\n  function logger() {\n    console.log(arg);\n  }\n  return logger;\n}\n\n// side-note, this could be written like this too\n// and it wouldn't make any difference whatsoever:\n// const getLogger = arg =\u003e () =\u003e console.log(arg)\n// I just decided to go more verbose to keep it simple```\n````\n\ngetLogger가 호출될 때, logger 함수는 생성된다. 이것은 새로운 함수이다. 새로운 함수가 생성될 때, 이것은 접근이 가능한 모든 변수를 둘러보고, closure라고 불리우는 형태로 닫혀진다. 이것은 이 logger 함수가 존재하는동안, 다른 모듈 레벨의 변수들과 부모의 함수 안에 있는 변수들에 접근이 가능하다는 것을 의미한다.\u003cbr/\u003e\n\n그래서 logger가 생성될 때, 접근이 가능한 변수가 무엇일까? 예제 코드를 다시 보자. 이것은 fruit, getLogger, arg, logger(자기 자신)에 접근할 수 있다. 목록을 다시 확인해라. 왜냐하면 코드가 동작하는 방법에 매우 중요하기 때문이다. 뭔가 눈치채셨나요? fruit와 argê³¼ 모두 동일한 값임에도 불구하고 둘 다 나열되었다!\u003cbr/\u003e\n\n단지 두 개의 변수는 같은 값이 할당 되었지, 같은 변수를 의미하는 것은 아니다. 이 컨셉의 간단한 예제가 여기 있다:\n\n```js\nlet a = 1;\nlet b = a;\n\nconsole.log(a, b); // 1, 1\n\na = 2;\nconsole.log(a, b); // 2, 1 ‼️\n```\n\nb가 변수 a의 값을 가리키게 만들었음에도 불구하고 변수 a의 값을 바꿀 수 있었고, b가 가리키고 있는 값은 변하
16지 않았음을 주의해라. 이것은 b를 ê·¸ 자체로 가리키지 않았기 때문이다. 우리는 ê·¸ 당시에 a가 가리켰던 값을 b가 가리키도록 했었다!\u003cbr/\u003e\n\n나는 변수들을 컴퓨터의 메모리에 있는 공간을 가리키는 작은 화살표로 생각하는 것을 좋아한다. 그래서 let a = 1라고 말하면, 우리는 \"자바스크립트 엔진아, 나는 메모리에 값이 1인 장소를 만들고, 메모리의 해당 장소를 기리키는 a라고 부르는 화살표를 생성하기를 원해.\"라고 말한다.\u003cbr/\u003e\n\n같은 방법으로, 우리가 함수를 호출할 때 자바스크립트 엔진은 함수 인자를 위해 새로운 변수를 생성한다. 우리의 경우에, getLogger(fruit)를 호출했고 자바스크립트 엔진은 기본적으로 이것을 수행했다:\n\n```js\nlet arg = fruit;\n```\n\n그래서 나중에 fruit = 'peach'를 수행할 때, arg는 fruit과는 완전히 다른 변수이기 때문에 영향을 미치지 않는다.\u003cbr/\u003e\n\n이것을 한계로 생각하든 기능으로 생각하든, 사실은 이것이 동작하는 방식이라는 것이다. 만약, 2개의 변수를 각자 최신으로 유지하고 싶다면, 그렇게 하는 방법이 있다! 자. 아이디어는 이와 같다: 화살표가 가리키는 위치를 바꾸는 것 대신에 가르키는 것을 바꿀 수 있다! 예를 들어:\u003cbr/\u003e\n\n```js\nlet a = { current: 1 };\nlet b = a;\n\nconsole.log(a.current, b.current); // 1, 1\n\na.current = 2;\nconsole.log(a.current, b.current); // 2, 2 🎉\n```\n\n이 경우에는, a를 재할당하는 것아 이나리, 가리키고 있는 값을 변경하였다. 그리고 b는 같은 값을 가리키고 있기 때문에, 둘 다 업데이트되었다.\n\n그럼, 우리의 logger 문제를 위해 이 해결책을 적용해보자:\n\n```js\nfunction getLatestLogger(argRef) {\n  function logger() {\n    console.log(argRef.current);\n  }\n  return logger;\n}\n\nconst fruitRef = { current: 'raspberry' };\n\nconst latestLogger = getLatestLogger(fruitRef);\n\nlatestLogger(); // \"raspberry\"\nfruitRef.current = 'peach';\nlatestLogger(); // \"peach\" 🎉\n```\n\n## ê²°ë¡ \n\nRef 접미사는 변수가 가리키고 있는 값이 단순히 다른 값을 참조하는데 사용됨을 말해주는 reference의 줄임말이다. ( 여기서는 오브젝트의 current 속성 값이다. )\u003cbr/\u003e\n\n이것은 자연스럽게 절충점이 있지만, 나는 자바스크립트 명세가 함수의 인자들을 참조가 아닌 값으로 전달하여 호출하는 것에 만족한다.\n그리고 해결방법은 필요할 때 큰 문제가 되지 않는다. (가변성은 프로그램을 보통 더 이해하기 어렵게 만들기 때문에 매우 드물다.) 도움이 되었기를 바란다! 굿 럭!\n\n## References\n\n- https://kentcdodds.com/blog/javascript-pass-by-value-function-parameters\n","data":{"id":"JavaScript Pass By Value Function Parameters","title":"JavaScript Pass By Value Function Parameters","description":"Kent C. Dodds의 JavaScript Pass By Value Function Parameters 글을 번역하였습니다.","preview":"Kent C. Dodds의 JavaScript Pass By Value Function Parameters 글을 번역하였습니다.","keywords":"javascript, kent.c.dodds, translate, pass by value","slug":"javascript-pass-by-value-function-parameters","createdAt":"2022.01.18","published":true},"filePath":"javascript-pass-by-value-function-parameters.mdx"},{"content":"\n타입스크립트에서는 아래 2가지 방식으로 타입 정의를 할 수 있다.\u003cbr/\u003e\n\n- **Interface**\n- **Type Aliases**\n\n타입 정의를 하고 추론하는데에 있어 어떤 방식을 사용해도 기능 구현에 문제가 없을테지만,\u003cbr/\u003e\n이 방식들이 어떤 특징을 가지고 있고 방식간 차이점이 무엇인지 알아보자.\n\n## Interfaces\n\n- **Interface 선언은 Object Type의 이름을 지정하는 또 다른 방식이다.**\n  \u003e 이는 Object Type으로 표현 가능한 타입에 대해서만 interface로의 선언이 가능하다는 것을 의미한다.\n\n```ts\nfunction track(point: { x: number; y: number }) {\n  // ..\n}\n\ninterface Point {\n  x: number;\n  y: number;\n}\n\nfunction track(point: Point) {\n  // ..\n}\n```\n\n위의 예제에서와 같이 `{ x: number, y: number }`를 `interface Point`로 선언하여 대체할 수 있다.\n\n### Extending\n\n- `interface`는 `extends` 키워드를 통해 **타입의 확장**이 가능하다.\n\n```ts\ninterface Point {\n  x: number;\n  y: number;\n}\n\ninterface Point3D extends Point {\n  z: number;\n}\n\n// 선언된 interface는 class가 구현해야 할 인터페이스 혹은 객체의 타입으로 사용이 가능하다.\nclass Pointer implements Point {\n  x = 0;\n  y = 1;\n\n  //...\n}\n\nconst pointB: Point3D = {\n  x: 0,\n  y: 1,\n  z: 2,\n};\n\nfunction getPointer({ x, y }: Point) {\n  return new Pointer({ x, y });\n}\n```\n\n### Declaration Merging\n\n- `interface`는 동일한 이름으로 추가 선언ì
16„ 통해 **병합**이 가능하다.\n  - 여기서 동일한 이름의 선언한 `interface` 갯수는 중요하지 않다. 모두 병합이 된다.\n\n```ts\ninterface Point {\n  x: number;\n  y: number;\n}\n\ninterface Point {\n  z: number;\n}\n\nconst pointA: Point = {\n  x: 0,\n  y: 1,\n  z: 3,\n};\n```\n\n이를 **선언 병합(Declaration Merging)**이라고 한다.\u003cbr/\u003e\n\n\u003e 선언 병합은 interface 뿐만아니라 namespace, enum도 같은 방식으로 선언 병합이 된다.\u003cbr/\u003e\u003cbr/\u003e\n\u003e\n\u003e namespace는 class, function, enum, interface, type aliasing과도 이름이 같다면 선언 병합이 된다.\n\n```ts\ninterface Point {\n  x: number;\n  y: number;\n  getDistance: (x: number) =\u003e number;\n}\n\n// Error: Property 'x' must be of type 'number';\ninterface Point {\n  x: string;\n}\n```\n\n선언 병합시, 같은 이름으로 되어있는 속성 중복된다면, 타입이 변해서는 안된다.\u003cbr/\u003e\n[개방 폐쇄 원칙](https://ko.wikipedia.org/wiki/%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84_%EC%9B%90%EC%B9%99)을 준수한 것이라고 생각해도 좋을 것 같다.\n\n```ts\ninterface Point {\n  x: number;\n  y: number;\n  equal(x: number, y: number): number;\n}\n\ninterface Point {\n  equal({ x, y }: { x: number; y: number }): number;\n}\n```\n\n또한, 선언 병합시, [함수 오버로드](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads)도 위와 같이 가능하다.\n\n## Type Aliases\n\n- **Type Aliasing은 특정 타입에 대해 다른 이름을 지어주는 것이다.**\n- 타입 종류에 대한 제약은 없다.\n\n```ts\ntype Point = {\n  x: number;\n  y: number;\n};\n\ntype Name = string;\n\ntype ID = string | number;\n\ntype SomeFunction = () =\u003e any;\n```\n\n위와 같이 `string`ê³¼ 같이 자바스크립트의 원시타입에 대해 별칭을 할 수 있으며, `Object Type`, `Union Type`에 대해서도 별칭이 가능하다.\n\n\u003e Union Type은 2가지 이상의 다른 타입으로 구성된 타입을 말한다.\n\n- **Type Aliasing은 단지 다른 이름일 뿐이다.**\n\n```ts\ntype UserInputSanitizedString = string;\n\nfunction sanitizeInput(str: string): UserInputSanitizedString {\n  return sanitize(str);\n}\n\nlet userInput = sanitizeInput(getInput());\n\n// 문자열 할당\nuserInput = 'new input';\n```\n\n\u003e `UserInputSanitizedString`는 `string`의 다른 이름이기 때문에 결과적으로는 `string`ê³¼ 동일한 타입으로 추론이 되어,\u003cbr/\u003e\n\u003e 위와 같이 `UserInputSanitizedString`의 타입으로 추론되는 변수에 다른 문자열의 할당이 가능하다.\n\n### Extending\n\n- `Type Aliasing`는 `intersection`을 통해 **타입의 확장**이 가능하다.\n\n```ts\ntype Animal = {\n  name: string;\n};\n\ntype Bear = Animal \u0026 {\n  honey: boolean;\n};\n\nconst bear = getBear();\nbear.name;\nbear.honey;\n```\n\n- `Type Aliasing`은 선언 병합(Declaration Merging)이 되지 않는다.\n  - 따라서, 동일한 이름의 `Type`에 새로운 속성을 추가 할 수 없다.\n  - 이는 `interface`와 가장 큰 차이라고 ë³¼ 수 있다.\n\n```ts\ntype Window = {\n  title: string;\n};\n\ntype Window = {\n  ts: TypeScriptAPI;\n};\n// Error: Duplicate identifier 'Window'.\n```\n\n### Use with extends, implements\n\n- `Type Aliasing`은 Object Type일 경우에 `Extends`, `implements` 키워드와도 같이 사용이 가능하다.\n\n```ts\ntype Point = {\n  x: number;\n  y: number;\n};\n\ninterface Point3D extends Point {\n  z: number;\n}\n\nclass Pointer implements Point {\n  x = 0;\n  y = 1;\n\n  //...\n}\n```\n\n## 마치며\n\n타입 정의하는데 2가지의 방법과 각 특징들에 대해 알아보았다.\u003cbr/\u003e\n가장 주된 차이점은 **어떤 타입을 정의할 수 있는가**와 **선언 병합이 되는가**라고 생각한다.\u003cbr/\u003e\n타입스크립트 공식 문서에는 둘 중 하나의 **선택은 개인의 취향이고, 직접적인 경험을 통해 판단하고자 한다면, `type`의 기능에 대한 필요성을 느끼기 전까지 먼저 `interface`를 사용**하라ê
16³  한다.\n\n## References\n\n- https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces\n- https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases\n- https://www.typescriptlang.org/docs/handbook/declaration-merging.html\n","data":{"id":"Interfaces vs Type Aliases","title":"Interfaces vs Type Aliases","description":"Typescript의 Interface와 Type Aliasing에 대해 알아봅니다.","preview":"Typescript의 Interface와 Type Aliasing에 대해 알아봅니다.","keywords":"typescript, javascript, interface, type aliasing","slug":"interface-vs-type","createdAt":"2022.01.03","published":true},"filePath":"interface-vs-type.mdx"},{"content":"\n웹 페이지 성능 향상 방법 중 하나인 이미지 최적화.\u003cbr/\u003e\n웹에서 자주 사용되어지는 이미지에 대해 알아보고, 최적화 원리와 방식에 대해 알아보자.\n\n## Image\n\n이미지 파일 형식은 **디지털 이미지를 구성하고 저장하는 표준적인 방법**을 의미한다.\u003cbr/\u003e\n따라서, 이미지 파일은 모니터나 프린터로 **레스터화**할 수 있는 **데이터의 모음**이라고 생각하면 된다.\u003cbr/\u003e\n\n\u003e 래스터화(Rasterization)란, 벡터 그래픽으로 설명된 이미지를 래스터이미지(=비트맵이미지)로 변환하는 작업.\n\n이미지(데이터의 모음)는 일반적으로 아래와 같은 포맷으로 저장할 수 있다.\n\n- Raster formats\n  - 데이터를 사각형 그리드(해상도) 안에 각 픽셀의 값을 인코딩하여 저장.\n  - 인코딩시, 압축 or 무압축 처리가 된다. (압축시 데이터 손실이 발생할 수 있음.)\n  - 해상도가 크고, 각 픽셀을 표현하는 색의 정보가 많을 수록 수록 데이터의 크기가 커진다.\n- Vector formats\n  - 데이터를 점, 선, 도형으로 저장.\n\n### file types\n\n2가지 포맷으로 되어 있는 많은 이미지 파일들이 있지만, 웹에서 흔히 사용되어지는 이미지 파일 타입들에 대해서만 알아보자.\n\n| type | File format                           | MIME type     | File extension(s)                | Summary                                                                                                                                                                                       |\n| ---- | ------------------------------------- | ------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| GIF  | Graphics Interchange Format           | image/gif     | .gif                             | 래스터 포맷, 무손실 압축\u003cbr/\u003e8비트 256색상과 단일 투명색 지원\u003cbr/\u003e여러 프레임을 데이터로 가질 수 있음(애니메이션 가능)                                                                        |\n| JPEG | Joint Photographic Expert Group image | image/jpeg    | .jpg, .jpeg, .jfif, .pjpeg, .pjp | 래스터 포맷, 손실 압축\u003cbr/\u003e압축률이 높을 경우, 이미지 손실 발생\u003cbr/\u003e동일한 이미지를 여러번 편집/저장시 점점 퀄리티 저하\u003cbr/\u003e가장 많이 사용되는 포맷                                           |\n| PNG  | Portable Network Graphics             | image/png     | .png                             | 래스터 포맷, 무손실 압축\u003cbr/\u003eGIF를 대체하기 위해 만들어짐\u003cbr/\u003e8비트(투명도 옵션 존재), 24비트 트루컬러(1600만 색), 48비트 트루컬러, 알파채널(옵셔널) 지원\u003cbr/\u003e애니메이션은 APNG 포맷으로 가능 |\n| SVG  | Scalable Vector Graphics              | image/svg+xml | .svg                             | 벡터 포맷\u003cbr/\u003exml 형식으로 데이터 저장.                                                                                                                                                       |\n\n\u003e [MDN](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types)에서는 몇가지의 타입(WebP, AVIF, APNG)에 대해 더 설명해 주고 있지만, 이 글에서는 브라우저 호환성을 고려하여(IE..) 작성하였다.\n\n## Optimize\n\n이미지는 대체로 웹 페이지의 HTML, CSS, Javascript 등의 리소스보다 많은 바이트를 필요로 한다.\u003cbr/\u003e\n가장 좋은 최적화 전략은 이미지를 대체할 수 있는 방식(ex. css 표현, 웹폰트 사용 등)를 활용하여 사용자에게 컨텐츠를 전달하는 것이겠지만 이미지 또한 좋은 표현 수단이므로\n올바른 포맷을 선택하는 기준, 최적화를 하는 방식/방법에 대해 알아보자.\u003cbr/\u003e\n\n\u003e 포맷을 선택하는데에 있어, 정답은 없지만, 최
16선의 노력은 존재한다.\n\n### svg\n\n**로고, 텍스트, 아이콘과 같은 기하학적인 모양**으로 구성된 이미지\u003cbr/\u003e\n또한, 해상도 및 배율에 독립적이므로 다양한 크기로 표현해야하는 경우에도 좋다.\u003cbr/\u003e\u003cbr/\u003e\n이미지가 복잡하여 svg 마크업 양이 많아지는 경우에는 사용하면 좋지 않다.\n\n**최적화**\n\n- svg를 서빙해 주는 서버에서 svg에 대한 gzip 설정.\n- xml내 불필요한 요소들 제거 / xml node 구조 변경.\n  - 플러그인 아키텍처로 이루어진 [SVGO](https://github.com/svg/svgo)(SVG Optimizer)의 플러그인 리스트들을 확인하면 어떤 항목들을 최적화시킬 수 있는지 알 수 있다.\n\n```\nremoveDoctype\nremoveXMLProcInst\nremoveComments\nremoveMetadata\nremoveEditorsNSData\ncleanupAttrs\nmergeStyles\ninlineStyles\nminifyStyles\ncleanupIDs\nremoveUselessDefs\ncleanupNumericValues\nconvertColors\nremoveUnknownsAndDefaults\nremoveNonInheritableGroupAttrs\nremoveUselessStrokeAndFill\nremoveViewBox\ncleanupEnableBackground\nremoveHiddenElems\nremoveEmptyText\nconvertShapeToPath\nconvertEllipseToCircle\nmoveElemsAttrsToGroup\nmoveGroupAttrsToElems\ncollapseGroups\nconvertPathData\nconvertTransform\nremoveEmptyAttrs\nremoveEmptyContainers\nmergePaths\nremoveUnusedNS\nsortDefsChildren\nremoveTitle\nremoveDesc\n```\n\n### gif\n\n**간단한 도형, 로고, 만화 그림처럼 색이 별로 필요 없는 이미지와 애니메이션을 표현하고자 할 때**\u003cbr/\u003e\n애니메이션 표현시 loop, delay에 대한 설정이 이미지 내에 저장되기 때문에 여러 환경에서 편리하게 사용할 수 있다.\u003cbr/\u003e\u003cbr/\u003e\n무손실 압축을 하기 때문에 프레임이 많아지면 용량도 매우 비대해지므로, 이럴 땐 다른 방식을 활용하는게 좋다.\u003cbr/\u003e\n웹 페이지가 모던 브라우저만 지원해도 된다면, `APNG`, `AVIF`를 사용하는게 훨씬 좋을 수 있다.\u003cbr/\u003e\n(avif는 edge와 safari에서는 지원되지 않는다..)\n\n**최적화**\n\ngif를 최적화를 할 수 있는 방법은.. **해상도 줄이기, 사용 색상 줄이기, 애니메이션 프레임 줄이기**와 같은 원초적인 행위 밖에 없는데,\u003cbr/\u003e\n이는 이미지의 표현을 의도한대로 사용자에게 전달할 수 있는 방법이 아니다.\u003cbr/\u003e\n\n### jpeg\n\n**사진과 같이 복잡한 패턴과 색상을 갖는 이미지**\u003cbr/\u003e\n**웹 페이지 전체를 뒤덮는 배경 이미지**\u003cbr/\u003e\nPC 웹의 배경 이미지는 대부분 고해상도로 되어 있는데 어느정도 이미지 손실을 감안하더라도 성능을 위해 jpeg를 사용하는 것이 좋다.\u003cbr/\u003e\u003cbr/\u003e\n고주파 성분이 많은 문자, 선, 격자 등 단색이나 뚜렷한 윤곽선을 가지는 이미지에서는 품질 저하(뭉개짐, 노이즈)가 다소 뚜렷하게\n보이는 현상이 있다. 이러한 이미지는 PNG와 같은 다른 형식을 사용하자.\
16n\n\u003e 주파수는 전자기파의 주파수를 말하는 것이 아니라, 데이터를 격자 상에 늘어 놓았을 때의 공간적인 변화를 말하는 것\n\n**최적화**\n\n- 불필요한 메타데이터 제거.\n  - **EX**changable **I**mage **F**ile, Adobe's **E**xtensible **M**etadata **P**latform 등\n- jpeg 퀄리티를 (적당히)낮은 값으로 설정.\n  - 퀄리티가 낮아지면 손실율이 올라가기 때문에 절충점을 찾아 여러 품질로 시도하자.\n\n**예시**\n\n위의 최적화 방법이 적용된 [tinyjpg](https://tinyjpg.com/)를 통한 비교예시\n\n|       | orginal                                                                                                                    | compressed                                                                                                                     |\n| ----- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |\n| Image | \u003cimg src=\"https://tinyjpg.com/images/jpg/example-original.jpg\" alt=\"https://tinyjpg.com/images/jpg/example-original.jpg\"/\u003e | \u003cimg src=\"https://tinyjpg.com/images/jpg/example-compressed.jpg\" alt=\"https://tinyjpg.com/images/jpg/example-compressed.jpg\"/\u003e |\n| Size  | 614KB                                                                                                                      | 162KB                                                                                                                          |\n| 출처  | https://tinyjpg.com/                                                                                                       |\n\n### png\n\n**문자, 선, 격자 등 단색이나 뚜렷한 윤곽선을 가지는 이미지**\u003cbr/\u003e\n**알파 채널을 표현해야되는 이미지**\u003cbr/\u003e\n무손실 압축을 하기 때문에 웹상에서 이미지가 추가적으로 편집/저장될 가능성이 있을 경우에 사용해도 좋다.\u003cbr/\u003e\n애니메이션을 표현해야 한다면 확장 포맷인 APNG 형식을 사용할 수도 있다.\u003cbr/\u003e\u003cbr/\u003e\n이미지에 사용되어지는 색상이 많을 경우, png의 용량이 비대해질 수 있으므로 다른 형식을 사용하자.\
16n\n**최적화**\n\n- 불필요한 메타데이터 제거.\n- 각 픽셀을 표현하는 `color depth`(색 심도)를 8-bit indexed color로 설정.\n\n**예시**\n\n위의 최적화 방법이 적용된 [tinypng](https://tinypng.com/)를 통한 비교예시\n\n|       | orginal                                                                                                    | compressed                                                                                                     |\n| ----- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| Image | \u003cimg src=\"https://tinypng.com/images/example-orig.png\" alt=\"https://tinypng.com/images/example-orig.png\"/\u003e | \u003cimg src=\"https://tinypng.com/images/example-shrunk.png\" alt=\"https://tinypng.com/images/example-shrunk.png\"/\u003e |\n| Size  | 57KB                                                                                                       | 15KB                                                                                                           |\n| 출처  | https://tinypng.com/                                                                                       |\n\n`color depth`(색 심도)에 따른 비교예시\n\n|       | 24 bit                                                                                                                                           | 8 bit                                                                                                                                          |\n| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n| Image | \u003cimg src=\"https://upload.wikimedia.org/wikipedia/commons/9/9f/24_bit.png\" alt=\"https://upload.wikimedia.org/wikipedia/commons/9/9f/24_bit.png\"/\u003e | \u003cimg src=\"https://upload.wikimedia.org/wikipedia/commons/f/ff/8_bit.png\" alt=\"https://upload.wikimedia.org/wikipedia/commons/f/ff/8_bit.png\"/\u003e |\n| Size  | 98KB                                                                                                                                             | 37KB(-62%)                                                                                                                                     |\n| 출처  | https://en.wikipedia.org/wiki/Color_depth                                                                                                        |\n\n## 마치며\n\n1. 이미지를 사용하지 않아도 되는 상황이라면 사용 X.\n2. 표현하는 컨텐츠에 적합한 최적의 이미지 형식 선택.\n3. 각 이미지 타입에 맞는 최적화 적용.\n4. 웹 페이지에서 보여주는 이미지의 해상도와 최대한 동일한 해상도를 가진 레스터 이미지 제공.\u003cbr/\u003e\n   가능하다면 `srcSet`, `sizes` 속성을 통하여 반응형 이미지 제공.\n\n## References\n\n- https://en.wikipedia.org/wiki/Image_file_format\n- https://en.wikipedia.org/wiki/Rasterisation\n- https://en.wikipedia.org/wiki/Color_depth\n- https://web.dev/fast/#optimize-your-images\n- https://developer.mozilla.org/ko/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images\n","data":{"id":"image optimize","title":"이미지 파일 최적화를 통한 웹 성능 향상","description":"웹에서 자주 사용되어지는 이미지에 대해 알아보고, 최적화 원리와 방식에 대해 알아봅니다.","preview":"웹에서 자주 사용되어지는 이미지에 대해 알아보고, 최적화 원리와 방식에 대해 알아봅니다.","keywords":"web, image, optimize, jpeg, jpg, png, gif, svg","slug":"image-optimize","createdAt":"2021.12.12","published":true},"filePath":"image-optimize.mdx"},{"content":"\n컴포넌트 제작의 마지막인 이번 포스팅은,\nnpm에 publish하는 과정과 관리에 대해 다루고자 한다.\n\n[react-simple-image-slider](https://github.com/kimcoder/react-simple-image-slider)를 제작하면서 쓰는 포스팅.\n\n## npm\n\nnpm에 먼저 배포를 하고자하려면 계정이 있어야 한다.\u003cbr/\u003e\n[npm](https://www.npmjs.com)에 방문하여 가입을 먼저 하자.\u003cbr/\u003e\n그리고 아래와 같은 단계를 거치면 손쉽게 배포할 수 있다.\n\n### npm init\n\n아래 명령어를 통해 package.json의 기본 정보들을 초기화할 수 있다.\u003cbr/\u003e\n이미 npm package가 존재하는 디렉토리에서도 실행이 가능하며,\u003cbr/\u003e\n비어있는 디렉토리에서 명령어를 실행해도 가능하다.\u003cbr/\u003e\n( 단, 이미 package.json이 존재한다면 입력받는 정보들을 overwrite 하게 된다. )\n\n```js\nnpm init\n```\n\n위의 터미널에 명령어를 입력하면, 아래와 같은 순으로 데이터 입력을 받는다.\u003cbr/\u003e\n\n- package name\n- ver
16sion\n- description\n- git repository\n- keywords\n- author\n- license\n\n여기서 개발한 컴포넌트의 package.json을 예시로 확인해보자.\n\n```js\n{\n  \"name\": \"react-simple-image-slider\",\n  \"version\": \"1.0.2\",\n  \"description\": \"simple image slider component for react\",\n  \"main\": \"dist/ImageSlider.js\",\n  \"scripts\": {\n    \"example\": \"webpack-dev-server --inline --mode development\",\n    \"build\": \"rollup -c\",\n    \"build:watch\": \"rollup -c --watch\"\n  },\n  \"keywords\": [\n    \"reactjs\",\n    \"react\",\n    \"react-component\",\n    \"react gallery\",\n    \"react slider\",\n    \"image gallery\",\n    \"image slider\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/kimcoder/react-simple-image-slider.git\"\n  },\n  \"author\": {\n    \"name\": \"kimcoder\",\n    \"email\": \"[email protected]\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/kimcoder/react-simple-image-slider/issues\"\n  },\
16n  \"homepage\": \"https://github.com/kimcoder/react-simple-image-slider#readme\",\n  \"dependencies\": {\n    ...\n  },\n  \"devDependencies\": {\n    ...\n  }\n}\n```\n\n여기에 크게 부가적인 설명이 필요할 것 같지 않지만 조금 신경써야 할 부분이 있다면,\u003cbr/\u003e\n컴포넌트 제작시 `dependencies`와 `devDependencies`를 잘 구분하여 모듈을 설치하고 작업을 해야한다.\u003cbr/\u003e\n배포가 될 필요가 없고 컴포넌트 개발에만 필요한 모듈들은 `devDependencies`로 설치를 해주어야 한다.\u003cbr/\u003e\n\n### npm ignore\n\ngitê³¼ 마찬가지로 npm에도 특정 디렉토리, 파일들을 ignore 할 수 있다.\u003cbr/\u003e\n\n```js\n.DS_Store\n.vscode\n.git*\n\nexample\nnode_modules\nnpm-debug.log\nyarn-debug.log\nyarn-error.log\n```\n\n이것은 역시 환경에 따라 다르겠지만, 기본적으로 컴포넌트 사용시에만 필요한 파일들을 기준으로 배포하자.\u003cbr/\u003e\n개발시 사용하였던 테스트나 예제, 모듈들, 로그 등의 파일들은 필요가 없으니 추가해주자.\n\n### npm adduser\n\n배포 전, 아래와 같은 npm cli를 통해 로그인을 해주도록 하자.\n\n```js\nnpm adduser\n```\n\n로그인이 완료되면 아래와 같이 **Logged in as 유저**라는 메시지를 ë³¼ 수 있을 것이다.\u003cbr/\u003e\u003cbr/\u003e\n\n\u003cimg src='/assets/images/react-component-3-adduser.png' alt='npm adduser' /\u003e\n\n### test for npm publish\n\n테스트는 실제 npm install을 통하여 하는 것이 좋다.\u003cbr/\u003e\n물론, 아직 배포를 하지 않았기 때문에 아래와 같이 컴포넌트의 루트디렉토리 경로를 통해 설치해보자.\u003cbr/\u003e\n\n```\nnpm install /Users/KIMCODER/Documents/react-simple-image-slider/\nor\nnpm install ../react-simple-image-slider/\n```\n\n실제 컴포넌트를 사용하는 환경에서 import하여 정상적으로 사용이 가능하다면, 배포해도 좋을 것이다.\u003cbr/\u003e\n\n#### npm publish\n\n배포를 위한 명령어는 아래와 같다.\n\n```\nnpm publish\n```\n\npackage.json의 version에 따라 배포가 되며, 만약 최초배포가 아닌 경우는 version을 업그레이드하여 publish를 하면 된다.\u003cbr/\u003e\n배포가 정상적으로 완료가 되면, `https://www.npmjs.com/package/이름`의 URL에서 확인할 수 있다.\u003cbr/\u003e\n또는, `https://www.npmjs.com/settings/${계정 아이디}/packages`에서도 확인이 가능하다.\u003cbr/\u003e\u003cbr/\u003e\nnpm에 모듈 배포를 직접 한 번 ê²½í—
16˜ì„ 해보면 어렵지않다는 것을 느낄 수 있다.\u003cbr/\u003e\n\n### Git\n\n추가적으로 npm 외에 git 관련 내용도 조금 다루어보자.\u003cbr/\u003e\nnpmê³¼ git의 소스는 기본적으로 동일한 버전으로 관리가 되어야 한다.\u003cbr/\u003e\n하지만, git에서 ignore 대상은 조금 다를 수 있다.\u003cbr/\u003e\nnpm에서는 사용되는 중심으로 소스가 배포되어야 한다면, git은 컴포넌트 개발중심의 소스가 배포하도록 하자.\u003cbr/\u003e\nnpmê³¼ github의 역할에 대해 생각하면 충분히 납득이 가능하리라 예상한다.\u003cbr/\u003e\n\n[CHANGELOG.md](https://github.com/kimcoder/react-simple-image-slider/blob/master/CHANGELOG.md)를 생성하여 버전별로 어떤 것들이 바뀌는지에 대해 기록하면 좋을 것 같다.\n\n## 마치며\n\n컴포넌트 제작의 결과물은 [Git repository](https://github.com/kimcoder/react-simple-image-slider)에서 확인할 수 있으며 [NPM Package](https://www.npmjs.com/package/react-simple-image-slider)에서도 ë³¼ 수 있다.\u003cbr/\u003e\n거창한 모듈, 컴포넌트가 아니더라도 개발자들이 ê¼­ 한 번쯤은 경험하면 좋을 것 같다 :)\n\n## References\n\n- https://www.npmjs.com/\n- https://docs.npmjs.com/getting-started/publishing-npm-packages\n- https://docs.npmjs.com/cli/publish\n- https://github.com/kimcoder/react-simple-image-slider\n- https://www.npmjs.com/package/react-simple-image-slider\n","data":{"id":"react-simple-image-slider-3","title":"React 컴포넌트 제작기 3 / 3","description":"react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.","preview":"react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.","keywords":"react, component, react carousel, image slider, npm","slug":"react-simple-image-slider-3","createdAt":"2018.10.11","published":true},"filePath":"react-simple-image-slider-3.mdx"},{"content":"\n이번 포스팅의 내용은 비교적 간단하다.\n\n컴포넌트를 제작시 실제로 고려해야 되는 부분들에 대하여 쓰고자 한다.\n[react-simple-image-slider](https://github.com/kimcoder/react-simple-image-slider)를 제작하면서 쓰는 포스팅.\n\n## 컴포넌트 제작시 고려할 사항\n\n1. 사용목적에 맞는 다양한 환경에서의 접근성을 ê³ ë ¤.\n2. 캡슐화 및 실제 개발 코드상에서의 사용성 ê³ ë ¤.\n3. 외부적인 요인에 의한 부작용 방지.\n\n컴포넌트가 어떤 종류의 것인지, 어떤 특징들이 있는지에 대해 먼저 생각해 보아야하며,\u003cbr/\u003e\n이 컴포넌트를 실제 사용하는 여러 개발자들의 개발 환경을 고려해주어야 한다.\u003cbr/\u003e\n여기서는 React 컴포넌트를 제작 후 기록하는 포스팅이기 때문에, 이미 모두 고려된부분이라 할 수 있겠다.\u003cbr/\u003e\n또, 컴포넌트 소스의 캡슐화가 잘 되어있어, 독립적으로 특정 기능들이 실행되는지, 외부요인의 영향을 받거나 끼치는 등의 부작용을 방지하는지, 다른 개발자들에게 편리한 인터페이스가 제공되는지 등을 생각해보아야 한다.\u003cbr/\u003e\n\n물론, 컴포넌트의 성격이나 종류에 따라 차이가 있겠지만, 아마 대부분 위의 사항들을 고려해야하지 않을까 싶다.\n\n## 컴포넌트 기능 정의\n\n내가 여기서 만들고자하였던 것은 \u003cb\u003e이미지슬라이더\u003c/b\u003e였다.\u003cbr/\u003e\n\n사실 프론트엔드개발자라면 이미지슬라이더는 매우 많이 접해보았을 것이고, 외부모듈을 가져다쓰기도하며, 직접 개발하여 작업한 적도 많이 있을 것이다. 이미지슬라이더를 제작하고자 마음을 먹은 이유는, 외부모듈을 가져다쓰는데 아쉬움이 있어 실제로 제작하여 배포를 해보면 좋을 것 같다고 생각했었다.\u003cbr/\u003e\u003cbr/\u003e\n만들고자할 때, 가장 염두에 두었던 사항들은 아래와 같았다.\u003cbr/\u003e\n\n- 현재 인덱스에 맞ê²
16Œ 다음 인덱스의 이미지를 사전에 로드.\n- 슬라이더를 정적인 크기로 지정하면 이미지도 비율에 맞춰 잘 보이도록.(이미지의 크기와 상관없이)\n- 2개의 객체를 활용하여 슬라이드 이미지들 표현.\n- ê·¸ 외 기본적인 사항들 지원.\n  - 네비게이션의 유무\n  - bullet 네비게이션의 유무\n  - GPU 렌더링 지원 유무\n  - 슬라이딩 속도 조절.\n  - 여러 이벤트에 대한 콜백 함수.\n\n## PropsTypes를 활용하여 React Component Prop 체크\n\n컴포넌트를 제작하고 배포하는 입장에서는,\u003cbr/\u003e\n사용하는 개발자들이 정확한 값을 전달하도록하여 오류없이 컴포넌트의 기능들을 사용하게끔 해야한다.\u003cbr/\u003e\n\n이는 `prop-types`를 사용하여 해결 할 수 있는데,\u003cbr/\u003e\n`ReactComponent` \u003cb\u003eProps의 유효성 검사 및 기본 값 지정, 타입체크\u003c/b\u003e를 할 수 있게 해준다.\u003cbr/\u003e\n`prop-types`는 react v15.5부터는 별도로 설치를 해주어야한다. ( npm 등을 통해 설치하자 )\u003cbr/\u003e\u003cbr/\u003e\n어떤 값들이 필수적으로 전달되어야 하는지, 선택적으로 전달되어야 하는지, 또 기본 값은 있는지를 구분지어보자.\u003cbr/\u003e\n결과적으로 아래와 같이 컴포넌트 클래스에 propTypes와 defaultProps 지정하면 된다.\n\n```js\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n...\n\nclass SimpleImageSlider extends React.Component {\n    ...\n}\n\nSimpleImageSlider.propTypes = {\n    // Required\n    width: PropTypes.number.isRequired,\n    height: PropTypes.number.isRequired,\n    images: PropTypes.arrayOf(PropTypes.shape({\n        url: PropTypes.string.isRequired,\n    })).isRequired,\n\n    // Optional\n    style: PropTypes.objectOf(PropTypes.string),\n    slideDuration: PropTypes.number,\n    showNavs: PropTypes.bool,\n    showBullets: PropTypes.bool,\n    bgColor: PropTypes.string,\n    useGPURender: PropTypes.bool,\n    onClickNav: PropTypes.func,\n    onClickBullets: PropTypes.func,\n    onStartSlide: PropTypes.func,\n    onCompleteSlide: PropTypes.func,\n\n    // Optional, Navigation Arrow Style\n    navStyle: (props, propName, componentName) =\u003e {\n        if (!isValidNavStyle(props[propName])) {\n            return new Error(`Invalid prop ${propName} supplied to ${componentName}. Validation failed.`);\n        }\n        return null;\n    }\n};\n\nSimpleImageSlider.defaultProps = {\n    slideDuration: 0.5,\n    showNavs: true,\n    showBullets: true,\n    bgColor: \"#000000\",\n    useGPURender: true,\n    navStyle: 1\n};\n```\n\n이제 위의 내용을 readme 혹은 다른 문서에 아래와 같이 정의를 해주자.\u003cbr/\u003e\n이러한 작업들은 컴포넌트를 사용할 개발자들에게 좋은 사용성을 제공해줄 것이다.\u003cbr/\u003e\n또, 고품질의 컴포넌트의 ì¡°ê±´ 중 일부라고 생각한다.\u003cbr/\u003e\n\n| Name                | Type       | Required   | Description                                                                                                                                                          | Default   |\n| ------------------- | ---------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |\n| **width**           | `Number`   | `Required` | Image Slider Width                                                                                                                                                   |           |\n| **height**          | `Number`   | `Required` | Image Slider Height                                                                                                                                                  |           |\n| **images**          | `Array`    | `Required` | Images,\u003cbr/\u003eArray Elements should be like this structure,\u003cbr/\u003e{ url: \"\" }                                                                                            |           |\n| **style**           | `String`   | `Optional` | css object                                                                                                                                                           |           |\n| **slideDuration**   | `Number`   | `Optional` | css transition-duration property                                                                                                                                     | `0.5`     |\n| **navStyle**        | `Number`   | `Optional` | Arrow Navgation Style,\u003cbr/\u003e1 or 2                                                                                                                                    | `1`       |\n| **showNavs**        | `Boolean`  | `Optional` | Toggle Arrow Navgation                                                                                                                                               | `true`    |\n| **showBullets**     | `Boolean`  | `Optional` | Toggle Bullets                                                                                                                                                       | `true`    |\n| **useGPURender**    | `Boolean`  | `Optional` | Toggle GPU Render                                                                                                                                                    | `true`    |\n| **bgColor**         | `String`   | `Optional` | slider container's css background-color property                                                                                                                     | `#000000` |\n| **onClickNav**      | `Function` | `Optional` | Arrow Navigation Callback function,\u003cbr/\u003e`onClickNav = (toRight) =\u003e { }`\u003cbr/\u003etoRight : Boolean : slide direction                                                      |           |\n| **onClickBullets**  | `Function` | `Optional` | Bullets Callback function,\u003cbr/\u003e`onClickBullets = (idx) =\u003e { }`\u003cbr/\u003eidx : Number : clicked bullet index (begin from 0)                                                |           |\n| **onStartSlide**    | `Function` | `Optional` | Slide Transition Start function,\u003cbr/\u003e`onStartSlide = (idx, length) =\u003e { }`\u003cbr/\u003eidx : Number : start index (begin from 1)\u003cbr/\u003elength : Number : image length          |           |\n| **onCompleteSlide** | `Function` | `Optional` | Slide TransitionEnd Callback function,\u003cbr/\u003e`onCompleteSlide = (idx, length) =\u003e { }`\u003cbr/\u003eidx : Number : start index (begin from 1)\u003cbr/\u003elength : Number : image length |           |\n\n[Props 정의 Gitub에서 보기](https://github.com/kimcoder/react-simple-image-slider)\n\n## Typescript를 위한 index.d.ts\n\nTypescript 개발 환경도 고려하여 index.d.ts파일을 컴포넌트 루트 경로에 생성하여 아래와 같이 작성하자.\u003cbr/\u003e\n파일명의 d는 일반적으로 declare의 약자로 표현하여 사용하고, 이 파일은 Typescript에서\u003cbr/\u003e\n특정 모듈, 인터페이스, 변수 등에 대한 타입 선언ì
16„ 하는 역할을 한다.\u003cbr/\u003e\n컴포넌트와 같이 개발자가 커스텀하여 정의한 컴포넌트 클래스는 Typescript에서 인식을 하지 못하기 때문에,\u003cbr/\u003e\n아래와 같이 index.d.ts라는 파일을 통해 선언을 해주어야 한다.\n\n```js\nimport * as React from 'react';\n\n// RSIS = React Simple Image Slider\nexport interface RSISImage {\n  url: string;\n}\n\nexport interface RSISProps {\n  width: number;\n  height: number;\n  images: RSISImage[];\n\n  style?: CSSStyleDeclaration;\n  slideDuration?: number;\n  showNavs?: boolean;\n  showBullets?: boolean;\n  bgColor?: string;\n  useGPURender?: boolean;\n  navStyle?: 1 | 2;\n  onClickNav?: (toRight: boolean) =\u003e void;\n  onClickBullets?: (idx: number) =\u003e void;\n  onStartSlide?: (current: number, length: number) =\u003e void;\n  onCompleteSlide?: (current: number, length: number) =\u003e void;\n}\n\ndeclare class ReactSimpleImageSlider extends React.Component\u003cRSISProps\u003e {}\n\nexport default ReactSimpleImageSlider;\n```\n\nindex.d.ts파일을 컴포넌트 디렉토리에 추가하지 않고도 [@types/패키지](https://github.com/DefinitelyTyped/DefinitelyTyped)를 추가하여 해결할 수도 있다.\u003cbr/\u003e\n( 이 경우 사용하는 개발자가 npm에서 `@types/패키지`을 별도로 설치해주어야 한다. )\n\n## 마치며\n\n여기까지의 과정들은 [Git Repository](https://github.com/kimcoder/react-simple-image-slider)에서 확인할 수 있다.\u003cbr/\u003e\n\nReact 컴포넌트의 구현 소스는 사실 React를 조금 다루어보았다면 크게 어려운 부분은 아니다.\u003cbr/\u003e\n그러하여 포스팅의 주된 내용을 어떤 것들을 고려해서 개발해야 하는지, 좋은 사용성과 높은 품질의 컴포넌트를 만들려면 어떤 것들을 생각해보아야 하는지로 선정하였다.\u003cbr/\u003e\n사실 부족한 부분이 많겠지만 아주 마이너하고 심플한 컴포넌트이기 때문에 최대한 간략히 정리를 해보았다.\n\n## References\n\n- https://reactjs.org/docs/typechecking-with-proptypes.html\n- http://definitelytyped.org/\n","data":{"id":"react-simple-image-slider-2","title":"React 컴포넌트 제작기 2 / 3","description":"react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.","preview":"react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.","keywords":"react, component, react carousel, image slider, npm","slug":"react-simple-image-slider-2","createdAt":"2018.10.08","published":true},"filePath":"react-simple-image-slider-2.mdx"},{"content":"\nreact 컴포넌트 제작부터 배포까지의 과정을 다뤄보고자 한다.\n\n이번 포스팅은 컴포넌트 제작을 위환 환경 구축 과정까지 할 예정이다.\n[react-simple-image-slider](https://github.com/kimcoder/react-simple-image-slider)를 제작하면서 쓰는 포스팅.\n\n## React 컴포넌트 제작을 위한 환경 구축\n\n세부적인 내용을 확인하기 전에, 대략적으로 필요한 것들을 정리해보자.\n\n1. **React 컴포넌트 모듈 번들 도구 설정**.\n2. jsx, ES6 이상의 문법 등 모던한 문법을 통해 개발을 할 예정이므로, 이를 위한 **babel 설정**.\n3. 작업하는 컴포넌트 배포 전, **실제 테스트 환경 설정**.\n4. 코드 컨벤션 적용을 위한 eslint 설정 등 기타 필요 플러그인들 설치.\n\n대략 정리를 해보면 위와 같지만, 사실 실제 할 일들은 그리 많지 않다.\n\n### 1. rollup\n\n나는 그동안 개발을 해오면서 `gulp`와 `webpack` 이 2가지를 사용한 경험이 있다.\n하지만, 이번 컴포넌트 제작은 `rollup`이라는 번들 도구를 사용할 예정이다.\n리액트를 제작한 페이스북에서도 rollup으로 현재 빌드를 하고 있고,\n\n[Webpack and Rollup: the same but different](https://medium.com/webpack/webpack-and-rollup-the-same-but-different-a41ad427058c)란 제목의 재밌는 글을 읽은 이유도 있다.\n\n이 글에서 `Use webpack for apps, and Rollup for libraries`구문이 눈에 들어왔다.\
16n\n또, rollup은 `react`, `vue`, `ember`, `D3`, `ThreeJs` 등에서도 사용하는 도구로 소개를\n하고 있으니, 이정도면 한 번 경험해 볼 만한 가치는 충분할 것이다.\n\n[Rollup 공식홈페이지](https://rollupjs.org/guide/en#quick-start)의 소개를 보면, `rollup`은 작거나 큰 단위 조각의 코드, 혹은 라이브러리나 애플리케이션과 같이 더 복잡한 조각들의 코드들을 컴파일해주는 도구로 소개되어 있다. 또한, ES6, CommonJS, AMD와 같은 방식들의 모듈 제작 환경을 모두 지원한다.\n\n**install**\n\n제작하고자하는 컴포넌트 디렉토리를 생성 후, 설치를 해주자.\n\n```js\nmkdir react-simple-image-slider\ncd react-simple-image-slider\n\n..\n( npm init을 통해 package.json을 생성 )\n..\n\nnpm install rollup rollup-plugin-babel --save-dev\n```\n\n위에서 설명했던대로 babel을 사용할 예정이므로 plugin도 설치를 같이 해준다.\nbabel에 대한 자세한 설정은 아래에서 다시 다루겠다.\n\n**rollup.config.js**\n\nrollup config 파일을 아래와 같이 작성하였다.\n\n```js\nimport babel from 'rollup-plugin-babel';\nimport pkg from './package.json';\n\nconst external = (id) =\u003e !id.startsWith('/') \u0026\u0026 !id.startsWith('.');\n\nexport default {\n  input: './src/ImageSlider.jsx',\n  output: {\n    file: pkg.main,\n    format: 'cjs',\n  },\n  plugins: [babel({ runtimeHelpers: true })],\n  external,\n};\n```\n\n- input : 번들링 할 소스의 index 파일의 path.\n- output : 번들링 결과물 파일의 path와 모듈 방식. ( cjs는 CommonJS방식 )\n- plugins : 필요한 플러그인들 지정. ( 초기에는 babel plugin만 추가 )\n- external : 소스에서 import하는 모듈들이 외부 의존적인 모듈 지정.\n\n만약, external에서 외부 의존성이 있는 모듈이라면 해당 모듈은 번들링시 포함하지 않게 된다.\n예를 들어 지금은 react 컴포넌트를 제작하는 것이므로 reactjs와 같은 모듈은 npm으로 설치하여 import하여 사용을 하게 될 것이다.\n여기서 이러한 external한 외부 모듈들은 번들링 결과물에 포함이 되지 않는다.\n\n```js\nexternal: ['react'];\n```\n\n위와 같이 직접 모듈명을 작성해주어도 되지만, 외부 모듈들이 많아지면 번거롭게 다 지정해주어야 하기 때문에, 함수를 하나 작성하여 처리해주었다.\n아래와 같이 import하는 모듈들의 경로를 기준으로 판단하면 쉽게 외부 모듈인지 확인이 가능하다.\n\n```js\nimport React from 'react';\nimport ImageSliderPropTypes from './ImageSliderPropTypes';\nimport ImagePreLoader from './ImageSliderPreLoader';\n```\n\n**package.json**\n\nscripts에 rollup 명령어를 아래와 같이 추가해준다.\n\n```js\n{\n  \"scripts\": {\n      \"build\": \"rollup -c\",\n      \"build:watch\": \"rollup -c --watch\"\n  }\n}\n```\n\n`-c` flag는 `--config`와 동일하며, config파일명이 `rollup.config.js`일 경우, 파일명은 적지 않아도 된다.\n`--config` 뒤에 파일명을 적어 특정 파일의 설정을 참조할 수 도 있다.\n또, watch flag을 통해 소스 변경시마다 번들링이 되게 해준다.\n\n### 2. babel\n\nbabel에 대해선 따로 설명을 하진 않겠으나, 간략하게 말하자면 ES2015+ 자바스크립트 코드를 구형 브라우저, 환경에서 적용할 수 있게 해주는 컴파일러이다.\nbabel은 버전이 바뀌면서 셋팅값들이 바뀌는 부분이 있으므로 꼭 [Babel 공식홈페이지](https://babeljs.io/docs/en/)에서 필요한 부분을 확인하자.\u003cbr/\u003e\n\n**install**\n\n설치해야 할 리스트는 아래와 같다.\n\n- @babel/core : babel.\n- @babel/preset-env : babel preset을 지정할 수 있게 해주는 모듈. ( babel7부터 사용 )\n- @babel/preset-react : babel react preset.\n- @babel/plugin-proposal-class-properties : Class에서 멤버 변수 관련 문법을 위한 플러그인.\n- @babel/plugin-transform-runtime : 컴포넌트 소스 중 필요한 부분만 polyfill 적용을 위한 플러그인.\n- @babel/polyfill : 테스트 환경 App 빌드시 사용 될 polyfill.\n- babel-loader : 테스트 환경을 위한 webpack 설정시 쓸 babel loader.\n\n위의 리스트들을 모두 설치해주자.\n\n```js\nnpm install ... --save-dev\n```\n\n**.babelrc**\n\nbabel의 설정은 아래와 같이 해준다.\n\n```js\n{\n    \"presets\": [\n        [\"@babel/preset-env\", {\n            \"loose\": true,\n            \"modules\": false,\n            \"targets\": \"\u003e 0.25%, not dead\"\n        }
16],\n        \"@babel/preset-react\"\n    ],\n    \"plugins\": [\n        \"@babel/plugin-transform-runtime\",\n        \"@babel/plugin-proposal-class-properties\"\n    ]\n}\n```\n\n어떤 preset과 plugin들을 쓸 것인지 명시만 해주면 된다.\n잠깐, 확인할 부분이 있다면 babel 7.0에서부터는 preset 설정이 조금 바뀌었다는 것이다.\n기존에 `es-2015`, `stage-0`과 같은 preset들은 deprecated 되었고 위처럼 target을 지정해주면 된다.\u003cbr/\u003e\n`\u003e 0.25%, not dead`은 점유율이 0.25%인 브라우저들을 타겟으로 컴파일 된다는 의미이다.\n( IE10, Blackberry와 같은 보안업데이트가 중단된 브라우저는 무시한다고 한다. )\n\n[Babel 공식홈페이지](https://babeljs.io/docs/en/v7-migration)에서 더 자세히 확인 할 수 있다.\n\n### 3. 테스트 개발 환경\n\n테스트 개발 환경 설정은, \u003cb\u003e실제 제작한 컴포넌트가 react App에서 import하여 잘 작동이 되는지\u003c/b\u003e를 위한 것이다.\n4.x대의 버전의 Webpack으로 테스트 환경을 설정할 예정이다.\nWebpack에 대한 소개나 설정 방법 등의 자세한 설명은 생략할 예정이다.\n( Webpack을 주목적으로 다루는 내용이 아니므로. )\n\n**install**\n\n아래와 같이 설치해준다.\n\n```js\nnpm install webpack webpack-cli webpack-dev-server --save-dev\n\n// \"webpack\": \"^4.17.2\",\n// \"webpack-cli\": \"^3.1.0\",\n// \"webpack-dev-server\": \"^3.1.7\"\n```\n\nwebpack 4.x버전부터는 webpack-cli를 같이 설치해줘야 커맨드라인에서 webpack 명령어를 사용할 수 있다.\n또, 개발모드 지원을 위해 webpack-dev-server를 설치하자.\n\n**webpack.config.js**\n\n```js\nconst path = require('path');\n\nmodule.exports = {\n  entry: {\n    index: ['@babel/polyfill', './example/App.jsx'],\n  },\n  output: {\n    filename: '[name].js',\n    path: path.join(__dirname, 'example'),\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.jsx$/,\n        exclude: /node_modules/,\n        use: 'babel-loader',\n      },\n    ],\n  },\n  mode: 'production',\n  plugins: [],\n  devServer: {\n    contentBase: './example',\n    host: '0.0.0.0',\n  },\n};\n```\n\n테스트 App을 위한 Webpack 설정파일이다.\n현재 컴포넌트의 디렉토리에서 테스트 App의 경로를 `./example`로 설정하였고,\n`babel-loader`와 `webpack-dev-server` 관련된 내용을 확인할 수 있다.\n( babel의 preset과 plugin인 설정 등은 위에서 생성한 .babelrc 파일을 따른다. )\n이외에 기본적인 React App을 빌드 시 필요한 html파일과 reactjs 설치 등은 생략하겠다.\n\n**./example/App.jsx**\n\n간략하게 React 소스의 index부분만 잠깐 살펴보자.\n\n```js\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport ImageSlider from '..';\n\nclass App extends React.Component {\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003ch1\u003eImage Slider Example\u003c/h1\u003e\n        \u003cImageSlider /\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nReactDOM.render(\u003cApp /\u003e, document.getElementById('App'));\n```\n\n`import ImageSlider from \"..\";`를 보면 상위 디렉토리( =컴포넌트 루트 디렉토리 )에서 모듈을 import하는 것을\n확인할 수 있다.\n( rollup으로 빌드한 컴포넌트의 소스가 `package.json`의 `main` 값으로 지정하였기 때문 )\n\n**package.json**\n\nscripts에 webpack 명령어를 아래와 같이 추가해준다.\n\n```js\n  \"scripts\": {\n      \"example\": \"webpack-dev-server --mode development\",\n      \"build\": \"rollup -c\",\n      \"build:watch\": \"rollup -c --watch\"\n  },\n```\n\n`npm run example`을 통해 webpack-dev-server를 실행시키면 된다.\n`--inline`이나 `--hot`과 같은 flag를 추가하여 Live Reloading, Hot Module Reloading의 기능을\n쓰면 더 편리할 수도 있다.\nLive Reloading은 전체 페이지가 리로드 되고, Hot Module Reloading은 수정된 소스의 해당 모듈만 리로딩이 된다.\n\n### 4. 기타 설정\n\n마지막으로 eslint 설정, 폴더구조 등 정리를 하자면..\n\n**eslint**\n\n아마 react를 사용하고 babel을 사용한다면 대부분 비슷한 설정이 될 수도 있겠지만,\n자신들의 코딩컨벤션을 적용하여 설정을 해두자.\n나는 여기서 아래와 같이 설정하였다.\n\n- eslint\n- eslint-config-airbnb\n- eslint-plugin-import\n- eslint-plugin-jsx-a11y\n- eslint-plugin-react\n- babel-eslint\n\n**directory structure**\n\n- dist : rollup으로 React 컴포넌트의 소스가 빌드되는 디렉토리. 배포를 위한 디렉토리.\n- example : 개발한 React 컴포넌트를 webpack으로 테스트앱을 제작하여 테스트하는 디렉토리.\n- node_modules : 개발시 사용되는 모듈들.\n- src : React 컴포넌트의 소스가 있는 디렉토리.\n\n현재 디렉토리의 index가 없지만, `package.json`에서 `main` 속성을 dist의 배포용 소스로 지정하였다.\n따라서 추후에 컴포넌트를 import할 때 index를 못 찾거나 하는 문제가 발생하지 않는다.\n\n## 마치며\n\nReact 컴포넌트 제작기를 총 3번에 나눠 포스팅 계획을 세웠는데,\n2번째는 실제 React 컴포넌트 소스를 개발과정을,\n3번째는 npm, git에 업로드까지의 과정을 다룰 예정이다.\n\n## References\n\n- https://medium.com/webpack/webpack-and-rollup-the-same-but-different-a41ad427058c\n- https://rollupjs.org/guide/en#quick-start\n- https://rollupjs.org/guide/en#configuration-files\n- https://babeljs.io/docs/en/v7-migration\n- https://webpack.js.org/\n- https://webpack.js.org/concepts/#entry\n","data":{"id":"react-simple-image-slider-1","title":"React 컴포넌트 제작기 1 / 3","description":"react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.","preview":"react-simple-image-slider 컴포넌트의 개발/배포의 과정에 대한 경험을 서술합니다.","keywords":"react, component, react carousel, image slider, npm","slug":"react-simple-image-slider-1","createdAt":"2018.09.21","published":true},"filePath":"react-simple-image-slider-1.mdx"},{"content":"\n오늘은 TDZ, Temporal Dead Zone에 대해 글을 쓰고자 한다.\u003cbr/\u003e\nTDZ에 대해 알려면, 호이스팅과 let, const, scope 등에 대한 기본적인 이해가 있어야 한다.\n\n## Temporal Dead Zone\n\n위에서 이야기한 것처럼 TDZ에 대해 알려면, `호이스팅`, `let \u0026 const`에 대해 알아야 한다.\n\n### 호이스팅\n\n호이스팅은 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미한다.\u003cbr/\u003e\n`var`로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화하게 된다.\n\n### let \u0026 const\n\n`let`과 `const`는 ES5+부터 사용할 수 있고 block scope 기반으로 동작하는 변수타입이다.\u003cbr/\u003e\n아래 코드를 보자.\n\n```js\nconsole.log(x); // undefined\nconsole.log(y); // ReferenceError\n\nvar x = 'x';\nlet y = 'y';
16\n```\n\n변수의 선언부는 해당 scope의 최상단으로 끌어올려져 해석된다.\u003cbr/\u003e\n하지만 위의 코드에서 `let`으로 선언한 변수 y에 접근시에는 `ReferenceError`를 내뱉는다.\u003cbr/\u003e\u003cbr/\u003e\n\n그럼 let은 호이스팅이 되지 않는 것인가?\u003cbr/\u003e\n그렇지 않다. `let`과 `const`도 마찬가지로 호이스팅되지만, 변수의 초기화가 실행되지 않는다.\u003cbr/\u003e\n\n위의 코드에서 let변수에 접근이 불가능한 이유가 바로 **TDZ**때문이다.\u003cbr/\u003e\nMDN문서의 내용을 참고하자면, 아래와 같이 한마디로 정의할 수 있다.\u003cbr/\u003e\n`let`과 `const`는 블록의 시작부터 초기화가 실행되기전까지 TDZ에 존재하게 된다는 것.\u003cbr/\u003e\n따라서, TDZ에 존재하면 위와 같이 접근을 할 수 없다.\u003cbr/\u003e\n\n### Temporal\n\nDeadZone 형성이 **시간상**(Temporal)인 이유는 이유는\u003cbr/\u003e\n사각지대가 코드의 작성 순서(위치)가 아니라 코드의 실행 순서(시간)에 의해 형성되기 때문이다.\n\n```js\n{\n  // TDZ가 스코프 맨 위에서부터 시작\n  const func = () =\u003e console.log(letVar); // OK\n\n  // TDZ 안에서 letVar에 접근하면 ReferenceError\n\n  let letVar = 3; // letVar의 TDZ 종료\n  func(); // TDZ 밖에서 호출함\n}\n```\n\n위 코드의 경우 let 변수 선언 코드가 그 변수에 접근하는 함수보다 아래에 위치하지만, 함수의 호출 시점이 사각지대 밖이므로 정상 동작한다.\n\n## 여러가지 상황들\n\n### TDZ with Lexical Scope\n\n```js\nfunction test() {\n  var foo = 33;\n  if (true) {\n    let foo = foo + 55; // ReferenceError\n    console.log(foo);\n  }\n}\ntest();\n```\n\nMDN에 있는 예제 소스이다.\u003cbr/\u003e\n함수 내부의 조건문 block의 소스에서, foo 참조시 TDZ로 인한 `ReferenceError`가 발생한다.\u003cbr/\u003e\nfoo의 초기화 구문에서 (foo + 55)의 foo가 TDZ에 있기(아직 초기화가 되지 않았기) 때문이다.\n\n### TDZ with Default Function Parameter\n\n```js\n(function (a = b, b) {})(undefined, 1); // ReferenceError\n```\n\n```js\n(function (a = a) {})(); // ReferenceError\n```\n\n위의 2가지 예제들도 똑같이 에러가 발생한다.\u003cbr/\u003e\n함수의 디폴트 파라미터가 TDZ에 있는(초기화실행이 되지 않은 상태의) 값을 참조하고 있기 때문이다.\u003cbr/\u003e\n위의 경우에선, a가 b를 참조하는데, b의 초기화가 아직 되지 않은 상태이다.\u003cbr/\u003e\n(디폴트 파라미터는 왼쪽에서 오른쪽으로 실행)\u003cbr/\u003e\n아래에선, 위의 foo 예제와 같은 케이스이다. a의 초기화 구문에서 접근하려는 a가 TDZ에 있기 때문이다.\u003cbr/\u003e\n\n## 마치며\n\nvar로 변수를 선언하고 사용할 때도 마찬가지이지만, 초기화 후 사용에 대한 안전한 방식의 코딩스타일을 지향하는 것이 좋다.\u003cbr/\u003e\n확실한 것은, `let`과 `const`를 사용하고 `TDZ`에 대한 개념을 잘 알고 있다면, 여러 부작용을 방지할 수 있을것이다.\u003cbr/\u003e\n\n## References\n\n- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_Dead_Zone\n- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Default_parameters\n","data":{"id":"tdz","title":"TDZ(Temporal Dead Zone)","description":"TDZ(Temporal Dead Zone)에 대해 알아봅니다.","preview":"TDZ(Temporal Dead Zone)에 대해 알아봅니다.","keywords":"javascript, es6, temporal dead zone, tdz, hoisting, const, let","slug":"javascript-pass-by-value-function-parameters","createdAt":"2018.09.17","published":true},"filePath":"tdz.mdx"}]},"__N_SSG":true},"page":"/blog","query":{},"buildId":"mYpFDr1DZDEyR2tzQItwY","isFallback":false,"gsp":true,"scriptLoader":[]}</script>
16</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.