<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Gatsby Starter Blog RSS Feed]]></title><description><![CDATA[A starter blog demonstrating what Gatsby can do.]]></description><link>https://haezzz.netlify.app</link><generator>GatsbyJS</generator><lastBuildDate>Mon, 24 Mar 2025 03:11:26 GMT</lastBuildDate><item><title><![CDATA[운영 환경에서 소스맵 적용으로 Sentry 오류 추적 개선하기]]></title><description><![CDATA[혹시 Sentry 에서도 소스 맵을 지원한다는 것을 알고계시나요? 그러나 기본적으로 Sentry에 표시되는 코드는 Uglify된 상태입니다. 비록 에러에 대한 다양한 정보를 제공해주지만, 개발자들은 Uglify…]]></description><link>https://haezzz.netlify.app/upload-sentry-sourcemap/</link><guid isPermaLink="false">https://haezzz.netlify.app/upload-sentry-sourcemap/</guid><pubDate>Tue, 25 Feb 2025 20:01:24 GMT</pubDate><content:encoded>&lt;br /&gt;
&lt;p&gt;혹시 Sentry 에서도 소스 맵을 지원한다는 것을 알고계시나요?&lt;/p&gt;
&lt;br /&gt;
Sentry는 에러 발생 시점의 정보를 수집하고 상세한 에러 보고서를 제공하여 효율적인 디버깅을 가능하게 합니다.
&lt;p&gt;그러나 기본적으로 Sentry에 표시되는 코드는 Uglify된 상태입니다. 비록 에러에 대한 다양한 정보를 제공해주지만, 개발자들은 Uglify된 코드를 해석하여 실제 발생 지점을 다시 찾아내는 데 추가적인 시간을 소모하게 됩니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;이러한 비효율적인 디버깅 플로우를 해결하기 위해 Sentry 에서는 &lt;code&gt;소스 맵을 Sentry로 업로드할 수 있도록 지원&lt;/code&gt;하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;소스 맵 업로드 구성 방법&lt;/code&gt;은 크게 세 가지로 아래와 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Sentry Wizard&lt;/code&gt;를 통한 소스 맵 업로드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;번들러 또는 빌드 도구 수동 설정&lt;/code&gt;을 통한 소스 맵 업로드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Sentry CLI&lt;/code&gt;를 통한 소스 맵 업로드&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;img src=&quot;/icon-info.svg&quot; alt=&quot;Information&quot; class=&quot;info-icon&quot;&gt;
이번 글에서는 Sentry Wizard와 번들러 또는 빌드 도구를 통한 소스 맵 업로드 방식을 다룹니다.
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h1 id=&quot;sentry-wizard를-통한-소스-맵-적용&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sentry-wizard%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%86%8C%EC%8A%A4-%EB%A7%B5-%EC%A0%81%EC%9A%A9&quot; aria-label=&quot;sentry wizard를 통한 소스 맵 적용 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sentry Wizard를 통한 소스 맵 적용&lt;/h1&gt;
&lt;p&gt;가장 간단한 방법으로, Sentry Wizard를 사용하는 것입니다.&lt;/p&gt;
&lt;p&gt;아래의 명령어를 실행하면, Sentry 연동부터 패키지 설치, 빌드 도구 설정 등이 자동으로 진행됩니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;bash&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;npx @sentry/wizard@latest -i sourcemaps&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;h1 id=&quot;번들러-또는-빌드-도구를-통해-소스-맵-수동-업로드&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%B2%88%EB%93%A4%EB%9F%AC-%EB%98%90%EB%8A%94-%EB%B9%8C%EB%93%9C-%EB%8F%84%EA%B5%AC%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%86%8C%EC%8A%A4-%EB%A7%B5-%EC%88%98%EB%8F%99-%EC%97%85%EB%A1%9C%EB%93%9C&quot; aria-label=&quot;번들러 또는 빌드 도구를 통해 소스 맵 수동 업로드 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;번들러 또는 빌드 도구를 통해 소스 맵 수동 업로드&lt;/h1&gt;
&lt;p&gt;Sentry 에서 지원하는 대표적인 번들러 또는 빌드 도구로 webpack, rollup, vite 등이 있습니다. (&lt;a href=&quot;https://docs.sentry.io/platforms/javascript/sourcemaps/#sentry-bundler-support&quot;&gt;Sentry 번들러 지원&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;번들러를 통한 소스 맵 업로드는 현업에서 다양한 환경에 따라 분기 처리할 수 있기 때문에 매우 유용합니다.&lt;/p&gt;
&lt;p&gt;보통 개발 환경에서는 개발중에도 배포가 진행되고, 반영 주기가 잦기 때문에 그에 따른 추가 비용이 발생하여 설정을 꺼놓는 경우가 있습니다. 이 외에도 브랜치별 도메인 분리로 Sentry를 사용하지 않는 도메인 같은 경우 예외적으로 번들러를 통해 소스 맵 생성과 업로드를 제어할 수 있습니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;번들러 또는 빌드 도구에서 Sentry 소스맵을 적용하기 위해선 Sentry 에서 제공하는 플러그인을 통해 설정할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;1-sentry-auth-token-발급&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-sentry-auth-token-%EB%B0%9C%EA%B8%89&quot; aria-label=&quot;1 sentry auth token 발급 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Sentry Auth Token 발급&lt;/h2&gt;
&lt;p&gt;Sentry 에 수동으로 소스 맵을 업로드하기 위해선 인증 토큰 발급이 필요합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sentry.io 접속 &gt; Settings &gt; Developer Settings 섹션 &gt; Auth Tokens 메뉴&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/af489ba88b82e4a404eeeb3694da1fc8/8bd7d/authToken1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 18.354430379746837%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAqUlEQVR42pWPSwrDIBRFs//ldNZRR11CoVAoNEhIwPjXGDXcPg1pBx11cLh65Kqv618MrB/grEfJG3Iqf1FyQfAJ18uI84mhG9iIcZgguITRDkpZKGmgjySU0LTfXU3T0rasCK7xfHDcbxM6Oeu9JDUkFcWs6AIFIT24CHBuQQyRWLE0YiMu6xfyJSdsJaOz9Ksq05p/qN4ZT6Plz4je0SM2tHVqfj87Om8mDDQ0LBUtjAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Auth Token 발급 메인 페이지&quot;
        title=&quot;&quot;
        src=&quot;/static/af489ba88b82e4a404eeeb3694da1fc8/f058b/authToken1.png&quot;
        srcset=&quot;/static/af489ba88b82e4a404eeeb3694da1fc8/c26ae/authToken1.png 158w,
/static/af489ba88b82e4a404eeeb3694da1fc8/6bdcf/authToken1.png 315w,
/static/af489ba88b82e4a404eeeb3694da1fc8/f058b/authToken1.png 630w,
/static/af489ba88b82e4a404eeeb3694da1fc8/40601/authToken1.png 945w,
/static/af489ba88b82e4a404eeeb3694da1fc8/78612/authToken1.png 1260w,
/static/af489ba88b82e4a404eeeb3694da1fc8/8bd7d/authToken1.png 2358w&quot;
        sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8e0228e2121d6d1a3b2dcd855e56126b/763a5/authToken2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.708860759493675%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAA7UlEQVR42mWRiY7CMAxE8/+fiVAXQYGm5GjOweNeSGvpKY6dzjipmSaLECIY09viehlwG+4Yrn94jm8sMSGGRQk+HvlZO3OeNZ/ZacKYrVOx+23ES8Sej5eIThhHB2s9nJx1H78R4IVz7+FdhGGxta6CdLHTDJrkVFBKVXJpsjbUKnCVWt16v+RcYIKIcKOCkWOv+5Ireu/KHl2MKdRa22AfCoRWOwyn2r9JS0aS6x/u4sip9qCIGsr0OWXd76aKGBhekaKM1b0f9CNvB32ra0/M+KM4CP8Dn0kF+aAcmQU288J1hXlOJ+m3l/7zBY9OI6Ow8EIrAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Auth Token 발급 상세 페이지&quot;
        title=&quot;&quot;
        src=&quot;/static/8e0228e2121d6d1a3b2dcd855e56126b/f058b/authToken2.png&quot;
        srcset=&quot;/static/8e0228e2121d6d1a3b2dcd855e56126b/c26ae/authToken2.png 158w,
/static/8e0228e2121d6d1a3b2dcd855e56126b/6bdcf/authToken2.png 315w,
/static/8e0228e2121d6d1a3b2dcd855e56126b/f058b/authToken2.png 630w,
/static/8e0228e2121d6d1a3b2dcd855e56126b/40601/authToken2.png 945w,
/static/8e0228e2121d6d1a3b2dcd855e56126b/78612/authToken2.png 1260w,
/static/8e0228e2121d6d1a3b2dcd855e56126b/763a5/authToken2.png 2804w&quot;
        sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id=&quot;2-sentry-플러그인-설치&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-sentry-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EC%84%A4%EC%B9%98&quot; aria-label=&quot;2 sentry 플러그인 설치 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. Sentry 플러그인 설치&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;해당 포스팅은 webpack 기준으로 작성되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;프로젝트에 Sentry 에서 제공하는 플러그인을 설치합니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;bash&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;npm i @sentry/webpack-plugin --save-dev&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;h2 id=&quot;3-sentry-플러그인-설정&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-sentry-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EC%84%A4%EC%A0%95&quot; aria-label=&quot;3 sentry 플러그인 설정 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Sentry 플러그인 설정&lt;/h2&gt;
&lt;p&gt;소스 맵 수동 업로드 전 실제 빌드 시점에 소스 맵이 생성되고, 생성된 소스 맵은 Sentry 플러그인의 소스 맵 설정에 따라 업로드 후 제거됩니다.&lt;/p&gt;
&lt;p&gt;❗️ &lt;code&gt;소스 맵 생성과 제거 로직은 함께 제어&lt;/code&gt;되어야하는 부분에 주의해주세요.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;소스 맵이 제거가 진행되지 않았다면 운영 환경에 이미 모든 코드가 노출된 상태입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// webpack.config.js
const { sentryWebpackPlugin } = require(&amp;#39;@sentry/webpack-plugin&amp;#39;)

const isProd = settings.SERVER !== &amp;#39;development&amp;#39;

const webpackConfig = {
  devtool: isProd ? &amp;#39;source-map&amp;#39; : false,
  ... // 그 외 설정
}

if (isProd) {
  webpackConfig.plugins.push(
    sentryWebpackPlugin({
      // 조직명
      org: settings.SENTRY_ORG,
      // 프로젝트명
      project: settings.SENTRY_PROJECT,
      // // Sentry 에서 발급 받은 인증 토큰
      authToken: settings.SENTRY_AUTH_TOKEN,
      // 소스맵 설정
      sourcemaps: {
        // 소스맵 파일 업로드 후 삭제할 파일 패턴
        filesToDeleteAfterUpload: &amp;#39;**/**/*.map&amp;#39;,
        ignore: [&amp;#39;node_modules&amp;#39;],
      },
    })
  )
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;h1 id=&quot;소스-맵-업로드-시-주의해야될-점&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%86%8C%EC%8A%A4-%EB%A7%B5-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%8B%9C-%EC%A3%BC%EC%9D%98%ED%95%B4%EC%95%BC%EB%90%A0-%EC%A0%90&quot; aria-label=&quot;소스 맵 업로드 시 주의해야될 점 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;소스 맵 업로드 시 주의해야될 점&lt;/h1&gt;
&lt;h2 id=&quot;1-소스-맵-업로드-과정-훑어보기&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-%EC%86%8C%EC%8A%A4-%EB%A7%B5-%EC%97%85%EB%A1%9C%EB%93%9C-%EA%B3%BC%EC%A0%95-%ED%9B%91%EC%96%B4%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;1 소스 맵 업로드 과정 훑어보기 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. 소스 맵 업로드 과정 훑어보기&lt;/h2&gt;
&lt;p&gt;소스 맵이 정상적으로 업로드되지 않았을 경우를 위해 실제 빌드 시 소스 맵이 어떻게 업로드되는 지 알아야합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;빌드 실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;소스 맵 생성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sentry에 소스 맵 업로드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;업로드 후 소스 맵 파일 제거&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;빌드 완료&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 과정을 통해 Sentry에 소스 맵이 업로드되고, 업로드 후에는 소스 맵이 안전하게 제거됩니다.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id=&quot;2-소스-맵-생성과-sentry-업로드는-동일한-조건으로-실행되어야-한다&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-%EC%86%8C%EC%8A%A4-%EB%A7%B5-%EC%83%9D%EC%84%B1%EA%B3%BC-sentry-%EC%97%85%EB%A1%9C%EB%93%9C%EB%8A%94-%EB%8F%99%EC%9D%BC%ED%95%9C-%EC%A1%B0%EA%B1%B4%EC%9C%BC%EB%A1%9C-%EC%8B%A4%ED%96%89%EB%90%98%EC%96%B4%EC%95%BC-%ED%95%9C%EB%8B%A4&quot; aria-label=&quot;2 소스 맵 생성과 sentry 업로드는 동일한 조건으로 실행되어야 한다 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. 소스 맵 생성과 Sentry 업로드는 동일한 조건으로 실행되어야 한다&lt;/h2&gt;
&lt;p&gt;위에서 소스 맵 생성과 Sentry 업로드는 동일한 조건으로 제어되어야한다고 말씀드렸습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;소스 맵 생성이 되지 않는 경우&lt;/code&gt;는 위와 같이 Sentry 업로드 시 파일을 찾을 수 없다는 오류가 발생합니다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9ed73053f6d526b5229099233e9a20b8/e04e6/sentryUploadFailed.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 10.126582278481013%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAcklEQVR42h2OSw7FIAwDe4/Ho0JQfm0WRUBB3P9cbprFaJxFZG/XZVFvAhEh5yyEEOCcQ4yRfbA9zvPPKDHRj62h9c5owRgDpRS273GWgjkGBrPWEvfeMedErQ3P01CK43ygNc92fHsujkgpyYBviLUWL5XqO/RGl0GEAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Sentry 업로드 시 오류 발생&quot;
        title=&quot;&quot;
        src=&quot;/static/9ed73053f6d526b5229099233e9a20b8/f058b/sentryUploadFailed.png&quot;
        srcset=&quot;/static/9ed73053f6d526b5229099233e9a20b8/c26ae/sentryUploadFailed.png 158w,
/static/9ed73053f6d526b5229099233e9a20b8/6bdcf/sentryUploadFailed.png 315w,
/static/9ed73053f6d526b5229099233e9a20b8/f058b/sentryUploadFailed.png 630w,
/static/9ed73053f6d526b5229099233e9a20b8/40601/sentryUploadFailed.png 945w,
/static/9ed73053f6d526b5229099233e9a20b8/78612/sentryUploadFailed.png 1260w,
/static/9ed73053f6d526b5229099233e9a20b8/e04e6/sentryUploadFailed.png 2152w&quot;
        sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;p&gt;&lt;code&gt;플러그인을 통해 Sentry 업로드가 되지 않을 경우&lt;/code&gt; 더 최악의 상황이 발생할 수 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;소스 맵 생성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;소스 맵 생성과 조건이 달라 Sentry 업로드 로직이 실행되지 않음 (😱)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;소스 맵 파일이 제거되지 않은 채 빌드 완료&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;img src=&quot;/icon-info.svg&quot; alt=&quot;Information&quot; class=&quot;info-icon&quot;&gt;
동일한 변수로 또는 필드로 소스 맵 생성과 Sentry 업로드 로직을 제어해야합니다.
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;img src=&quot;/icon-info.svg&quot; alt=&quot;Information&quot; class=&quot;info-icon&quot;&gt;
한 프로젝트안에 여러 환경이 존재하는 경우 피처 플래그 방식을 통해 안전하게 제어할 수 있습니다.
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h1 id=&quot;소스-맵-적용-전--소스-맵-적용-후&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%86%8C%EC%8A%A4-%EB%A7%B5-%EC%A0%81%EC%9A%A9-%EC%A0%84--%EC%86%8C%EC%8A%A4-%EB%A7%B5-%EC%A0%81%EC%9A%A9-%ED%9B%84&quot; aria-label=&quot;소스 맵 적용 전  소스 맵 적용 후 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;소스 맵 적용 전 / 소스 맵 적용 후&lt;/h1&gt;
&lt;div style=&quot;display: flex; flex-direction: row; align-items: stretch; gap: 12px;&quot;&gt;
  &lt;div style=&quot;flex: 1;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f9b45276b9c5bf6800f2fe84c1fe4f65/21b4d/sentryLogBefore.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 41.77215189873418%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABOUlEQVR42pWRTUvEMBCG+/9/jEdv4mFBT4KgghfZBRe7bbdNNl+Tr/Z1kla86GEDL0OSyTMzbxopLrDW4dqVUkYIEc4Rx4AYE5ZlQVM2zjnknJGCx+z9lehl07oaIsLQn9F3PdqvE/RFc7WIOc9chMWdFJWOUixKtZuqsODpboe3h3scP8Z61xC3XEDTKCCFxEWyBcZinpdf6AZegZlBG9AmvNy+Y3fziHbfrSM700JpAlGonnhOXv1Jm+J2vslHzl1jgR6ejzi8fsIY4kIZjfcOQhoMg8QwSoyTxjQpCGGgjIfVriYrS9BFfKZMicSf6UExwLJt1pXCuXjo67jnYYJgmJwklNQgS3W0nzH/Uk4ze87NdAO/cwh+83Bk2NCPDFYVLqbipYJ3YfXrHxWo4k88tR3E6OAp4RvSzWzNn3+6cQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;소스 맵 적용 전&quot; title=&quot;&quot; src=&quot;/static/f9b45276b9c5bf6800f2fe84c1fe4f65/f058b/sentryLogBefore.png&quot; srcset=&quot;/static/f9b45276b9c5bf6800f2fe84c1fe4f65/c26ae/sentryLogBefore.png 158w,
/static/f9b45276b9c5bf6800f2fe84c1fe4f65/6bdcf/sentryLogBefore.png 315w,
/static/f9b45276b9c5bf6800f2fe84c1fe4f65/f058b/sentryLogBefore.png 630w,
/static/f9b45276b9c5bf6800f2fe84c1fe4f65/40601/sentryLogBefore.png 945w,
/static/f9b45276b9c5bf6800f2fe84c1fe4f65/78612/sentryLogBefore.png 1260w,
/static/f9b45276b9c5bf6800f2fe84c1fe4f65/21b4d/sentryLogBefore.png 1280w&quot; sizes=&quot;(max-width: 630px) 100vw, 630px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex: 1;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f3bbeccfe8402ea7a5522f1391ab54f8/21b4d/sentryLogAfter.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 55.06329113924051%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABQklEQVR42qWSOU8DMRCF/f//CD2ipKGgIi0FElcIgogcrL3rY3w9ZuwkBRCJw9KnlbWe671RdrIgivjtISJMHGsnB+8CQggopUANg4a1DpUflZxRmZ+cWmvj81HBe+jBYHhnNluQc5DstVSuWFvVRu7kA1yc3y0f51g/32LShMiTKqMNnuYLvL4sG5v1FiLDPklOGUmInUipExlXcHU2w/nJKW5mD62I0pxQtEi7wB4QObgHHU1I8q/g7nKB64t7GJ5S7mr1tmJRCUZPcM6zuBHes8ieWtB+1GPI+DI7hV5Q5ZTYsdQSNfH+cURT5bibyQaM3KHlDkfGcXLiar8lSocxsl7c5Xcr8KcOZallMSlQd47v8qW9k2IQm1IOa/SVvDNT9FSSTMY1emzGaN5H+Y6jY2O4mO0GJQ44RmSJRuPb+w++Sl34B99MBgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;소스 맵 적용 후&quot; title=&quot;&quot; src=&quot;/static/f3bbeccfe8402ea7a5522f1391ab54f8/f058b/sentryLogAfter.png&quot; srcset=&quot;/static/f3bbeccfe8402ea7a5522f1391ab54f8/c26ae/sentryLogAfter.png 158w,
/static/f3bbeccfe8402ea7a5522f1391ab54f8/6bdcf/sentryLogAfter.png 315w,
/static/f3bbeccfe8402ea7a5522f1391ab54f8/f058b/sentryLogAfter.png 630w,
/static/f3bbeccfe8402ea7a5522f1391ab54f8/40601/sentryLogAfter.png 945w,
/static/f3bbeccfe8402ea7a5522f1391ab54f8/78612/sentryLogAfter.png 1260w,
/static/f3bbeccfe8402ea7a5522f1391ab54f8/21b4d/sentryLogAfter.png 1280w&quot; sizes=&quot;(max-width: 630px) 100vw, 630px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;/a&gt;
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;h1 id=&quot;마무리&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%A7%88%EB%AC%B4%EB%A6%AC&quot; aria-label=&quot;마무리 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;마무리&lt;/h1&gt;
&lt;p&gt;소스 맵이 실서비스에 업로드되게 되면 코드가 사용자에게 공개되어 취약점 분석을 통한 공격이나 인증 토큰 탈취 등 큰 보안 문제가 발생할 수 있습니다. 그렇기 때문에 반드시 Sentry 로 소스맵 업로드 후 제거되었는 지 환경별로 확인해야 합니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;이번 Sentry 소스맵 업로드 작업은 소소해 보이지만, 이런 개선이 팀의 효율성을 크게 높이면서도, 적절한 주의와 테스트를 통해 보안 위험을 최소화할 수 있다는 점이 인상적이었습니다. :)&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;앞으로도 이런 작은 개선들을 통해 개발 환경과 프로세스를 지속적으로 발전시켜 나가려고 합니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[[Book Review] 타입으로 견고하게 다형성으로 유연하게]]></title><description><![CDATA[…]]></description><link>https://haezzz.netlify.app/type-polymorphism-book/</link><guid isPermaLink="false">https://haezzz.netlify.app/type-polymorphism-book/</guid><pubDate>Fri, 02 Aug 2024 23:35:37 GMT</pubDate><content:encoded>&lt;br /&gt;
&lt;p&gt;지인을 통해 알게된 책으로 타입을 제대로 알기위해선 타입 검사기의 동작 원리를 알야한다는 것을 일깨워준 책입니다.&lt;/p&gt;
&lt;p&gt;뒤로 갈수록 앞에서나온 개념이 심화되어 설명되고, 함수형 언어에서의 타입검사기의 확장된 기능까지 소개되어 매우 흥미로웠던 책이었던 것 같습니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8bae376c4d9d0358a4c48942bce5c7e5/25260/bookCover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 127.21518987341773%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAIAAAC+dZmEAAAACXBIWXMAAAsTAAALEwEAmpwYAAAF3ElEQVR42hWS+1eSBxjH3z9kywzlfXnvd1O6uDXnAoQtt7VWp611Zmups9Qy7eJWBGoyx4myLcUmXqamgimIgGYmoALDW2alW6UspynmBaxzNjzn+8vznOdzvs/5Pg9g7WhorC2/pVOWlxVG9Jv2UqVOWXFNWaFT6a8XVerUVeVX9Tc1Bv315kZDbXVFraHy56sqEYxBAhC422qortBo1XmlP2UXFWYVKXNKSgqKSvKLi/PV6rNqZV7RpTOll/O0qnMVupJafXlt1a91v9/KPpm5ZWsMYGmrq7pZolWdURZmXf3ms/vvxdsluyypH945pDAc/fTm8YMl6Yd/yv72fN6JgvyMK+qL+hq9of52Q01VxvfHgQ5jza1rSu3l3PycNN2H4imBwCHExiFwFtr2XARNY+jfFPKIw9xxhD1xe90HO/UHDpmuKJvqb9dXVwKtjfobpRd+uXQqN+OrmsS4RzDYixHjGDKNiR4T+CDBzaDwUwQdAgmfEB0B0fF33h1PkHTqbbXVvwKNNeVa9ZnS8xlZxw60J1DjItCGs16cnsFhL8GNEew8Dr3EwGkEmkLgUZz2xwqnpGl2w0BD7S2gRv+LVnW6KP945uGP+1gsAEW9RKIXsNggAc4TogCJLRFQEBe+QmMXUOEkTr+AYh98nFt122Fs0ANVN4vLLmWr80/k7JeOkKIX8LZ/IyQeu0qAYUIYJsA1QriCg0s49AoXzRHYBiLMkeVdvH3X1lwNRA5bVpj1Y05awb6kv3DhLBy9HJkmwBAJbVCityQUJqF1CnmNi4IkskQTEVibo6nrsjtM9cANzQXNhcyzmV8pJbvmsNgAsm110w16S4GrNDJF8ysUFt6E4RUGX+Xp1zgy19bg9ffbO+4AuuJ8zbnMU999qdsTP48I/kFj1glwgwSXGaKLSW7h5BPcjjAJrxHIGkeusuRrng3cs4yMDtyztgLXVGc1BRnp3+6v3c29QmLmUcEGIXxC8W2szMpJLbzcxCkmafEGja5y5AqNre5JnB3oHh1x9feYAa0yt/Rcxomv95nj6TVUMIuCg+TOVlZh5lK6eJk57pNWVt7FS2YoJsQRazS2ppDMTnjG/M6hfhtQ9uNJ1em09AMyD49Po4iZ2F3PyLs5STsrN/KfWONk7YzExMktrGR+O/+WxtYPfv7877FRb5/X3Q1E0irMOpKVmjzFYCZkZxO118Lsjbh1cvKOiFhpOytrYhUdvMIllq5TRCg97fnMxJjvvtfdA1wtOFGQfviMdPc8KfLDeB0uaWekJkZuY2UWVtrEKCJlJyc18wqjeP9jamf4/OnpTede32APUHz2+5PHvlDtSVjEwAC01YWJjUyKnZU0U/K7tKyDkbYzMguX0sSlOnbsC5BxIY3yybMx/4DD47IBqty041/vK9/NL8MxL8CoIArbyKRmWn6XlFgYqZWRNFLyFlbRx++dF4v/I+CVyutPno37nF0DD6zA5VNHjx1SNCRQQdG252D0OiL8ByM6iWQzKWmhUkxUShuT4qbeW2apcAL7hiGX/qianB4ZHnAM9ncBhT8cOXZA2h1HLUAxAUgQhME3cMwjlDPhEiMpa6Mk4/j2FUwUYolwPPs2nn9laXn8dHhkqMfjsgPXSwq/S/3IB8e+FES9hASLKLiIit5gkIdM7KGTHuPcOipcIZH1yHvxdHiXeNFpf/rUP+rp9UROZTHVV5eph5OTpjhmGoZmo7csRm8JCaJCW98Nbo0KxWxZgmKXMdE6SwY4ZuGD9xeGneNjbv9gj8dpB+zm5gf3zD5X98Ne6yOLcbLRMPXbtcCVi3PZmXNHDv2bKl9IFAc5apVngqgolJw083DI7+n1ueybazs6W+yWZlevxTt4z+93+oZdHn+/1+8cnfA9nPBN/Nk/6XRMWo1TTYZnOs3MjbIJ/wOPO0LaNtOOkDbznZ4uY+TRB/qsg/et7l6Lq6djqM/q67d53Q6/t+/PYad/1D004vKMuCKdIafN3dfpum/+Hw2BAnlOcY4SAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;book&quot;
        title=&quot;&quot;
        src=&quot;/static/8bae376c4d9d0358a4c48942bce5c7e5/f058b/bookCover.png&quot;
        srcset=&quot;/static/8bae376c4d9d0358a4c48942bce5c7e5/c26ae/bookCover.png 158w,
/static/8bae376c4d9d0358a4c48942bce5c7e5/6bdcf/bookCover.png 315w,
/static/8bae376c4d9d0358a4c48942bce5c7e5/f058b/bookCover.png 630w,
/static/8bae376c4d9d0358a4c48942bce5c7e5/40601/bookCover.png 945w,
/static/8bae376c4d9d0358a4c48942bce5c7e5/25260/bookCover.png 1113w&quot;
        sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h1 id=&quot;첫-단원-요약&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%B2%AB-%EB%8B%A8%EC%9B%90-%EC%9A%94%EC%95%BD&quot; aria-label=&quot;첫 단원 요약 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;첫 단원 요약&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;1장 타입 검사 흝어보기&lt;/code&gt;에서는 &lt;code&gt;타입 검사가 왜 필요한 지&lt;/code&gt; 그리고 &lt;code&gt;타입 검사기&lt;/code&gt;와 &lt;code&gt;정적, 동작 타입 언어&lt;/code&gt; 등 타입의 기본적인 개념을 설명하며 마지막으로 앞으로 나올 &lt;code&gt;다형성&lt;/code&gt;에 대해 설명합니다.&lt;/p&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;왜 불편함을 감수하면서까지 타입 검사를 해야되나요?
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;잠재적으로 존재하는 버그를 찾기 위해서&lt;/code&gt;입니다. 수 많은 버그 중 가장 흔한 원인은 &lt;code&gt;타입 오류(type error)&lt;/code&gt;입니다. 타입 오류는 프로그램이 예기치 않게 종료되버리기 때문에 매우 치명적입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;타입 검사기는 무엇이고, 어떤 원리로 동작되나요?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;타입 검사기(type checker)는 작성된 프로그램이 &lt;code&gt;타입 오류를 일으키는 지 자동으로 판단해주는 검사기&lt;/code&gt;입니다.
타입은 &lt;code&gt;작은 부품들이 결합한 하나의 큰 타입&lt;/code&gt;이고, 작은 타입의 정보를 읽고, 얻은 정보를 통해 큰 타입을 구성할 때 각각의 작은 타입들이 올바르게 사용되었는 지를 확인하는 것을 반복합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;타입 검사의 원칙 : &lt;strong&gt;&quot;타입 검사기는 계산 결과를 절대 알아내려 하지 않는다.&quot;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2 id=&quot;타입-검사의-장점&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%ED%83%80%EC%9E%85-%EA%B2%80%EC%82%AC%EC%9D%98-%EC%9E%A5%EC%A0%90&quot; aria-label=&quot;타입 검사의 장점 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;타입 검사의 장점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;타입 오류를 빠트리지 않고 모두 찾을 수 있다.&lt;/li&gt;
&lt;li&gt;오류 메시지를 통해 올바르게 고치기 쉽다&lt;/li&gt;
&lt;li&gt;코드 편집기에서 다양한 기능을 제공하고, 대표적인 기능으론 자동 완성 기능과 이름 바꾸기 기능이 있다.&lt;/li&gt;
&lt;li&gt;뛰어난 성능, 타입 검사에서 얻은 정보를 바탕으로 실행 중에 할 일을 줄일 수 있다.
&lt;ul&gt;
&lt;li&gt;실행 중에 불필요한 검사(매번 인자가 정수인지 확인) 시간을 줄여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;img src=&quot;/icon-info.svg&quot; alt=&quot;Information&quot; class=&quot;info-icon&quot;&gt;
타입 추론은 기본적으로 사용하되, 타입 검사기가 잘못 추론하거나 추론할 수 없는 경우, 또는 코드를 명확히 해야 할 때만 명시적인 타입 표시를 사용하는 것이 좋습니다.
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;typescript&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// ❌ file 이 File 타입인지 File Path인 string 타입인 지 정확히 알 수 없다.
function readFile(file) { ... }

// 🅾️ readFile 의 인자로 File 타입을 넘기면 된다는 것을 명확히 알 수 있다.
function readFile(file: File) { ... }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;h2 id=&quot;다형성&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%8B%A4%ED%98%95%EC%84%B1&quot; aria-label=&quot;다형성 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;다형성&lt;/h2&gt;
&lt;p&gt;다형성은 타입 검사기 동작 원리의 가장 중요한 개념입니다. 다형성은 &lt;code&gt;하나의 타입에 여러 객체를 대입할 수 있는 성질&lt;/code&gt;로 &lt;code&gt;하나의 값이 여러 타입&lt;/code&gt;에 속할 수 있고, &lt;code&gt;하나의 함수를 여러 타입의 합수로&lt;/code&gt; 사용할 수 도 있다는 것입니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;다형성은 어떤 개체에 어떻게 부여하는지에 따라 나눌 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;서브타입&lt;/code&gt;에 의한 다형성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;매개변수&lt;/code&gt;에 의한 다형성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;오버로딩&lt;/code&gt;에 의한 다형성&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;hr&gt;
&lt;h1 id=&quot;다형성-규칙-한-번에-보기&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%8B%A4%ED%98%95%EC%84%B1-%EA%B7%9C%EC%B9%99-%ED%95%9C-%EB%B2%88%EC%97%90-%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;다형성 규칙 한 번에 보기 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;다형성 규칙 한 번에 보기&lt;/h1&gt;
&lt;br /&gt;
&lt;h2 id=&quot;서브타입에-의한-다형성-규칙&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%84%9C%EB%B8%8C%ED%83%80%EC%9E%85%EC%97%90-%EC%9D%98%ED%95%9C-%EB%8B%A4%ED%98%95%EC%84%B1-%EA%B7%9C%EC%B9%99&quot; aria-label=&quot;서브타입에 의한 다형성 규칙 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;서브타입에 의한 다형성 규칙&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;요구 조건 : &lt;code&gt;A가 B이다.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A가 B이면 &lt;code&gt;A는 B의 서브타입&lt;/code&gt;이다.&lt;/li&gt;
&lt;li&gt;A가 B의 서브타입이면 &lt;code&gt;B는 A의 슈퍼타입&lt;/code&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Person { ... }

// 이 때 Student 는 Person 의 서브타입입니다.
// Student(학생)는(은) Person(사람)이기 떄문입니다.
interface Student extends Person { ... }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;반대로 Person은 Student 일 수 없습니다. 그러므로 Person 은 Student의 슈퍼타입!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h3 id=&quot;이름과-구조에-의해-결정되는-서브타입&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%9D%B4%EB%A6%84%EA%B3%BC-%EA%B5%AC%EC%A1%B0%EC%97%90-%EC%9D%98%ED%95%B4-%EA%B2%B0%EC%A0%95%EB%90%98%EB%8A%94-%EC%84%9C%EB%B8%8C%ED%83%80%EC%9E%85&quot; aria-label=&quot;이름과 구조에 의해 결정되는 서브타입 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code&gt;이름&lt;/code&gt;과 &lt;code&gt;구조&lt;/code&gt;에 의해 결정되는 서브타입&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;이름에 의한 서브타입(nominal subtyping)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;타입 검사기가 &lt;code&gt;interface&lt;/code&gt; (or &lt;code&gt;class&lt;/code&gt;) 이름과 타입 간의 extends된 관계만 고려합니다.
A가 B를 extends(확장/상속)한다면 A는 B의 서브타입입니다.&lt;/p&gt;
&lt;p&gt;다음과 같이 Student 는 extends 를 통해 Person 으로 확장합니다.
class 에서는 상속을 말하고, 아래와 같이 명시적으로 확장/상속한 것을 &lt;code&gt;직접확장/상속&lt;/code&gt; 이라 합니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Person { ... }
interface Student extends Person { ... }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;Student는 간접적으로 School을 확장/상속하고 있습니다. 이를 &lt;code&gt;간접확장/상속&lt;/code&gt; 이라 합니다.
Student는 Person 과 School 의 서브타입입니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface School { ... }
interface Person extends School { ... }
interface Student extends Person { ... }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;구조에 의한 서브타입(structure subtyping)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;구조적으로 정의된 필드와 메서드가 동일하게 정의되어있다면 A는 B의 서브타입입니다.&lt;/p&gt;
&lt;p&gt;다음의 Student는 Person 의 필드가 동일하게 정의되어있습니다.
그러므로 Student는 Person의 서브타입입니다. 반대로 Person은 Student의 no 필드가 없으므로 슈터파입이 됩니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Person {
  name: string
}

interface Student {
  no: number
  name: string
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;h2 id=&quot;매개변수에-의한-다형성&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EC%97%90-%EC%9D%98%ED%95%9C-%EB%8B%A4%ED%98%95%EC%84%B1&quot; aria-label=&quot;매개변수에 의한 다형성 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code&gt;매개변수&lt;/code&gt;에 의한 다형성&lt;/h2&gt;
&lt;p&gt;이번장에서는 제네릭 함수, 제니릭 메서드와 같이 타입 매개변수를 통해 다형성을 만드는 기능에 대해 소개합니다.&lt;/p&gt;
&lt;h3 id=&quot;제네릭-함수&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%95%A8%EC%88%98&quot; aria-label=&quot;제네릭 함수 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;제네릭 함수&lt;/h3&gt;
&lt;p&gt;제네릭 함수는 아래와 같이 &lt;code&gt;한 개 이상의 타입 매개변수를 갖는 함수&lt;/code&gt;입니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 차례대로 &amp;lt;T&amp;gt; 는 타입 매개변수, 매개변수 타입, 결과타입이다.
const testFun = &amp;lt;T&amp;gt; (a:T, b:T): T =&amp;gt; { ... }

// &amp;lt;int&amp;gt; 타입인자를 통해 타입 매개변수를 설정한다.
testFuc&amp;lt;int&amp;gt;(1, 2);&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;왜 제네릭 함수를 사용하나요?&lt;/strong&gt; &lt;br/&gt;
같은 일을 하는 함수를 타입만 다르게 하나 더 만드는 중복을 피하고 싶기 때문입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h3 id=&quot;제네릭-메서드&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%A9%94%EC%84%9C%EB%93%9C&quot; aria-label=&quot;제네릭 메서드 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;제네릭 메서드&lt;/h3&gt;
&lt;p&gt;제네릭 함수와 동일하지만, 클래스 내에서 정의된다는 차이점이 있습니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Person&amp;lt;T&amp;gt; {
  getName(name: T): T {
    return name
  }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;h3 id=&quot;제네릭-타입&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%83%80%EC%9E%85&quot; aria-label=&quot;제네릭 타입 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;제네릭 타입&lt;/h3&gt;
&lt;p&gt;타입에 타입 매개변수를 추가한 타입을 제네릭 타입이라합니다.
List, Map 과 같은 자료구조는 제네릭 타입으로 되어있습니다. (e.g List&lt;T&gt;, Map&lt;T&gt;)&lt;/p&gt;
&lt;p&gt;아래와 같이 제네릭 타입은 타입을 재사용하기 위해 사용됩니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 이미지 타입의 공통된 필드를 재사용한다.
type Img&amp;lt;T&amp;gt; = T &amp;amp; {
  width: number
  height: number
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;T가 타입 매개변수일 때, 특정 타입으로 좁히지 않은 T 타입 부품은 아무 능력도 요구되지 않는 곳에서만 사용해야 합니다. 그렇지 않으면 그 사용 방식을 예측할 수 없기 때문입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;!-- TODO: 오버로딩에 의한 다형성 --&gt;
&lt;hr&gt;
&lt;h1 id=&quot;타입-챌린지&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%ED%83%80%EC%9E%85-%EC%B1%8C%EB%A6%B0%EC%A7%80&quot; aria-label=&quot;타입 챌린지 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;타입 챌린지&lt;/h1&gt;
&lt;p&gt;책을 읽고, &lt;a href=&quot;https://github.com/type-challenges/type-challenges&quot;&gt;타입 챌린지&lt;/a&gt;를 통해 타입 검사기의 원리를 생각하면 타입을 작성해볼 수 있었습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;요구 조건 : Omit 제네릭 타입을 사용하지 않고 만들기&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;ts&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;type MyOmit&amp;lt;T extends {}, K extends keyof T&amp;gt; = {
  [P in keyof T as P extends K ? never : P]: T[P]
}

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from &amp;#39;@type-challenges/utils&amp;#39;

type cases = [
  Expect&amp;lt;Equal&amp;lt;Expected1, MyOmit&amp;lt;Todo, &amp;#39;description&amp;#39;&amp;gt;&amp;gt;&amp;gt;,
  Expect&amp;lt;Equal&amp;lt;Expected2, MyOmit&amp;lt;Todo, &amp;#39;description&amp;#39; | &amp;#39;completed&amp;#39;&amp;gt;&amp;gt;&amp;gt;,
  Expect&amp;lt;Equal&amp;lt;Expected3, MyOmit&amp;lt;Todo1, &amp;#39;description&amp;#39; | &amp;#39;completed&amp;#39;&amp;gt;&amp;gt;&amp;gt;
]&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;T는 타입 매개변수로 object 를 확장한 타입 매개변수입니다.&lt;/li&gt;
&lt;li&gt;타입 좁히기를 통해 객체 리터럴 타입만 인자로 넘길 수 있습니다.&lt;/li&gt;
&lt;li&gt;K는 T의 타입 매개변수를 확장한 T 객체 리터럴에 속한 key 타입으로 확장합니다. 그러기때문에 객체 리터럴의 key에 해당되는 인자만 넘길 수 있습니다.&lt;/li&gt;
&lt;li&gt;MyOmit 함수는 객체 리터럴의 타입 매개변수를 받아 객체 리터럴에 속한 key 중 K를 제외한 타입이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h1 id=&quot;마치며&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%A7%88%EC%B9%98%EB%A9%B0&quot; aria-label=&quot;마치며 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;마치며&lt;/h1&gt;
&lt;p&gt;타입 검사기가 다형성을 원리로 동작되는 것은 알고 있었지만, 책에서 소개되고 있는 규칙들에 대해선 자세히 알지 못했습니다. 이번 기회에 알게되어 타입 작성 시 타입 오류가 발생했을때 왜 타입 오류가 발생하는지를 어느정도 유추해볼 수 있게되었습니다.&lt;/p&gt;
&lt;p&gt;이 포스팅에서 책의 모든 내용을 담지 못했지만, 4장부터는 소개되는 동적 언어인 하스켈에서 타임 검사기의 다양한 기능이 소개됩니다. 하스켈을 사용해보지 않았기 때문에 이러한 확장된 기능이 제공되었을때 어떤식으로 사용해볼 수 있을지 무척 궁금하네요. 그리하여 다음 챕터로는 &lt;code&gt;프로그래밍 언어론&lt;/code&gt; 이란 하스켈로 알아보는 의미구조와 타입 시스템이란 책을 읽어보려고합니다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[다크 모드 적용 및 SSR 화면 깜박임 해결 (with Gatsby)]]></title><description><![CDATA[들어가기에 앞서 Gatsby 를 이용해 블로그 만들기를 시작하면서, 다크 모드 적용과 UI를 빠르게 구현하기 위해서 TailwindCSS, Material UI 등 UI…]]></description><link>https://haezzz.netlify.app/gatsby-dark-mode/</link><guid isPermaLink="false">https://haezzz.netlify.app/gatsby-dark-mode/</guid><pubDate>Tue, 28 May 2024 09:35:37 GMT</pubDate><content:encoded>&lt;h2 id=&quot;들어가기에-앞서&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%93%A4%EC%96%B4%EA%B0%80%EA%B8%B0%EC%97%90-%EC%95%9E%EC%84%9C&quot; aria-label=&quot;들어가기에 앞서 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;들어가기에 앞서&lt;/h2&gt;
&lt;p&gt;Gatsby 를 이용해 블로그 만들기를 시작하면서, 다크 모드 적용과 UI를 빠르게 구현하기 위해서 TailwindCSS, Material UI 등 UI 라이브러리 도입을 고민했습니다. 하지만 구현해야할 페이지와 컴포넌트가 다소 적기 때문에 배보다 배꼽이 더 크다 생각되어, SCSS와 모듈 방식을 선택했습니다.&lt;/p&gt;
&lt;p&gt;이번 포스팅에선 Gatsby 블로그 구축 과정 중 CSS를 이용해 다크 모드를 적용한 경험과 마주친 문제를 해결해나간 과정을 공유해보려고합니다.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&quot;테마-모드에-따른-색상-토큰-적용하기&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%ED%85%8C%EB%A7%88-%EB%AA%A8%EB%93%9C%EC%97%90-%EB%94%B0%EB%A5%B8-%EC%83%89%EC%83%81-%ED%86%A0%ED%81%B0-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0&quot; aria-label=&quot;테마 모드에 따른 색상 토큰 적용하기 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;테마 모드에 따른 색상 토큰 적용하기&lt;/h2&gt;
&lt;p&gt;일반적으로 테마 모드를 적용하는 방법은 두 가지가 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;prefers-color-schema&lt;/code&gt; 미디어 쿼리를 이용한 방법&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;장점 : CSS 만으로 테마를 구현할 수 있다. &lt;br /&gt;
단점 : light, dark 테마 모드만 적용 가능하고, 사용자의 시스템 설정을 무조건 따라야한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;body 태그 class 추가하는 방법&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;장점 : light, dark 이외의 다른 테마 모드도 적용 가능하고, 테마 변경이 자유롭다. &lt;br /&gt;
단점 : CSS 만으로 제어할 수 없고, 제어를 위한 추가 스크립트를 구현해야한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h3 id=&quot;1-prefers-color-schema-미디어-쿼리를-이용한-다크-모드-적용&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-prefers-color-schema-%EB%AF%B8%EB%94%94%EC%96%B4-%EC%BF%BC%EB%A6%AC%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EC%A0%81%EC%9A%A9&quot; aria-label=&quot;1 prefers color schema 미디어 쿼리를 이용한 다크 모드 적용 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. &lt;code&gt;prefers-color-schema&lt;/code&gt; 미디어 쿼리를 이용한 다크 모드 적용&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt; 는 CSS 미디어 쿼리로, &lt;code&gt;prefers-color-scheme&lt;/code&gt; 를 통해 사용자의 화면 모드에 따라 미리 정의한 모드의 컬러가 적용됩니다.&lt;/p&gt;
&lt;br /&gt;
&lt;details&gt;
    &lt;summary&gt;스크립트에서 시스템 화면 모드 확인 방법&lt;/summary&gt;
&lt;div&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// prefers-color-scheme 미디어 쿼리 값이 `dark` 인 지 매칭
window.matchMedia(&amp;#39;(prefers-color-scheme: dark)&amp;#39;).matches&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;deckgo-highlight-code language=&quot;css&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@media (prefers-color-scheme: dark) {
  :root {
    --bg1: #1b1b1e;
    --bg2: #f8f9fc;
    --textColor: #f8f9fc;
    --borderColor: #f8f9fc;
  }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;p&gt;미디어 쿼리를 이용해 쉽게 다크 모드를 적용할 수 있지만, 주의해야될 점이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt; 미디어 쿼리는 몇몇 구 버전에서는 지원되지 않습니다. (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme#browser_compatibility&quot;&gt;브라우저 호환성 확인&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;항상 시스템의 결정에 따라 컬러모드를 결정해야 한다는 한계가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h3 id=&quot;2-body-태그에-테마-모드-값-부여하기&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-body-%ED%83%9C%EA%B7%B8%EC%97%90-%ED%85%8C%EB%A7%88-%EB%AA%A8%EB%93%9C-%EA%B0%92-%EB%B6%80%EC%97%AC%ED%95%98%EA%B8%B0&quot; aria-label=&quot;2 body 태그에 테마 모드 값 부여하기 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. body 태그에 테마 모드 값 부여하기&lt;/h3&gt;
&lt;p&gt;body 태그에 테마 클래스를 추가하여 CSS를 적용하는 방법입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🙋🏻 그럼 테마별로 CSS를 여려번 작성해줘야되지않나요?! &lt;br /&gt;
🍎 전역 CSS 변수를 이용해 토큰을 사전에 정의한 후 해당 토큰을 이용해 CSS를 작성함으로써 CSS를 여러벌 작성해야되는 번거로움을 최소화할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;적용 순서&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;테마별 컬러 토큰을 CSS 전역 변수로 정의.&lt;/li&gt;
&lt;li&gt;정의한 CSS 변수를 활용해 CSS를 작성.&lt;/li&gt;
&lt;li&gt;테마 모드 변경을 위한 스크립트를 작성.&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;p&gt;body 태그에 CSS 변수를 정의하여 body 태그의 하위의 요소에서 정의한 변수를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;body 태그의 테마 클래스에 따라 CSS 변수가 적용되므로, &lt;code&gt;컨텐츠 CSS 작성 시 정의한 토큰을 활용&lt;/code&gt;합니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;css&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;body {
  -—background-color: #ffffff;
  —-text-color: #000000;
}

body.dark {
  -—background-color: #000000;
  -—text-color: #ffffff;
}

button {
  background-color: var(-—background-color);
  color: var(-—text-color);
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;h3 id=&quot;테마-모드-제어&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%ED%85%8C%EB%A7%88-%EB%AA%A8%EB%93%9C-%EC%A0%9C%EC%96%B4&quot; aria-label=&quot;테마 모드 제어 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;테마 모드 제어&lt;/h3&gt;
&lt;p&gt;간단히 로컬 스토리지를 활용한 모드 제어 방식을 사용자 시나리오로 작성해봤습니다.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;사용자 시나리오 (User Scenario)&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용자가 처음으로 웹 사이트 방문 시 시스템 모드의 테마가 적용된다.
&lt;blockquote&gt;
&lt;p&gt;웹 사이트는 저장된 로컬 스토리지가 있는지 확인하고, 없다면 사용자의 시스템 모드를 적용한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;사용자가 테마 모드를 변경한다.
&lt;blockquote&gt;
&lt;p&gt;사용자가 선택한 모드를 로컬 스토리지에 저장한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;웹 사이트를 종료한다.&lt;/li&gt;
&lt;li&gt;다시 웹 사이트에 방문한다. 이때 이 전에 사용자가 변경한 테마로 적용된다.
&lt;blockquote&gt;
&lt;p&gt;로컬 스토리지에 저장된 값이 있다면, 해당 값으로 테마가 적용된다.
이제 테마 모드 제어를 위해 스크립트를 추가해줍니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;
&lt;p&gt;이제 작성된 사용자 시나리오를 기반으로 &lt;code&gt;현재 테마 모드 조회&lt;/code&gt;와 &lt;code&gt;테마 모드 변경&lt;/code&gt; 함수를 구현할 수 있습니다.&lt;/p&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;p&gt;먼저 &lt;code&gt;테마 모드 조회 함수&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;matchMedia&lt;/code&gt; window 내장 메서드를 이용해 시스템 모드를 조회하고, 로컬 스토리지에 특정 키가 있는지 조회합니다.&lt;/p&gt;
&lt;p&gt;마지막으로 스토리지에 특정 키가 없다면 시스템 모드 테마를, 있다면 스토리지에 저장된 테마를 반환합니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 현재 테마 모드 가져오기
const getThemeMode = () =&amp;gt; {
  // STEP 1. 로컬 스토리지 값 조회
  const localMode = localStorage.getItem(&amp;#39;theme-mode&amp;#39;)

  // STEP 2. 시스템 모드 값 조회
  const isSystemDarkMode = window.matchMedia(&amp;#39;(prefers-color-scheme: dark)&amp;#39;).matches
  const systemMode = isSystemDarkMode ? &amp;#39;dark&amp;#39; : &amp;#39;light&amp;#39;

  // STEP 3. 우선적으로 사용자가 설정한 로컬 스토리지 값을 가져온다.
  return localMode ?? systemMode // 로컬 스토리지 값이 없다면 시스템 모드 적용
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;p&gt;&lt;code&gt;테마 모드 변경 함수&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p&gt;인자로 넘어온 변경될 모드를 스토리지에 저장한 후, 현재 적용된 모드를 &lt;code&gt;prevMode&lt;/code&gt; 변수에 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;prevMode&lt;/code&gt; 이전 테마 모드는 body 태그 클래스에서 제거하고, 변경될 테마 모드를 body 태그에 추가해줍니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 테마 모드 변경하기
const setThemeMode = mode =&amp;gt; {
  // STEP 1. 사용자가 설정한 모드를 로컬 스토리지에 저장
  localStorage.setItem(mode)

  // STEP 2. 설정한 모드로 body 태그에 클래스를 추가/삭제한다.
  const prevMode = mode === &amp;#39;light&amp;#39; ? &amp;#39;dark&amp;#39; : &amp;#39;light&amp;#39;
  window.document.body.classList.remove(prevMode)
  window.document.body.classList.add(mode)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;p&gt;이제 첫 렌더링 시 테마 적용을 위한 hook을 추가해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getThemeMode&lt;/code&gt; 함수를 통해 현재 테마 모드를 가져오고, body 태그에 클래스를 추가해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;페이지 첫 접근 시 적용된 테마 설정&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;useEffect(() =&amp;gt; {
  // STEP 1. 현재 테마 모드 가져오기
  const currentMode = getThemeMode()

  // STEP 2. body 태그에 class 추가
  window.document.body.classList.add(currentMode)

  // STEP 3. 로컬 스토리지에 테마 모드 값 갱신
  localStorage.setItem(&amp;#39;theme-mode&amp;#39;, currentMode)
}, [])&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;h2 id=&quot;서버-사이드-환경ssr에서-다크-모드-적용-시-화면-깜박임-해결&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%84%9C%EB%B2%84-%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%99%98%EA%B2%BDssr%EC%97%90%EC%84%9C-%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EC%A0%81%EC%9A%A9-%EC%8B%9C-%ED%99%94%EB%A9%B4-%EA%B9%9C%EB%B0%95%EC%9E%84-%ED%95%B4%EA%B2%B0&quot; aria-label=&quot;서버 사이드 환경ssr에서 다크 모드 적용 시 화면 깜박임 해결 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;서버 사이드 환경(SSR)에서 다크 모드 적용 시 화면 깜박임 해결&lt;/h2&gt;
&lt;p&gt;마지막으로 CSR과 SSR의 상태가 달라 발생하는 화면 깜박임 오류에 대해 다뤄보려고합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💣 서버가 터졌습니다..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0b7b9358fcb6ba65061fa712589a2974/f0551/buildFailed.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 22.78481012658228%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAx0lEQVR42oWQ226CQBRF+f9Pso2pkmLbl1ZUEIVyiUO8NUocNBJmVmdsH3ySneyXk5x1zt6O1hpZ1yzj2DjhO81YrQRt2/JISis+1hPiKsUt3xmKNwZihKOU4iQl82hBEM5v0GixpGmaTuDnOiA5ZgzyV/rJkJfC+wNaW+n7hf/5I9t01vdyLGa//+Fr7DP2J8zCkNpU0CVtKqmeXaRJJnpPbL2ROdLi3DqUNWmWI8qSvCg4Xy7dQPPhNUq5bnYcpgEnU5X98BcUNX8abvyynAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;buildFailed&quot;
        title=&quot;&quot;
        src=&quot;/static/0b7b9358fcb6ba65061fa712589a2974/f058b/buildFailed.png&quot;
        srcset=&quot;/static/0b7b9358fcb6ba65061fa712589a2974/c26ae/buildFailed.png 158w,
/static/0b7b9358fcb6ba65061fa712589a2974/6bdcf/buildFailed.png 315w,
/static/0b7b9358fcb6ba65061fa712589a2974/f058b/buildFailed.png 630w,
/static/0b7b9358fcb6ba65061fa712589a2974/f0551/buildFailed.png 862w&quot;
        sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;p&gt;Gatsby 는 정적으로 페이지를 생성하는 SSG 방식을 사용합니다. v4가 공개되고부터는 SSG 방식 외에도 서버 사이드 렌더링(SSR) 방식 또한 지원합니다.&lt;/p&gt;
&lt;p&gt;Gatsby 로컬 서버는 CSR 방식으로 동작되기 때문에 발생하지 않았던 오류가 실제 서버에 반영되었을때 발생한 것이였습니다..&lt;/p&gt;
&lt;p&gt;먼저 오류를 해결하기 위해서 로컬 서버에서도 SSR 로 동작되어야하는데, 다행이 Gatsby 에서는 로컬 서버에서도 SSR 로 동작될 수 있게 옵션을 제공합니다.&lt;/p&gt;
&lt;p&gt;gatsby-config.js 파일에 &lt;code&gt;DEV_SSR&lt;/code&gt;옵션을 추가해줍니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;module.exports = {
  // ...
  flags: {
    // NOTE: 로컬 서버에서 SSR 확인을 위해 true 로 변경
    DEV_SSR: true,
  },
  // ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3 id=&quot;-localstorage-is-not-undefined&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#-localstorage-is-not-undefined&quot; aria-label=&quot; localstorage is not undefined permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;❌ localStorage is not undefined&lt;/h3&gt;
&lt;p&gt;localStorage 는 브라우저에서만 사용 가능한 API이기 때문에 예외 처리가 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/87d8b380f31f716a2aee01a0837a5097/5ba90/buildError.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.316455696202528%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA9klEQVR42m2Qa2rDMBCEfYKWUnBkS7t6Wn4lTvqj0LsUCmkaev8TTFcyFAr98TFoJe3ObMPWg3xE6wOUi+DokAaCCwZkiyaElCtkGZoYnTZQ/f80jh2MC2iHDB5nTOuK88sJ45SQ84D1tFWO2wXTPCCPGj4QVFcwvxw6vTc00pAEpQnaWDAHcRJBJHWy9Y6tg5OhxZ0i+Uhmx+zaEaH00fK+6SXysxwevEe4JCyvM7a3I3jJOMQEnmbQtAgrnsTZY+xwCHon7dp7EudzXUtzNoSrOLixxbdn3BzhU7jLvkrtQ1xdmfAlepfB72VFNaL5E7lVfY39A1Uvi1BVxWahAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;localStorage&quot;
        title=&quot;&quot;
        src=&quot;/static/87d8b380f31f716a2aee01a0837a5097/f058b/buildError.png&quot;
        srcset=&quot;/static/87d8b380f31f716a2aee01a0837a5097/c26ae/buildError.png 158w,
/static/87d8b380f31f716a2aee01a0837a5097/6bdcf/buildError.png 315w,
/static/87d8b380f31f716a2aee01a0837a5097/f058b/buildError.png 630w,
/static/87d8b380f31f716a2aee01a0837a5097/40601/buildError.png 945w,
/static/87d8b380f31f716a2aee01a0837a5097/78612/buildError.png 1260w,
/static/87d8b380f31f716a2aee01a0837a5097/5ba90/buildError.png 1604w&quot;
        sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;localStorage 예외 처리 방법&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;if (typeof window !== undefined) { ... }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3 id=&quot;화면-깜박임-현상-screen-flickering&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%ED%99%94%EB%A9%B4-%EA%B9%9C%EB%B0%95%EC%9E%84-%ED%98%84%EC%83%81-screen-flickering&quot; aria-label=&quot;화면 깜박임 현상 screen flickering permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;화면 깜박임 현상 (Screen Flickering)&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/d8f852d6383b5d7ca5fde080f297c451/screenFlickering.gif&quot; alt=&quot;gif&quot;&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;사용자는 &lt;code&gt;dark&lt;/code&gt; 테마를 선택했지만, 초기 테마 모드가 &lt;code&gt;light&lt;/code&gt;이기 때문에 화면 깜박임이 발생합니다.&lt;/p&gt;
&lt;p&gt;SSR과 CSR의 상태가 다르기 때문인데, 하이드레이팅되는 시점에 &lt;code&gt;light&lt;/code&gt; -&gt; &lt;code&gt;dark&lt;/code&gt; 테마로 변경되기 때문입니다.
실제 서버에서 html를 생성하는 시점에는 typeof window !== undefined 로 모드 적용 코드가 예외 처리되었기 때문에 서버에서는 light 테마의 html 를 생성합니다.
그 후 하이드레이팅되며 테마가 적용되었기 때문에 화면 깜박임 현상이 발생하는 것입니다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해선 SSR과 CSR의 상태를 매칭해줘야하는데, 테마를 적용하는 로직을 스크립트 최상단에서 먼저 실행함으로써 해결할 수 있다.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;화면의 첫 진입점인 &lt;code&gt;index.html&lt;/code&gt; 내에 스크립트를 추가하여 초기 모드 값을 가져와 세팅한다.
gatsby 에선 gatsby.ssr.js 파일에서 dom이 페인팅되기 전에 스크립트를 먼저 실행되도록 추가해주었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;html&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  highlight-lines=&quot;7,15&quot;&gt;
          &lt;code slot=&quot;code&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    ...
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;script&amp;gt;
      const localThemeMode = localStorage.getItem(&amp;#39;${THEME_KEY}&amp;#39;)
      const systemThemeMode = window.matchMedia(&amp;#39;(prefers-color-scheme: dark)&amp;#39;).matches
        ? &amp;#39;dark&amp;#39;
        : &amp;#39;light&amp;#39;

      const currentMode = localThemeMode ?? systemThemeMode
      document.body.classList.add(currentMode)
    &amp;lt;/script&amp;gt;
    &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;gatsby.ssr.js&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;/**
 * @type {import(&amp;#39;gatsby&amp;#39;).GatsbySSR[&amp;#39;onRenderBody&amp;#39;]}
 */
export const onRenderBody = ({ setHtmlAttributes, setPreBodyComponents }) =&amp;gt; {
  const preloadScript = `
    const localThemeMode = localStorage.getItem(&amp;#39;${THEME_KEY}&amp;#39;)
    const systemThemeMode = window.matchMedia(&amp;#39;(prefers-color-scheme: dark)&amp;#39;).matches ? &amp;#39;dark&amp;#39; : &amp;#39;light&amp;#39;

    const currentMode = localThemeMode ?? systemThemeMode
    document.body.classList.add(currentMode)
  `

  setHtmlAttributes({ lang: `en` })
  setPreBodyComponents(&amp;lt;script dangerouslySetInnerHTML={{ __html: preloadScript }} /&amp;gt;)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;p&gt;마지막으로 페이지 첫 렌더링 시 초기값을 적용하기 위해 useEffect hook 을 수정합니다.&lt;/p&gt;
&lt;p&gt;body 에 추가된 테마 클래스를 가져와 SSR과 CSR 테마 상태를 hydration 해줍니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;js&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;useEffect(() =&amp;gt; {
	// STEP 1. 현재 테마 모드 가져오기
	const currentMode = getThemeMode()

	// STEP 2. body 태그에 class 추가
	window.document.body.classList.add(currentMode)

	// STEP 3. 로컬 스토리지에 테마 모드 값 갱신
	localStorage.setItem(&amp;#39;theme-mode&amp;#39;, currentMode)

  useEffect(() =&amp;gt; {
    // NOTE: 브라우저 환경에서만 실행
    if (isBrowser) {
      const currentMode = window.document.body.classList.contains(THEME_MODE.DARK)
        ? THEME_MODE.DARK
        : THEME_MODE.LIGHT
      setTheme({ ...theme, mode: currentMode })
    }
  }, [])&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;h2 id=&quot;마치며&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%A7%88%EC%B9%98%EB%A9%B0&quot; aria-label=&quot;마치며 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;마치며&lt;/h2&gt;
&lt;p&gt;CSS 를 이용해 다크 모드를 적용하는 방법에 대해 정리해보았습니다.&lt;/p&gt;
&lt;p&gt;개발을 하다보면 스펙에 비해 과도한 성능과 유지보수의 부담으로 다양한 상황에 유연하게 대응하기 위해서 자체적으로 개발해야될때가 올 것입니다.&lt;/p&gt;
&lt;p&gt;&quot;바퀴를 다시 발명하지 마라&quot;라는 말과 같이 미리 만들어진 바퀴는 개발 시간을 단축시킬 수 있지만, 때로는 우리가 바퀴를 발명해야 할 필요성을 느낄 때도 있습니다.&lt;/p&gt;
&lt;p&gt;이러한 순간에는 순수한 개발에 집중하여, HTML, CSS, JavaScript만을 사용하여 개발할 수 있어야 합니다.&lt;/p&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;h2 id=&quot;참고&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%B0%B8%EA%B3%A0&quot; aria-label=&quot;참고 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fe-developers.kakaoent.com/2021/211118-dark-mode&quot;&gt;웹에서 다크모드 지원하기 | 카카오엔터테인먼트 FE 기술블로그&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://godsenal.com/posts/gatsby-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%8B%A4%ED%81%AC-%ED%85%8C%EB%A7%88-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0/&quot;&gt;Gatsby 블로그 다크 테마 적용하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Mixin의 한계와 Composition API 도입]]></title><description><![CDATA[믹스인(Mixin) 란? 믹스인(mixin)은 소프트웨어 개발 원칙 중 DRY(Do not Repeat Yourself) 원칙을 지키기 위해 만들어졌습니다. DRY…]]></description><link>https://haezzz.netlify.app/avoid-using-mixins/</link><guid isPermaLink="false">https://haezzz.netlify.app/avoid-using-mixins/</guid><pubDate>Tue, 14 May 2024 15:04:55 GMT</pubDate><content:encoded>&lt;h2 id=&quot;믹스인mixin-란&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%AF%B9%EC%8A%A4%EC%9D%B8mixin-%EB%9E%80&quot; aria-label=&quot;믹스인mixin 란 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;믹스인(Mixin) 란?&lt;/h2&gt;
&lt;p&gt;믹스인(mixin)은 소프트웨어 개발 원칙 중 &lt;code&gt;DRY(Do not Repeat Yourself)&lt;/code&gt; 원칙을 지키기 위해 만들어졌습니다.&lt;/p&gt;
&lt;p&gt;DRY는 중복을 배제하는 개념으로 &lt;code&gt;같은 정보의 중복을 지양하라&lt;/code&gt; 이러한 원칙을 통해 믹스인이 설계되었습니다. 믹스인은 여러 컴포넌트 간에 공통된 기능을 분리하여 하나의 mixin 으로 컴포넌트간의 기능을 공유할 수 있도록 합니다.&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&quot;vuejs-3에서-믹스인-사용이-권장되지-않는-이유&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#vuejs-3%EC%97%90%EC%84%9C-%EB%AF%B9%EC%8A%A4%EC%9D%B8-%EC%82%AC%EC%9A%A9%EC%9D%B4-%EA%B6%8C%EC%9E%A5%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; aria-label=&quot;vuejs 3에서 믹스인 사용이 권장되지 않는 이유 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Vue.js 3에서 믹스인 사용이 권장되지 않는 이유&lt;/h3&gt;
&lt;p&gt;이러한 믹스인은 겉으로 봤을땐 코드 재사용성과 확장성에 용이하다는 이점이 있지만, &lt;code&gt;근본적인 단점&lt;/code&gt;이 존재합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;네이밍스페이스 &lt;code&gt;충돌&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;암시적&lt;/code&gt; mixin간의 충돌&lt;/li&gt;
&lt;li&gt;&lt;code&gt;불분명한&lt;/code&gt; 출처&lt;/li&gt;
&lt;li&gt;&lt;code&gt;예측하지&lt;/code&gt; 못한 사이드 이펙트 발생 우려&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;믹스인은 &lt;code&gt;암시적&lt;/code&gt;으로 data, method, life cycle hook 를 컴포넌트에 등록합니다. 이로 인해 네임스페이스 충돌이 발생하고, 코드의 추적이 어려워질 수 있습니다.&lt;/p&gt;
&lt;p&gt;또한 컴포넌트와 믹스인 사이에 &lt;code&gt;동일한 라이프 사이클을 가졌을때&lt;/code&gt;, 모두 실행되기 때문에 동시성 문제을 야기할 수 있습니다.&lt;/p&gt;
&lt;p&gt;vue3 공식문서 또한 이러한 문제를 들어 Mixin 을 지양하고, &lt;a href=&quot;https://ko.vuejs.org/guide/reusability/composables#comparisons-with-other-techniques&quot;&gt;Composition API 사용을 권장&lt;/a&gt;하고있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&quot;composition-api의-등장&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#composition-api%EC%9D%98-%EB%93%B1%EC%9E%A5&quot; aria-label=&quot;composition api의 등장 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Composition API의 등장&lt;/h2&gt;
&lt;h3 id=&quot;composition-api-란&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#composition-api-%EB%9E%80&quot; aria-label=&quot;composition api 란 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Composition API 란?&lt;/h3&gt;
&lt;p&gt;Composition API는 Vue.js 3에서 도입된 새로운 API입니다.&lt;/p&gt;
&lt;p&gt;기존 믹스인의 장점인 &lt;code&gt;재사용성을 유지&lt;/code&gt;하며, 컴포넌트 로직을 &lt;code&gt;더 유연한 코드 구조&lt;/code&gt;를 제공합니다.&lt;/p&gt;
&lt;br /&gt;
&lt;h3 id=&quot;믹스인과-composition-api-비교해보기&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%AF%B9%EC%8A%A4%EC%9D%B8%EA%B3%BC-composition-api-%EB%B9%84%EA%B5%90%ED%95%B4%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;믹스인과 composition api 비교해보기 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;믹스인과 Composition API 비교해보기&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;코드 재사용&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;믹스인&lt;/code&gt;은 컴포넌트 간에 코드를 공유하는 반면, &lt;code&gt;Composition API&lt;/code&gt;는 함수를 통해 로직을 공유합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;코드 충돌&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;믹스인&lt;/code&gt;은 data, method의 이름과 life cycle hook 을 통해 충돌이 발생할 수 있고, &lt;code&gt;Composition API&lt;/code&gt;는 함수의 스코프를 이용하기 때문에 이러한 문제를 방지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;코드 구조화&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;믹스인&lt;/code&gt;은 컴포넌트의 옵션을(data, methods, life cycle hook) 기반으로 코드를 구조화하지만, &lt;code&gt;Composition API&lt;/code&gt;는 로직(기능)을 기반으로 코드를 구조화합니다.
이로 인해 비슷한 관심사의 로직의 캡슐화와 응집도를 높일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;불분명한 출처(명시성)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;믹스인&lt;/code&gt;은 컴포넌트에 암시적으로 기능을 추가하는 반면, &lt;code&gt;Composition API&lt;/code&gt;는 명시적으로 기능을 추가합니다. 이로 인해 로직이의 가독성이 향상되고, 로직을 쉽게 추적할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;러닝 커브&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;믹스인&lt;/code&gt;은 기존 Vue.js 2에서 제공한 기능이며, 기존의 Vue.js 사용 경험이 있다면 쉽게 익힐 수 있습니다.
&lt;code&gt;Composition API&lt;/code&gt;는 Vue.js 버전 3에서 새로 등장한 개념 및 패턴으로 기존의 옵션 기반 AP에 익숙한 개발자들에게는 생소할 수 있습니다.
하지만 React.js 를 기본적으로 학습하는 프론트엔드 개발자로써는 오히려 더 쉽게 다가올 수 있는 개념이라고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;따라서, &lt;code&gt;Composition API는 믹스인의 이점을 유지하면서도 더욱 명확하고 유연한 코드 구조를 제공&lt;/code&gt;하므로, Vue.js 3에서는 믹스인 대신 Composition API 사용을 권장하고 있습니다.&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&quot;코드를-통해-mixin과-composition-api-살펴보기&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%BD%94%EB%93%9C%EB%A5%BC-%ED%86%B5%ED%95%B4-mixin%EA%B3%BC-composition-api-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;코드를 통해 mixin과 composition api 살펴보기 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;코드를 통해 Mixin과 Composition API 살펴보기&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;e.g. 멤버 정보 조회하는 API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Composition API&lt;/code&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;javascript&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// useMemberInfo.js
import { ref, onMounted } from &amp;#39;vue&amp;#39;

export const useMemberInfo = () =&amp;gt; {
  const [memberInfo, setMemberInfo] = ref({
    email: &amp;#39;&amp;#39;,
    nickName: &amp;#39;&amp;#39;,
    phoneNumber: &amp;#39;&amp;#39;,
  })

  const getMemberInfo = async () =&amp;gt; {
    // 멤버 정보 조회 API 호출
  }

  onMounted(() =&amp;gt; {
    getMemberInfo()
  })

  return {
    memberInfo,
    getMemberInfo,
  }
}

// Component.vue
import { useMemberInfo } from &amp;#39;./useMemberInfo&amp;#39;

export default {
  setup() {
    // 명식적으로 data, method 가 정의된다.
    const { memberInfo, getMemberInfo } = useMemberInfo()

    onMounted(() =&amp;gt; {
      console.log(memberInfo.value)
    })

    return { memberInfo, getMemberInfo }
  },
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code&gt;mixin&lt;/code&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;javascript&quot; terminal=&quot;none&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// mixin.js
export const memberInfoMixin = () =&amp;gt; {
  data() {
    return {
      memberInfo: {
        email: &amp;#39;&amp;#39;,
        nickName: &amp;#39;&amp;#39;,
        phoneNumber: &amp;#39;&amp;#39;,
      }
    }
  },
  methods: {
    getMemberInfo() {
      // 멤버 정보 조회 API 호출
    }
  },
  created() {
    getMemberInfo()
  }
}

// Component.vue
import { memberInfoMixin } from &amp;#39;./mixin&amp;#39;

export default {
  mixins: [memberInfoMixin],
  data() {
	  return {
		  // mixin 내에 암시적으로 data, method 가 정의된다.
		  memberInfo: null // ❗️ 코드 충돌
	  }
  },
  created() {
	// mixin life cycle hook이 먼저 호출되기 때문에 this.memberInfo 는 null 이다.
    console.log(this.memberInfo); // null
  }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br&gt;
&lt;h2 id=&quot;mixin을-composition-api-로-교체하기&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mixin%EC%9D%84-composition-api-%EB%A1%9C-%EA%B5%90%EC%B2%B4%ED%95%98%EA%B8%B0&quot; aria-label=&quot;mixin을 composition api 로 교체하기 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mixin을 Composition API 로 교체하기&lt;/h2&gt;
&lt;p&gt;믹스인 사용하는 방식은 회사의 다릅니다. 이 전 회사에서는 믹스인이 여러곳에서 재사용할 수 있다는 장점을 이용해 유틸리티성 함수까지 믹스인에 작성되어있었습니다. 하지만 600 라인이 넘어가는 믹스인 파일들과 this의 출처를 추적하기 불편하고, 네이밍스페이스 충돌로 인한 사이드 이펙트까지 발생하여 기존 팀원들과 논의 후 점진적으로 리팩터링하기로 결정했었습니다.&lt;/p&gt;
&lt;p&gt;마침 vue3 마이그레이션을 진행 중이기도 해서, Composition API로의 교체까지 고려하여 리팩터링 단계를 계획했습니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;메서드의 역할 분리&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;첫번째 단계로 믹스인의 믹스인내의 메서드들의 역할을 구분했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;유틸리티성 메서드&lt;/li&gt;
&lt;li&gt;도메인 로직과 연관된 메서드&lt;/li&gt;
&lt;li&gt;Options API를 포함한 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;위와 같은 역할의 메서드를 분리하여 아래와 같이 분리 작업을 진행했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;유틸리티성 메서드 -&gt; /utils 폴더로 이동&lt;/li&gt;
&lt;li&gt;도메인 로직과 연관된 메서드 -&gt; /features 폴더 추가 후 이동&lt;/li&gt;
&lt;li&gt;Options API를 포함한 메서드 -&gt; 추후 Vue3 마이그레이션 시 Composition API로 교체&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;위와 같은 방식으로 점차적으로 믹스인 함수를 걷어내고, 각 관심사에 맞게 구조를 변경해나갔습니다.
당연히 믹스인을 보존하거나, Composition API로 교체하거나, 또 다른 방식으로 리팩터링하는 방법이 있을 것입니다. 팀원들과 충분한 논의를 통해 알맞은 방법을 선택해나가야합니다.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2 id=&quot;참고&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%B0%B8%EA%B3%A0&quot; aria-label=&quot;참고 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://javascript.plainenglish.io/you-shouldnt-be-using-mixins-in-vue-js-anymore-ec8c09824f9f&quot;&gt;You Shouldn’t be Using Mixins in Vue.js Anymore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.vuejs.org/guide/reusability/composables#comparisons-with-other-techniques&quot;&gt;Vue.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codedamn.com/news/vuejs/vuejs-mixins-composition-api-code-reusability&quot;&gt;Vue.js Mixins and Composition API: Advanced Code Reusability&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Formatter 당신의 선택은?]]></title><description><![CDATA[Deprecation of formatting rules - ESLint - Pluggable JavaScript Linter 포맷팅 규칙 관리의 부담감 ESLint…]]></description><link>https://haezzz.netlify.app/code-formatter/</link><guid isPermaLink="false">https://haezzz.netlify.app/code-formatter/</guid><pubDate>Fri, 29 Dec 2023 21:00:37 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://eslint.org/blog/2023/10/deprecating-formatting-rules/&quot;&gt;Deprecation of formatting rules - ESLint - Pluggable JavaScript Linter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id=&quot;포맷팅-규칙-관리의부담감&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%ED%8F%AC%EB%A7%B7%ED%8C%85-%EA%B7%9C%EC%B9%99-%EA%B4%80%EB%A6%AC%EC%9D%98%EB%B6%80%EB%8B%B4%EA%B0%90&quot; aria-label=&quot;포맷팅 규칙 관리의부담감 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;포맷팅 규칙 관리의 부담감&lt;/h2&gt;
&lt;p&gt;ESLint 는 기존에 &lt;code&gt;정적 코드 분석&lt;/code&gt; + &lt;code&gt;코드 포맷팅&lt;/code&gt; 기능을 지원해왔습니다.&lt;/p&gt;
&lt;p&gt;코드 포맷팅 기능이란 &lt;code&gt;사용자가 미리 정의해놓은 규칙으로 자동 정렬해주는 기능&lt;/code&gt;을 말합니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;ESLint는 JavaScript의 큰 성장과 동시에 인기가 증가함에 따라 유지 관리에 점차 부담이 가기 시작했습니다.
ESLint 팀은 JavaScript 빠른 성장에 맞춰 구문의 변화에 따라가고자 기존의 규칙을 업데이트하거나, 새로운 규칙을 추가해달라는 요청이 쇄도했고, 점차 유지 보수에 부담이 가고, 벅찬 지경까지 오게 되었다고 합니다.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;이에 따라 ESLint는 하나의 목표인 정적 코드 분석에 집중하기 위해 코드 포맷팅 기능을 제거하기로 결정을 내렸습니다. 결정을 내리게 된 것 중 하나는 이미 시중에 전용 코드 포맷터 라이브러리들이 편중화되어 있기 때문이었다고 합니다.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id=&quot;포맷팅-규칙-제거로-인한대체방안&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%ED%8F%AC%EB%A7%B7%ED%8C%85-%EA%B7%9C%EC%B9%99-%EC%A0%9C%EA%B1%B0%EB%A1%9C-%EC%9D%B8%ED%95%9C%EB%8C%80%EC%B2%B4%EB%B0%A9%EC%95%88&quot; aria-label=&quot;포맷팅 규칙 제거로 인한대체방안 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;포맷팅 규칙 제거로 인한 대체방안&lt;/h2&gt;
&lt;p&gt;ESLint 팀에서는 포맷 규칙이 제거됨에 따라 이를 대체하기 위해 &lt;code&gt;전문 코드 포맷터&lt;/code&gt;를 도입하는 것을 제안합니다.&lt;/p&gt;
&lt;p&gt;아래는 ESLint 팀에서 제안한 코드 포맷터입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prettier&lt;/li&gt;
&lt;li&gt;DPrint&lt;/li&gt;
&lt;li&gt;ESLint Stylistic&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;hr&gt;
&lt;h3 id=&quot;prettier&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#prettier&quot; aria-label=&quot;prettier permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Prettier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;포맷팅 전문 라이브러리.&lt;/li&gt;
&lt;li&gt;포맷팅에 전문화되어있기 때문에 세세한 규칙들까지 적용되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;dprint&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dprint&quot; aria-label=&quot;dprint permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;DPrint&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Rust 기반 포맷팅 전문 라이브러리.&lt;/li&gt;
&lt;li&gt;Prettier와 거의 유사하고, 속도가 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;eslint-stylistic&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#eslint-stylistic&quot; aria-label=&quot;eslint stylistic permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ESLint Stylistic&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ESLint에서 제거된 포맷팅 관련 규칙들을 모아놓은 라이브러리.&lt;/li&gt;
&lt;li&gt;기존에 포맷팅 규칙을 사용한 프로젝트에서 제거된 규칙들을 해당 라이브러리를 통해 사용 가능하다.&lt;/li&gt;
&lt;li&gt;각각의 규칙에 대해 옵션을 지정할 수 있으며 총 91가지의 규칙을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;br /&gt;
&lt;h2 id=&quot;코드-포맷터-선호도조사&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%BD%94%EB%93%9C-%ED%8F%AC%EB%A7%B7%ED%84%B0-%EC%84%A0%ED%98%B8%EB%8F%84%EC%A1%B0%EC%82%AC&quot; aria-label=&quot;코드 포맷터 선호도조사 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;코드 포맷터 선호도 조사&lt;/h2&gt;
&lt;p&gt;실제로 &lt;code&gt;Prettier 가 시중에 많이 사용&lt;/code&gt;되고 있고, &lt;code&gt;기존 ESLint 포맷터를 사용하던 사용자들&lt;/code&gt;은 &lt;code&gt;ESLint Stylistic 도입을 고려&lt;/code&gt;할 것으로 유추됩니다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e013f346e90fa35d7edd3122ddda8722/71e8d/careerlyQNA.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.24050632911392%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAyUlEQVR42p1SywrCMBDM/3+aiKB4qwgqaFulSfNo8+y4qdaDF1sHhsAyu5nZhFnn0fUWPgSEEIFhwBJk9UA9iZhbGRcS9YND6g6amFIaBXP5DSZkh83+iMOpwvUucanaWTyXAjU34xBHKRsyFmICM9ajahQaGiy0hVAWrf7NrDO9f8XOkd/J2IMrrHcHYoHVtiCR++xmLkKM4K1+OXQ+0v56KNOPZ6TiUuTLPw5z/ltZw/uAf5GHSWVGMyxPjVRI08tNX2Ehp2/zBG1McrnqL+KKAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;png&quot;
        title=&quot;&quot;
        src=&quot;/static/e013f346e90fa35d7edd3122ddda8722/f058b/careerlyQNA.png&quot;
        srcset=&quot;/static/e013f346e90fa35d7edd3122ddda8722/c26ae/careerlyQNA.png 158w,
/static/e013f346e90fa35d7edd3122ddda8722/6bdcf/careerlyQNA.png 315w,
/static/e013f346e90fa35d7edd3122ddda8722/f058b/careerlyQNA.png 630w,
/static/e013f346e90fa35d7edd3122ddda8722/40601/careerlyQNA.png 945w,
/static/e013f346e90fa35d7edd3122ddda8722/78612/careerlyQNA.png 1260w,
/static/e013f346e90fa35d7edd3122ddda8722/71e8d/careerlyQNA.png 1304w&quot;
        sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Link : &lt;a href=&quot;https://careerly.co.kr/qnas/5871?utm_campaign=user-share&quot;&gt;Prettier vs ESLint Stylistic vs DPrint&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2 id=&quot;eslint-stylistic-을-선택한이유&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#eslint-stylistic-%EC%9D%84-%EC%84%A0%ED%83%9D%ED%95%9C%EC%9D%B4%EC%9C%A0&quot; aria-label=&quot;eslint stylistic 을 선택한이유 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ESLint Stylistic 을 선택한 이유&lt;/h2&gt;
&lt;p&gt;ESLint Stylistic 을 선택한 이유는 기존에 코드 컨벤션이 어느정도 잡혀있었기 때문에 변경점을 최소화하며 팀 내의 규칙을 세밀하게 정할 수 있기 때문에 선택하게 되었습니다.&lt;/p&gt;
&lt;br /&gt;
DPrint는 적용 사례가 많지 않고, 사실상 ESLint Stylistic과 Prettier 사이에서 고민을 했었는데, 아래는 그 당시 두 포맷터의 장/단점을 정리한 내용입니다.
&lt;br /&gt;
&lt;h3 id=&quot;prettier-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#prettier-1&quot; aria-label=&quot;prettier 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Prettier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기존 프로덕트들에서 Prettier 가 쉽게 사용되기 때문에, 신규 입사자분이 오셨을때 해당 규칙에 더 익숙하다.&lt;/li&gt;
&lt;li&gt;다른 포맷터들에 비해 더 오래된 전문 포맷터로, 세세한 규칙들가지 적용해준다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://runebook.dev/ko/docs/prettier/options?source=post_page-----ac5a16f6b3db--------------------------------#print-width&quot;&gt;printWidth 규칙 논쟁과 제한적으로 제공하는 규칙들&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h3 id=&quot;eslint-stylistic-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#eslint-stylistic-1&quot; aria-label=&quot;eslint stylistic 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ESLint Stylistic&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기존 규칙들을 반영하여 변경점을 최소화할 수 있다.&lt;/li&gt;
&lt;li&gt;90개 이상의 규칙을 제공하며, 팀 내에서 원하는 코드 스타일을 적용할 수 있다.&lt;/li&gt;
&lt;li&gt;세세한 포맷팅까지 해주진 않는다. (Prettier 는 규칙을 제한적으로 제공하는 대신 세세한 규칙들을 자동으로 포맷팅해준다의 차이..?)&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;결론적으로 ESLint Stylistic을 채택했고, 기존 컨벤션을 재정비하며 90개 이상의 규칙들을 논의하는 자리를 가졌습니다.
ESLint Stylistic 은 아직 점유율이 낮은편이고, 곧 많은 사람들이 넘어와 기여를 통해 더 높은 품질의 코드 포맷터가 될 것이라고 생각합니다.&lt;/p&gt;
&lt;br /&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/posts/haezzz_release-v170-eslint-stylisticeslint-stylistic-activity-7175782836568788992-qMnu?utm_source=share&amp;#x26;utm_medium=member_ios&quot;&gt;ESLint Stylistic 컨트리뷰터가 되었습니다!&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id=&quot;마치며&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%A7%88%EC%B9%98%EB%A9%B0&quot; aria-label=&quot;마치며 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;마치며&lt;/h2&gt;
&lt;p&gt;이전 회사에서는 Prettier를 사용하며 코드 스타일에 대한 고민 없이 개발에 집중할 수 있었습니다. 하지만 ESLint만으로 구성된 현재 프로젝트에 참여하면서, 개발자마다 다른 코드 스타일로 인해 PR 코멘트가 빈번했고 리뷰 시간이 늘어나는 경험을 했습니다.
포맷팅 이슈가 앞으로의 유지보수까지 고려해야 하는 중요한 논의 주제가 되었고, 이를 통해 자동화된 코드 포맷터의 중요성을 다시 한번 실감하게 되었습니다.&lt;/p&gt;
&lt;p&gt;이번 작업으로 &lt;code&gt;코드 스타일에 대한 코멘트가 줄어들었고&lt;/code&gt;, ESLint Stylistic의 90개 이상의 규칙을 팀원들과 같이 논의하며 규칙들 하나하나에 대해 알아보고 적용하는 과정이 매우 흥미로운 경험이었습니다.&lt;/p&gt;</content:encoded></item></channel></rss>