<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>자라는 것을 잘하는 개발자</title>
    <link>https://xiubindev.tistory.com/</link>
    <description>자라는 것을 잘하는 개발자가 되고 싶은 xiubin입니다.
⛏ 꾸준한 삽질과 기록을 통해 자라나고 있습니다. 
  함께 성장하는 것을 좋아합니다. 
  가치있는 서비스 개발을 위해 노력합니다. 
  중국어를 배웠고 이젠 프로그래밍 언어를 배웁니다. </description>
    <language>ko</language>
    <pubDate>Mon, 8 Jun 2026 19:56:54 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>xiubin</managingEditor>
    <image>
      <title>자라는 것을 잘하는 개발자</title>
      <url>https://tistory1.daumcdn.net/tistory/3195017/attach/eed18ddb8a71462da3c0abf1b99249f0</url>
      <link>https://xiubindev.tistory.com</link>
    </image>
    <item>
      <title>Electron 배포 및 자동 업데이트 (feat. AWS S3 &amp;amp; electron-updater)</title>
      <link>https://xiubindev.tistory.com/145</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntt2l/btss71JZAPv/YEqfuSw9CtRHiO4Ls7nFMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntt2l/btss71JZAPv/YEqfuSw9CtRHiO4Ls7nFMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntt2l/btss71JZAPv/YEqfuSw9CtRHiO4Ls7nFMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fntt2l%2Fbtss71JZAPv%2FYEqfuSw9CtRHiO4Ls7nFMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Electron Build Lifecycle&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vQ3QR/btsshlqpIM9/9khAjKnPSlxmPgXoS2aFIK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vQ3QR/btsshlqpIM9/9khAjKnPSlxmPgXoS2aFIK/img.webp&quot; data-alt=&quot;https://www.electronforge.io/core-concepts/build-lifecycle&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vQ3QR/btsshlqpIM9/9khAjKnPSlxmPgXoS2aFIK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvQ3QR%2FbtsshlqpIM9%2F9khAjKnPSlxmPgXoS2aFIK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;613&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.electronforge.io/core-concepts/build-lifecycle&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;데스크탑 애플리케이션을 빌드하고 배포하는 과정에서 주목해야 할 부분은 &lt;span style=&quot;color: #ee2323;&quot;&gt;배포 가능한 파일을 생성&lt;/span&gt;하는 단계이다. 웹과 달리 패키징 단계에서 생성된 번들을 가지고 OS별 설치 프로그램을 생성해야 한다. 이 단계에서 대응하고자 하는 OS별, 플랫폼별로 모두 빌드해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;electron-builder를 선택한 이유&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron 앱을 패키징하고 배포하기 위해 사용하는 대표적인 도구는&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.electronjs.org/docs/latest/tutorial/application-distribution&quot;&gt;Electron Forge&lt;/a&gt;로 문서에서 소개되어 있다. Electron Forge의 가장 큰 장점은 분산되어 있는 배포 작업용 패키지들을 통합해서 빌드 파이프라인을 최소한의 구성으로 생성할 수 있다는 것이다. electron-builder 역시 분산되어 있는 패키지들을 어느 정도 통합해 두긴 했지만 배포에 필요한 스크립트를 대부분 직접 짜야한다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;서로 장단점이 있지만 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;electron-builder를 사용하고자 했던 가장 큰 이유는 자동 업데이트 기능에 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;업데이트가 있을 때마다 유저가 새로운 설치 파일을 직접 다운로드하는 것은 굉장히 번거로운 일이다. 그렇다면 애플리케이션에서 자동으로 업데이트가 있음을 감지하고 다운로드한다면 어떨까? 이것을 가능하게 해주는 기능이 바로 electron의 자동 업데이트 기능이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Electron Forge로 자동 업데이트를 구성하려면&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.electronforge.io/advanced/auto-update&quot;&gt;오픈 소스로 깃허브에 릴리즈 하거나 자체 업데이트 서버를 구성해야 했다.&lt;/a&gt; 그러나 우리 프로젝트는 오픈 소스가 아닐뿐더러 업데이트 서버를 자체적으로 운영하기에는 관리 포인트가 늘어나기 때문에 좋은 방법이 아니었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 자동 업데이트를 지원하는&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/electron-userland/electron-builder&quot;&gt;electron-builder&lt;/a&gt;에서 소개하는&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;electron-updater를&lt;/span&gt;&amp;nbsp;함께 사용&lt;/span&gt;하기로 했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;A complete solution to package and build a ready for distribution&amp;nbsp;Electron,&amp;nbsp;Proton Native&amp;nbsp;app for macOS, Windows and Linux with &amp;ldquo;auto update&amp;rdquo; support out of the box.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron-builder를 소개하는 문구에서 볼 수 있듯이 &lt;u&gt;별도의 구성 없이 &quot;auto update&quot;를 지원&lt;/u&gt;한다는 것을 강조하고 있음을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;By default, Electron Forge will only build your app for the operating system it's running on.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 &lt;a href=&quot;https://www.electronforge.io/core-concepts/build-lifecycle#cross-platform-build-systems&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;빌드에 관한 electron-forge 문서&lt;/a&gt;를 살펴보면 실행 중인 운영체제용으로만 앱을 빌드할 수 있고, 크로스 플랫폼 빌드를 하기 위해서는 별도로 빌드 머신을 이용해서 빌드 파이프라인을 구성해야 한다고 되어있다. 그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;개발 편의성을 위해 macOS에서도 windows 앱을 빌드하고 배포할 수 있도록 electron-builder를 선택하기로 결정&lt;/span&gt;했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;AWS S3 버킷 생성 및 AWS Access key 구성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Window App Store 또는 Mac App Store에 올리지 않는다면 누구나 웹에서 앱을 다운로드할 수 있도록 배포할 필요가 있다. 이를 위해 AWS S3에 public으로 게시하는 방법을 아래에서 소개하겠다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1. AWS IAM 서비스에서 access 키 발급&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oomYC/btsswimr3SL/Tys8smgV8PKKNdLnV4BZb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oomYC/btsswimr3SL/Tys8smgV8PKKNdLnV4BZb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oomYC/btsswimr3SL/Tys8smgV8PKKNdLnV4BZb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoomYC%2Fbtsswimr3SL%2FTys8smgV8PKKNdLnV4BZb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;263&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;우선 AWS IAM 서비스에서 사용자를 추가해서 발급받은 키(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)를 저장해 둔다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 파일 읽기 및 업로드 등을 위해&amp;nbsp;AmazonS3FullAccess 권한을 부여한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. AWS S3 버킷 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot; data-token-index=&quot;0&quot;&gt;버킷을 생성할 때 객체 소유권 - ACL은 활성화하고, 퍼블릭 액세스 차단은 모두 비활성화해 준다. 그리고 버킷 정책을 아래와 같이 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693381544018&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Id&quot;: &quot;Policy1234&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;asdf&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: &quot;s3:GetObject&quot;,
            &quot;Resource&quot;: &quot;arn:aws:s3:::your-app/*&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;3. AWS access key 추가&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Amazon S3 options. AWS credentials are required, please see&amp;nbsp;&lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;getting your credentials&lt;/a&gt;. Define AWS_ACCESS_KEY_ID and&amp;nbsp;AWS_SECRET_ACCESS_KEY environment variables. Or in the &lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;~/.aws/credentials&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위와 같이 문서에서 제공한 방법은 두 가지이다. 환경변수를 추가하는 것과&amp;nbsp; 컴퓨터 루트 폴더에 추가하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;방법1. env 파일에 환경변수로 추가하기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693535064016&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AWS_ACCESS_KEY_ID = 
AWS_SECRET_ACCESS_KEY =&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;방법2. AWS CLI 설치해서 설정하기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1693380060365&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;최신 버전의&amp;nbsp;AWS CLI&amp;nbsp;설치 또는 업데이트 - AWS Command Line Interface&quot; data-og-description=&quot;이전 버전에서 업데이트하는 경우 unzip 명령을 실행하면 기존 파일을 덮어쓸지 묻는 메시지가 표시됩니다. 스크립트 자동화와 같은 경우에 이러한 프롬프트를 건너뛰려면 unzip에 대한 -u 업데이&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;최신 버전의&amp;nbsp;AWS CLI&amp;nbsp;설치 또는 업데이트 - AWS Command Line Interface&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 버전에서 업데이트하는 경우 unzip 명령을 실행하면 기존 파일을 덮어쓸지 묻는 메시지가 표시됩니다. 스크립트 자동화와 같은 경우에 이러한 프롬프트를 건너뛰려면 unzip에 대한 -u 업데이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위 문서를 참고해서 AWS CLI를 설치한다. 설치 후 키를 추가하기 위해 터미널에서 &lt;span style=&quot;background-color: #000000; color: #ffffff;&quot;&gt;aws configure&lt;/span&gt; 명령어를 실행한다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;실행 후 IAM에서 발급받은 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 를 입력하고, 생성한 버킷의 region을 입력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;완료 후 터미널에서&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #000000; color: #ffffff;&quot; data-token-index=&quot;1&quot;&gt;&amp;nbsp;aws configure list&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;명령어를 실행해서 잘 추가됐는지 확인한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FUUwj/btssAECBWq1/ODZMBgAd1x4smmTZfTpmw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FUUwj/btssAECBWq1/ODZMBgAd1x4smmTZfTpmw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FUUwj/btssAECBWq1/ODZMBgAd1x4smmTZfTpmw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFUUwj%2FbtssAECBWq1%2FODZMBgAd1x4smmTZfTpmw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;97&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;electron-builder 구성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1693385114854&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Publish - electron-builder&quot; data-og-description=&quot;Publish The publish key contains a set of options instructing electron-builder on how it should publish artifacts and build update info files for auto update. String | Object | Array where Object it is Keygen, Generic Server, GitHub, S3, Spaces or Snap Sto&quot; data-og-host=&quot;www.electron.build&quot; data-og-source-url=&quot;https://www.electron.build/configuration/publish#continuous-deployment-workflow-on-amazon-s3-and-other-non-github&quot; data-og-url=&quot;https://www.electron.build/configuration/publish#continuous-deployment-workflow-on-amazon-s3-and-other-non-github&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.electron.build/configuration/publish#continuous-deployment-workflow-on-amazon-s3-and-other-non-github&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.electron.build/configuration/publish#continuous-deployment-workflow-on-amazon-s3-and-other-non-github&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Publish - electron-builder&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Publish The publish key contains a set of options instructing electron-builder on how it should publish artifacts and build update info files for auto update. String | Object | Array where Object it is Keygen, Generic Server, GitHub, S3, Spaces or Snap Sto&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.electron.build&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;파일을 AWS S3에 배포하기 위해서 &lt;span style=&quot;color: #ee2323;&quot;&gt;publish 옵션을 구성해줘야 한다.&lt;/span&gt; 세부적인 옵션은 위 문서를 참고하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron-builder 구성은 디폴트로는 electron-builder.yml 파일에서 작성하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693385288343&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;appId: com.electron.app
//...생략
publish:
    provider: s3 //배포 공급자
    bucket: your-app //S3 버킷 이름
    region: ap-southeast-1 //S3 버킷 지역
    acl: public-read //S3 ACL 권한
    publishAutoUpdate: true // 자동 업데이트 정보 파일 배포 여부&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 package.json에 OS 별로 빌드 스크립트를 작성해 준다. &lt;a href=&quot;https://www.electron.build/multi-platform-build&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서에는 빌드가 병렬로 진행&lt;/a&gt;되기 때문에 하나의 빌드 명령어에 멀티 플랫폼을 지정해주는 것이 좋다고 나와 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693547197103&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;build&quot;: &quot;npm run typecheck &amp;amp;&amp;amp; electron-vite build&quot;,
&quot;deploy:win&quot;: &quot;npm run build &amp;amp;&amp;amp; electron-builder --win --x64 --config --publish always&quot;,
&quot;deploy:mac-x64&quot;: &quot;npm run build &amp;amp;&amp;amp; electron-builder --mac --x64 --config --publish always&quot;,
&quot;deploy:mac-arm64&quot;: &quot;npm run build &amp;amp;&amp;amp; electron-builder --mac --arm64 --config --publish always&quot;
&quot;deploy:all&quot;: &quot;npm run build &amp;amp;&amp;amp; electron-builder -mwl --config --publish always&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위와 같이 구성해 놓으면 S3로 배포할 준비는 완료된 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;배포 완료&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;테스트로 &lt;i&gt;deploy:mac-arm64&lt;/i&gt; 배포 스크립트를 실행하면 S3로 배포되는 것을 아래 로그에서 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3VTG/btssOuzi2w8/ijaneofFhNxmYFtRZPkvVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3VTG/btssOuzi2w8/ijaneofFhNxmYFtRZPkvVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3VTG/btssOuzi2w8/ijaneofFhNxmYFtRZPkvVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3VTG%2FbtssOuzi2w8%2FijaneofFhNxmYFtRZPkvVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;311&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;S3 버킷을 확인하면 아래와 같이 파일이 업로드된 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-01 오후 4.30.16.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HflOM/btssPwDSYEs/rf5lznLqX2Jz2fqnX0ozf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HflOM/btssPwDSYEs/rf5lznLqX2Jz2fqnX0ozf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HflOM/btssPwDSYEs/rf5lznLqX2Jz2fqnX0ozf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHflOM%2FbtssPwDSYEs%2Frf5lznLqX2Jz2fqnX0ozf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;308&quot; data-filename=&quot;스크린샷 2023-09-01 오후 4.30.16.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;electron-updater 자동 업데이트&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron-updater를 사용하면 간단한 설정 몇 가지만 진행하면 자동 업데이트 기능을 구현할 수 있다. 자동 업데이트 가능한 대상은 디폴트로 DMG, AppImage, NSIS 가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 macOS의 경우 자동 업데이트가 작동하려면 반드시 코드 서명이 필요하고, windows의 경우는 코드 서명이 없어도 가능하다. 코드 서명에 대한 글은 &lt;a href=&quot;https://xiubindev.tistory.com/144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;에서 자세히 작성해 뒀다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron-updater 라이브러리에서 자동 업데이트의 핵심 함수는 &lt;span style=&quot;color: #ee2323;&quot;&gt;checkForUpdates이다.&lt;/span&gt; 이 함수를 통해 업데이트 여부를 체크하고 업데이트가 필요하다면 업데이트를 진행하게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그렇다면&lt;span style=&quot;color: #333333;&quot;&gt; 업데이트 여부를 어떻게 체크하는 걸까? 정답은 &lt;span style=&quot;color: #ee2323;&quot;&gt;latest.yml&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; 파일&lt;/span&gt;에 있다. 위 배포 스크립트를 실행하면 latest.yml &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;(또는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;latest-mac.yml&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt; for macOS, 또는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;latest-linux.yml&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;for Linux)&amp;nbsp;&lt;/span&gt; 파일이 생성되고 함께 올라간다. 이 파일에는 파일의 버전 정보, 배포 날짜 등이 담겨있는데, 버전을 변경해서 배포하게 되면 버전 정보, 배포 날짜 등이 수정돼서 올라간다. 앱이 실행되면 이 파일을 읽어서 버전이 다를 경우 자동 업데이트가 실행되는 구조이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;자동 업데이트를 진행하는 방식에는 두 가지가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;사용자의 의사와는 관계없이 자동으로 업데이트를 진행하는 방식 -&amp;nbsp;checkForUpdatesAndNotify&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;사용자가 직접 업데이트를 진행해야 하는 방식 - checkForUpdates&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;checkForUpdatesAndNotify&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;를 사용하게 되면 업데이트 가능한 상황에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;운영체제의 시스템 알림 창을 띄워서 업데이트 진행 여부를 묻게 된다&lt;/span&gt;. 여기서 주의해야할 점은 &lt;u&gt;시스템 알림 설정에서 앱에 대한 알림을 꺼두면 업데이트 알림을 볼 수 없다&lt;/u&gt;. 다만 알림은 오지 않더라도 업데이트 다운로드는 자동으로 되고 앱을 종료하고 다시 실행하게 되면 업데이트된 버전이 실행된다. 따라서 업데이트 알림을 반드시 보여주고 싶거나 업데이트 여부를 유저가 결정하도록 하게 하려면 직접 업데이트 알림창 UI를 구현해서 렌더링해야한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이와 달리 &lt;/span&gt;&lt;b&gt;checkForUpdates&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;를 사용하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;사용자에게 별도의 알림 없이 업데이트를 진행&lt;/span&gt;하게 된다. 흔히 말하는 잠수함 패치와 같은 것으로 보면 된다. 여기서 주의해야 할 점은 업데이트를 진행한다는 것은 업데이트 버전 파일만 다운로드하는 것이다. &lt;u&gt;업데이트된 파일을 실행하려면 프로그램을 종료한 후에 다시 실행&lt;/u&gt;해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이를 위해 유저가 직접 프로그램을 종료한 후에 다시 실행하는 방법도 있지만, 업데이트 다운로드 완료 이후에 바로 업데이트 된 파일을 실행하고 싶다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;다운로드 완료 시점에&amp;nbsp;&lt;b&gt;quitAndInstall&lt;/b&gt; 메소드를 호출&lt;/span&gt;하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 12.00.56.png&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XJ1TR/btssZdwW9po/OAW5QG0DSMb0XQkvHmdmZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XJ1TR/btssZdwW9po/OAW5QG0DSMb0XQkvHmdmZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XJ1TR/btssZdwW9po/OAW5QG0DSMb0XQkvHmdmZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXJ1TR%2FbtssZdwW9po%2FOAW5QG0DSMb0XQkvHmdmZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;218&quot; height=&quot;123&quot; data-filename=&quot;스크린샷 2023-09-04 오후 12.00.56.png&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 macOS 앱은 일반적으로 닫기 버튼을 클릭해도 애플리케이션이 완전하게 종료되는 것이 아니라 'active' 상태로 있다. 위와 같이 앱에 흰색 dot이 표시가 되어 있으면 'active' 상태라고 할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이처럼 macOS 전용 데스크톱앱은 기본적으로 close와 quit 액션이 나뉘어 있어서 electron에서도 두 가지 액션에 대한 가이드를 주고 있다. &lt;u&gt;닫기 버튼을 클릭해서 윈도우 창이 닫힐 때(close) 앱이 종료되지(quit) 않도록&lt;/u&gt; 데스크탑앱을 개발해야 한다. electron 앱에서는 디폴트로 닫기 버튼을 클릭하면 앱이 종료되는데, macOS에서는 앱을 종료하지 않도록&amp;nbsp; &lt;a href=&quot;https://www.electronjs.org/docs/latest/api/app#event-window-all-closed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;'window-all-closed' 이벤트&lt;/a&gt;에서 예외처리를 해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위에서 언급한 'active' 된 상태는 macOS 운영체제에서만 있는 상태이므로 electron 공식 문서에도 보면 &lt;a href=&quot;https://www.electronjs.org/docs/latest/api/app#event-activate-macos&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;'activate'라는 이벤트&lt;/a&gt;가 macOS 전용으로 있는 것을 확인할 수 있다. 그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;자동 업데이트 여부 체크하는 코드를 'activate' 이벤트가 발생했을 때도 추가&lt;/span&gt;해줘야 한다. 그렇지 않으면 앱을 종료하고 다시 실행했을 때만 자동 업데이트 여부가 체크된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;checkForUpdates&amp;nbsp;&lt;/b&gt;함수는 앱이 실행됐을 때 한 번 호출되기 때문에 앱이 실행 중일 때 업데이트를 지속적으로 감지하려면 &lt;span style=&quot;color: #ee2323;&quot;&gt;setInterval을 통해 업데이트 여부를 주기적으로 체크해 주는&lt;/span&gt; 코드를 추가하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위 내용을 바탕으로 electron-updater를 활용한 자동 업데이트 기능 구현 코드는 아래와 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693550865682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { app, shell, BrowserWindow } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { autoUpdater } from 'electron-updater' //  추가
import log from 'electron-log' //  추가

function createWindow(): void {
  //...생략
}

/**  자동 업데이트 log start **/
autoUpdater.on('checking-for-update', () =&amp;gt; {
  log.info('checking-for-update')
})
autoUpdater.on('update-available', () =&amp;gt; {
  log.info('update-available')
})
autoUpdater.on('update-not-available', () =&amp;gt; {
  log.info('update-not-available')
})
autoUpdater.on('error', (err) =&amp;gt; {
  log.info('error : ' + err)
})
autoUpdater.on('download-progress', (progressObj) =&amp;gt; {
  const log_message = 'download: ' + progressObj.percent + '%'
  log.info(log_message)
})
/**  자동 업데이트 log end **/

autoUpdater.on('update-downloaded', () =&amp;gt; {
  log.info('update-downloaded')
  //   업데이트 파일 자동 다운로드 이후 애플리케이션 종료하고 다시 실행
  autoUpdater.quitAndInstall() 
})

app.whenReady().then(() =&amp;gt; {
  electronApp.setAppUserModelId('com.electron')

  app.on('browser-window-created', (_, window) =&amp;gt; {
    optimizer.watchWindowShortcuts(window)
  })


  //  자동 업데이트 등록 
  autoUpdater.checkForUpdates()
  /**
  주기적으로 업데이트 감지하려면 아래와 같이 코드 작성
  setInterval(() =&amp;gt; {
  	autoUpdater.checkForUpdates()
  }, 30000)
  */

  createWindow()

  app.on('activate', () =&amp;gt; {
    if (BrowserWindow.getAllWindows().length === 0) {
		autoUpdater.checkForUpdates() //  mac os용
		createWindow()
  })
})

app.on('window-all-closed', () =&amp;gt; {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 위 코드를 보면 &lt;span style=&quot;color: #ee2323;&quot;&gt;electron-log를 활용&lt;/span&gt;한 부분을 볼 수 있다. 자동 업데이트에 대한 로그를 쉽게 볼 수 있도록 위와 같이 로깅 작업을 해두면 좋다. 로그 파일은 아래와 같은 위치에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;on Linux:&lt;/b&gt;&amp;nbsp;~/.config/{app name}/logs/{process type}.log&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;on macOS:&lt;/b&gt;&amp;nbsp;~/Library/Logs/{app name}/{process type}.log&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;on Windows:&lt;/b&gt;&amp;nbsp;%USERPROFILE%\AppData\Roaming\{app name}\logs\{process type}.log&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/Frontend</category>
      <category>electron</category>
      <category>electron-builder</category>
      <category>electron-updater</category>
      <category>일렉트론 s3 배포</category>
      <category>일렉트론 자동업데이트</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/145</guid>
      <comments>https://xiubindev.tistory.com/145#entry145comment</comments>
      <pubDate>Mon, 4 Sep 2023 16:10:42 +0900</pubDate>
    </item>
    <item>
      <title>Electron Code Signing (코드 서명)</title>
      <link>https://xiubindev.tistory.com/144</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbys5Z/btssgsg0pUd/oqRZ5fHRir5Jehbvordchk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbys5Z/btssgsg0pUd/oqRZ5fHRir5Jehbvordchk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbys5Z/btssgsg0pUd/oqRZ5fHRir5Jehbvordchk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbys5Z%2Fbtssgsg0pUd%2FoqRZ5fHRir5Jehbvordchk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내 맥북에서는 잘 되는데 다른 사람 맥북에서는 실행이 안되네?&lt;br /&gt;윈도우에서 실행은 가능한데 SmartScreen filter에 걸리네?&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;데스크탑 앱을 처음 만들어 보는 사람이라면 위와 같은 상황을 모두 한 번 쯤은 겪어봤을 것이다. 나 역시 그랬다  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;이번에 Electron으로 데스크탑 앱을 만들어야했는데, 데스크탑 앱 개발에 대한 기본 지식이 부족한 상황이었다. &lt;a href=&quot;https://electron-vite.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;electron-vite&lt;/a&gt;를 사용해서 개발 환경을 구축하는데는 무리가 없었지만, 역시나 예상대로 빌드와 배포를 하는데 엄청난 시간과 노력이 필요했다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;위 문제를 해결하는데도 며칠 동안 리서치하며 해결방안을 찾아냈다. 위 문제의 정답은 코드 서명에 있었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코드 서명이란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코드 서명(Code Signing)은 실행 파일과 스크립트에 디지털 서명을 하는 과정으로, 서명 이후에 코드가 변조되거나 손상되지 않음을 보장한다. 진위와 무결성 확인을 위해 암호화 해시를 사용한다. 코드 서명을 하지 않으면 각 운영 체제에서 보안 검사가 트리거되어 제대로 앱이 실행되지 않을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코드 서명을 해야하는 이유&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgyft4/btsrv6OcrbE/HA2j3BWKN97LaBbta49uKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgyft4/btsrv6OcrbE/HA2j3BWKN97LaBbta49uKK/img.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;481&quot; data-is-animation=&quot;false&quot; style=&quot;width: 52.9558%; margin-right: 10px;&quot; data-widthpercent=&quot;53.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgyft4/btsrv6OcrbE/HA2j3BWKN97LaBbta49uKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgyft4%2Fbtsrv6OcrbE%2FHA2j3BWKN97LaBbta49uKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;523&quot; height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk7uqo/btsrypM63bC/9lPUqij2k5P3nWVKE1BD3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk7uqo/btsrypM63bC/9lPUqij2k5P3nWVKE1BD3k/img.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2123&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.8814%;&quot; data-widthpercent=&quot;46.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk7uqo/btsrypM63bC/9lPUqij2k5P3nWVKE1BD3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk7uqo%2FbtsrypM63bC%2F9lPUqij2k5P3nWVKE1BD3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2123&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(1) windows에서 앱 실행 방지 (2) macOS에서 앱 실행 방지&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcq9dP/btsrv8L7r39/qPAt6cihCHPYAZL3ZPfZFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcq9dP/btsrv8L7r39/qPAt6cihCHPYAZL3ZPfZFk/img.png&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;100&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 62.4849%; margin-right: 10px;&quot; data-widthpercent=&quot;63.22&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcq9dP/btsrv8L7r39/qPAt6cihCHPYAZL3ZPfZFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcq9dP%2Fbtsrv8L7r39%2FqPAt6cihCHPYAZL3ZPfZFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;437&quot; height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crJZFY/btsrwXXQAFo/hjMka8IIWCB46kAwzYT4vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crJZFY/btsrwXXQAFo/hjMka8IIWCB46kAwzYT4vK/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;118&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;36.78&quot; style=&quot;width: 36.3524%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crJZFY/btsrwXXQAFo/hjMka8IIWCB46kAwzYT4vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrJZFY%2FbtsrwXXQAFo%2FhjMka8IIWCB46kAwzYT4vK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(3) 브라우저에서 실행 파일 다운로드 경고&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;패키징을 완료한 앱을 사용자들에게 배포했을 때 위 화면과 같이 &lt;u&gt;운영 체제 보안 검사를 트리거하지 않도록&lt;/u&gt; 앱에 서명해야 한다.&amp;nbsp;&lt;span style=&quot;color: #ee2323; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Windows와 macOS는 기본적으로 서명되지 않은 응용 프로그램의 다운로드 또는 실행을 방지&lt;/span&gt;한다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;Windows에서는 인증서가 없거나 신뢰 수준이 낮은 경우 사용자가 앱을 실행하려고 할 때 보안 경고창이 표시된다. macOS에서 시스템은 변경 사항이 실수로 도입되었는지 또는 악성 코드에 의해 도입되었는지 여부에 관계없이 앱에 대한 모든 변경 사항을 감지할 수 있다. 서명되지 않은 앱을 배포할 수는 있지만 권장하지 않는다. macOS Catalina(버전 10.15)부터 사용자는 서명되지 않은 앱을 열려면 여러 수동 단계를 거쳐야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 코드 서명을 해야 하는 가장 큰 이유는 &lt;span style=&quot;color: #ee2323;&quot;&gt;macOS의 경우 코드 서명을 하지 않으면 자동 업데이트가 불가능&lt;/span&gt;하다는 것이다. 진행중인 프로젝트에서 구현해야 할 기능 중에 electron 앱의 자동 업데이트 기능이 있기 때문에 코드 서명을 꼭 진행해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;macOS 앱 코드 서명 하는 방법&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;macOS에는 애플리케이션 배포를 위한 두 가지 보안 기술인 &lt;u&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/code_signing_services&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;코드 서명(code signing)&lt;/a&gt; 및 &lt;a href=&quot;https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공증(notarization)&lt;/a&gt;&lt;/u&gt;이 있다. 코드 서명은 앱 작성자의 신원을 인증하고 배포 전에 변조되지 않았는지 확인하는 행위이고, 공증은 자동화된 malware 검사를 위해 앱을 Apple 서버로 보내는 추가 확인 단계이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Mac App Store(MAS) 외부에 배포될 앱은 반드시 코드 서명이 되어있어야 한다. &lt;/span&gt;macOS 10.15(Catalina)부터 &lt;span style=&quot;color: #ee2323;&quot;&gt;운영 체제 보안 검사를 비활성화하지 않고 사용자 컴퓨터에서 실행하려면 응용 프로그램에 코드 서명 및 공증이 모두 필요&lt;/span&gt;하다. Mac App Store(MAS)에 배포하는 앱은 MAS 제출 프로세스에 공증과 유사한 자동 검사가 포함되기 때문에 공증이 필요하지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;macOS 앱은 MAS에 배포하는 것과 MAS 외부에 배포하는 것으로 나뉘는데, 아래에서는 외부에 배포하는 것을 기준으로 &lt;u&gt;macOS(darwin) 애플리케이션 파일(.app)에 코드 서명과 공증하는 방법&lt;/u&gt;을 안내하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. Developer ID Application certificate 생성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;macOS 앱용 코드 서명 인증서는&lt;span style=&quot;color: #333333;&quot;&gt; &lt;a style=&quot;color: #333333;&quot; href=&quot;https://developer.apple.com/programs/&quot;&gt;Apple Developer Program&lt;/a&gt; 멤버십을 구입&lt;/span&gt;하여 Apple을 통해서만 얻을 수 있다. 인증서 타입은 여러 가지가 있는데 Electron 앱에 &lt;span style=&quot;color: #ee2323;&quot;&gt;코드 서명 및 공증까지 하려면 &lt;a style=&quot;color: #ee2323;&quot; href=&quot;https://developer.apple.com/help/account/create-certificates/create-developer-id-certificates/&quot;&gt;Developer ID Application certificate&lt;/a&gt;가 필요&lt;/span&gt;하다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Developer ID Application certificate는 Mac App Store 외부에서 배포되는 앱에 서명하는 용도로 사용한다. 만약&amp;nbsp;&lt;/span&gt;다른 타입의 인증서로 코드 서명을 하면 공증에 실패한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://www.rocketride.io/blog/macos-code-sign-notarize-electron-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.rocketride.io/blog/macos-code-sign-notarize-electron-app&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692611413648&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to code sign and notarize an electron app in 2022&quot; data-og-description=&quot;8 steps to make your Electron app ready for distribution on Mac OS&quot; data-og-host=&quot;www.rocketride.io&quot; data-og-source-url=&quot;https://www.rocketride.io/blog/macos-code-sign-notarize-electron-app&quot; data-og-url=&quot;https://www.rocketride.io/blog/macos-code-sign-notarize-electron-app&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.rocketride.io/blog/macos-code-sign-notarize-electron-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.rocketride.io/blog/macos-code-sign-notarize-electron-app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to code sign and notarize an electron app in 2022&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;8 steps to make your Electron app ready for distribution on Mac OS&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.rocketride.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;인증서 생성 단계를 스크린샷과 함께 구체적으로 보고 싶다면 위 블로그를 참고하면 좋을 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/programs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Apple Developer Program 멤버십&lt;/a&gt;을 구입한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;인증서 서명 요청(CSR/CertificateSigningRequest.certSigningRequest) 파일을 생성하기 위해 맥북에서 '키체인 접근' 실행 후 상단 메뉴에서 '인증서 지원 &amp;gt; 인증기관에서 인증서 요청'을 선택한다. 이 파일은 코드 서명에 필요한 인증서를 생성하는데 필요하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;입력창이 뜨면 애플 개발자 계정 이메일 및 이름을 작성하고 요청 항목에 &amp;lsquo;디스크에 저장&amp;rsquo;을 선택한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Developer ID Application 인증서를 생성하기 위해 애플 개발자 계정 사이트의&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/account/resources/certificates/add%20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Certificates, IDs &amp;amp; Profiles 메뉴&lt;/a&gt;로 들어간다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;Certificates 메뉴에서 파란색 플러스 아이콘을 클릭하고, 목록에서 &lt;span style=&quot;text-align: left;&quot;&gt;Developer ID Application 인증서를 선택한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;다음 스텝에서 'Profile Type: G2 Sub-CA (Xcode 11.4.1 or later)' 선택하고, 2번 단계에서 받았던 인증서 서명 요청 파일(CSR)을 파일 업로드 항목에서 업로드해서 인증서 생성을 요청한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;인증서 파일(developerID_application.cer) 다운로드 후 키체인에 등록한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;키체인에 등록이 완료되면 아래와 같은 화면을 키체인 접근에서 확인할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;인증서가 잘 설치 됐는지 터미널에서 &lt;span style=&quot;background-color: #000000; color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&amp;nbsp;security find-identity -p codesigning -v&lt;/span&gt; &lt;/span&gt;명령어를 입력해서 사용 가능한 코드 서명 인증서가 있는지 확인한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQUur/btsrUsuQJdF/ViHDyCdjQ5O30ToskhdgWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQUur/btsrUsuQJdF/ViHDyCdjQ5O30ToskhdgWk/img.png&quot; data-alt=&quot;키체인 등록 완료 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQUur/btsrUsuQJdF/ViHDyCdjQ5O30ToskhdgWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQUur%2FbtsrUsuQJdF%2FViHDyCdjQ5O30ToskhdgWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;162&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;키체인 등록 완료 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yxG73/btsrNzhnzeH/pk0YXexaaTVkD31yTOVpn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yxG73/btsrNzhnzeH/pk0YXexaaTVkD31yTOVpn0/img.png&quot; data-alt=&quot;터미널 명령어 입력해서 코드 서명 가능 인증서 있는지 체크&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yxG73/btsrNzhnzeH/pk0YXexaaTVkD31yTOVpn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyxG73%2FbtsrNzhnzeH%2Fpk0YXexaaTVkD31yTOVpn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;67&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;터미널 명령어 입력해서 코드 서명 가능 인증서 있는지 체크&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span&gt;2. electron-builder-config.js&lt;/span&gt;&amp;nbsp;, entitlements.mac.plist 파일 작성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;/* eslint-disable node/no-unpublished-require */
require('dotenv').config()

const config = {
  appId: 'com.electron.app',
  productName: 'app',
  directories: {
    buildResources: 'build'
  },
  artifactName: '${productName}-${os}-${arch}-latest.${ext}',
  //...생략
  mac: {
    target: [
      {
        target: 'default',
        arch: ['arm64', 'x64']
      }
    ],
    notarize: {
      teamId: process.env.APPLE_TEAM_ID
    },
    category: 'public.app-category.utilities',
    hardenedRuntime: true,
    gatekeeperAssess: true,
    entitlements: 'build/entitlements.mac.plist',
    entitlementsInherit: 'build/entitlements.mac.plist',
  }
}

module.exports = config&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron-builder-config,js 파일에 mac용 빌드 옵션 구성을 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;hardendRuntime: macOS 앱에 대한 보안 보호 및 리소스 액세스를 관리하는 기능이고, 이것을 활성화해야 공증이 가능하다.&lt;br /&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.apple.com/documentation/security/hardened_runtime&quot;&gt;참고링크1,&lt;/a&gt;&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.notion.so/FTX-com-FTT-4206b30e348d492ead2618a035964599?pvs=21&quot;&gt;참고링크2&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;gatekeeperAssess: 이 항목을 true로 설정하면 코드 서명을 했는지 &lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;@electron/osx-sign 패키지에서 검증해준다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;entitlements: entitlements란 macOS 에서 실행 파일에 특정 기능(예: 카메라,마이크 등 장치에 대한 엑세스)을 부여하는 권한이다. 이 권한들은 코드 서명 시에 저장된다. 여기에는 권한을 작성한 파일의 경로를 작성한다. entitlementsInherit란 배포 번들에 대한 보안 설정을 상속하는 하위 권한에 대한 경로이다.&lt;br /&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.electron.build/configuration/mac&quot;&gt;참고링크1,&lt;/a&gt;&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/electron/osx-sign/blob/main/entitlements/default.darwin.plist&quot;&gt;참고링크2&lt;/a&gt;,&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.apple.com/documentation/bundleresources/entitlements&quot;&gt;참고링크3&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1692842986220&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt;
&amp;lt;plist version=&quot;1.0&quot;&amp;gt;
  &amp;lt;dict&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.cs.allow-jit&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.cs.allow-dyld-environment-variables&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;
  &amp;lt;/dict&amp;gt;
&amp;lt;/plist&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;build 폴더에 위와 같이 entitlements 권한을 작성한 &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;entitlements.mac.plist&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;파일을 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;build 실행 시 electron-builder 내부적으로 코드 서명 진행&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;진행 중인 프로젝트에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.electron.build/&quot;&gt;electron-builder&lt;/a&gt;를 사용해서 패키징과 배포를 진행하고 있다. 이 도구를 사용하게 된 이유에 대해서는 자동 업데이트 글에서 더 상세하게 작성하도록 하겠다. 아무튼&amp;nbsp;&lt;/span&gt;electron-builder는 내부적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/electron/osx-sign&quot;&gt;@electron/osx-sign 패키지&lt;/a&gt;를 사용해서 코드 서명을 진행한다. 빌드 타임에 macOS의 keychain에서 유효한 인증서를 찾으면, @electron/osx-sign을 사용해서 코드 서명을 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 빌드 플랫폼에 따라 인증서를 자동으로 선택해 준다. mas일 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;3rd Party Mac Developer Application: * (*) 인증서&lt;/b&gt;를 선택하고 darwin일 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Developer ID Application: * (*) 인증서&lt;/b&gt;를 선택해서 코드 서명을 한다.&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/codeSign/macCodeSign.ts&quot;&gt;(참고 링크)&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692684530774&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
	&quot;build:mac&quot;: &quot;electron-vite build &amp;amp;&amp;amp; electron-builder --mac --config electron-builder-config.js&quot;
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json 에서 위와 같이 mac용 빌드 스크립트를 구성하고 빌드를 실행하면, &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron-builder의 빌드 스크립트 중에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;a href=&quot;https://github.com/electron-userland/electron-builder/blob/6bcea7f8a449daf9af09095b93bee71e22682488/packages/app-builder-lib/src/macPackager.ts#L25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MacPackager.ts 스크립트 파일&lt;/a&gt;에서 코드 서명이 진행&lt;/span&gt;된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;macOS 앱 공증하는 방법&lt;/span&gt;&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;공증은 앱 심사가 아니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Apple 공증(Notarizaion) 서비스&lt;/a&gt;는&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; Developer ID 인증서로 서명한 소프트웨어에 악성 코드가 있는지 자동으로 스캔하고 보안 검사를 수행하는 시스템&lt;/span&gt;이다. 검사를 마치고 배포를 위해 내보낼 준비가 완료되면 Gatekeeper에서 소프트웨어가 공증을 받았음을 식별할 수 있도록 해당 소프트웨어에 티켓이 첨부된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;한마디로 &lt;span style=&quot;text-align: center; letter-spacing: 0px;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/kr/developer-id/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gatekeeper&lt;/a&gt;가 MAS 외부에서 배포되는 앱이 안전한지 체크하고 사용자를 악성 코드로부터 보호해 주는 역할을 한다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;text-align: center; letter-spacing: 0px;&quot;&gt;&lt;u&gt;&lt;i&gt;&quot;앱스토어 외부에서 다운로드한 이 소프트웨어는 신뢰할 수 있습니다.&quot;라고&lt;/i&gt;&lt;/u&gt; 도장을 쾅쾅 찍어준다고 생각하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;사용자가 소프트웨어를 처음 설치하거나 실행할 때&lt;/span&gt;&lt;span style=&quot;text-align: center; letter-spacing: 0px;&quot;&gt; 공증을 받았음을 식별할 수 있는 티켓이 있으면 &lt;/span&gt;&lt;span style=&quot;text-align: center; letter-spacing: 0px;&quot;&gt;Gatekeeper에 Apple이 소프트웨어를 공증했음을 알려준다. 그런 다음 Gatekeeper는 초기 실행 알림 창에 설명 정보를 배치하여 사용자가 앱 실행 여부에 대해 정보에 입각한 선택을 할 수 있도록 돕는다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;공증이 완료된 실행파일을 처음 실행할 경우에는 &lt;a href=&quot;https://support.apple.com/ko-kr/HT202491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;아래와 같은 알림 창이&lt;/a&gt; 뜬다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAkRvo/btsrSNNxlVI/vuu1qo0yKC2ahoJxkhL7wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAkRvo/btsrSNNxlVI/vuu1qo0yKC2ahoJxkhL7wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAkRvo/btsrSNNxlVI/vuu1qo0yKC2ahoJxkhL7wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAkRvo%2FbtsrSNNxlVI%2Fvuu1qo0yKC2ahoJxkhL7wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;278&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. ASP(App-Specific-Password) 생성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://support.apple.com/ko-kr/HT204397&quot;&gt;App-Specific-Password&lt;/a&gt;는&amp;nbsp;&amp;nbsp;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;공증에 사용하는 툴인 notarytool에 Apple 관련 자격 증명을 제공할 때 사용한다. 이것을 사용해서 공증하게 되면&lt;/span&gt;&amp;nbsp;Apple 이외의 개발자가 만든 앱에 안전하게 로그인할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qqF9A/btsrTPekExx/2X4WKKzGZYxGEg0deCVzKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qqF9A/btsrTPekExx/2X4WKKzGZYxGEg0deCVzKk/img.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1189&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 51.5845%; margin-right: 10px;&quot; data-widthpercent=&quot;52.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qqF9A/btsrTPekExx/2X4WKKzGZYxGEg0deCVzKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqqF9A%2FbtsrTPekExx%2F2X4WKKzGZYxGEg0deCVzKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1189&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3Qekg/btsrTrR5DON/McFT4HF0Jf0DmRiv1ftc01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3Qekg/btsrTrR5DON/McFT4HF0Jf0DmRiv1ftc01/img.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1298&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;47.81&quot; data-filename=&quot;blob&quot; style=&quot;width: 47.2527%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3Qekg/btsrTrR5DON/McFT4HF0Jf0DmRiv1ftc01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3Qekg%2FbtsrTrR5DON%2FMcFT4HF0Jf0DmRiv1ftc01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1298&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://appleid.apple.com/&quot;&gt;https://appleid.apple.com/&lt;/a&gt; 에 애플 개발자 계정으로 로그인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;App-Specific-Password 생성 후 복사&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;env 파일에 Apple 관련 정보 입력&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;APPLE_ID = 애플_개발자_계정
APPLE_APP_SPECIFIC_PASSWORD = 생성된_App-Specific-Password 
APPLE_TEAM_ID = 애플_개발자_멤버십_팀_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. electron-builder-config.js 파일에서 공증 옵션 추가&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서에서 공증에 대한 블로그 포스팅 링크&lt;/a&gt;를 제공하길래 처음에는 포스팅 그대로 따라했었다. afterSign Hook에 직접 작성한 notarize.js 스크립트를 실행하도록 했다. 근데 멀티 아키텍쳐로 빌드 스크립트를 구성해서 실행하는데 첫 번째 아키텍처는 afterSign Hook이 잘 실행되는데, 두 번째 아키텍처는 &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;afterSign Hook이 돌지 않고, electron-builder 빌드 스크립트 중 MacPackager 스크립트 파일이 실행되는데 심지어 &lt;/span&gt;공증할 때 notarytool을 사용하는 것이 아니라 altool을 사용하는 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;notarize&lt;/b&gt;:&amp;nbsp; module:app-builder-lib/out/options/macOptions.NotarizeOptions | Boolean | &amp;ldquo;undefined&amp;rdquo; - Options to use for @electron/notarize (ref: https://github.com/electron/notarize). Supports both legacy and notarytool notarization tools. Use false to explicitly disable&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이를 해결하기 위해 다시 공식 문서를 훑어보기 시작했고 Mac 빌드 옵션에 &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;notarize 라는 것을 찾을 수 있었다. 하지만 옵션을 추가해도 공증 툴을 계속 altool을 사용하길래&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;원인이 무엇인지 파악하기 위해 electron-builder 의 &lt;a href=&quot;https://github.com/electron-userland/electron-builder/blob/4fc7a3c3b857380bcbdd2a10e26989e3b1af50a2/packages/app-builder-lib/src/macPackager.ts#L499&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MackPackager 스크립트 코드&lt;/a&gt;를 훑어보기 시작했다. 코드를 살펴보니 notarize 옵션을 추가할 때 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; text-align: start;&quot;&gt;반드시&lt;/span&gt; teamId를 넣어야 notarytool이 실행되는 코드&lt;/span&gt;를 발견했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693989320260&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private generateNotarizeOptions(appPath: string, appleId: string, appleIdPassword: string): NotarizeOptions {
    const baseOptions = { appPath, appleId, appleIdPassword }
    const options = this.platformSpecificBuildOptions.notarize
    if (typeof options === &quot;boolean&quot;) {
      return {
        ...baseOptions,
        tool: &quot;legacy&quot;,
        appBundleId: this.appInfo.id,
      }
    }
    if (options?.teamId) { //  코드 핵심
      return {
        ...baseOptions,
        tool: &quot;notarytool&quot;,
        teamId: options.teamId,
      }
    }
    return {
      ...baseOptions,
      tool: &quot;legacy&quot;,
      appBundleId: options?.appBundleId || this.appInfo.id,
      ascProvider: options?.ascProvider || undefined,
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위 코드를 보면 teamId가 옵션으로 들어와야지만 notarytool을 사용하는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;/* eslint-disable node/no-unpublished-require */
require('dotenv').config()

const config = {
  appId: 'com.electron.app',
  productName: 'app',
  directories: {
    buildResources: 'build'
  },
  artifactName: '${productName}-${os}-${arch}-latest.${ext}',
  //...생략
  mac: {
    target: [
      {
        target: 'default',
        arch: ['arm64', 'x64']
      }
    ],
    //   아래 옵션 추가
    notarize: {
      teamId: process.env.APPLE_TEAM_ID
    },
    category: 'public.app-category.utilities',
    hardenedRuntime: true,
    gatekeeperAssess: true,
    entitlements: 'build/entitlements.mac.plist',
    entitlementsInherit: 'build/entitlements.mac.plist',
  }
}

module.exports = config&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 electron-builder에서 공증을 위해 내부적으로 @electron/notarize 패키지를 사용하고, 코드 서명 이후에 공증이 진행된다. 공증을 위해서는 mac 빌드 옵션에 위와 같이 notarize 옵션을 작성해주고 애플 개발자 멤버십 팀 id 를 입력해줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-06 오후 5.55.45.png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zpJCe/btss9CSzPtk/2CbnY1dREyCWHBDUt9JiB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zpJCe/btss9CSzPtk/2CbnY1dREyCWHBDUt9JiB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zpJCe/btss9CSzPtk/2CbnY1dREyCWHBDUt9JiB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzpJCe%2Fbtss9CSzPtk%2F2CbnY1dREyCWHBDUt9JiB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;298&quot; data-filename=&quot;스크린샷 2023-09-06 오후 5.55.45.png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;빌드 실행 후 위와 같은 로그가 뜨면 코드 서명과 공증이 모두 완료된 것이다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;macOS 앱 코드 서명 및 공증 Trouble shooting&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;CloudKit query for test.app ... failed due to &quot;Record not found&quot;.
Could not find base64 encoded ticket in response for ...
The staple and validate action failed! Error 65.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;다른 타입의 인증서(Apple Development)로 코드 서명 했을 때 에러 발생&lt;br /&gt;➡️ 올바른 타입의 인증서(Developer ID Application)로 코드 서명하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692844214702&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Notarizing using the legacy altool system. The altool system will be disabled on November 1 2023. Please switch to the notarytool system before then.
You can do this by setting &quot;tool: notarytool&quot; in your &quot;@electron/notarize&quot; options. Please note that the credentials options may be slightly different between tools.
Error: Failed to upload app to Apple's notarization servers

xcrun: error: unable to find utility &quot;altool&quot;, not a developer tool or in PATH&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. electron/notarize 패키지 버전이 낮을 때(&quot;^1.2.3&amp;rdquo;) default로 설정된 공증 툴이 &amp;lsquo;altool&amp;rsquo; 이어서 에러 발생&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;➡️&lt;span&gt; &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: left;&quot;&gt;해결 방법1: 최신 버전(2.1.0)으로 업그레이드 (electron-builder 24.6.3 버전 말고 24.6.4 버전 사용 필요)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;➡️&lt;span&gt; 해결 방법2: 공증 옵션 중 tool을 &amp;lsquo;notarize&amp;rsquo;로 명시&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GqtJA/btsrUtbdwP6/lKzq0UirnpzrI43s7J55Jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GqtJA/btsrUtbdwP6/lKzq0UirnpzrI43s7J55Jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GqtJA/btsrUtbdwP6/lKzq0UirnpzrI43s7J55Jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGqtJA%2FbtsrUtbdwP6%2FlKzq0UirnpzrI43s7J55Jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;1621&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1621&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. 빌드된 실행파일의 아키텍처가 다를 경우 오류 발생&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;➡️&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;아키텍처 별로 빌드할 수 있도록 수정&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693989123204&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mac: {
    target: [
      {
        target: 'default',
        arch: ['arm64', 'x64']
      }
    ],
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 키체인에서 코드 서명에 사용할 인증서가 신뢰되지 않은 인증서라고 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;➡️&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.apple.com/certificateauthority/&quot;&gt;Apple Worldwide Developer Relations Certification Authority&lt;/a&gt;를 설치&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;macOS에서 Windows 앱 코드 서명하는 방법&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Windows 앱을 서명할 수 있는 인증서는&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;EV 코드 서명 인증서,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;코드 서명 인증서 두 가지 타입이 있다. 일반적인&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;코드 서명 인증서는 앱을 설치할 때 경고를 표시하는데, 이 경고는 충분한 사용자가 앱을 설치하고 신뢰가 쌓이면 사라진다고 한다. 그러나 &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;EV 코드 서명 인증서는 경고 없이 즉시 실행 가능하다. 아래에서는 EV 코드 서명 인증서를 활용해서 서명하는 방법을 작성했다.&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;코드 서명 인증서 구매&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-key=&quot;de6c5edcb34b4e349b099df900707775&quot;&gt;&lt;span data-offset-key=&quot;de6c5edcb34b4e349b099df900707775:0&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows-hardware/drivers/install/authenticode&quot; data-rnwi-5xr8s6-dse9kg-2fw26j-1fa8070-focus-visible=&quot;true&quot; data-rnwi-handle=&quot;link&quot;&gt;Windows Authenticode&lt;/a&gt; 코드 서명 인증서는 다양한 업체에서 구매할 수 있고, 가격도 다양하다. 일반적인 코드 서명 인증서는 신뢰도가 쌓일 때까지 경고창을 띄우기 때문에, 회사에서는 Digicert에서 EV 코드 서명 인증서를 구매했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;EV CodeSign 인증서&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;EV(Extended Validation) Codesign 인증서는 Microsoft의 SmartScreen 경고 메시지를 즉시 해제할 수 있고, &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;엄격한 심사 프로세스 및 하드웨어 보안 요구 사항이 포함되어 있기 때문에 최종 사용자의 신뢰도를 높일 수 있는 인증서이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;하지만 가장 큰 단점은 코드 서명에 사용할 &lt;span style=&quot;color: #ee2323;&quot;&gt;프라이빗키를 하드웨어 토큰 형식(ex. USB)으로 관리해야 한다는&lt;/span&gt; 것이다. 그래서 CI 프로세스에 사용하기 위해 키를 export 할 수가 없다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Jsign을 사용해서 macOS에서 Windows 앱에 코드 서명하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;Unix에서 Windows 앱 서명이 지원된다.&amp;nbsp;이를 달성하기 위한 여러 가지 방법이 있다.&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #0070d1; text-align: start;&quot; href=&quot;https://en.wikipedia.org/wiki/PKCS_11&quot;&gt;기본적으로 PKCS 11을&lt;/a&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;사용하여 코드에 서명할 수 있는 애플리케이션이 필요하다. 여기서 사용할 애플리케이션은 Jsign이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 electron-builder 공식 문서 내용을 참고해서 진행해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;&lt;a href=&quot;https://www.electron.build/tutorials/code-signing-windows-apps-on-unix#signing-windows-app-on-maclinux-using-jsign&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.electron.build/tutorials/code-signing-windows-apps-on-unix#signing-windows-app-on-maclinux-using-jsign&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692854307867&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Sign a Windows app on macOS/Linux - electron-builder&quot; data-og-description=&quot;Sign a Windows app on macOS/Linux Info Described setup and configuration is required only if you have EV code signing certificate. The regular certificates supported out of the box. Signing Windows apps on Unix is supported. There are multiple methods to a&quot; data-og-host=&quot;www.electron.build&quot; data-og-source-url=&quot;https://www.electron.build/tutorials/code-signing-windows-apps-on-unix#signing-windows-app-on-maclinux-using-jsign&quot; data-og-url=&quot;https://www.electron.build/tutorials/code-signing-windows-apps-on-unix#signing-windows-app-on-maclinux-using-jsign&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.electron.build/tutorials/code-signing-windows-apps-on-unix#signing-windows-app-on-maclinux-using-jsign&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.electron.build/tutorials/code-signing-windows-apps-on-unix#signing-windows-app-on-maclinux-using-jsign&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Sign a Windows app on macOS/Linux - electron-builder&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Sign a Windows app on macOS/Linux Info Described setup and configuration is required only if you have EV code signing certificate. The regular certificates supported out of the box. Signing Windows apps on Unix is supported. There are multiple methods to a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.electron.build&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;코드 서명을 위한 사전 작업&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://ebourg.github.io/jsign/&quot;&gt;Jsign&lt;/a&gt;&amp;nbsp;은 플랫폼 독립적인 Microsoft Authenticode의 Java 구현이며 Linux의&amp;nbsp;&lt;a href=&quot;https://docs.digicert.com/en/software-trust-manager/sign-with-digicert-signing-tools/third-party-signing-tool-integrations/osslsigncode.html&quot;&gt;osslsigncode 및 Windows의&lt;/a&gt;&amp;nbsp;&lt;a href=&quot;https://docs.digicert.com/en/software-trust-manager/sign-with-digicert-signing-tools/third-party-signing-tool-integrations/signtool.html&quot;&gt;SignTool&lt;/a&gt;&amp;nbsp;또는 Unix 시스템의 Mono 개발 도구와&amp;nbsp;같은 기본 도구에 대한 대안을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jsign을 사용해서 코드 서명을 하려면 Java를 설치해야 한다. 그리고 USB 토큰을 읽어서 코드 서명을 진행할 수 있는 클라이언트 툴이 필요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a title=&quot;SafeNet Authentication Client&quot; href=&quot;https://knowledge.digicert.com/generalinformation/INFO1982.html&quot;&gt;safeNet Authentication Client&lt;/a&gt;&amp;nbsp;를 다운로드한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;u&gt;SafeNet Authentication Client Tools&lt;/u&gt;를 실행한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;digicert USB 삽입 후 로그온을 진행해서 패스워드를 입력한다. 인증서의 패스워드는 반드시 비공개로 유지해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;루트 경로에 &lt;u&gt;hardwareToken.cfg 파일&lt;/u&gt; 생성 후 아래 코드 작성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;library 경로는 PKCS 11 module을 가진 library의 경로인지 체크해 보고 작성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Java를 설치(현재 14.0.2 설치) 하고 코드서명 툴인 &lt;a href=&quot;https://docs.digicert.com/en/software-trust-manager/sign-with-digicert-signing-tools/third-party-signing-tool-integrations/jsign.html&quot;&gt;Jsign&lt;/a&gt; 을 동일 경로에 설치한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// hardwareToken.cfg 파일
name = HardwareToken
library = /Library/Frameworks/eToken.framework/Versions/A/libeToken.dylib
slotListIndex = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. 루트 경로에 sign.js 파일 생성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CERTIFICATE_NAME은 SafeNet Authentication Client Tools 실행 후 &lt;span style=&quot;color: #ee2323;&quot;&gt;Issued To 에 해당하는 이름&lt;/span&gt; 입력&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;TOKEN_PASSWORD는 &lt;span style=&quot;color: #ee2323;&quot;&gt;EV Codesign 인증서의 패스워드&lt;/span&gt; 입력&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// eslint-disable-next-line node/no-unpublished-require
require('dotenv').config()

exports.default = async function (configuration) {
  // do not include passwords or other sensitive data in the file
  // rather create environment variables with sensitive data

  const CERTIFICATE_NAME = process.env.WINDOWS_SIGN_CERTIFICATE_NAME
  const TOKEN_PASSWORD = process.env.WINDOWS_SIGN_TOKEN_PASSWORD

  console.log('start sign')

  require('child_process').execSync(
    // your commande here ! For exemple and with JSign :
    `java -jar jsign-5.0.jar --keystore hardwareToken.cfg --storepass &quot;${TOKEN_PASSWORD}&quot; --storetype PKCS11 --tsaurl &amp;lt;http://timestamp.digicert.com&amp;gt; --alias &quot;${CERTIFICATE_NAME}&quot; &quot;${configuration.path}&quot;`,
    {
      stdio: 'inherit'
    }
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. 빌드 옵션에 인증서 이름 작성 및 sign.js 파일 경로 추가&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://www.electron.build/configuration/win#WindowsConfiguration-certificateSubjectName&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.electron.build/configuration/win#WindowsConfiguration-certificateSubjectName&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692868791873&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Any Windows Target - electron-builder&quot; data-og-description=&quot;Any Windows Target The top-level win key contains set of options instructing electron-builder on how it should build Windows targets. These options applicable for any Windows target. target = nsis String | TargetConfiguration - The target package type: lis&quot; data-og-host=&quot;www.electron.build&quot; data-og-source-url=&quot;https://www.electron.build/configuration/win#WindowsConfiguration-certificateSubjectName&quot; data-og-url=&quot;https://www.electron.build/configuration/win#WindowsConfiguration-certificateSubjectName&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.electron.build/configuration/win#WindowsConfiguration-certificateSubjectName&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.electron.build/configuration/win#WindowsConfiguration-certificateSubjectName&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Any Windows Target - electron-builder&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Any Windows Target The top-level win key contains set of options instructing electron-builder on how it should build Windows targets. These options applicable for any Windows target. target = nsis String | TargetConfiguration - The target package type: lis&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.electron.build&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;If you are using an EV Certificate, you need to provide&amp;nbsp;&lt;a href=&quot;https://www.electron.build/configuration/win#WindowsConfiguration-certificateSubjectName&quot;&gt;win.certificateSubjectName&lt;/a&gt;&amp;nbsp;in your electron-builder configuration.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;certificateSubjectName&lt;/b&gt;&amp;nbsp;String | &amp;ldquo;undefined&amp;rdquo; - The name of the subject of the signing certificate, which is often labeled with &lt;span style=&quot;color: #ee2323;&quot;&gt;the field name&amp;nbsp;issued to&lt;/span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위 문서에 따르면 아래와 같이 빌드 옵션에 인증서의 이름을 작성하고, sign.js 파일 경로를 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;afterSign: build/notarize.js

//   인증서 이름 작성 및 sign 스크립트 경로 추가
win:
  executableName: sampleApp
  certificateSubjectName: CERT_NAME
  sign: ./sign.js&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. windows 전용 빌드 스크립트 추가 및 실행&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1692869370923&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &quot;build:win&quot;: &quot;electron-vite build &amp;amp;&amp;amp; electron-builder --win --x64 --config&quot;,&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kZC8i/btssfwK69GU/iCNJS4mVxX8TdeD5Zjl5Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kZC8i/btssfwK69GU/iCNJS4mVxX8TdeD5Zjl5Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kZC8i/btssfwK69GU/iCNJS4mVxX8TdeD5Zjl5Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkZC8i%2FbtssfwK69GU%2FiCNJS4mVxX8TdeD5Zjl5Ok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;787&quot; height=&quot;127&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코드 서명을 완료하게 되면 위와 같은 로그가 뜬다. &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;i&gt;Adding Authenticode signature to ...&lt;/i&gt; 라는 문구가 나오면 코드 서명이 정상적으로 실행된 것으로 확인 가능하다. 그리고 앱을 실행해 보면 MS SmartScreen Filter에 걸리지 않고 정상적으로 실행된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Study/Frontend</category>
      <category>apple 공증</category>
      <category>code signing</category>
      <category>electron</category>
      <category>electron 개발</category>
      <category>electron 공증</category>
      <category>electron-builder</category>
      <category>코드서명</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/144</guid>
      <comments>https://xiubindev.tistory.com/144#entry144comment</comments>
      <pubDate>Fri, 25 Aug 2023 16:10:45 +0900</pubDate>
    </item>
    <item>
      <title>Storybook과 Vitest에서 MSW 모킹 핸들러 재사용하기</title>
      <link>https://xiubindev.tistory.com/143</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceXXwQ/btsrv58AgTt/Bbt2UJZo06NJ81Ho2Wyda0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceXXwQ/btsrv58AgTt/Bbt2UJZo06NJ81Ho2Wyda0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceXXwQ/btsrv58AgTt/Bbt2UJZo06NJ81Ho2Wyda0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceXXwQ%2Fbtsrv58AgTt%2FBbt2UJZo06NJ81Ho2Wyda0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;프론트엔드 개발자의 작업 능률은 Storybook과 MSW를 만나기 전과 후로 나뉜다고 할 수 있다. 프론트엔드 개발을 하면서 가장 불편한 점 중 하나가 &lt;span style=&quot;color: #ee2323;&quot;&gt;백엔드 개발에 대한 의존성이 높다&lt;/span&gt;는 점이다. 백엔드 API가 완성되어야 데이터 처리를 진행할 수 있는데, 보통 UI 작업을 먼저 진행해도 백엔드 작업이 끝나기 전까지 대기하는 상황이 종종 있었다. 물론 Mocking 데이터를 직접 만들어서 UI가 제대로 렌더링 되는지 확인할 수는 있었지만 꽤나 번거로운 일이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 Storybook을 만나면서 백엔드 API 없이도 데이터에 대한 다양한 케이스를 만들어서 미리 정확한 UI를 만들어 보며 시각적 테스트를 진행할 수 있게 됐다. 그리고 MSW를 도입하면서 API가 완성되지 않아도 백엔드팀에서 Schema만 받을 수 있다면 데이터 처리 작업도 수월하게 진행할 수 있었다. API 리스폰스를 직접 변경하면서 다양한 케이스의 UI를 바로 확인하면서 효율적으로 개발할 수 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 훌륭한 두 가지 도구에 Vitest 라는 테스트 러너를 기반으로 테스트 코드를 작성하게 됐는데, 여기서 문제점이 발생했다. &lt;span style=&quot;color: #ee2323;&quot;&gt;Storybook과 테스트 파일에서 MSW로 모킹하는 코드가 중복으로 작성&lt;/span&gt;되고 있었다. 이를 해결하기 위해 Storybook에서 작성한 Story 컴포넌트를 Vitest 테스트에서 재활용하고, 중복 작성된 MSW 코드를 제외하고 테스트 코드를 깔끔하게 정리한 방법을 아래에서 소개하도록 하겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; 프로젝트 환경&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://electron-vite.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;electron-vite&lt;/a&gt; + React + TypeScript&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; 테스트 코드 작성 흐름&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;시각적 테스트&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Storybook에 Story 작성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Story에 MSW 모킹 코드 추가&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;컴포넌트 단위 통합 테스트&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Vitest 기반 test 파일에서 작성한 Story Import 해서 재사용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스토리에 추가된 MSW 모킹 재사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; 테스트 도구 설치&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;yarn add -D vitest @storybook/testing-react @testing-library/jest-dom @testing-library/react jsdom msw-storybook-addon&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://vitest.dev/&quot;&gt;Vitest&lt;/a&gt; : Vite 환경에서 테스트를 진행할수 있도록 도와주는 프레임워크 (Jest와 호환)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://testing-library.com/docs/ecosystem-jest-dom/&quot;&gt;@testing-library/jest-dom&lt;/a&gt; : Dom 요소 matcher를 제공하는 라이브러리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://testing-library.com/docs/react-testing-library/intro/&quot;&gt;@testing-library/react&lt;/a&gt; : React 컴포넌트를 테스팅할 수 있도록 도와주는 라이브러리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://storybook.js.org/addons/msw-storybook-addon&quot;&gt;msw-storybook-addon&lt;/a&gt; : Storybook에서 MSW 모킹을 도와주는 라이브러리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://storybook.js.org/addons/@storybook/testing-react&quot;&gt;@storybook/testing-react&lt;/a&gt; : 단위 테스트에서 스토리를 재사용 할 수 있도록 도와주는 라이브러리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://github.com/jsdom/jsdom&quot;&gt;jsdom&lt;/a&gt; : node 환경에서도 브라우저 환경을 테스트할 수 있도록 Dom 구현체를 제공해 주는 라이브러리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; Vitest Configuration&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { defineConfig } from 'vitest/config'
import { resolve } from 'path'

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: 'vitest-setup.ts',
    alias: {
      '@renderer': resolve('src/renderer/src')
    }
  },
  build: {
    rollupOptions: {
      input: {
        index: resolve(__dirname, 'src/renderer/index.html')
      }
    },
    sourcemap: true
  }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;.storybook/preview.tsx&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Vitest의 주요 장점 중 하나는 &lt;span style=&quot;color: #ee2323;&quot;&gt;Vite와의 통합 구성이 가능&lt;/span&gt;하다는 것이다. Vitest를 사용하기 전에 Vite 환경에 Jest를 사용해보려고 했는데 Jest를 실행하기 위해 여러 디펜던시들이 필요했고, 설정도 Vitest보다 복잡했다. 그래서 차라리 Vite 환경에는 Vitest를 사용하는 것이 더 적합하다는 생각이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;간단하게 vite.config.ts 파일에서 test 프로퍼티에 테스트 관련 옵션을 추가해 주면 테스트 가능한 환경이 된다. 또한, resolve.alias가 설정되어 있다면 그 설정 그대로 테스트 코드에서도 활용 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 현재 프로젝트에서 사용하는 electron-vite에서 vitest 설정을 지원하지 않고 있다. (&lt;a href=&quot;https://github.com/alex8088/electron-vite/issues/88&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;관련 Github Issue&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;따라서 기존 vite configuration 파일이었던&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;electron.vite.config.ts 에서 테스팅 관련 설정이 불가&lt;/span&gt;하므로 &lt;u&gt;vite.config.ts 파일을 생성&lt;/u&gt;해서 테스팅 및 빌드 관련 옵션을 추가해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;build 프로퍼티의 rollupOptions에서 entryPoint를 지정해 준다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;test 프로퍼티에 Vitest 관련 옵션을 추가한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;globals: vitest는 글로벌 API를 기본적으로 제공하지 않는다. 하지만 Jest와 같이 describe, it, expect 함수를 글로벌로 사용하고 싶다면 &lt;u&gt;true로 설정해 준다.&lt;/u&gt; 그리고 타입스크립트가 작동하려면 tsconfig.json에 types 필드에 &lt;u&gt;vitest/globals를&lt;/u&gt; 추가해 준다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;environment: vitest는 기본적으로 node 환경에서 실행되기 때문에 브라우저 환경을 테스트하기 위해서는 &lt;u&gt;jsdom으로 설정해줘야 한다.&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;setupFiles: 테스트 전에 실행되는 파일의 경로이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;alias: 테스트 파일에서 사용할 경로 별칭을 설정해 줄 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; Storybook에서 MSW 사용하기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1. MSW 초기화 함수 실행 및 MSW addon loader를 글로벌하게 제공하도록 설정한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692259558724&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react'
import type { Preview } from '@storybook/react'
import '../src/renderer/src/config/index.css'
import { initialize, mswLoader } from 'msw-storybook-addon'
import { handlers } from '../src/renderer/src/mocks/handler'
import SampleProvider from '../SampleProvider'

initialize()

const preview: Preview = {
  parameters: {
    msw: {
      handlers //optional
    }
  },
  loaders: [mswLoader],
  decorators: [
    (Story) =&amp;gt; (
      &amp;lt;SampleProvider&amp;gt;
        &amp;lt;Story /&amp;gt;
      &amp;lt;/SampleProvider&amp;gt;
    )
  ]
}

export default preview&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;.storybook/preview.tsx&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;msw-storybook-addon의 initialize 함수 호출해서 스토리북에서 MSW를 사용할 수 있도록 MSW를 초기화해 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;만약 전체 스토리에서 전역적으로 필요한 리퀘스트 핸들러가 있다면, parameters 속성에서 MSW로 모킹한 리퀘스트 핸들러들을 넣어준다. 그렇지 않다면 이 속성은 생략해 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;MSW addon loader를 글로벌하게 제공하도록 loaders에 addon을 추가한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;번외로 Storybook에서 Context Provider를 사용하는 법은 decorators를 활용하면 된다. decorators는 스토리에 필요한 요소들로 감싸서 렌더링 하는 데 사용된다. 이를 활용해서 글로벌로 Context Provider로 감싸서 스토리를 렌더링 하면 스토리에서 context에 접근할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. Storybook에서 기존에 사용하던 service worker 파일을 읽을 수 있도록 &lt;a href=&quot;https://storybook.js.org/docs/react/configure/images-and-assets#serving-static-files-via-storybook-configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;static 파일 경로 설정&lt;/a&gt;을 해준다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692264338599&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { StorybookConfig } from '@storybook/react-vite'

const config: StorybookConfig = {
  staticDirs: ['../src/renderer'], 
}
export default config&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;.storybook/main.ts&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;electron-vite에서 제공하는 템플릿에서 정적 에셋을 담는 폴더는 src/renderer 폴더에 해당하므로 service worker 등록에 필요한 파일은 여기에 위치해 있다. 따라서 Storybook 역시 정적 에셋 폴더 위치를 동일한 위치로 지정해야 MSW가 작동할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;보통 React 애플리케이션은 &lt;u&gt;npx msw init public/&lt;/u&gt; 명령어를 통해 public 폴더에 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. MSW를 브라우저와 노드 환경에 따라 실행되도록 구성한다.&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;동일한 리퀘스트 핸들러를 공유할 수 있지만 &lt;a href=&quot;https://mswjs.io/docs/getting-started/integrate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실행되는 환경마다 다르게 프로세스를 구성&lt;/a&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { setupServer } from 'msw/node'
import { handlers } from '../../mocks/handlers'

export const server = setupServer(...handlers)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;server.ts&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692264521886&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { setupWorker } from 'msw'
import { handlers } from '../../mocks/handlers'

export const worker = setupWorker(...handlers)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;browser.ts&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위와 같이 노드와 브라우저 각각 서비스 워커를 별도로 등록해 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import '@testing-library/jest-dom'
import { server } from './src/renderer/src/test/env/server'

beforeAll(() =&amp;gt; server.listen({ onUnhandledRequest: 'error' }))

afterAll(() =&amp;gt; server.close())

afterEach(() =&amp;gt; server.resetHandlers())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;vitest-setup.ts&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;테스트 별로 모킹에 의한 사이드 이펙트를 없애기 위해 테스트 수행 후 모킹한 핸들러를 초기화하는 코드를 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 이 파일을 각 테스트마다 실행할 수 있도록 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;vite.config.ts 파일에서 setupFiles에 위 파일 경로를 작성해 준다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;import { worker } from './test/env/browser'

if (process.env.NODE_ENV === 'development') {
  worker.start()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;src/renderer/src/main.tsx&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;브라우저에서 실행할 서비스 워커는 렌더러 폴더의 main 파일에서 등록해 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;4. 스토리에서 MSW 모킹 하는 코드를 추가한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import Sample from './Sample'
import { sampleHandlers } from '@renderer/mocks/handlers/sample-handler'

export const Default: Story = {
  render: (args) =&amp;gt; &amp;lt;Sample /&amp;gt;
  parameters: {
    msw: {
      handlers: sampleHandlers
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;sample.stories&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;parameters에 스토리에서 필요한 모킹 핸들러를 추가해 준다. 위와 같이 작성하면 컴포넌트 내부에서 API를 호출했을 때 MSW가 가로채서 모킹 한 리스폰스로 응답한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; 테스트에 사용할 customRender 함수 생성&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;전역으로 공급되는 요소들을 매번 테스트 코드에 작성하기 불편하므로 render 함수를 커스텀해서 사용하는 것이 좋다. 아래에서는 &lt;a href=&quot;https://testing-library.com/docs/react-testing-library/setup/#custom-render&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;@testing-library/react의 render 함수를 래핑&lt;/span&gt;&lt;/a&gt;하는 customRender 함수를 생성해서 export 해서 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { RenderOptions, render } from '@testing-library/react'
import { server } from '../env/server'
import SampleProvider from '../SampleProvider'

function mockStoryHandlers(story) {
  server.use(...(story.type.parameters?.msw?.handlers || []))
}

interface IProvidersProps {
  children: React.ReactElement
}

const AllTheProviders = ({ children }: IProvidersProps) =&amp;gt; {
  return (
    &amp;lt;SampleProvider&amp;gt;
		{children}
    &amp;lt;/SampleProvider&amp;gt;
  )
}

const customRender = (ui: React.ReactElement, options?: Omit&amp;lt;RenderOptions, 'queries'&amp;gt;) =&amp;gt; {
  mockStoryHandlers(ui)
  return render(ui, { wrapper: AllTheProviders, ...options })
}

export * from '@testing-library/react'

export { customRender as render }&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;mockStoryHandlers 함수를 통해 테스트할 스토리에 &lt;u&gt;parameters?.msw?.handlers&lt;/u&gt; 가 있는지 확인하고 스토리에서 사용한 API를 모킹하도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Context Provider로 렌더링 할 요소를 감싸준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; VITEST에서 MSW 사용 및 Story 재사용&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { render, waitFor } from '@renderer/test/utils/test-utils'
import { composeStories } from '@storybook/testing-react'
import * as SampleStories from './Sample.stories'

const { SampleAmpty } = composeStories(SampleStories)

describe('Sample', () =&amp;gt; {
  test('Sample 리스트가 없는 경우 빈 화면을 표시한다', async () =&amp;gt; {
    const { getByText } = render(&amp;lt;SampleAmpty /&amp;gt;)
    await waitFor(() =&amp;gt; expect(getByText('Empty')).toBeInTheDocument())
  })
})&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;composeStories 함수를 통해 테스트에서 스토리 컴포넌트를 재사용&lt;/span&gt;할 수 있도록 변환해 준다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위에서 만들었던 customRender 함수로 렌더링을 해서 MSW 모킹 코드를 재사용하고 context를 전역적으로 사용할 수 있도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;좋은 도구들을 최대한 활용해서 개발 능률을 최대치로 끌어올려보자!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;참고 자료&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://fe-developers.kakaoent.com/2022/220317-integrate-msw-storybook-jest/&quot;&gt;https://fe-developers.kakaoent.com/2022/220317-integrate-msw-storybook-jest/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://testing-library.com/docs/react-testing-library/setup/#custom-render&quot;&gt;https://testing-library.com/docs/react-testing-library/setup/#custom-render&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://mswjs.io/docs/getting-started/integrate&quot;&gt;https://mswjs.io/docs/getting-started/integrate&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://storybook.js.org/addons/msw-storybook-addon&quot;&gt;https://storybook.js.org/addons/msw-storybook-addon&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://blog.mathpresso.com/msw로-api-모킹하기-2d8a803c3d5c&quot;&gt;https://blog.mathpresso.com/msw로-api-모킹하기-2d8a803c3d5c&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://vitest.dev/guide/&quot;&gt;https://vitest.dev/guide/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/Frontend</category>
      <category>api 모킹</category>
      <category>msw</category>
      <category>storybook</category>
      <category>vitest</category>
      <category>스토리북</category>
      <category>프론트엔드 테스트 코드</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/143</guid>
      <comments>https://xiubindev.tistory.com/143#entry143comment</comments>
      <pubDate>Fri, 18 Aug 2023 12:03:37 +0900</pubDate>
    </item>
    <item>
      <title>React 프로젝트 구조 설계를 넘어서 프론트엔드 아키텍처 설계에 대한 고찰</title>
      <link>https://xiubindev.tistory.com/142</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xkxZG/btsgC1PA4L0/5RW34jMPSmktw1jCB9gCnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xkxZG/btsgC1PA4L0/5RW34jMPSmktw1jCB9gCnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xkxZG/btsgC1PA4L0/5RW34jMPSmktw1jCB9gCnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxkxZG%2FbtsgC1PA4L0%2F5RW34jMPSmktw1jCB9gCnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;i&gt;React 폴더 구조를 어떻게 구성할까?&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;i&gt;비즈니스 로직은 어떻게 분리할까?&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React와 같은 라이브러리로 신규 서비스를 개발하게 되면 프론트엔드 팀에서 가장 첫 번째로 고민해야 할 요소는 &lt;b&gt;프로젝트의 폴더 구조&lt;/b&gt;라고 생각한다. Next.js와 같은 프레임워크로 개발을 하게 되면 폴더 구조가 어느 정도는 정해져있기 때문에 고민해야 할 부분이 비교적 적다. 하지만 React의 경우에는 정해진 폴더 구조는 없고 일반적인 접근 방식만을 제안하고 있다. 따라서 개발자에 따라서 프로젝트 설계 방법이 천차만별일 것이고 설계에 정답은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년 차때는 프론트엔드 프로젝트 구조 설계에 대해 깊은 고민을 하지 않고 내가 자주 쓰고 익숙한 패턴을 그대로 가져다가 새로운 서비스에 적용하곤 했다. 하지만 10년 차 동료 개발자가 프론트엔드 구조 설계에 있어서 충분히 고민하고 우리 비즈니스에 적합한 구조로 설계하는 것을 보게 됐다. 이를 통해 프론트엔드 아키텍처를 설계할 때 좋은 설계가 어떤 것인지 조금은 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 React 프로젝트 구조 설계에 DDD(도메인 주도 설계)를 기반으로 한 Structure based on Feature를 사용해보며 느꼈던 점과 프론트엔드에서 좋은 아키텍처 설계란 무엇일까에 대한 고찰을 작성해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DDD(Domain Driven Design)란?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  소프트웨어의 본질은 해당 소프트웨어 사용자를 위해 도메인에 관련된 문제를 해결하는 능력에 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD(Domain Driven Design)란 도메인 주도 설계 라고 하며, 말 그대로 &lt;b&gt;도메인을 중심으로 설계&lt;/b&gt;하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;977&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8wTk2/btsgATMq2Q4/W0iClU5b0mGYfcGbuqFfC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8wTk2/btsgATMq2Q4/W0iClU5b0mGYfcGbuqFfC0/img.png&quot; data-alt=&quot;https://vueschool.io/articles/vuejs-tutorials/domain-driven-design-in-nuxt-apps/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8wTk2/btsgATMq2Q4/W0iClU5b0mGYfcGbuqFfC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8wTk2%2FbtsgATMq2Q4%2FW0iClU5b0mGYfcGbuqFfC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;977&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;977&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://vueschool.io/articles/vuejs-tutorials/domain-driven-design-in-nuxt-apps/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;u&gt;도메인이란 소프트웨어로 해결하고자 하는 문제 영역(problem domain)&lt;/u&gt;을 의미한다. 예를 들어 온라인 쇼핑몰이라는 큰 서비스에서 결제, 주문, 배송 등과 같이 전체 서비스를 분리한 각각의 비즈니스 영역을 의미한다. 그리고 이렇게 분리한 도메인을 중심으로 설계하는 것을 도메인 주도 설계라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD의 주요 설계 원칙은 &lt;b&gt;Loose Coupling(느슨한 결합)과 High Cohesion(높은 응집)&lt;/b&gt;이다. 도메인들 간에는 느슨한 결합을 하고 도메인 내에서는 높은 응집을 해야한다. 이를 위해 도메인을 잘 분리해야 하는 것이 필수적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DDD와 Hexagonal Architecture&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hexagonal Architecture의 핵심은 &lt;b&gt;관심사의 분리를 통해 유연한 소프트웨어를 만드는 것&lt;/b&gt;이라고 할 수 있다. DDD의 원칙에 헥사고날 아키텍처를 사용한다면 &lt;u&gt;도메인을 중심으로 설계하면서 계층간의 분리로 인해 시스템의 확장성을 높일 수&lt;/u&gt; 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O4DJK/btsgDJBa2gU/QEbB8rjDuONrm3NyETxjsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O4DJK/btsgDJBa2gU/QEbB8rjDuONrm3NyETxjsk/img.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1056&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.5615%; margin-right: 10px;&quot; data-widthpercent=&quot;51.16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O4DJK/btsgDJBa2gU/QEbB8rjDuONrm3NyETxjsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO4DJK%2FbtsgDJBa2gU%2FQEbB8rjDuONrm3NyETxjsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1056&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr8g5m/btsgCo5FGr7/712EmBKPiangfZm1IrPp0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr8g5m/btsgCo5FGr7/712EmBKPiangfZm1IrPp0K/img.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1106&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.2757%;&quot; data-widthpercent=&quot;48.84&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr8g5m/btsgCo5FGr7/712EmBKPiangfZm1IrPp0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr8g5m%2FbtsgCo5FGr7%2F712EmBKPiangfZm1IrPp0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1106&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;https://www.youtube.com/watch?v=FeDBlSBPUz8&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헥사고날 아키텍처에서 의존성은 바깥에서 안으로 단방향 의존성을 가지며 어댑터를 통해서만 인프라 영역에 접근할 수 있게 된다. 이와 같이 어댑터 요소가 생김으로써 애플리케이션의 외부 인터페이스를 비교적 자유롭게 교체할 수 있는 유연함을 가지게 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UI (Presentaion) Layer : 애플리케이션과의 커뮤니케이션을 담당하는 계층이다. 쉽게 말해 일반적인 UI 컴포넌트라고 보면 된다. 예로 React UI 컴포넌트들의 구성이라고 할 수 있다.&lt;/li&gt;
&lt;li&gt;Adapter Layer: Redux가 이 계층에 해당한다. 어댑터는 포트와 애플리케이션 코어 간의 요청과 응답을 변환하는 역할을 한다. 예를 들어 Redux를 활용해서 UI에서 사용할 state와 action을 정의한다고 볼 수 있다.&lt;/li&gt;
&lt;li&gt;Infrastructure Layer: 레이어 간의 커뮤니케이션을 책임진다. 예를 들어 서버와 통신하는 구현체를 인프라 레이어에 두게 되면 도메인이나 애플리케이션에 의존하는 구조를 만들 수 있다. 그리고 예를 들어 data fetching 기술을 fetch API를 사용하다가 이것을 Axios 라이브러리로 변경할 수 있는 작업이 용이해지고 안쪽 레이어에 영향을 최소화시킬 수 있다.&lt;/li&gt;
&lt;li&gt;Application Layer: 비즈니스 로직이 들어있는 Use Cases들의 집합이다. 예를 들어 온라인 쇼핑몰에서 &amp;lsquo;카트에 추가하기&amp;rsquo; 라는 유스 케이스에서 발생하는 액션들을 구현한다.&lt;/li&gt;
&lt;li&gt;Domain Layer: 도메인과 관련된 객체들의 집합이다. 도메인 레이어의 핵심은 외부로부터 독립적이라는 것이다. 예를 들어 온라인 쇼핑몰의 &amp;lsquo;카트에 추가하기&amp;rsquo; 기능에서 유저 스스로 카트 추가 버튼을 눌러서 아이템이 추가됐는지 또는 프로모션 코드를 통해 자동으로 아이템이 카트에 추가됐는지는 도메인 레이어에서 신경 쓰지 않는다. 도메인 레이어에서는 추가할 아이템을 받아서 카트 아이템을 업데이트해주기만 하면 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;React 프로젝트 폴더 구조에 DDD를 적용해보자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 팀에서 사용한 프로젝트 폴더 구조는 DDD를 기반으로 한 &amp;lsquo;&lt;b&gt;Structure based on Feature&amp;rsquo;이다.&lt;/b&gt; 이 폴더 구조는 동료 개발자와 함께 &lt;a href=&quot;https://reboot.studio/blog/folder-structures-to-organize-react-project&quot;&gt;이 링크&lt;/a&gt;를 참조해서 설계를 진행했다. Structure based on Feature 구조에서 키포인트는 &lt;b&gt;도메인 별로 폴더를 분리해서 도메인 내의 응집성을 높이고 서로 다른 도메인 간의 의존도를 낮추는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 적용한 React 프로젝트 폴더 구조는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;src/
|-- assets/
|-- components/
|-- config/
|-- constants/
|-- features/
|   |-- user/
|   |   |-- api/
|   |   |-- assets/
|   |   |-- data/
|   |   |-- hooks/
|   |   |-- pages/
|   |   |-- routes/
|   |   |-- utils/
|   |   |-- views/
|-- hooks/
|-- layout/
|-- pages/
|-- routes/
|-- styles/
|-- utils/
|-- App.tsx
|-- index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;assets: 공통으로 사용하는 이미지, 폰트 등&lt;/li&gt;
&lt;li&gt;components: 디자인 시스템에 기반한 공통으로 재사용하는 컴포넌트&lt;/li&gt;
&lt;li&gt;features: 도메인별 집합 폴더
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;api: 도메인에서 사용하는 api&lt;/li&gt;
&lt;li&gt;assets: 도메인에서 사용하는 이미지, 비디오 등&lt;/li&gt;
&lt;li&gt;data: 도메인에서 사용하는 swr 데이터 (상태 관리 도구에 따라 store, contexts 등 다양한 명칭 가능)&lt;/li&gt;
&lt;li&gt;hooks: 도메인에서 사용하는 react hook&lt;/li&gt;
&lt;li&gt;pages: 도메인 routes path와 맵핑되는 페이지&lt;/li&gt;
&lt;li&gt;routes: 도메인별 routes&lt;/li&gt;
&lt;li&gt;utils: 도메인에서 사용하는 유틸리티 함수&lt;/li&gt;
&lt;li&gt;views: 도메인 페이지에 사용되는 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;hooks: 공통으로 사용하는 react hook&lt;/li&gt;
&lt;li&gt;layout: 장치별(PC/Mobile) 레이아웃 컴포넌트&lt;/li&gt;
&lt;li&gt;pages: 정적 콘텐츠 페이지들 집합 폴더&lt;/li&gt;
&lt;li&gt;routes: 최상위 및 하위 도메인 경로 맵핑&lt;/li&gt;
&lt;li&gt;styles: 전역 스타일&lt;/li&gt;
&lt;li&gt;utils: 공통으로 사용하는 유틸리티 함수 및 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서비스를 운영해 보며 느낀 DDD기반 Structure based on Feature 구조의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 유지보수 및 확장에 용이하다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 별로 유지보수 작업이 용이하다. 현재 프로젝트에서 도메인 별로 R&amp;amp;R을 했기 때문에 수정사항이 생겨도 작업하기 수월하다. 또한, 기존 작업자가 공백이 생겨 다른 작업자가 해당 도메인을 맡게 됐을 때도 그 도메인에 대한 이해만 한다면 유지보수에 용이하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 도메인 간에 결합이 낮기 때문에 여러 개발자들이 동시에 서로 다른 기능을 개발할 때 각자의 개발 영역이 다른 개발 영역에 미치는 영향이 낮다. 현재 운영 중인 프로젝트의 경우 동시다발적으로 신규 기능이 추가되거나 기존 기능 수정사항 요청이 많은 상황이다. 따라서 각자의 맡은 도메인 영역이 다른 도메인에 주는 영향을 최소화하며 사이드이펙트를 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 추후 특정 도메인을 별도로 분리해서 배포하고자 하는 니즈가 있는 것으로 파악되기 때문에 DDD를 적용한 프로젝트는 확장성 측면에서도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 협업에 있어서 커뮤니케이션이 원활하다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업에는 항상 커뮤니케이션이 따라오는데 서로 같은 용어를 사용하더라도 다르게 이해할 수 있다. 예를 들어 온라인 쇼핑몰 비즈니스에서 Product라는 용어가 있다고 가정하자. Order 도메인을 다루는 팀은 Product를 바라볼 때 가격이나 재고 상품의 옵션을 볼 수 있고, Customer Support 도메인을 다루는 팀은 제품의 배송기간이나 평점을 보게 된다. 이처럼 같은 용어에 대해 서로 다른 관점으로 바라보게 되면 커뮤니케이션에 혼동이 올 수 있다. 하지만 DDD의 핵심 중 하나인 유비쿼터스 언어를 활용하게 되면 커뮤니케이션의 어려움을 낮출 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유비쿼터스 언어란 특정 도메인에서 특정 용어가 해당 도메인에서의 의도를 명확히 반영하고 핵심 개념을 잘 전달할 수 있는 언어이다. 그리고 모든 구성원들이 같은 용어에 대해 같은 것을 바라보는 것이 핵심이다. 용어가 정의될 때마다 용어 사전에 기록하고 명확하게 정의함으로써 모든 구성원이 공유해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 도메인에서 사용하는 용어를 코드에 반영함으로써 개발자에게 코드의 의미를 해석해야 하는 부담을 줄여주고 코드의 가독성을 높이고 이해하는 시간을 절약할 수 있다. 특히 업계 특성상 일반적이지 않은 용어들이 많은데 이러한 용어들을 명확히 정의하고 코드에 사용하면 개발자가 비즈니스 이해도를 높이는 동시에 타직군과의 커뮤니케이션도 원활하게 진행될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서비스를 운영해 보며 느낀 아쉬운 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 유비쿼터스 언어 사전을 만들지 못했다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 팀에서는 비즈니스 이해도를 기반으로 도메인을 분리하고 유비쿼터스 언어를 코드에 반영했다. 하지만 비즈니스라는 것이 중간에 바뀌지 말란 법이 없기 때문에, 특정 도메인에서 사용했던 용어가 개념은 같지만 용어가 바뀐 경우도 발생했다. 이런 경우 코드에 사용한 유비쿼터스 언어를 모두 교체해줘야 하는데 시간에 쫓겨 그렇지 못한 상황이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 서비스들이 추가되면서 유비쿼터스 언어들이 많이 늘어났는데 실무자들이 그것에 대해 정확히 인지를 하지 못하고 기획, 서버, 프런트 각각 다른 용어를 사용하면서 커뮤니케이션에 혼선이 생기기도 했다. &lt;b&gt;유비쿼터스 언어 사전&lt;/b&gt;을 만들어서 프로젝트를 진행했다면 좋았겠다는 아쉬움이 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 특정 도메인에 종속된 컴포넌트를 다른 도메인에서도 사용했다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 user 도메인에서 사용하던 &amp;lsquo;스팸 메일함 확인&amp;rsquo;이라는 컴포넌트가 있었다. 그런데 이 컴포넌트가 다른 도메인에서도 동일하게 사용되어야 하는 케이스가 발생했다. DDD에 따르면 이런 경우는 공통 컴포넌트 쪽으로 해당 컴포넌트를 옮겼어야 했는데, 이를 지키지 못하고 user 도메인에서 export 해서 사용한 부분이 있다. 해당 부분은 추후 리팩토링을 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;또 다른 프론트엔드 아키텍처 설계 방법론, FSD&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FSD는 DDD와 견주어도 괜찮을 법 한 프론트엔드 아키텍처 설계 방법론이라고 생각한다. 이 방법론은 동료 개발자가 알려줬는데 처음에는 현재 프로젝트 구조와 비교해서 장점을 캐치하지 못했다. 하지만 문서를 전반적으로 읽어보니 비즈니스 요구 사항들이 빠르게 변화하고 추가되는 우리 회사 비즈니스에 적합한 방법론이라는 생각이 들었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &amp;nbsp;Feature-Sliced Design (FSD)는 프론트엔드 애플리케이션을 스캐폴딩하기 위한 아키텍처 방법론이다. 이 방법론의 주요 목적은 끊임없이 변화하는 비즈니스 요구 사항에 직면하여 프로젝트를 더 이해하기 쉽고 체계적으로 만드는 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://feature-sliced.design/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://feature-sliced.design/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1684486600999&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Welcome | Feature-Sliced Design&quot; data-og-description=&quot;Architectural methodology for frontend projects&quot; data-og-host=&quot;feature-sliced.design&quot; data-og-source-url=&quot;https://feature-sliced.design/&quot; data-og-url=&quot;https://feature-sliced.design//&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XhDMP/hySF4hBY0B/5suduCxIXMnG6RAoJTiq7k/img.png?width=1200&amp;amp;height=630&amp;amp;face=298_67_408_177,https://scrap.kakaocdn.net/dn/eeNHQt/hySF2qxIIG/rf7AbDVqgML1ViMbuzJbk0/img.png?width=1200&amp;amp;height=630&amp;amp;face=298_67_408_177,https://scrap.kakaocdn.net/dn/cOR1we/hySF4PrfPK/Y28dx5FAJhbk8n2CnW7c9k/img.png?width=2920&amp;amp;height=1040&amp;amp;face=0_0_2920_1040&quot;&gt;&lt;a href=&quot;https://feature-sliced.design/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://feature-sliced.design/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XhDMP/hySF4hBY0B/5suduCxIXMnG6RAoJTiq7k/img.png?width=1200&amp;amp;height=630&amp;amp;face=298_67_408_177,https://scrap.kakaocdn.net/dn/eeNHQt/hySF2qxIIG/rf7AbDVqgML1ViMbuzJbk0/img.png?width=1200&amp;amp;height=630&amp;amp;face=298_67_408_177,https://scrap.kakaocdn.net/dn/cOR1we/hySF4PrfPK/Y28dx5FAJhbk8n2CnW7c9k/img.png?width=2920&amp;amp;height=1040&amp;amp;face=0_0_2920_1040');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Welcome | Feature-Sliced Design&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Architectural methodology for frontend projects&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;feature-sliced.design&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FSD의 특징은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Explicit business logic&lt;/b&gt;: 도메인 범위 덕분에 쉽게 찾을 수 있는 아키텍처&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Adaptability&lt;/b&gt;: 새로운 요구 사항에 따라 아키텍처 구성 요소를 유연하게 교체하고 추가할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tech debt &amp;amp; Refactoring&lt;/b&gt;: 사이드이펙트 없이 각각의 모듈을 독립적으로 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Explicit code reuse&lt;/b&gt;: DRY(Do not Repeat Yourself)와 로컬 맞춤형에서 밸런스를 유지할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FSD에서 프로젝트는 다음으로 구성된다. layers가 있고, 각 layer는 slices로 구성되고, 각 slice는 segments로 구성된다. 자세한 사항은 FSD 문서를 참조하면 좋고, 현재 우리 비즈니스에 어떻게 적용해 볼지 고민해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 프로젝트 폴더 구조 설계에 FSD를 적용해 봤다. 문서에 있는 예제들과는 살짝 다르게 각 레이어에서 최대한 도메인별로 분리를 했고, 레이어의 도메인마다 슬라이스를 생성하는 방향으로 설계해 봤다.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;src/
|-- shared/
|   |-- ui/
|   |-- lib/
|   |   |-- utils/
|   |   |-- hooks/
|   |-- config/
|   |-- constants/
|   |-- types/
|-- entities/
|   |-- user/
|   |   |-- ui/
|   |   |-- model/
|   |   |-- lib/
|   |   |-- api/
|-- features/
|   |-- user/
|   |   |-- signup/
|   |   |-- signin/
|-- widgets/
|   |-- user/
|   |   |-- ui/
|   |   |   |-- profile/
|   |   |-- lib/
|-- pages/
|   |-- user/
|   |   |-- signup/
|-- app/
|   |-- styles/
|   |-- routes/
|   |-- assets/
|-- App.tsx
|-- index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 FSD의 정석은 아닐 수 있지만 비즈니스 요구 사항에 따라 체계적이고 깔끔하게 만들 수 있는 아키텍처 중 하나라고 생각이 든다. 새로운 프로젝트를 진행하거나 기존 프로젝트를 리팩터링 할 때 이 방법론을 사용해 보고자 한다. 사용해보고 이 방법론에 대해서는 또 어떤 것을 느꼈는지 언젠가 글을 남겨보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그래서 좋은 설계란 무엇일까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처 설계 방법론은 굉장히 다양하고, 좋은 설계를 위한 원칙도 굉장히 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 생각하는 좋은 설계란 &lt;b&gt;프로그램을 유지보수하기 쉬운 설계라고&lt;/b&gt; 생각한다. 유지보수하기 쉽다는 것은 다양한 비즈니스 요구 사항에 맞춰서 확장 가능하고 유연하게 설계가 되어있다는 것이다. 그리고 이렇게 유지보수 하기 쉬운 설계는 구성원들 간의 커뮤니케이션을 도와주는 역할도 한다. 도메인에 대한 이해를 기반으로 좋은 설계를 하게 되면 서로 커뮤니케이션이 쉬워지고 끊임없이 변화하는 비즈니스에 따라 개발도 수월해질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 좋은 설계를 위한 방법론들 중에서 정답은 없다고 생각한다. 어떤 설계 방법론이 옳은지 따지기 보다 개발을 용이하게 할 수 있도록, 비즈니스의 복잡도로 인한 개발에 드는 비용을 줄일 수 있도록 하는 것이 좋은 설계라고 생각한다. 설계는 코드를 작성하기 전에 팀원들과 함께 고민해서 큰 틀을 잡아두고, 지속적인 리팩토링을 통해 좋은 설계로 나아가면 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;아래 문서를 참고하며 글을 작성했으며 보기를 꼭 추천드린다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://bespoyasov.me/blog/clean-architecture-on-frontend/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://bespoyasov.me/blog/clean-architecture-on-frontend/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thesametech.com/domain-driven-design-in-micro-frontend-architecture/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://thesametech.com/domain-driven-design-in-micro-frontend-architecture/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=FeDBlSBPUz8&quot;&gt;https://www.youtube.com/watch?v=FeDBlSBPUz8&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/1884/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://yozm.wishket.com/magazine/detail/1884/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://feature-sliced.design/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://feature-sliced.design/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/Frontend</category>
      <category>DDD 프론트엔드</category>
      <category>react 폴더 구조</category>
      <category>프론드엔드 설계 방법론</category>
      <category>프론트엔드 아키텍처 설계</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/142</guid>
      <comments>https://xiubindev.tistory.com/142#entry142comment</comments>
      <pubDate>Fri, 19 May 2023 17:52:00 +0900</pubDate>
    </item>
    <item>
      <title>[컨퍼런스 리뷰 / 고퍼런스] Onboard your Life to the Web3</title>
      <link>https://xiubindev.tistory.com/141</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RuQEt/btr6NljLTs3/okQSyGWu9jMlKpKPcUxqs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RuQEt/btr6NljLTs3/okQSyGWu9jMlKpKPcUxqs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RuQEt/btr6NljLTs3/okQSyGWu9jMlKpKPcUxqs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRuQEt%2Fbtr6NljLTs3%2FokQSyGWu9jMlKpKPcUxqs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1815&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Web3 업계에 종사하면서 프로젝트의 성공 요인 중에 하나는 커뮤니티라는 것을 크게 느끼고 있다. 이번 고퍼런스 역시 고스트 프로젝트의 커뮤니티를 좀 더 탄탄하게 만드는 동시에 더 확장할 수 있는 것을 목표로 한 것 같다. 고스트 프로젝트 홀더뿐만 아니라 비홀더까지 초청한 점이 눈에 띄었다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강연과 네트워킹 시간을 적절히 섞어서 시간 분배를 잘 한 점도 눈에 띄었다. &lt;/span&gt;그리고 단순한 친목 모임이 아니라 블록체인 지식을 쉽게 더 전파하기 위해 훌륭한 연사까지 초청했다! 그 모든 것을 NFT 홀더들이 직접 준비한 것도 정말 멋있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고퍼런스와 같은 밋업이 많아져서 Web3 세상에 많은 사람들이 관심을 조금이라도 가진다면 Mass Adoption이 가능할 수 있다고 본다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 고퍼런스에 대한 상세 내용은 아래에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://creative1y.notion.site/creative1y/Ghoference-Season2-2023-0ae2cf18426f4d15a27472d14a4a67fa&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://creative1y.notion.site/creative1y/Ghoference-Season2-2023-0ae2cf18426f4d15a27472d14a4a67fa&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Ghoference Season2 (2023)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;고퍼런스 시즌2: Onboard your Life to the Web3&quot; data-og-host=&quot;creative1y.notion.site&quot; data-og-source-url=&quot;https://creative1y.notion.site/creative1y/Ghoference-Season2-2023-0ae2cf18426f4d15a27472d14a4a67fa&quot; data-og-url=&quot;https://creative1y.notion.site/0ae2cf18426f4d15a27472d14a4a67fa&quot;&gt;&lt;a href=&quot;https://creative1y.notion.site/0ae2cf18426f4d15a27472d14a4a67fa&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://creative1y.notion.site/creative1y/Ghoference-Season2-2023-0ae2cf18426f4d15a27472d14a4a67fa&quot;&gt;
&lt;div class=&quot;og-image&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Ghoference Season2 (2023)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;고퍼런스 시즌2: Onboard your Life to the Web3&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;creative1y.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고퍼런스를 주최한 홀더분의 인터뷰도 아래에서 확인할 수 있다.&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://kr.nestree.news/news/articleView.html?idxno=2913&quot;&gt;https://kr.nestree.news/news/articleView.html?idxno=2913&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1680255798337&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;홀더들이 자발적으로 완성한 웹3 콘퍼런스, &amp;lsquo;고퍼런스&amp;rsquo; - 네스트리NFT뉴스&quot; data-og-description=&quot;많은 전문가들이 꼽는 NFT 투자 요소 중 하나는 &amp;lsquo;탄탄한 커뮤니티&amp;rsquo;다. 홀더들을 팬으로 만들지 못하는 프로젝트는 금세 사그라들기 마련. 잠시 잠깐 빛이 나는 순간도 있지만, 오래가지 않는다&quot; data-og-host=&quot;kr.nestree.news&quot; data-og-source-url=&quot;https://kr.nestree.news/news/articleView.html?idxno=2913&quot; data-og-url=&quot;https://kr.nestree.news/news/articleView.html?idxno=2913&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4ufng/hyR6Pyy7sY/1zEOxTWeThuSmNoiOA4GdK/img.jpg?width=600&amp;amp;height=450&amp;amp;face=0_0_600_450,https://scrap.kakaocdn.net/dn/bzz4GM/hyR6FQhfJO/pEee8J3yT404o9Yxqu5xcK/img.jpg?width=600&amp;amp;height=800&amp;amp;face=0_0_600_800,https://scrap.kakaocdn.net/dn/btUmiz/hyR6MhwqXx/Rm3WkzBecWxgcnvuomhVnk/img.jpg?width=600&amp;amp;height=800&amp;amp;face=0_0_600_800&quot;&gt;&lt;a href=&quot;https://kr.nestree.news/news/articleView.html?idxno=2913&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kr.nestree.news/news/articleView.html?idxno=2913&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4ufng/hyR6Pyy7sY/1zEOxTWeThuSmNoiOA4GdK/img.jpg?width=600&amp;amp;height=450&amp;amp;face=0_0_600_450,https://scrap.kakaocdn.net/dn/bzz4GM/hyR6FQhfJO/pEee8J3yT404o9Yxqu5xcK/img.jpg?width=600&amp;amp;height=800&amp;amp;face=0_0_600_800,https://scrap.kakaocdn.net/dn/btUmiz/hyR6MhwqXx/Rm3WkzBecWxgcnvuomhVnk/img.jpg?width=600&amp;amp;height=800&amp;amp;face=0_0_600_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;홀더들이 자발적으로 완성한 웹3 콘퍼런스, &amp;lsquo;고퍼런스&amp;rsquo; - 네스트리NFT뉴스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;많은 전문가들이 꼽는 NFT 투자 요소 중 하나는 &amp;lsquo;탄탄한 커뮤니티&amp;rsquo;다. 홀더들을 팬으로 만들지 못하는 프로젝트는 금세 사그라들기 마련. 잠시 잠깐 빛이 나는 순간도 있지만, 오래가지 않는다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kr.nestree.news&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GiOZQ/btr6QqjXntK/rmKsJjk0gjTijQbVxykKf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GiOZQ/btr6QqjXntK/rmKsJjk0gjTijQbVxykKf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GiOZQ/btr6QqjXntK/rmKsJjk0gjTijQbVxykKf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGiOZQ%2Fbtr6QqjXntK%2FrmKsJjk0gjTijQbVxykKf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;2250&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;12시 30분부터 식사 및 1차 네트워킹 시간이었는데 너무 일찍 도착하는 바람에 가보고 싶었던 샐러드 집에 왔다. 육회 샐러드라니!! 육회랑 아보카도랑 이렇게 잘 어울리다니,, 충격과 감동,, 소스도 너무 잘 만들었더라! 앞으로 자취하는 동안 내 최애 샐러드 집이 될 듯 &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RF3Mj/btr6Jomzl7t/jBTtfR0VS0Ae2vxC6M4jI0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RF3Mj/btr6Jomzl7t/jBTtfR0VS0Ae2vxC6M4jI0/img.jpg&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RF3Mj/btr6Jomzl7t/jBTtfR0VS0Ae2vxC6M4jI0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRF3Mj%2Fbtr6Jomzl7t%2FjBTtfR0VS0Ae2vxC6M4jI0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8mvk3/btr6Bk0g2fM/JRBji5erPWYmzaSy0aZxyK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8mvk3/btr6Bk0g2fM/JRBji5erPWYmzaSy0aZxyK/img.jpg&quot; style=&quot;width: 49.4186%;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8mvk3/btr6Bk0g2fM/JRBji5erPWYmzaSy0aZxyK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8mvk3%2Fbtr6Bk0g2fM%2FJRBji5erPWYmzaSy0aZxyK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행사장에 들어서자마자 눈을 사로잡는 포스터들과 굿즈들이 놓여있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd43zF/btr6GrYbdbf/k0dpfJw8h6fcoAoiPM7r7k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd43zF/btr6GrYbdbf/k0dpfJw8h6fcoAoiPM7r7k/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd43zF/btr6GrYbdbf/k0dpfJw8h6fcoAoiPM7r7k/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd43zF%2Fbtr6GrYbdbf%2Fk0dpfJw8h6fcoAoiPM7r7k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQlqy7/btr6BC7vIMd/KOKKg7X02cyCtN6IW6NAp1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQlqy7/btr6BC7vIMd/KOKKg7X02cyCtN6IW6NAp1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; style=&quot;width: 49.4186%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQlqy7/btr6BC7vIMd/KOKKg7X02cyCtN6IW6NAp1/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQlqy7%2Fbtr6BC7vIMd%2FKOKKg7X02cyCtN6IW6NAp1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두둑이 먹고 1시쯤 도착했는데 아기자기한 포장박스에 샌드위치가 담겨 있었다. 세션 중간에 샌드위치 먹어봤는데 너무 맛있었다! 음료수뿐만 아니라 맥주까지 있더라 ㅋㅋㅋ 강연 시작 전에 네트워킹 시간이 있어서 나도 네트워킹에 참여해 봤다. 쟁글 팀원 분이랑 얘기도 나눠보고 국내 거래소, NFT 크리에이터까지 세션이 시작하기 전에도 즐거운 시간을 가질 수 있어서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmea7o/btr6NwkfZrg/iyeQAKHETYlYNWKE51pxA0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmea7o/btr6NwkfZrg/iyeQAKHETYlYNWKE51pxA0/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmea7o/btr6NwkfZrg/iyeQAKHETYlYNWKE51pxA0/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmea7o%2Fbtr6NwkfZrg%2FiyeQAKHETYlYNWKE51pxA0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Brd3q/btr6CfqELg6/pmv9KnkWztP5vmz4sw5Ww1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Brd3q/btr6CfqELg6/pmv9KnkWztP5vmz4sw5Ww1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; style=&quot;width: 49.4186%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Brd3q/btr6CfqELg6/pmv9KnkWztP5vmz4sw5Ww1/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBrd3q%2Fbtr6CfqELg6%2Fpmv9KnkWztP5vmz4sw5Ww1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시드 라운지도 처음 와서 구경하고, 해시드 캠핑 의자 같은 것도 너무 귀엽고 편하고 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw88dU/btr6DY3exui/HV81ewAIysemVdOd49pKC0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw88dU/btr6DY3exui/HV81ewAIysemVdOd49pKC0/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; style=&quot;width: 28.3461%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;29.02&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw88dU/btr6DY3exui/HV81ewAIysemVdOd49pKC0/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw88dU%2Fbtr6DY3exui%2FHV81ewAIysemVdOd49pKC0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TspWe/btr6N3i1jrc/fHtdX82aa7nOfcP2sIFAkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TspWe/btr6N3i1jrc/fHtdX82aa7nOfcP2sIFAkK/img.png&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;2075&quot; data-filename=&quot;blob&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.9823%; margin-right: 10px;&quot; data-widthpercent=&quot;41.96&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TspWe/btr6N3i1jrc/fHtdX82aa7nOfcP2sIFAkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTspWe%2Fbtr6N3i1jrc%2FfHtdX82aa7nOfcP2sIFAkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2250&quot; height=&quot;2075&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WJpkO/btr6NkqD1YW/cRNKXK2gaiXsrwBQehCJKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WJpkO/btr6NkqD1YW/cRNKXK2gaiXsrwBQehCJKk/img.jpg&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2880&quot; style=&quot;width: 28.3461%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;29.02&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WJpkO/btr6NkqD1YW/cRNKXK2gaiXsrwBQehCJKk/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWJpkO%2Fbtr6NkqD1YW%2FcRNKXK2gaiXsrwBQehCJKk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2880&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고퍼런스에서 굿즈를 나눠주는 이벤트도 진행했다. 트위터 인증하면 추첨해서 굿즈 주고, 연사 분들이 강연 중간에도 나눠주시기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 운 좋게도 굿즈를 3개나 받아왔다ㅋㅋㅋㅋ 반팔티, 후드티, 장패드 싹쓸이!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLmjCN/btr6OVkvGmp/x8473oukmsiL3WMov9BvSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLmjCN/btr6OVkvGmp/x8473oukmsiL3WMov9BvSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLmjCN/btr6OVkvGmp/x8473oukmsiL3WMov9BvSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLmjCN%2Fbtr6OVkvGmp%2Fx8473oukmsiL3WMov9BvSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;2250&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 오프라인 참석 규모는 120명이고 온라인은 40명 규모로 꽤나 크게 진행됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 아쉬웠던 점은 연사 분의 목소리와 한쪽 공간에서 네트워킹 하시는 분들의 목소리가 뒤섞여서 약간 어수선한 분위기에서 강연이 진행됐다. 물론 연사 분의 목소리는 마이크를 사용해서 크기 때문에 더 잘 들렸지만 웅성 웅성하는 소리들이 강연 듣는 것에 조금 방해가 되긴 했다 :(&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKF9xM/btr6DKRuCJQ/jszO5obgdsfBUjIyHITPh1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKF9xM/btr6DKRuCJQ/jszO5obgdsfBUjIyHITPh1/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKF9xM/btr6DKRuCJQ/jszO5obgdsfBUjIyHITPh1/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKF9xM%2Fbtr6DKRuCJQ%2FjszO5obgdsfBUjIyHITPh1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ot86z/btr6ANuYaNp/1QlSQRPkR1CdWjk5CvZ9IK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ot86z/btr6ANuYaNp/1QlSQRPkR1CdWjk5CvZ9IK/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; style=&quot;width: 49.4186%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ot86z/btr6ANuYaNp/1QlSQRPkR1CdWjk5CvZ9IK/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOt86z%2Fbtr6ANuYaNp%2F1QlSQRPkR1CdWjk5CvZ9IK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고퍼런스 첫 번째 연사는 이화체인 학회장인 IRENE님이 진행해 주셨다. 주제는 &lt;b&gt;&quot;학생의 시선에서 바라본 크립토 시장과 Web3 개발자의 삶&quot;&lt;/b&gt; 으로 진행됐다. 블록체인 업계에서 Web3 개발자 학생에게 유리한 점과 학습 방법을 위주로 강연을 진행하셨다. 강연 내용은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 학생과 주니어에게 열려있는&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 학생 지원 프로그램들이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상대적으로 주니어들의 대기업 취업 입사 기회가 많고 연봉과 근무환경이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 다양한 기회들&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로젝트 앰버서더, 펠러우쉽, 커피챗 등을 통해 프로젝트 협업, 채용 제안 등의 기회를 잡을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 꾸준히 공부하면서 개인 미디어 활동 관리가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 커뮤니티를 통한 소중한 인연들&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 밋업, 홀더 커뮤니티, 커피챗 등을 통한 네트워킹이 많고 좋은 멘토, 파트너, 친구를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 학습 방법 : 학회, 스터디, 교육 모임, 밋업&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 학회에서 공부하는 것을 추천한다. 커리큘럼과 함께 꾸준히 공부할 수 있고 프로젝트,해커톤,외부행사,연계 인턴십,학회원 네트워킹 등 다양한 기회가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지금 운영되고 있는 학회는 이화체인, 디사이퍼, BAY, 블록체인밸리, CURG 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스터디나 교육커뮤니티로는 루디움,붐랩스,블파스 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 학습 방법 : 온라인 공부&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 온라인 강의로는 Bildspace, Figment Learn, 각 체인별 리소스를 참고하면 좋다. 부트캠프로는 Encode Club이 있고, 기본 개념이나 리서치는 쟁글,a41,마마벤처스,Binance Academy, 암호화폐 정리노트(텔레그램채널), Jayplayco(텔레그램채널) 등을 참고하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 학습 방법 : Web3 생태계에 직접 참여하기, 커리어 시작하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 소액 투자해서 Web3 서비스 단계별로 체험해 보는 것이 가장 직관적이고, 프로젝트 커뮤니티들에 참여해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 직접 업계에서 일을 해보는 것이 최고의 방법이다. 많은 시간을 쏟아서 학습이 가능하고 트렌트를 빠르게 캐치할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wBw9r/btr6CfKXUMz/HFbMUs2RjtbZbXIQQJyNr1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wBw9r/btr6CfKXUMz/HFbMUs2RjtbZbXIQQJyNr1/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wBw9r/btr6CfKXUMz/HFbMUs2RjtbZbXIQQJyNr1/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwBw9r%2Fbtr6CfKXUMz%2FHFbMUs2RjtbZbXIQQJyNr1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLbGdd/btr6GU0i4IY/z1dfnikF8hMsPSgWpeA7CK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLbGdd/btr6GU0i4IY/z1dfnikF8hMsPSgWpeA7CK/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; style=&quot;width: 49.4186%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLbGdd/btr6GU0i4IY/z1dfnikF8hMsPSgWpeA7CK/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLbGdd%2Fbtr6GU0i4IY%2Fz1dfnikF8hMsPSgWpeA7CK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 연사로는 해시드의 안수빈 님이 진행해 주셨고, 주제는 &lt;b&gt;&quot;Welcome to On-Chain Data World(Feat. Web3 DA Life)&quot;&lt;/b&gt; 였다. 원래도 좋은 아티클을 많이 작성하시고 텔레그램 채널도 잘 구독하고 있어서 꼭 뵙고 싶었는데 이렇게 고퍼런스에서 뵙게 돼서 정말 영광이었다. 나랑 이름이 같아서 그런지 자꾸 비교하게 되는... 같은 안수빈이지만 정말 대단한 안수빈님! 나도 언젠간 내 이름으로 사람들 앞에서 강연을 할 수 있는 그런 사람이 됐으면 좋겠다 :) 안수빈님의 강연 내용은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;온체인 데이터 사용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 온체인 데이터로 사용자의 행동을 알면 무엇을 할 수 있는지 생각해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 분석가의 대표적인 유형&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Trader: DaaS로 좋은 거래 기회 발견&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Detective/ Alert: Explorer를 통한 추적 및 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Trend Setter: 생태계 정보를 다양한 방면으로 수집 및 분석&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 용도에 따라 다양한 온체인 서비스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Explorer: Etherscan과 친구들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Query: Dune, Flipside Crypto&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- No-Code: TokenTerminal, DefiLlama,L2Beat,Artemis&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. No-code: 하루의 시작과 함께&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TokenTerminal: 요새 어떤 서비스가 잘나가나? (Trending Contract, Fee, Revenue)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DefiLlama: 디파이에는 별일 없지? (DeFi TVL, Volume)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- L2Beat: 요새 L2가 대세라며? (L2 TVL)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Artemis: 체인에는 무슨 일이 있을까?(L1,L2 DAU, Daily Txs)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 더 깊은 온체인 데이터 분석을 하고 싶다면?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로토콜 매커니즘: 컨트랙트와 토크노믹스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 분석과 시각화: 통계 기법과 인지과학&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 관계형 데이터베이스와 쿼리문: SQL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도메인 지식: 다양한 지표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cowa2i/btr6BB1QYB4/fPJFgO7XbqHF6ue2J4M6H1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cowa2i/btr6BB1QYB4/fPJFgO7XbqHF6ue2J4M6H1/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cowa2i/btr6BB1QYB4/fPJFgO7XbqHF6ue2J4M6H1/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcowa2i%2Fbtr6BB1QYB4%2FfPJFgO7XbqHF6ue2J4M6H1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p3lMn/btr6AMQkMc0/r9GEd559WU3e0ux2ZBgM11/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p3lMn/btr6AMQkMc0/r9GEd559WU3e0ux2ZBgM11/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; style=&quot;width: 49.4186%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p3lMn/btr6AMQkMc0/r9GEd559WU3e0ux2ZBgM11/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp3lMn%2Fbtr6AMQkMc0%2Fr9GEd559WU3e0ux2ZBgM11%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 다음 강연은 &lt;b&gt;&quot;Web2/Web3 VC는 어떤 일을 할까 about Web 2.5&quot;&lt;/b&gt; 라는 주제로 미래에셋벤처투자의 유수형님과 필로소피아벤처스의 오영택님이 진행해 주셨다. Web3 업계에서 전통 VC들이 어떤 고민들을 하고 있고 어떤 방향으로 나아가면 좋을지 개인적인 의견을 공유해 주셨다. 강연 내용은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Web3 업계에서 VC 투자란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Web3 업계에서 VC 투자란 주로 private round에서 SAFT 또는 Equity with Token warrant 등의 계약으로 프로젝트가 ICO, IDO 등 을 통해 불특정 다수에게 뿌려지기 전 투자하는 것을 의미한다. 또한 Web3에 관련 있는 기업의 지분 투자를 의미하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Web3 업계에서 VC의 역할이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어떤 팀에게 투자해야 하는지에 대한 마땅한 기준이 아직 없다. 왜냐하면 업계가 early이고 valuation 로직도 발전되지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 투자한 팀에게 어떤 역할을 해주는지는 기존 VC에서 좀 더 추가되는 부분이 있다. 기존 VC는 브랜딩, HR, 라운드 리드, 네트워킹 등을 해줬다면 여기에 더해 같이 토크노믹스를 짜는 등 같이 연구를 진행하기도 한다. Hashed의 Unopnd, 블로코어 같이 프로젝트를 직접 만들어 나가는 경우도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 토큰의 경우 유동성이 조기에 열려서 exit 타이밍을 잡는 게 비교적 유연하다. 하지만 Exit 흐름이 대중들에게 보이기 때문에 펌프 앤 덤프 비판을 받을 수는 있다. 그러나 기본적으로 락업 스케줄이 SAFT 등에 명시되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 좋은 Web3 VC란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 강력한 믿음이 필요하다. 믿음에 의한 롱 텀 뷰를 갖고 윤리적인 플레이를 하는 VC가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 투자자이자 플레잉 코치 역할이 필요하다. 자본이 아니라 지혜로 승부 보는 투자자가 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 전통 VC에게 Web3 투자가 어려운 이유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Due Diligence(실사/투자심사)에서 무엇을 봐야 할지에 대해서 어려움을 겪는다. 회계 재무를 볼 수 있는 지표가 없다. 그렇다면 다른 점에서 심사를 거쳐야 한다. 성장하는 시장인지, 비즈니스 모델은 어떤지, 그동안의 실험 성과가 있는지, 팀 구성은 어떻게 되어있는지를 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 일반적으로 한 번의 투자로 승부를 본다. 주주로서 지속적인 모니터링을 진행하지만 대부분 한번의 투자뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아직 블록체인 업계에서 성공적인 M&amp;amp;A 사례가 없고, 투자자 락업이 있고, 너무 빠른 상장으로 구주 판매 기회가 없어서 Exit 이 쉽지 않다. 더 큰 난관은 법인 계좌를 개설하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ORMfD/btr6DLpi8fP/VFY5BWvhFKHYfSN34n59CK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ORMfD/btr6DLpi8fP/VFY5BWvhFKHYfSN34n59CK/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ORMfD/btr6DLpi8fP/VFY5BWvhFKHYfSN34n59CK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FORMfD%2Fbtr6DLpi8fP%2FVFY5BWvhFKHYfSN34n59CK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyOGkg/btr6Nj7wt4o/zYtChhkMoOgPrDR0EPifv1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyOGkg/btr6Nj7wt4o/zYtChhkMoOgPrDR0EPifv1/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyOGkg/btr6Nj7wt4o/zYtChhkMoOgPrDR0EPifv1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyOGkg%2Fbtr6Nj7wt4o%2FzYtChhkMoOgPrDR0EPifv1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어지는 강연은 전 해시드 팀원이자 현재 Factomind 소속이신 Nathan 님이 &lt;b&gt;&quot;22년 Recap 및 23년 Market Preview&quot;&lt;/b&gt; 라는 주제로 진행됐다. 시장 예측은 사실상 불가능하다는 것을 다들 알고 있을 것이다. 하지만 시장 내에서 예측이란 무엇일까에 대한 고찰을 정말 알기 쉽게 풀어주셨다. 요즘 내가 읽고 있는 책에서도 블랙 스완에 대한 내용을 다루고 있는데, 블랙 스완을 대처하는 자세가 정말 중요한 것 같다는 생각이 많이 들었다. 자세한 내용은 &lt;a href=&quot;https://twitter.com/NathanYJLee/status/1639945238897897473?s=20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nathan님의 트위터 스레드&lt;/a&gt;를 참고하면 좋을 것 같다. 내용을 간단하게 요약해 보자면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 블랙 스완이란 아무도 예측하지 못한 이례적인 사건을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예측은 연속적인 행동이고 변하는 상황에 맞추어 예측치를 업데이트하기를 주저하지 말자.&lt;br /&gt;- 현재 시장의 유동성이 현저히 낮기 때문에 예측의 난이도가 높다는 것을 인지하자.&lt;br /&gt;- 블랙 스완 이벤트가 일어났을 때 행동할 수 있는 대비 전략을 준비해 두자.&lt;br /&gt;-&amp;nbsp;금융시장&amp;nbsp;내&amp;nbsp;원론과&amp;nbsp;정의&amp;nbsp;공부를&amp;nbsp;통해&amp;nbsp;기회를&amp;nbsp;포착하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7mTJj/btr6Fhaqcel/mp7CCKkKbrsnnM2AK5gKFK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7mTJj/btr6Fhaqcel/mp7CCKkKbrsnnM2AK5gKFK/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7mTJj/btr6Fhaqcel/mp7CCKkKbrsnnM2AK5gKFK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7mTJj%2Fbtr6Fhaqcel%2Fmp7CCKkKbrsnnM2AK5gKFK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MWths/btr6K2ES8hm/rnCeQTCwuDhwnKLFwTRyd0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MWths/btr6K2ES8hm/rnCeQTCwuDhwnKLFwTRyd0/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MWths/btr6K2ES8hm/rnCeQTCwuDhwnKLFwTRyd0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMWths%2Fbtr6K2ES8hm%2FrnCeQTCwuDhwnKLFwTRyd0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 강연은 슈퍼블록 김재윤님이 &lt;b&gt;&quot;다음세대 DeFi는 어떻게 진화할까?&quot;&lt;/b&gt; 라는 주제로 진행해 주셨다. 평소에 재윤티비 유튜브를 즐겨보고 있었는데 요즘은 영상이 자주 안올라와서 아쉬운 ㅎㅎ 재윤님께서는 디파이의 변천사를 위주로 강연을 진행해주셨다. 내용은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 이자 농사(yield farming)의 등장&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스시스왑이 등장하며 유니스왑을 공격했다. 유동성 공급자들에게 SUSHI 토큰을 분배하면서 유니스왑의 유동성을 많이 빼앗아왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이처럼 토큰을 발행하여 유동성을 끌어들이는 전략이 보편화 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 현실의 문제를 해결하기 시작&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;실제 자산의 가격정보를 온체인 상에 반영한 미러링된 자산을 거래할 수 있는 디파이 프로토콜인 미러 프로토콜 등장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 헤지펀드들의 공매도 포지션을 청산시키는 게임스탑 사태가 발생하고 증권거래 앱인 로빈후드에서 게임스탑 주식 매수 버튼을 비활성화했다. 이와 달리 검열 저항성을 보여주는 디파이 미러프로토콜로 많은 사람들이 이주하기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 기존 자산 시장의 한계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 세상에는 수많은 유무형의 가치가 존재하지만 자산화된 가치들은 그 종류도 매우 적을뿐더러 자유롭게 거래하기가 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 블록체인의 효용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 블록체인은 누구나 원하는 가치를 쉽게 데이터화, 즉 토큰화시킬 수 있도록 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- STO 가이드라인을 살펴보면, 다양한 권리를 증권화하여 발행 및 유통할 수 있다. 비정형적 증권을 장외시장에서 거래할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고스트 프로젝트 홀더들이 자발적으로 모여서 Web3 시장을 리뷰하고 앞으로의 발전 방향성에 대해 토론할 수 있는 의미 있는 시간이었다. 고퍼런스의 목표인 많은 사람들에게 보다 쉬운 정보로 블록체인 세상을 전파하고자 하는 목표는 어느 정도 달성할 수 있게 된 자리라고 생각한다. 추후에 또 열리게 된다면 꼭 참여하고 싶은 고퍼런스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Record/review</category>
      <category>Ghoference</category>
      <category>고스트프로젝트</category>
      <category>고퍼런스</category>
      <category>슈퍼블록</category>
      <category>이화체인</category>
      <category>해시드</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/141</guid>
      <comments>https://xiubindev.tistory.com/141#entry141comment</comments>
      <pubDate>Tue, 4 Apr 2023 12:12:25 +0900</pubDate>
    </item>
    <item>
      <title>블록체인 스타트업 프론트엔드 개발자의 입사 1주년 회고</title>
      <link>https://xiubindev.tistory.com/140</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-23 오후 3.41.31.png&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uqt7F/btrUo4hMqTH/8hip2rfYJ1ydLdmlwm58S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uqt7F/btrUo4hMqTH/8hip2rfYJ1ydLdmlwm58S1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uqt7F/btrUo4hMqTH/8hip2rfYJ1ydLdmlwm58S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuqt7F%2FbtrUo4hMqTH%2F8hip2rfYJ1ydLdmlwm58S1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;293&quot; data-filename=&quot;스크린샷 2022-12-23 오후 3.41.31.png&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;중국어통번역학과를 졸업한 내가 어느덧 2년 차 개발자가 됐다!!  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만 개발자로 지내면서 학습 내용은 틈틈이 개인 노션이나 블로그에 작성해 왔지만, 나를 돌아볼 시간은 없었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 이번 회고를 통해서 내가 1년 동안 무엇을 했고 어떤 걸 배웠는지, 그리고 앞으로 해야 할 일들을 정리하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개발자로서 작성하는 첫 번째 회고의 주제는 &lt;b&gt;블록체인 스타트업 프론트엔드 개발자의 입사 1주년&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;1. 프리랜서 개발자로 시작하다&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;현재 다니는 회사에 오기 전 어떤 경험을 쌓았는지부터 시작해야 할 것 같아서 이야기를 한 번 풀어본다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개발자로서의 내 첫 커리어는 상주 프리랜서 프론트엔드 개발자로 시작을 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개발 공부를 병행하며 YAPP 동아리 활동을 마치고 취업 준비를 하려던 즈음에, 노마드코더 커뮤니티에서 많은 도움을 주시던 Flynn님께서 리액트 프리랜서 개발자 채용 공고를 공유해 주셨다. 지원할 당시 개발 공부한 지 1년도 안된 초보 였기 때문에 프리랜서는 전혀 생각지 못한 선택지였다. 하지만 채용 공고의 기술 스택이 굉장히 매력적이었고, 업무 경험을 하루빨리 쌓아 보고 싶다는 욕구가 강했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 개인 프로젝트 하나와 동아리에서 진행한 팀 프로젝트 하나를 포트폴리오에 담고 면접을 보러 갔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개발자로서 첫 면접이었는데, 정말 떨렸던 기억이 난다. 주로 개인 프로젝트의 기술 사용에 대해 많은 질문을 하셨고, 최선을 다해 답변했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 결과 &lt;b&gt;개발자로서 첫 면접에 첫 합격 통보&lt;/b&gt;를 받게 됐고, 프론트엔드 개발자로서의 첫 발걸음을 내딛게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-27 오후 1.21.12.png&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VCE0I/btrUHSAmo3q/fxYSd32BP3OLwOjT2BjQ60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VCE0I/btrUHSAmo3q/fxYSd32BP3OLwOjT2BjQ60/img.png&quot; data-alt=&quot;채용 공고 내용 일부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VCE0I/btrUHSAmo3q/fxYSd32BP3OLwOjT2BjQ60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVCE0I%2FbtrUHSAmo3q%2FfxYSd32BP3OLwOjT2BjQ60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;383&quot; height=&quot;238&quot; data-filename=&quot;스크린샷 2022-12-27 오후 1.21.12.png&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;채용 공고 내용 일부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프리랜서로 참여하게 된 프로젝트는 &lt;b&gt;LG 생활건강에서 발주한 미국 온라인 쇼핑몰 리뉴얼 작업&lt;/b&gt;이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;꽤나 대형 프로젝트였던걸로 기억하는데, 구현해야 하는 페이지가 130여 개였고, 페이지마다 구현해야 할 기능도 다양했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;투입 초기에는 프론트엔드 개발자 1명, 퍼블리셔 1명이 전부여서 지레 겁을 먹었지만, 몇 달 후에 프론트엔드 개발자가 3명이 더 채용돼서 안도의 한숨을 내쉬었던 기억이 있다. 이렇게 큰&amp;nbsp;프로젝트에 실무자로 투입되자마자 트렌디했던 기술 스택은 다 사용해 본 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(React, Redux, Redux-Saga, Redux-Thunk, Storybook, TypeScript, styled-components)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;물론 학습하면서 바로 바로 실무에 사용해야 해서 꽤나 버겁긴 했지만, 좋은 기회였고 짧은 시간 내에 많은 걸 배울 수 있었다.&amp;nbsp;그리고 함께 일하는 백엔드 개발자들 뿐만 아니라 LG CNS 직원분들과도 협업해야 해서 다양한 포지션의 분들과 끊임없이 소통하며 개발해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;첫 직장에서 기술적으로도 성장했지만 협업을 하는 부분에 있어서도 많은 성장을 이룬 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(이와 관련해서 더 자세한 얘기는 추가적으로 글을 쓸 시간이 되면 다른 포스팅에서 다뤄보고 싶다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;경험이라고는 팀 프로젝트 하나 있는 비전공자 신입 개발자를 믿고 채용해 준 것에 대해 감사했고, 이에 보답하기 위해 약 7개월 동안 개인적인 성장과 동시에 팀 내에서 1인분을 하기 위해 정말 고군분투했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만, 시작을 프리랜서로 시작했던 나는 프리랜서보다는 좀 더 안정적인 정규직을 구하고 싶었고 다시 취업 준비를 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;2. 취업 준비, 그리고 블록체인 스타트업으로 입사&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;첫 번째 취업을 단 한 번의 면접으로 하게 됐기 때문에 사실상 제대로 취업 준비를 해본 경험이 없었다. 그래서 두 번째 취업 준비를 할 땐 많은 준비가 필요했다. 알고리즘 공부, 과제 전형 준비, 기술 면접 공부 등 다양한 준비를 하며 약 6개월을 보냈다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코딩테스트는 자바스크립트로 준비했고 여러 인터넷 강의를 시청하고 문제는 프로그래머스에서 주로 풀었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;과제 전형은 주로 바닐라 자바스크립트로 구현하는 것이 나와서 이에 따른 공부를 추가적으로 했고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://xiubindev.tistory.com/119#google_vignette&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;기술 면접&lt;/a&gt;은 인터넷 강의, 유튜브, 블로그 자료들을 참고해서 노션에 따로 정리해서 질의응답 리스트를 만들어서 준비했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://xiubindev.tistory.com/119&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실제로 받았던 프론트엔드 개발 면접 질문&lt;/a&gt;은 따로 포스팅을 했는데, 생각보다 많은 유입이 들어와서 놀랐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그리고 취업 준비를 하면서 백수인 상태로 지낼 수 없었기에 크몽에서 간단한 퍼블리싱 작업을 통해 지속적인 수입을 창출해 냈다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;퍼블리싱 의뢰를 받으면서 HTML, CSS에 대한 이해도를 끌어올려 나가는 동시에, 좋은 작업물로 의뢰인들에게 만족감을 줘서 뿌듯했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RxtQr/btrUNU4ZjEr/yKGftldZW8xk7iDkag2JF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RxtQr/btrUNU4ZjEr/yKGftldZW8xk7iDkag2JF0/img.png&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;371&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.6026%; margin-right: 10px;&quot; data-widthpercent=&quot;49.17&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RxtQr/btrUNU4ZjEr/yKGftldZW8xk7iDkag2JF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRxtQr%2FbtrUNU4ZjEr%2FyKGftldZW8xk7iDkag2JF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;414&quot; height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EfjPG/btrUL66a1fj/7KIEVEGfKd78Qfo1nPVftK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EfjPG/btrUL66a1fj/7KIEVEGfKd78Qfo1nPVftK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;652&quot; data-filename=&quot;edited_스크린샷 2022-12-27 오후 2.37.18.png&quot; data-widthpercent=&quot;50.83&quot; style=&quot;width: 50.2346%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EfjPG/btrUL66a1fj/7KIEVEGfKd78Qfo1nPVftK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEfjPG%2FbtrUL66a1fj%2F7KIEVEGfKd78Qfo1nPVftK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌)면접 질문/답변 노션 정리 (우)판매한 크몽 서비스 리뷰&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개발자로 취업 준비를 하기 이전에도 수많은 낙방을 경험했기에   많은 기업에 지원하고 탈락의 고배를 마셔도 크게 좌절하진 않았던 것 같다. 대기업의 신입 전형 코딩테스트는 정말 대부분 어려웠고, 합격하는 게 나에게는 하늘의 별따기였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래도 포기하지 않고 여기저기 문을 두드려본 결과 면접까지 가게 된 회사들이 있었고 최선을 다해 면접에 임했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 결과 운 좋게 현재 다니고 있는 회사를 알게 됐고 블록체인에 관심이 많았던 나는 입사를 결심하게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;사실 내가 개발자의 꿈을 가지게 된 건 이전에 다녔던 블록체인 회사에서 블록체인 공부를 개발자들과 함께 하면서부터였던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;블록체인이라는 기술이 탄생하게 된 스토리텔링부터가 굉장히 신선했고, 공부할수록 흥미가 생겼다. 그래서 원래는 블록체인 개발자가 되는걸 꿈꿨는데, 비록 지금은 그 꿈과는 약간은 멀어졌지만 그래도 DApp 개발자로 일을 할 수 있다는 게 입사 당시에는 정말 기뻤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;3. &lt;/b&gt;&lt;b&gt;패럴랙스 스크롤링으로 한 편의 애니메이션을 만들다&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;출근 첫날, 여러 층을 둘러보며 감탄을 금치 못했던 기억이 난다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아무것도 없는 곳에서 일하다가 17층 건물을 통째로 쓰는 회사로 입사를 하다니... 성공했다 나 자신 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;상주 프리랜서로 근무할 때는 복지라고는 1도 없었고, 장비 지원도 없고, 심지어 모니터도 내 돈으로 샀었는데..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;최신 장비 지원! 간식 무제한 지원! 맛있는 밥 제공! 코인 노래방, 당구, 포켓볼, 탁구대 구비!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;s&gt;&lt;span style=&quot;color: #333333;&quot;&gt;너무 회사 홍보 글 같은데 &lt;/span&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q8I8I/btrUMPR0l5a/wC41D24q1KKti1LbaIBP0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q8I8I/btrUMPR0l5a/wC41D24q1KKti1LbaIBP0k/img.png&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;613&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.9792%; margin-right: 10px;&quot; data-widthpercent=&quot;32.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q8I8I/btrUMPR0l5a/wC41D24q1KKti1LbaIBP0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq8I8I%2FbtrUMPR0l5a%2FwC41D24q1KKti1LbaIBP0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;890&quot; height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blUxug/btrUMPxIcvW/BovcsKFtZbrxmoYYrabWYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blUxug/btrUMPxIcvW/BovcsKFtZbrxmoYYrabWYk/img.png&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;613&quot; data-is-animation=&quot;false&quot; style=&quot;width: 36.327%; margin-right: 10px;&quot; data-widthpercent=&quot;37.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blUxug/btrUMPxIcvW/BovcsKFtZbrxmoYYrabWYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblUxug%2FbtrUMPxIcvW%2FBovcsKFtZbrxmoYYrabWYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1011&quot; height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pvX0P/btrUPZ0qAmr/YI3AUjcBLgjDAfYZGKNeU0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pvX0P/btrUPZ0qAmr/YI3AUjcBLgjDAfYZGKNeU0/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-filename=&quot;20211203_101255.jpg&quot; width=&quot;445&quot; height=&quot;334&quot; style=&quot;width: 29.3682%;&quot; data-widthpercent=&quot;30.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pvX0P/btrUPZ0qAmr/YI3AUjcBLgjDAfYZGKNeU0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpvX0P%2FbtrUPZ0qAmr%2FYI3AUjcBLgjDAfYZGKNeU0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;입사 당시 회사 내부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아무튼 입사하고 일주일 정도는 비즈니스에 대한 설명과 블록체인에 대한 이해도를 높이는 시간을 가졌다. 그리고 나는 입사한 지 일주일 만에 바로 비즈니스와 관련된 티저 페이지와 랜딩 페이지 개발을 해야 했다. 당시 개발 인력은 컨트랙트 개발자 1명과 나, 총 2명이 전부였다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;랜딩페이지에 패럴랙스 스크롤링을 사용한 이유&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;원래는 패럴랙스 스크롤링 말고 서비스를 소개하는 애니메이션 영상을 띄우기로 했었다. 하지만 회사 측에서 한 패럴랙스 스크롤 사이트를 접하게 되면서 이 기법에 완전히 빠져들어버렸다. 하지만 나는 랜딩페이지를 패럴랙스 스크롤링 기법으로 구현하는 것에 처음에는 반대하는 입장이었다. 왜냐하면 애니메이션으로도 이미 충분히 영상이 잘 뽑혔는데, 이걸 굳이 다시 각 scene마다 리소스를 따서 패럴랙스 스크롤링으로 만든다는 것이 이해되지 않았다. 그리고 랜딩 페이지라는 것은 서비스를 소개하기 위한 것인데, 서비스를 파악하기 위해 사용자가 상당히 긴 스크롤을 내려가면서 꽤나 상당한 노력을 들여야 한다니.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그러나 당시 블록체인 서비스의 소개 사이트들을 대부분 둘러봤을 때 인터랙티브한 사이트는 많이 없었기 때문에 화려한 인터랙션이 들어간 랜딩 페이지 하나만으로도 눈길을 끌 수 있을 거라는 의견이 강했고, 나도 이에 설득당했다.. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예상되는 씬이 생각보다는 많지 않았고 스크롤 길이가 엄청나게 길지는 않아서 조금의 노력으로 화려한 인터랙티브 효과를 볼 수 있었다. 이를 통해 다른 사이트보다 좀 더 차별화를 두고 관심을 끌 수 있었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3440&quot; data-origin-height=&quot;1930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6Djjw/btrVcmIDBi4/QLgQ8ASz1PQDzkDwH7YB5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6Djjw/btrVcmIDBi4/QLgQ8ASz1PQDzkDwH7YB5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Djjw/btrVcmIDBi4/QLgQ8ASz1PQDzkDwH7YB5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Djjw%2FbtrVcmIDBi4%2FQLgQ8ASz1PQDzkDwH7YB5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;291&quot; data-origin-width=&quot;3440&quot; data-origin-height=&quot;1930&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;입사하자마자 만들게 된 티저 페이지를 구현할 때도&lt;u&gt; svg 파일의 path를 이용해서 애니메이션을 구현&lt;/u&gt;하며 신기한 애니메이션의 세계로 진입하게 됐는데, 랜딩 페이지는 패럴랙스 스크롤링을 구현해야 한다니! 입사하자마자 처음 시도하는 것들의 연속이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만 프론트 개발자는 나 하나..! 반드시 구현해야 한다는 일념 하나로 열심히 서칭하고 강의도 들어보고 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 결과, 빠른 시일 내에 익히고 도입할 수 있는 &lt;a style=&quot;color: #333333;&quot; href=&quot;https://greensock.com/docs/v3/Plugins/ScrollTrigger&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;GSAP의 Scroll Trigger 라이브러리&lt;/b&gt;&lt;/a&gt;를&amp;nbsp;사용하는 것을 선택했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스크롤 트리거는 말 그대로 스크롤 기반으로 애니메이션을 구현하는 건데,&amp;nbsp; 트리거 엘리먼트를 지정하고 뷰포트에 트리거 엘리먼트가 등장하면 지정한 엘리먼트의 애니메이션이 작동된다. 사용법도 익히기 쉽고 성능도 좋아서 처음 시도한 것 치고 금방 구현할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;패럴랙스 스크롤 랜딩 페이지 개발에 대한 자세한 이야기는 아래 포스팅에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1673591090626&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;애플처럼 화려한 Parallax Scroll 랜딩 페이지 개발해보기&quot; data-og-description=&quot;Parallax scrolling is a web site trend where the background content (i.e. an image) is moved at a different speed than the foreground content while scrolling. 패럴랙스 스크롤링 기법을 간단히 말하면 스크롤에 따른 애니메이션 효&quot; data-og-host=&quot;xiubindev.tistory.com&quot; data-og-source-url=&quot;https://xiubindev.tistory.com/139&quot; data-og-url=&quot;https://xiubindev.tistory.com/139&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0aEuk/hyRgo2CsIu/6sbziQJyGy6jnckltWLpKK/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/Y0ePE/hyRgjttEAL/I1eMZDilhhxRdsLW2Cz1PK/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/cbQbCj/hyRgig4Cqo/eoRfe8A1jScHfxwwsAeoFk/img.png?width=2000&amp;amp;height=861&amp;amp;face=0_0_2000_861&quot;&gt;&lt;a href=&quot;https://xiubindev.tistory.com/139&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://xiubindev.tistory.com/139&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0aEuk/hyRgo2CsIu/6sbziQJyGy6jnckltWLpKK/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/Y0ePE/hyRgjttEAL/I1eMZDilhhxRdsLW2Cz1PK/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/cbQbCj/hyRgig4Cqo/eoRfe8A1jScHfxwwsAeoFk/img.png?width=2000&amp;amp;height=861&amp;amp;face=0_0_2000_861');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;애플처럼 화려한 Parallax Scroll 랜딩 페이지 개발해보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Parallax scrolling is a web site trend where the background content (i.e. an image) is moved at a different speed than the foreground content while scrolling. 패럴랙스 스크롤링 기법을 간단히 말하면 스크롤에 따른 애니메이션 효&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;xiubindev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4.&amp;nbsp; 설레이는 첫 DApp 개발&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;디앱(DApp)이란 블록체인을 기반으로 돌아가는 탈중앙화 분산 애플리케이션을 말한다. 쉽게 말해서 이더리움 기반 디앱이면, 디앱에서 상호작용하는 데이터들이 이더리움 블록체인에 기록되고 불러내어지는 애플리케이션이라고 이해하면 된다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;입사하고 랜딩페이지를 만들었던 시간을 제외하고 약 한 달 정도는 블록체인과 DApp 개발 강의를 많이 보고 기초를 다졌다. 이전 직장에서 기본 개념은 익혔고, 퇴사 이후에도 판교에서&amp;nbsp;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://xiubindev.tistory.com/9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;블록체인 강의&lt;/a&gt;도 듣고 &lt;a style=&quot;color: #333333;&quot; href=&quot;https://xiubindev.tistory.com/121&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;멋사에서 진행했던 프로젝트&lt;/a&gt;도 진행해봤기 때문에 다행히도 배우는 데 진입 장벽은 그나마 높지 않았던 것 같았다. 혼자 스터디하는 데 도움이 됐던 링크들을 공유한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 유튜브&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/@DappUniversity&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/@DappUniversity&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/@stone_chain&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/@stone_chain&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/@jaeyuntv&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/@jaeyuntv&lt;/a&gt;&lt;br /&gt;2. 깃허브&lt;br /&gt;&lt;a href=&quot;https://github.com/ConsenSys/ethereum-developer-tools-list/blob/master/README_Korean.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/ConsenSys/ethereum-developer-tools-list/blob/master/README_Korean.md&lt;/a&gt;&lt;br /&gt;3. 문서&lt;br /&gt;&lt;a href=&quot;https://academy.binance.com/en%20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://academy.binance.com/en&amp;nbsp;&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만... 공부를 하는 것과 실무에 적용하는 것은 천지 차이라는 것을...  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 참고할 만한 DeFi 오픈소스 프로젝트가 있는지 열심히 찾아보다가 &lt;a style=&quot;color: #333333;&quot; href=&quot;https://pancakeswap.finance/?chain=bscTestnet&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PancakeSwap&lt;/a&gt; 이라는 프로젝트를 발견했다. 정말 사막의 오아시스 같은 존재였고, 실무에서 사용하기 위해 필요한 부분만 골라서 분석하기 시작했다. 모든 소스 코드를 다 분석하기에는 나 혼자서는 역부족이었기에... 모듈 위주로 분석하고 차용하기로 결정했다. 그래도 이 오픈소스 하나만 분석해도 어떤 방향으로 나아가야 할지 명확해졌고 정말 큰 도움이 됐다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4-1. DApp 프론트엔드 개발 환경 구축하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일반 App과 DApp의 가장 큰 차이점은 서버와 데이터베이스가 중앙화되어있는지 여부이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예를 들어, &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일반 애플리케이션은 서버에서 아이디와 비밀번호를 관리하고 유저가 이를 애플리케이션과 상호작용하는 데 사용하지만, DApp은 별도로 아이디와 비밀번호를 위한 서버를 구축하지 않고, 블록체인에서 &lt;b&gt;스마트 컨트랙트와 상호작용할 수 있는 Address(EOA)와 Private key&lt;/b&gt;를 유저가 직접 가지고 있는 상태에서 서비스를 이용하게 된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;즉, 서버가 아닌 프론트엔드에서 유저가 블록체인과 상호작용하도록 하는 기능을 구현해야 한다. 이를 위해서는 Address(EOA)와 Private key가 필요한데 상용화된 블록체인 지갑을 사용할 수 있다. DApp 개발에서 프론트엔드의 핵심적인 역할은 &lt;b&gt;유저가 블록체인 지갑을 애플리케이션에서 사용할 수 있도록 연동해주고 스마트 컨트랙트와 상호작용&lt;/b&gt;할 수 있도록 해주는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;우리는 블록체인 메인넷 중에 &lt;b&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://www.binance.com/en&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BNB 스마트체인&lt;/a&gt; 위에서 DApp 개발&lt;/b&gt;을 진행하기로 했고, 블록체인 지갑 연동은 &lt;a href=&quot;https://docs.metamask.io/guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;메타마스크&lt;/b&gt;&lt;/a&gt;(Metamask) 지원하기로 했다. 개발환경은 기존에 익숙한 React 개발 환경에 PancakeSwap을 참고하여 Web3 관련 개발 환경까지 구축하기로 결정했다. 개발 시작 당시 프론트엔드는 나 혼자여서 기술 스택을 함께 상의할 팀원이 없어서 아쉽긴 했다. 그래도 약 한 달 후에 시니어 개발자 분이 오셔서 천만다행이었지만!! 역시 시니어 개발자시라 빠르게 기술 습득을 하고 적용하시는 게 멋졌다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아무튼 내가 제로베이스에서 구축한 DApp 프론트엔드 개발 환경은 아래와 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Yarn, Webpack, React.js,&lt;/b&gt;&lt;b&gt;&amp;nbsp;TypeScript,&lt;/b&gt;&lt;b&gt;&amp;nbsp;SWR, Emotion, ESLint&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://docs.ethers.org/v5/&quot;&gt;Ethers.js&lt;/a&gt;&amp;nbsp;&lt;br /&gt;&lt;/b&gt;이더리움 블록체인과 상호 작용하기 위한 라이브러리이며, 유용한 유틸리티가 많다. BNB 스마트체인은 EVM과 호환되기 때문에 이더리움 관련 개발 도구를 사용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://mikemcl.github.io/bignumber.js/#&quot;&gt;&lt;b&gt;bignumber.js&lt;/b&gt;&lt;/a&gt; &lt;br /&gt;블록체인에서 많은 작업은 JavaScript에서 사용할 수 있는 안전한 값의 범위를 벗어난 숫자에서 작동한다. 이 라이브러리는 모든 크기의 숫자에 대해서 연산을 안전하게 할 수 있도록 도와준다. Ethers.js 라이브러리에도 BigNumber 관련 유틸리티가 있지만, bignumber.js 라이브러리가 더 정교하게 다룰 수 있고 유틸리티도 훨씬 많아서 좋은 것 같다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://github.com/Uniswap/web3-react/tree/v6&quot;&gt;web3-react&lt;/a&gt; &amp;nbsp;&lt;br /&gt;&lt;/b&gt;디앱 빌딩 프레임워크로, 블록체인 지갑 연동뿐만 아니라 스마트컨트랙트와 상호작용에 사용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CvBme/btrVvudq6Mu/78SevghMod1Q76skL6hU21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CvBme/btrVvudq6Mu/78SevghMod1Q76skL6hU21/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;600&quot; data-filename=&quot;스크린샷 2023-01-05 오후 9.34.15.png&quot; style=&quot;width: 28.9268%; margin-right: 10px;&quot; data-widthpercent=&quot;29.62&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CvBme/btrVvudq6Mu/78SevghMod1Q76skL6hU21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCvBme%2FbtrVvudq6Mu%2F78SevghMod1Q76skL6hU21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf2v3N/btrVujp0lcz/AuxblKmXjhoJ8Hmp5AToX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf2v3N/btrVujp0lcz/AuxblKmXjhoJ8Hmp5AToX0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;379&quot; data-filename=&quot;스크린샷 2023-01-05 오후 9.34.40.png&quot; style=&quot;width: 45.4777%; margin-right: 10px;&quot; data-widthpercent=&quot;46.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf2v3N/btrVujp0lcz/AuxblKmXjhoJ8Hmp5AToX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf2v3N%2FbtrVujp0lcz%2FAuxblKmXjhoJ8Hmp5AToX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/99QUX/btrVzE17tun/Qrjr60CVKGtrSkZhuZPe1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/99QUX/btrVzE17tun/Qrjr60CVKGtrSkZhuZPe1K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;881&quot; data-filename=&quot;스크린샷 2023-01-06 오후 6.15.31.png&quot; style=&quot;width: 23.2699%;&quot; data-widthpercent=&quot;23.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/99QUX/btrVzE17tun/Qrjr60CVKGtrSkZhuZPe1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F99QUX%2FbtrVzE17tun%2FQrjr60CVKGtrSkZhuZPe1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;854&quot; height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;2021년 12월, 노션에 정리해뒀던 메타마스크 사용 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 web3-react 라이브러리를 몰랐을 때는 Metamask 공식 문서를 참고해서 블록체인 연동 관련 코드를 다 짜놨었다. 노션에 개발 기능 정의, 코드 예제 등 다 작성했었는데, PancakeSwap 오픈소스를 알게 되고 나서 코드를 다시 새로 다 짰다  어쨌든 메타마스크 지갑을 연결하는 원리는 같지만 web3-react는 DApp과 관련된 데이터의 상태들을 쉽게 관리할 수 있어서 편리해서 사용하게 됐다. 그리고 메타마스크 외에 다른 지갑들도 쉽게 연동할 수 있는 것이 큰 장점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;web3-react 사용 방법&lt;/b&gt;에 대해서는 간단하게 포스팅을 한 번 해서 아래 글에서 확인해 볼 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1673314228151&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;web3-react로 DApp에 지갑 연동 구현하기&quot; data-og-description=&quot;이더리움 DApp을 개발하기 위해서 블록체인 지갑 연동은 필수라고 할 수 있다. 이더리움 기반 블록체인 지갑에는 여러 종류가 있는데 레저 나노, 트레저원, 메타마스크, 마이이더월렛, 트러스트&quot; data-og-host=&quot;xiubindev.tistory.com&quot; data-og-source-url=&quot;https://xiubindev.tistory.com/130&quot; data-og-url=&quot;https://xiubindev.tistory.com/130&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zcn82/hyReLbDhDe/ibLWzWkDJmWLDUc4jHXUu0/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/j1RF0/hyReKqgek4/67Iv1jve1oIuuv4UVe5Lw0/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/bRbDyx/hyReLpanoX/xe5qKOmCFiPDoE80Q4hR9k/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://xiubindev.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://xiubindev.tistory.com/130&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zcn82/hyReLbDhDe/ibLWzWkDJmWLDUc4jHXUu0/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/j1RF0/hyReKqgek4/67Iv1jve1oIuuv4UVe5Lw0/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/bRbDyx/hyReLpanoX/xe5qKOmCFiPDoE80Q4hR9k/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;web3-react로 DApp에 지갑 연동 구현하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이더리움 DApp을 개발하기 위해서 블록체인 지갑 연동은 필수라고 할 수 있다. 이더리움 기반 블록체인 지갑에는 여러 종류가 있는데 레저 나노, 트레저원, 메타마스크, 마이이더월렛, 트러스트&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;xiubindev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4-2. &lt;b&gt;ESLint를 자동화해보자&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋하기 전에 lint 에러가 있는 경우 커밋을 하지 못하도록 하기 위해 git hook을 활용했다. 이를 위해 &lt;b&gt;husky와 lint-staged를&lt;/b&gt; 사용했고 커밋 전 staging area에 있는 파일들을 lint로 검사했다. 이와 관련해서 ESLint를 자동화해보자 라는 제목으로 &lt;span&gt;포스팅을 했고, 아래 글에서&amp;nbsp;&lt;/span&gt;확인할 수 있다. 함께 작업하는 동료가 늘어날 때 굉장히 유용한 것 같다. commit 외에 다른 hook을 활용해서 프로젝트에 적용해 봐도 좋을 것 같다.&lt;/p&gt;
&lt;figure id=&quot;og_1673314276790&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ESLint를 자동화해보자 (feat. husky &amp;amp; lint-staged)&quot; data-og-description=&quot;Lint 코드의 오류나 버그, 스타일 등을 점검하는 것을 lint라고 부른다. 린트는 코드의 가독성을 높이는 것뿐만 아니라 자바스크립트와 같은 동적 언어의 특성인 런타임 버그를 예방하는 역할도 &quot; data-og-host=&quot;xiubindev.tistory.com&quot; data-og-source-url=&quot;https://xiubindev.tistory.com/136&quot; data-og-url=&quot;https://xiubindev.tistory.com/136&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/6XC5u/hyReSPmADJ/y2zWagzn2VA5mknKwKX8Ik/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/t6qKb/hyRdSqaxTV/EMrxYl7mbsWpWVPP2mtU0K/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/bImI1G/hyRdO8Q6bX/8fJPk5QJy6EVAgyk0uVt80/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402&quot;&gt;&lt;a href=&quot;https://xiubindev.tistory.com/136&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://xiubindev.tistory.com/136&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/6XC5u/hyReSPmADJ/y2zWagzn2VA5mknKwKX8Ik/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/t6qKb/hyRdSqaxTV/EMrxYl7mbsWpWVPP2mtU0K/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/bImI1G/hyRdO8Q6bX/8fJPk5QJy6EVAgyk0uVt80/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ESLint를 자동화해보자 (feat. husky &amp;amp; lint-staged)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Lint 코드의 오류나 버그, 스타일 등을 점검하는 것을 lint라고 부른다. 린트는 코드의 가독성을 높이는 것뿐만 아니라 자바스크립트와 같은 동적 언어의 특성인 런타임 버그를 예방하는 역할도&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;xiubindev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4-3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;웹팩 성능 최적화를 통해 개발 효율성을 극대화해보자&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 예전에는 웹팩으로 큰 사이즈의 프로젝트를 번들링 해본 적이 없어서 빌드 속도가 느릴 수 있다는 것을 인지하지 못했다. 그리고 입사해서 m1 맥북 13 프로를 지원받았기 때문에 더더욱 성능 이슈를 겪을 일이 없었다. 하지만 동료 개발자분이 들어오시고 이전에 사용하셨던 윈도우 노트북으로 개발을 하시는데 내 개발환경 성능과 비교했을 때 상당히 느리다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 빌드가 30초 걸리고 dev server를 첫 구동하는데 90초라니...  이게 2022년도에 있을 수 있는 일인가!! 심지어 프로젝트 사이즈가 엄청나게 큰 시점도 아니었는데 말이다. 이를 계기로 동료 개발자분이 먼저 최적화를 조금 하시다가, 내가 이어받아서 최대한의 성능 최적화는 다 하게 됐다. 웹팩을 최적화한 결과, &lt;b&gt;성능을 5배 ~ 18배 개선&lt;/b&gt;할 수 있었다. 최적화와 관련된 자세한 사항은 아래 글에 작성했다.&lt;/p&gt;
&lt;figure id=&quot;og_1673314295024&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;웹팩 성능 최적화를 통해 개발 효율성을 극대화 해보자 ✨&quot; data-og-description=&quot;  이 글은 윈도우로 개발하는 동료로부터 시작되었습니다..! 사실 m1 맥북 13 프로를 쓰는 나로서는 개발하는데 성능의 문제성을 크게 느끼지 못했다. 서버 사양도 나쁘지 않아서 배포하는데도 &quot; data-og-host=&quot;xiubindev.tistory.com&quot; data-og-source-url=&quot;https://xiubindev.tistory.com/135&quot; data-og-url=&quot;https://xiubindev.tistory.com/135&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pkHkz/hyReNUOX2T/35TdbKKKstnlbd3gbTYwsk/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/QAebp/hyRdD7ofp8/JLrlygppGk7iDPen3N1H0K/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/cKK3zK/hyRdQZZFUZ/lgnrkybLyj2tBJilKlnpg1/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402&quot;&gt;&lt;a href=&quot;https://xiubindev.tistory.com/135&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://xiubindev.tistory.com/135&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pkHkz/hyReNUOX2T/35TdbKKKstnlbd3gbTYwsk/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/QAebp/hyRdD7ofp8/JLrlygppGk7iDPen3N1H0K/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/cKK3zK/hyRdQZZFUZ/lgnrkybLyj2tBJilKlnpg1/img.png?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;웹팩 성능 최적화를 통해 개발 효율성을 극대화 해보자 ✨&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  이 글은 윈도우로 개발하는 동료로부터 시작되었습니다..! 사실 m1 맥북 13 프로를 쓰는 나로서는 개발하는데 성능의 문제성을 크게 느끼지 못했다. 서버 사양도 나쁘지 않아서 배포하는데도&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;xiubindev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4-4. DApp 개발 트러블 슈팅&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메타마스크 지갑 연결, 네트워크 감지 및 변경, Token 조회/전송, Token 추가, DeFi(staking, unstaking, approve, claim)&lt;/b&gt; 등 다양한 기능을 개발하면서 정말 많은 이슈가 있었지만 해결하면서 뿌듯함을 느꼈고 재미있었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1. 일반 애플리케이션을 개발할 때도 개발 서버와 운영 서버를 나눠서 개발하듯이 블록체인도 마찬가지로 테스트넷과 메인넷의 개념이 있다. 그래서 개발할 때 &lt;b&gt;테스트넷과 메인넷을 구별해서 환경 설정&lt;/b&gt;을 해야 한다. 아래 코드를 참고하면 좋을 것 같다.&lt;br /&gt;그리고 테스트넷 환경에서 스마트컨트랙트를 실행시키려면 tBNB 토큰이 있어야 하는데, 이 토큰을 얻기 위해서는 &lt;b&gt;faucet&lt;/b&gt;이라는 것을 활용해야 한다. &lt;a href=&quot;https://testnet.bnbchain.org/faucet-smart&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기서&lt;/a&gt; 캡차를 풀고 개발용으로 개인 메타마스크 지갑을 생성하고 지갑 주소를 넣으면 무료로 들어오는데, 24시간 기준 1번 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1672971654174&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const RPC_URL = isDevelopment
  ? 'https://data-seed-prebsc-1-s1.binance.org:8545'
  : 'https://bsc-dataseed.binance.org';
export const BASE_BSC_SCAN_URL = isDevelopment ? 'https://testnet.bscscan.com' : 'https://bscscan.com';
export const SNAPSHOT_BASE_URL = isDevelopment ? 'https://testnet.snapshot.org' : 'https://hub.snapshot.org';
export const CHAIN_ID = isDevelopment ? 97 : 56;
export const CHAIN_NAME = isDevelopment ? 'BNB Smart Chain Testnet' : 'BNB Smart Chain';
export const TOKEN_ADDRESS = isDevelopment
  ? '0x12346'
  : '0x12345';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. 메타마스크 연결을 시도할 때 웹은 익스텐션, 모바일은 앱이 설치 돼있는지 먼저 확인을 해야한다. 온보딩 예제는 메타마스크 공식 문서에 친절하게 나와있어서 그대로 따라 하면 된다. 온보딩 외에 마주했던 이슈는 &lt;u&gt;메타마스크가 잠금 상태일 때&lt;/u&gt; 연결 시도를 요청하면 아무 반응이 없었던 것이다. 그래서 이를 해결하기 위한 방법을 공식 문서에서 찾게 됐다. 개발을 하면 할수록 문서를 꼼꼼하게 읽어야겠다는 생각이 든다. 해결법은 &lt;a href=&quot;https://docs.metamask.io/guide/ethereum-provider.html#experimental-methods&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기서&lt;/a&gt; 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실험적 API라고 적혀서 언제 없어질지는 모르는 메소드이긴 하지만 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ethereum._metamask.isUnlocked() &lt;/span&gt;를 호출하면 잠금상태인지 아닌지 boolean 값을 리턴해준다. 잠금상태이면 지갑 잠금을 풀어야 한다는 팝업을 띄워주고 &lt;b&gt;잠금이 해제된 상태에서 지갑 연결을 시도&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp; 3. 블록체인에서 많은 작업&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;은 JavaScript에서 사용할 수 있는 안전한 값의 범위를 벗어난 숫자에서 작동한다. 따라서 안전한 값을 벗어난 숫자에 대해서 연산을 할 수 있도록 개발하는 것이 가장 중요하다. &lt;b&gt;최대한 number 연산자는 쓰지 않고 모든 크기의 숫자를 다룰 수 있는 라이브러리를 활용해서 정교하게 연산&lt;/b&gt;을 하는 것이 좋다. 그리고 &lt;a href=&quot;https://docs.metamask.io/guide/sending-transactions.html#example&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;메타마스크에서 트랜잭션을 보낼 때&lt;/a&gt; 대부분 &lt;b&gt;Hex string으로 변환&lt;/b&gt;해서 보내야 한다. 예를 들어 BNB 토큰을 전송할 때는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ethers.utils.parseUnits&lt;/span&gt; 유틸리티 함수를 통해 input에 입력된 value를 토큰 decimals에 맞게 BigNumber로 변환한 후, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ethers.utils.hexValue&lt;/span&gt;&amp;nbsp;유틸리티 함수를 통해 이를 Hex string으로 변환하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Next.js로 풀스택 개발 도전&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업에게 홈페이지가 왜 중요할까? 당연한 얘기지만 스타트업은 인재 채용에 있어서 정말 많은 어려움을 겪는다. 그중에 가장 큰 이유는 회사에 대한 정보가 많이 부족하기 때문일 것이다. 알려진 것이 많이 없으니 지원자 입장에서는 채용 공고를 보더라도 스타트업에 지원하기가 쉽지 않다. 그래서 회사가 추구하는 비전, 문화, 복지 등을 회사 홈페이지에서 잘 노출해야 할 필요성이 있다. 채용 공고에 회사에 대한 내용을 구구절절 써놓는 것보다 제대로 된 회사 홈페이지 하나만 있어도 지원자의 눈길을 끌 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DSiOd/btrYqS3LSZK/u3H6bppkOw6snKy81cEYA1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DSiOd/btrYqS3LSZK/u3H6bppkOw6snKy81cEYA1/img.jpg&quot; data-alt=&quot;우리 회사 진짜 좋은데... 어떻게 말로 표현할 방법이 없네...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DSiOd/btrYqS3LSZK/u3H6bppkOw6snKy81cEYA1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDSiOd%2FbtrYqS3LSZK%2Fu3H6bppkOw6snKy81cEYA1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;371&quot; height=&quot;295&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우리 회사 진짜 좋은데... 어떻게 말로 표현할 방법이 없네...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;우리도 채용에 있어서 난관을 겪고 있는 상황이었어서 리브랜딩을 하는 것과 동시에 회사 소개 및 채용 홈페이지를 개발하기로 결정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5-1. 개발 환경 구축하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 홈페이지를 개발하는 데 사용한 기술 스택은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1675754567792&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Next.js, TypeScript, SWR, emotion.js, prisma, SQLite, jsonwebtoken, gsap&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js를 선택한 가장 큰 이유는 SSR, SEO 등 다양한 장점을 가지고 있지만, 그중에서도 Next.js 하나로 풀스택 개발을 할 수 있기 때문이었다. &lt;span&gt;정적인 페이지가 아니라 어드민 기능(로그인, 로그아웃, 채용공고 CRUD, 팀원 CRUD, 아티클 CRUD)까지 있어야 했기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;풀스택 프레임워크인 Next.js를 사용했다. 당시 개발 리소스가 넉넉하지 않았고 채용을 위해서는 빠른 시일 내에 개발을 완료해야 했기 때문에 혼자 개발하기에는 적절한 프레임워크라고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 데이터베이스는 팀원이 추천해 준 SQLite를 사용했다. SQLite가 프로젝트에 적합한 이유는 다음과 같다. 가볍고, db 파일 하나로 데이터베이스 정보를 관리하기 때문에 간편하고, 초기 설정이 거의 없기 때문에 빠른 개발이 가능하다. 큰 단점 중에 하나는 동시성이 제한되는 점이 있는데, 동시 쓰기 작업이 거의 이뤄지지 않을 것이라는 판단하에 이 단점은 제쳐두고 사용하기로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ORM으로는 Prisma를 사용했다. Prisma는 Node.js와 TypeScript 환경에서 데이터베이스에 쉽게 접근할 수 있도록 해주는데, 직접 사용해 보면서 이렇게 편할 수가 있나 싶을 정도로 개발 생산성이 무척 뛰어났다. 실무에서 백엔드를 다뤄보는 건 처음이었는데도 불구하고 개발 편의성이 높은 도구들을 활용해서 빠른 시일 내에 개발할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5-2. 트러블 슈팅&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Next.js Middleware로 토큰 검증 및 리다이렉트하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채용 홈페이지를 만들면서 어드민 기능도 추가해야 해서, 서브 도메인을 별도로 생성하지 않고 같은 도메인에 /admin 경로를 추가하여 작업하기로 했다. 어드민 경로만 알고 있으면 누구나 어드민 페이지에 접근은 할 수 있는 상황이었다. 따라서 어드민 경로로 접근하면 로그인하지 않았을 때는 로그인 화면을 보여주고, 로그인을 했을 때는 어드민 세부 페이지로 리다이렉트 해줘야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 생각했던 방법은&lt;b&gt; _app.tsx 파일에서 useEffect를 사용해서 pathname이 어드민 경로일 경우 토큰 검증을 해서 리디렉션 조건&lt;/b&gt;을 걸고자 했다. 그런데 이렇게 하면 접근 불가한 페이지로 바로 진입했을 경우 해당 페이지 화면이 잠깐 보였다가 리다이렉트가 되는 문제가 있었다. 이 문제는 토큰 검증이 끝나기 전에는 로딩 화면을 렌더링 하고 검증이 끝난 후 해당 페이지를 렌더링 하면 된다. 하지만 로그아웃 상태에서 모든 어드민 경로 페이지에 접근했을 때 로딩 화면을 보여주지 않고 바로 /signin 페이지로 리다이렉트 해주고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 두 번째로 적용해 본 방법은 모든 어드민 페이지가 서버 사이드 렌더링이었기 때문에&lt;b&gt; getServerSideProps를 활용해 &lt;a href=&quot;https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props#redirect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;redirect&lt;/a&gt; 객체를 리턴&lt;/b&gt;하는 것이었다. 서버에서 토큰을 검증하고 원하는 페이지를 바로 렌더링 해주기 때문에 로딩 화면이나 접근 불가 페이지가 노출되지 않는 장점이 있다. 하지만 이 방식은 모든 어드민 페이지에 동일한 코드를 넣어줘야 한다는 단점이 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Middleware allows you to run code before a request is completed, then based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;마지막으로 적용한 방법은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Next.js의&amp;nbsp;&lt;a href=&quot;https://nextjs.org/docs/advanced-features/middleware&quot;&gt;Middleware&lt;/a&gt;를 사용&lt;/b&gt;하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미들웨어를 사용하면 request를 기반으로 response를 직접 제어할 수 있다. 쉽게 말해 요청이 들어오면 요청이 완료되기 전에Authentication, Bot protection, Redirects and rewrites 등의 작업을 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미들웨어에서 쿠키에 토큰이 있으면 토큰 검증해서 성공한 경우 어드민 컨텐츠 페이지로 리다이렉트를 해주고, 만약 토큰이 없거나 유효하지 않은 토큰일 경우 /signin 페이지로 리다이렉트를 해줬다. 그리고 미들웨어에서 &lt;u&gt;jsonwebtoken&lt;/u&gt; 라이브러리를 사용하지 못해서 찾아보니까 미들웨어가 Edge runtime에서 작동하기 때문에 라이브러리가 동작하지 않는 것이었다. 그래서 Edge runtime에서도 작동하는 &lt;a href=&quot;https://github.com/panva/jose/blob/HEAD/docs/functions/jwt_verify.jwtVerify.md#readme&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jose 라이브러리의&lt;/a&gt;&amp;nbsp;&lt;b&gt;jwtVerify 메소드&lt;/b&gt;를 활용해서 토큰을 검증해 줬다. 그리고 /admin 경로에서만 미들웨어를 실행할 수 있도록 config에 matcher를 어드민 경로로 작성해 줬다.&lt;/p&gt;
&lt;pre id=&quot;code_1676283569999&quot; class=&quot;javascript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { jwtVerify } from 'jose'

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('token') ?? ''
  const JWT_TOKEN_KEY = process.env.JWT_TOKEN_KEY ?? ''

  try {
    if (token) {
      const { payload } = await jwtVerify(token, new TextEncoder().encode(JWT_TOKEN_KEY))
      if (!request.nextUrl.pathname.startsWith('/admin/signin') &amp;amp;&amp;amp; !payload?.loginId) {
        return NextResponse.redirect(new URL('/admin/signin', request.url))
      }
    } else {
      return NextResponse.rewrite(new URL('/admin/signin', request.url))
    }
  } catch (error) {
    return NextResponse.rewrite(new URL('/admin/signin', request.url))
  }

  if (request.nextUrl.pathname === '/admin') {
    return NextResponse.rewrite(new URL('/admin/careers', request.url))
  }
}
export const config = {
  matcher: '/admin/:path*',
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Prisma와 SQLite 활용해서 DB 구축하기&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Prisma is an open source next-generation ORM.&lt;br /&gt;- Prisma Client: Auto-generated and type-safe query builder for Node.js &amp;amp; TypeScript&lt;br /&gt;- Prisma Migrate: Migration system&lt;br /&gt;- Prisma Studio: GUI to view and edit data in your database.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prisma를 처음 사용해 보면서 타입스크립트와 정말 잘 맞는 ORM이라고 생각했다. type-safe 쿼리를 통해 데이터를 안전하게 관리할 수 있고, 데이터베이스에 접근할 때 에디터에서 자동완성이 제공되는 게 정말 편리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prisma를 셋업 하면 가장 중요한 게 prisma schema인데, 이 스키마를 베이스로 직관적인 데이터 모델링을 할 수 있다. schema 파일에 데이터베이스 연결 및 generator를 정의하고, 데이터베이스에 사용할 모델을 생성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 SQLite를 사용했기 때문에 prisma 폴더 내에 db 파일을 생성해서 서버에 업로드하는 방식을 채택했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676457105463&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;generator client {
  provider        = &quot;prisma-client-js&quot;
  previewFeatures = [&quot;referentialIntegrity&quot;]
}

datasource db {
  provider             = &quot;sqlite&quot;
  url                  = env(&quot;DATABASE_URL&quot;)
  referentialIntegrity = &quot;prisma&quot;
}

model User {
  id        Int      @id @default(autoincrement())
  role      Int      @default(1)
  loginId   String   @unique
  password  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
...생략&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-16 오후 3.55.43.png&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EdoXT/btrZz5HFeAn/XSYV2pxduxfO7WqTwOaz01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EdoXT/btrZz5HFeAn/XSYV2pxduxfO7WqTwOaz01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EdoXT/btrZz5HFeAn/XSYV2pxduxfO7WqTwOaz01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEdoXT%2FbtrZz5HFeAn%2FXSYV2pxduxfO7WqTwOaz01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;171&quot; data-filename=&quot;스크린샷 2023-02-16 오후 3.55.43.png&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prisma는 기본적으로 환경변수를 사용하기 위해 prisma를 셋업 하면 &lt;a href=&quot;https://www.prisma.io/docs/guides/development-environment/environment-variables#using-env-files&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;루트 디렉토리에 생기는 .env 파일&lt;/a&gt;을 불러온다. 그래서 .env 파일에 DATABASE_URL 환경변수를 저장하고 url에 불러와서 사용했다. 하지만 데이터베이스를 개발용과 프로덕션용으로 구분하기 위해 &lt;b&gt;.env.development, .env.production&lt;/b&gt; 으로 구분지어서 파일명을 변경했더니, prisma cli를 사용할 때 환경변수를 찾지 못한다는 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서를 찾아보니 &lt;a href=&quot;https://www.prisma.io/docs/guides/development-environment/environment-variables/using-multiple-env-files&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;개발 환경에 따라 여러 env 파일을 사용하는 방법&lt;/a&gt;이 나와있었다. &lt;b&gt;dotenv-cli&lt;/b&gt; 패키지를 사용해서 prisma가 사용해야 하는 환경변수 파일을 지정할 수 있다. &lt;u&gt;dotenv의 -e 옵션으로 env 파일을 지정&lt;/u&gt;할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 데이터베이스를 파일 하나로 관리하고 있기 때문에, &lt;u&gt;로컬에 있는 프로덕션 DB와 서버에 있는 DB를 동기화하기&lt;/u&gt; 위해서는 추가적인 작업이 필요했다. 이를 위해 현재 DB 파일이 있는 우분투 서버에 접속해서 파일을 받아와서 동기화하는 작업을 migrate 스크립트에 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1676517943413&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; &quot;migrate:dev&quot;: &quot;dotenv -e .env.development -- npx prisma migrate dev --name sqlite-init&quot;,
 &quot;migrate:prod&quot;: &quot;scp -r -i '프라이빗키_경로' 서버_접속_경로 ./prisma &amp;amp;&amp;amp; dotenv -e .env.production -- npx prisma migrate --name sqlite-init&quot;,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. DApp&amp;nbsp; 교육 과정 수료 및 해커톤 참가, 그리고 수상까지&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록체인 업계에서 일하는 건 처음이다 보니 아무래도 스스로 스터디를 많이 하려고 했다. 그래서 온라인 강의도 많이 듣고 무료 교육 과정이 있으면 찾아서 듣곤 했다. 교육 과정 중에 해커톤이 있는 과정도 있어서 교육을 수료하고 실제로 디앱을 만들어 해커톤에 참가해 보면서 실무에 도움이 될만한 기술을 많이 습득하게 됐다. 온라인 강의를 제외하고 교육 과정은 2번을 수료했고 해커톤은 총 3번을 참가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6-1. NFT 블록체인 마켓 앱 만들기 with 그라운드X&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxa3Ji/btrZIT8iDf8/tI9ZkPxGvGO4furYYLupk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxa3Ji/btrZIT8iDf8/tI9ZkPxGvGO4furYYLupk0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;2092&quot; data-origin-height=&quot;1476&quot; data-filename=&quot;스크린샷 2023-02-17 오후 5.13.50.png&quot; data-widthpercent=&quot;59&quot; style=&quot;width: 58.3165%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxa3Ji/btrZIT8iDf8/tI9ZkPxGvGO4furYYLupk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxa3Ji%2FbtrZIT8iDf8%2FtI9ZkPxGvGO4furYYLupk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2092&quot; height=&quot;1476&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ozFON/btrZItPA84d/4Kwur7KLcQzMkJf9Q99MTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ozFON/btrZItPA84d/4Kwur7KLcQzMkJf9Q99MTK/img.png&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;725&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.5207%;&quot; data-widthpercent=&quot;41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ozFON/btrZItPA84d/4Kwur7KLcQzMkJf9Q99MTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FozFON%2FbtrZItPA84d%2F4Kwur7KLcQzMkJf9Q99MTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 초창기에 Klaytn 기반으로 디앱을 만들 가능성이 있다고 해서 미리 스터디도 해둘 겸 멋쟁이사자처럼에서 진행하는 &lt;u&gt;NFT 블록체인 마켓 앱 만들기라는&lt;/u&gt; 교육과정에 참여하게 됐다. 원래 회사 입사 전 취준생이었던 시절에도 같은 교육 과정을 잠깐 들었었는데 취준과 병행하다 보니 제대로 수료하지 못했었다. 심지어 그때는 유료 강의였는데   이번에는 무료 교육 과정으로 풀려서 한 번 더 참여하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5주 동안 온라인 교육을 들으면서 클레이튼 기반 애플리케이션을 설계하고 개발하는 방법에 대해 익히고 3주 동안 해커톤을 진행했다. 해커톤은 서버 개발자2,기획자1,앱 개발자1 그리고 나까지 다섯 명이 팀을 이루게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스마트 컨트랙트와 프론트엔드 부분은 거의 내가 담당했는데, 스마트 컨트랙트는 레퍼런스가 많아서 개발에 엄청난 어려움은 없었지만, 프론트엔드에서 컨트랙트 호출을 연동하는 부분에 있어서 클레이튼 관련 레퍼런스가 생각보다 부족해서 어려움이 있었다. caver.js 버전에 따라 달라지는 부분도 많았고, caver.js 공식 문서를 정독해도 해결이 안됐던 부분은 클레이튼 개발자 포럼을 적극 활용했는데, 그래도 하루 이틀이면 답변이 달려서 다행이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 디앱 개발 해커톤이어서 부족한 점도 많았지만, 팀원들도 바쁜 와중에 끝까지 포기하지 않고 참여해 준 결과 &lt;b&gt;4위라는&lt;/b&gt; 값진 결과를 얻을 수 있었다! 개발과 관련된 문서나 코드는 아래 깃허브에서 확인할 수 있다. 지금 와서 보면 굉장히 부족하지만 5주간의 교육과 3주간의 작업물 치고는 나름 만족스럽다.&lt;/p&gt;
&lt;figure id=&quot;og_1676954152106&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;BadgeMeal&quot; data-og-description=&quot;BadgeMeal has 3 repositories available. Follow their code on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/BadgeMeal&quot; data-og-url=&quot;https://github.com/BadgeMeal&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bda7TS/hyRICkBLc4/HMSwYsEH6b8voeP3xY2NJ0/img.png?width=280&amp;amp;height=280&amp;amp;face=0_0_280_280&quot;&gt;&lt;a href=&quot;https://github.com/BadgeMeal&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/BadgeMeal&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bda7TS/hyRICkBLc4/HMSwYsEH6b8voeP3xY2NJ0/img.png?width=280&amp;amp;height=280&amp;amp;face=0_0_280_280');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;BadgeMeal&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;BadgeMeal has 3 repositories available. Follow their code on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6-2. BNB 체인 해커톤&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H60ZQ/btrZH10nn2M/khzu7PAFOqmrjiytkhm6Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H60ZQ/btrZH10nn2M/khzu7PAFOqmrjiytkhm6Ik/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;1506&quot; data-filename=&quot;스크린샷 2023-02-17 오후 5.14.14.png&quot; data-widthpercent=&quot;51.64&quot; style=&quot;width: 51.0373%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H60ZQ/btrZH10nn2M/khzu7PAFOqmrjiytkhm6Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH60ZQ%2FbtrZH10nn2M%2Fkhzu7PAFOqmrjiytkhm6Ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2144&quot; height=&quot;1506&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/neKCx/btrZJcfCyJr/RYgCiTbHmUU8UNuQK7vTPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/neKCx/btrZJcfCyJr/RYgCiTbHmUU8UNuQK7vTPk/img.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;2160&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;48.36&quot; data-filename=&quot;blob&quot; style=&quot;width: 47.7999%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/neKCx/btrZJcfCyJr/RYgCiTbHmUU8UNuQK7vTPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FneKCx%2FbtrZJcfCyJr%2FRYgCiTbHmUU8UNuQK7vTPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2880&quot; height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Klaytn 해커톤은 회사에서 나 혼자 참여했는데, 이번 &lt;u&gt;BNB 체인 해커톤&lt;/u&gt;은 회사 동료들이 모여서 참가하기로 했다. 동료 한 명이 새롭게 블록체인 개발 파트에 합류하게 돼서, 동료의 컨트랙트 개발 스터디 겸 다 같이 해커톤에 참여하기로 했다. 사실 상금이 목적에 조금 더 가깝긴 했는데..  아무튼 이번 교육은 지난번 멋사에서 진행했던 교육 과정보다 훨씬 알찬 내용이어서 유익했다. 기술적인 측면과 동시에 블록체인 프로젝트들의 현황도 알려주고 그에 따른 어떤 기술이 필요한지 파악하기 수월했다. 예를 들어 화이트리스트 수집 기능, 리빌 기능, 오픈제플린 적극 활용하기 등 실무에서 쓸법한 것들을 알게 돼서 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 해커톤 보다 개발은 수월하게 진행됐다. 기능 구현하면서 서버 쪽 리소스가 필요 없어서 스마트 컨트랙트와 프론트에만 집중하면 됐다. 클레이튼 메인넷으로 개발할 때는 카이카스와 클립 모두 연동해야 웹,모바일 모두 실행할 수 있었는데, 이번에는 메타마스크 하나만 연동하면 되니 개발에 공수가 덜 들었다. 워들 게임비 BNB 송금, NFT 민팅/조회 등등 원했던 기능 대부분을 시간 내에 구현할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4주 간의 온라인 교육 과정이 끝나고 오프라인으로 해커톤 데모데이가 진행됐다. 팀별로 부스를 설치하고 팀 상호 평가 및 심사위원 평가가 진행됐다. 그 결과 우리 팀은 &lt;b&gt;최우수상&lt;/b&gt;을 수상했다! 퇴근하고 며칠간 고생했던 보람이 있었다. 나중에 받은 상금으로 다 같이 회사 근처에서 때깔 좋은 소고기도 맛있게 먹었다ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아 그리고 지난번 온라인 교육과정에서 같은 팀원이었던 분을 이번 해커톤에서 오프라인으로 처음 뵙게 돼서 정말 반가웠다. 그분은 군대 전역 직전에 해커톤을 참여하러 휴가 중에 시간을 냈다고 하신다... 정말 리스펙!&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1677039687985&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;BDDC&quot; data-og-description=&quot;BNB Chain Defi Degen Club. BDDC has 2 repositories available. Follow their code on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/BNB-Chain-Defi-Degen-Club&quot; data-og-url=&quot;https://github.com/BNB-Chain-Defi-Degen-Club&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cc2WfY/hyRIw6rU3a/x3odlxkIdMGz8l2m7EEkT1/img.png?width=90&amp;amp;height=90&amp;amp;face=0_0_90_90&quot;&gt;&lt;a href=&quot;https://github.com/BNB-Chain-Defi-Degen-Club&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/BNB-Chain-Defi-Degen-Club&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cc2WfY/hyRIw6rU3a/x3odlxkIdMGz8l2m7EEkT1/img.png?width=90&amp;amp;height=90&amp;amp;face=0_0_90_90');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;BDDC&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;BNB Chain Defi Degen Club. BDDC has 2 repositories available. Follow their code on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6-3. BNB 체인 이노베이션 해커톤&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dauHo4/btrZH1TAIPR/l8QcCQ5x5GaARo08jxPRg1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dauHo4/btrZH1TAIPR/l8QcCQ5x5GaARo08jxPRg1/img.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.7395%; margin-right: 10px;&quot; data-widthpercent=&quot;49.31&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dauHo4/btrZH1TAIPR/l8QcCQ5x5GaARo08jxPRg1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdauHo4%2FbtrZH1TAIPR%2Fl8QcCQ5x5GaARo08jxPRg1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b59TE9/btrZERK4Kqa/3On07FMZlcisNlgEY52MB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b59TE9/btrZERK4Kqa/3On07FMZlcisNlgEY52MB1/img.png&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2189&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 50.0977%;&quot; data-widthpercent=&quot;50.69&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b59TE9/btrZERK4Kqa/3On07FMZlcisNlgEY52MB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb59TE9%2FbtrZERK4Kqa%2F3On07FMZlcisNlgEY52MB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;2189&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BNB 체인 해커톤에서 수상한 팀에게 &lt;span&gt;&lt;u&gt;BNB 체인 이노베이션 해커톤&lt;/u&gt; 참가 기회를 부여해 줘서 참가하게 됐다. 이전에 사용했던 소스에서 추가 기능을 덧붙여서 디벨롭시켰다. 우리는 디파이 프로토콜 부문에 지원했는데, 해당 부문에는 정말 잘 갖춰진 프로젝트들이 쟁쟁하게 지원했고 우리 프로젝트는 그에 비해 부족한 점이 많이 보였다  그래서 아쉽게도 수상은 하지 못했지만 좋은 경험이었고 좀 더 프로젝트를 디벨롭해 볼 수 있어서 좋았다. 나중에 회사에서 지원해 준다면 사이드 프로젝트로 이어나가고 싶은 마음도 크다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. Notion 200% 활용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 업무 협업툴로 노션을 사용하고 있다. 프로젝트 관리, 문서 아카이브 등 다양한 용도로 활용하고 있는데, 나는 회사에서 제공하는 템플릿 외에 개인적으로도 다양하게 사용하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7-1. To Do List&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv7N04/btr0glo32Lz/a3gSLq9vOVJmuhmJj4rNX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv7N04/btr0glo32Lz/a3gSLq9vOVJmuhmJj4rNX1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;482&quot; data-filename=&quot;스크린샷 2023-02-21 오후 4.03.58.png&quot; style=&quot;width: 56.2438%; margin-right: 10px;&quot; data-widthpercent=&quot;56.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv7N04/btr0glo32Lz/a3gSLq9vOVJmuhmJj4rNX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv7N04%2Fbtr0glo32Lz%2Fa3gSLq9vOVJmuhmJj4rNX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4fMXS/btr0e8RqtBO/15vBkuE0Gmraeo8kXNl8Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4fMXS/btr0e8RqtBO/15vBkuE0Gmraeo8kXNl8Gk/img.png&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;503&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;43.09&quot; data-filename=&quot;blob&quot; style=&quot;width: 42.5934%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4fMXS/btr0e8RqtBO/15vBkuE0Gmraeo8kXNl8Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4fMXS%2Fbtr0e8RqtBO%2F15vBkuE0Gmraeo8kXNl8Gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;456&quot; height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 항상 월요일에 할 일 목록을 쭉 적고 일주일을 시작하는 편이다. MBTI는 ESFP로 나왔지만 일할 때만큼은 J가 되는 나.. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 스페이스에 각자 할 일 목록을 적는 공간이 있어서 나는 늘 그곳에 나의 업무 리스트를 늘 작성해 둔다. 템플릿은 각자 편한 대로 유연하게 하면 돼서, 나는 테이블 리스트에 이름은 통일하고 &lt;u&gt;보기 편하도록 기간을 설정하고 최신 순으로 정렬&lt;/u&gt;을 해뒀다. 그리고 문서를 열면 현재 진행 중인 프로젝트마다 할 일 목록을 구분해서 &lt;u&gt;체크리스트로 작성하고, 퇴근 전 마친 일들을 체크해 둔다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 프로젝트마다 보드를 만들고 to do, doing, done 그리고 기간 이렇게 썼는데 한눈에 보기에 칸반 보드는 불편한 것 같아서, 그냥 개인적으로는 주 단위로 할 일 목록을 작성하고, 프로젝트 별 WBS는 회사에서 작성하는 것으로 파악하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이렇게 할 일 목록을 작성하다 보면 WBS를 작성할 때 나의 개발 일정 산출이 조금 수월해지는 것 같다. 입사 초반에 개발 일정을 산출해 달라는 요청을 받았는데, 해당 업무에 내가 얼마나 시간이 소요될지 측정이 어려웠다. 하지만 할 일 목록을 작성하고 그것이 얼마나 걸렸는지 대략 파악하다 보니 이제는 개발 일정을 산출하는데 나름 요령이 생긴 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7-2. Study &amp;amp; Issues&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-21 오후 4.06.07.png&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nT4RQ/btrZ78rZIss/JgzIcVinzkF1uFVQlVKUa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nT4RQ/btrZ78rZIss/JgzIcVinzkF1uFVQlVKUa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nT4RQ/btrZ78rZIss/JgzIcVinzkF1uFVQlVKUa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnT4RQ%2FbtrZ78rZIss%2FJgzIcVinzkF1uFVQlVKUa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;251&quot; data-filename=&quot;스크린샷 2023-02-21 오후 4.06.07.png&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일하면서 스터디가 필요한 부분이 많아서 &lt;u&gt;공부했던 내용들을 정리&lt;/u&gt;하기 위해 Study 페이지를 만들었다. 공부한 내용이 동료 개발자들에게 도움이 될 것 같으면 보기 좋게 한 번 더 정리해서 노션 링크를 공유하기도 했다. 노션에 공부한 내용을 정리한 것을 바탕으로 블로그에 포스팅하기도 편해서 정리만 잘해두면 좋은 글감이 되기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OkK7m/btrZC4BYI9M/SX72dkeR1exGmkSmjFJpUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OkK7m/btrZC4BYI9M/SX72dkeR1exGmkSmjFJpUk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;413&quot; data-filename=&quot;스크린샷 2023-02-17 오전 11.51.08.png&quot; data-widthpercent=&quot;72.95&quot; style=&quot;width: 72.1022%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OkK7m/btrZC4BYI9M/SX72dkeR1exGmkSmjFJpUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOkK7m%2FbtrZC4BYI9M%2FSX72dkeR1exGmkSmjFJpUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;945&quot; height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi56fy/btr0dplXL1u/kpU5YU2PlzkONP7U0SGZc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi56fy/btr0dplXL1u/kpU5YU2PlzkONP7U0SGZc0/img.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1016&quot; data-is-animation=&quot;false&quot; width=&quot;436&quot; height=&quot;514&quot; data-widthpercent=&quot;27.05&quot; style=&quot;width: 26.735%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi56fy/btr0dplXL1u/kpU5YU2PlzkONP7U0SGZc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi56fy%2Fbtr0dplXL1u%2FkpU5YU2PlzkONP7U0SGZc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 가장 유용하게 사용하고 있는 페이지는 Issues 페이지이다. 개발을 하다 보면 꼭 하루에 하나 이상 이슈를 접하게 된다. 해당 이슈를 바로 해결하더라도 다음에 또 똑같은 이슈를 마주할 확률은 꽤나 높다. 사람은 똑같은 실수를 반복하기 마련이기 때문에 이슈를 해결했더라도 해결 방안을 정확히 기억하기는 쉽지 않다. 그래서 나는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;겪었던 이슈들과 해결방안을 정리해 두는 습관&lt;/u&gt;을 들여놨다. 물론 접했던 모든 에러나 이슈를 적는 것은 아니다. 나만의 기준에서 적어야 할 것만 필터링해서 작성하는 편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Issues 페이지에는 React,TypeScript,HTML,CSS 등&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;큰 카테고리로 분류&lt;/b&gt;를 해놓았다. 그리고 이슈에 해당하는 대략적인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내용을 제목으로&lt;/b&gt;&lt;span&gt;&amp;nbsp;알아보기 쉽게&amp;nbsp;&lt;/span&gt;작성한다. 그리고 블럭 안 내용에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;발생한 이슈, 해결 방법과 참고했던 링크&lt;/b&gt;를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 나중에 다시 같은 이슈를 만났을 때 적었다는 사실만 기억해 내면 여기서 찾아볼 수도 있고, 블로그에도 좋은 글감으로 활용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 통계로 보는 나의 1년&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doPYbQ/btr0dqZAmX5/MCoSiXOjabiXErLKWgVZb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doPYbQ/btr0dqZAmX5/MCoSiXOjabiXErLKWgVZb1/img.png&quot; width=&quot;659&quot; height=&quot;157&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;450&quot; data-filename=&quot;스크린샷 2022-11-25 오후 4.01.01.png&quot; data-widthpercent=&quot;51.87&quot; style=&quot;width: 51.267%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doPYbQ/btr0dqZAmX5/MCoSiXOjabiXErLKWgVZb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoPYbQ%2Fbtr0dqZAmX5%2FMCoSiXOjabiXErLKWgVZb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1886&quot; height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JBzD2/btrZ9MIYw6a/hm6NRhgndPNzTm7KDKblO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JBzD2/btrZ9MIYw6a/hm6NRhgndPNzTm7KDKblO1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;162&quot; data-filename=&quot;스크린샷 2023-02-21 오후 5.22.22.png&quot; style=&quot;width: 47.5702%;&quot; data-widthpercent=&quot;48.13&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JBzD2/btrZ9MIYw6a/hm6NRhgndPNzTm7KDKblO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJBzD2%2FbtrZ9MIYw6a%2Fhm6NRhgndPNzTm7KDKblO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌) 회사 깃랩&amp;nbsp; &amp;nbsp; (우) 개인 깃헙&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 깃랩은 아주 파랑 파랑한 반면에! 나의 깃헙 잔디는 시멘트 바닥... &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만큼 회사에 집중을 했다고 보면 되지 않을까...ㅎㅎ 사이드 프로젝트로 혼자 뭔가 디벨롭해보기엔 시간이 없었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이제 집도 회사 근처로 옮겼으니 개인 시간이 거의 두 시간이 늘어났다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시간을 잘 활용해서 자기 계발에 좀 더 집중해 봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 깃헙 시멘트 바닥을 영양가 가득한 잔디로 채우는 그날까지!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-21 오후 5.18.13.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7l212/btr0do1N53B/3JjnM5rVVTigKQOG5GNEi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7l212/btr0do1N53B/3JjnM5rVVTigKQOG5GNEi1/img.png&quot; data-alt=&quot;티스토리 블로그 방문자 통계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7l212/btr0do1N53B/3JjnM5rVVTigKQOG5GNEi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7l212%2Fbtr0do1N53B%2F3JjnM5rVVTigKQOG5GNEi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;229&quot; data-filename=&quot;스크린샷 2023-02-21 오후 5.18.13.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;티스토리 블로그 방문자 통계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사 후에도 시간이 날 때마다 블로그를 열심히 했다. 더 나은 양질의 정보를 공유하기 위해, 사람들에게 도움이 되는 글을 작성하기 위해 고민만 하다가 결국 글을 쓰지 못한 글감들도 많아서 아쉬움이 남기는 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 내가 작성하는 글들이 다 정답이 아닐 수도 있고, 누군가에게 큰 도움이 되지는 못할 수도 있지만 나는 글을 쓰며 내 생각을 공유하는 것을 오랫동안 좋아했다. 개발자가 되기 이전에도 네이버 블로그로 정보 공유하는 것을 즐겨했고 나름 파워 블로거(?) 였던 시절이 있었다ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든 앞으로도 내가 겪었던 일들과 다양한 정보들을 열심히 공유하는 개발자가 되고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;개발자로서 첫 회고를 마치며.&lt;br /&gt;입사하고 1년 동안 내 나름대로 치열하게 살았지만,&lt;br /&gt;2023년에는 더 치열하게 살아보자!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 블로그를 제대로 시작하고 나서, 내 블로그 타이틀을 &quot;자라는 것을 잘하는 개발자&quot;라고 명명했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 타이틀에 걸맞게 나는 계속해서 자라나고 싶고, 더 나은 개발자가 되고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 실현하기 위해 올해 목표는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;다양한 개발 컨텐츠 접하기&lt;/b&gt;&lt;br /&gt;강의도 듣고, IT 아티클도 읽고, 책도 읽어서 최대한 다양한 지식을 접해보고 싶다. 지금은 너무 회사에 갇힌 우물 안의 개구리처럼 느껴진다. 물론 회사에서도 얻는 게 정말 많지만, 개발자로서 시야를 넓히는 노력을 하고자 한다. 그래서 일단 YES24 북클럽 정기결제를 했고, 한 달에 한 번 개발 서적 읽기를 도전해보고자 한다 &lt;/li&gt;
&lt;li&gt;&lt;b&gt;체력 증진하기&lt;/b&gt;&lt;br /&gt;입사 이후 나름 운동을 꾸준히 했다. 복싱도 하고, PT도 하고, 수영도 했다. 하지만 인바디 결과를 보거나 내가 느끼는 나의 체력 상태를 봤을 때 아직 한참 부족한 상태이다. 올해 운동은 수영과 헬스장 웨이트 2가지에 집중해보려고 한다. 웨이트 운동을 정말 싫어하는 나지만... 근육량이 남들보다 현저히 낮기 때문에 무조건 해야 한다.. 체력은 내가 개발자로서 오래오래가기 위한 초석을 다지는 중요한 요소 중 하나이기 때문에 소홀히 하지 않을 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대외 활동 시작하기&lt;/b&gt;&lt;br /&gt;취업 준비할 때는 정말 활발하게 대외 활동을 했던 것 같은데, 입사하고 나서부터 대외 활동에 시간을 투자하지를 못했다. 속해있는 커뮤니티도 간간히 눈팅만 하고  대외 활동을 통해 다양한 사람들을 만나고 그들과 함께 소통하면서 시너지를 낼 수 있다는 게 얼마나 소중한 것인지 알고 있기 때문에, 올해는 조금 적극적으로 활동해보고 싶다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #006dd7;&quot;&gt;2023년에도 자라는 것을 잘하는 개발자가 되자!&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Record/daily</category>
      <category>개발자 회고</category>
      <category>블록체인 스타트업 개발자</category>
      <category>프론트엔드 개발자 회고</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/140</guid>
      <comments>https://xiubindev.tistory.com/140#entry140comment</comments>
      <pubDate>Wed, 22 Feb 2023 13:17:56 +0900</pubDate>
    </item>
    <item>
      <title>애플처럼 화려한 Parallax Scroll 랜딩 페이지 개발해보기</title>
      <link>https://xiubindev.tistory.com/139</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCBZqU/btrWcq2aJCE/k32BY5Ywpt5BsbKcGbAwQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCBZqU/btrWcq2aJCE/k32BY5Ywpt5BsbKcGbAwQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCBZqU/btrWcq2aJCE/k32BY5Ywpt5BsbKcGbAwQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCBZqU%2FbtrWcq2aJCE%2Fk32BY5Ywpt5BsbKcGbAwQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
  Parallax scrolling is a web site trend where the background content (i.e. an image) is moved at a different speed than the foreground content while scrolling. 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;패럴랙스 스크롤링 기법을 간단히 말하면 스크롤에 따른 애니메이션 효과라고 할 수 있다. 패럴랙스 스크롤링 기법을 적용한 대표적인 웹사이트는 &lt;a href=&quot;https://www.apple.com/kr/airpods-3rd-generation/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;애플 제품 소개 페이지&lt;/span&gt;&lt;/a&gt;이다. 볼 때마다 감탄사가 나왔었는데, 회사에서 이와 같은 인터랙션 사이트를 구현할 기회가 생겼다!&lt;br&gt;&lt;b&gt;&quot;나도 애플처럼 화려한 인터랙션을 넣을 거야 &quot;라고&lt;/b&gt; 속으로 호기롭게 외치며 스터디를 시작했다.&lt;br&gt; &lt;br&gt;회사에서 &lt;u&gt;인프런 바우처를 지원해 줬기&lt;/u&gt; 때문에 &lt;a href=&quot;https://www.inflearn.com/course/%EC%95%A0%ED%94%8C-%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%9D%B8%ED%84%B0%EB%9E%99%EC%85%98-%ED%81%B4%EB%A1%A0#&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;강의 1, 강의 2를&lt;/span&gt;&lt;/a&gt; 들으며 패럴랙스 스크롤링 원리를 익혔다. 정말 많은 도움이 됐던 강의다.&lt;br&gt;패럴랙스 스크롤링 기법의 핵심은 스크롤 높이와 움직일 객체의 포지션을 동적으로 계산하고, css 중 transform 속성에서 translate, scale 등을 이용하는 게 핵심이다. 그리고 transition에서 easing을 조절해서 스크롤에 따른 애니메이션의 가속도를 자연스레 설정하는 것이 중요하다. 그리고 &lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #24292e;&quot;&gt;requestAnimationFrame을 활용해서 부드럽게 화면에 그려주는 것이 중요하다.&lt;/span&gt;&lt;/span&gt;&lt;br&gt; &lt;br&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #24292e;&quot;&gt;하지만 실무에 바로 적용하기에는 스터디 시간이 약간 부족했고, 결국 라이브러리를 활용하기로 했다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #24292e;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #24292e;&quot;&gt;패럴랙스 스크롤링을 구현하기에 가장 적합한 라이브러리로 &lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://greensock.com/docs/v3/Plugins/ScrollTrigger&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;GSAP의 Scroll Trigger 라이브러리&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #24292e;&quot;&gt;를 택했고, 빠른 시일 내에 구현할 수 있었다. 스크롤 트리거는 말 그대로 스크롤 기반으로 애니메이션을 구현하는 건데, 트리거 엘리먼트를 지정하고 뷰포트에 트리거 엘리먼트가 등장하면 지정한 엘리먼트의 애니메이션이 작동된다. 사용법도 익히기 쉽고 성능도 좋아서 처음 시도한 것 치고 금방 구현할 수 있었다.&lt;/span&gt;&lt;/span&gt;&lt;br&gt; &lt;br&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;  패럴랙스 스크롤 데모 구경하기&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Parallax Scroll Site&quot; data-ke-align=&quot;alignCenter&quot; data-og-host=&quot;parallax-scroll-site.netlify.app&quot; data-og-source-url=&quot;https://parallax-scroll-site.netlify.app/&quot; data-og-url=&quot;https://parallax-scroll-site.netlify.app/&quot;&gt;
 &lt;a href=&quot;https://parallax-scroll-site.netlify.app/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://parallax-scroll-site.netlify.app/&quot;&gt;
  &lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;Parallax Scroll Site&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;parallax-scroll-site.netlify.app&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;GSAP 홈페이지에 가보면 문서가 잘 돼있어서 굳이 사용법은 적지 않아도 될 것 같고, 겪었던 문제점과 해결법을 공유해보려고 한다.&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;  GSAP 스크롤 트리거 트러블 슈팅&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어려웠던 점은 회사 측에서 요청했던 패럴랙스 스크롤링은 애플 사이트와는 조금 다르게 정말 한 편의 애니메이션을 제작하는 것과 같았다.&lt;br&gt;다양한 scene이 존재했고, 스크롤링하면서 여러 리소스들이 뷰포트 안으로 들어와서 움직여야 했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 스크롤 높이는 최대한 넉넉하게 잡기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스크롤에 따라 리소스가 움직이기 때문에&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;트리거를 애니메이션에 필요한 총 스크롤 높이의 엘리먼트로 고정해 놓고,&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정해진 높이 안에서 움직임을 계산하는 게 편리했다. 그리고 최대한&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;스크롤 높이는 넉넉하게&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;잡는 것이 좋다. 처음에 너무 작게 잡았다가 여러 기기로 테스트해 보면서 부족함을 느껴서 다시 재조정하며 굉장한 노가다를 반복했다..&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 뷰포트 사이즈 대응하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;반응형으로 제작하기 위해 뷰포트 사이즈에 맞게 움직이는 속도를 조절해야 해서, resize 이벤트가 일어나거나 뷰포트 사이즈에 따라&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;스크롤 속도를 유동적으로 계산&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하도록 했다. &lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;3. 뷰포트 바깥에 리소스 배치하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;각 scene마다 뷰포트 안으로 들어오는 리소스의 위치가 제각각이기 때문에&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;position: fixed&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;로 뷰포트 바깥에 리소스를 배치&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;한 후 움직이는 게 좋다. 그리고 반응형으로 제작할 경우 scene을 기획할 때&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;최대한 화면 가운데서 인터랙션&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일어나는 것이 보여야 하는 것이 관건이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4. 다양한 스크롤 기기 대응하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스크롤 기기(트랙패드, 마우스 휠, 모바일 터치 기기 등)에 따라 속도가 다 다르기 때문에 우선은 기기마다 디폴트 설정값을 기본으로 테스트해 보고 속도를 계산하는 것이 최선인 것 같다. 그리고 스크롤 트리거의&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;scrub&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;속성&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;을 활용해서 적절히 속도를 조절할 수도 있다. &lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;5. 크로스 브라우징 테스트하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사파리, 크롬, 파이어폭스, 엣지, 네이버 인앱 브라우저까지 크로스 브라우징 테스트로 잘 돌아가는 것을 확인했는데, 이상하게 유독 &lt;u&gt;ios 기기의 &lt;/u&gt;&lt;u&gt;카카오톡 인앱 브라우저에서만 버그가 발생&lt;/u&gt;했다. 디버깅을 하고 싶었으나 카카오톡 인앱 브라우저는 디버깅조차 할 수 없어서 정말 애먹었다. 디버깅을 위해 원인을 예측해 보고 수정하고 배포해서 인앱 브라우저에서 열어서 확인하는 동작을 수없이 반복했다. 결국 디버깅엔 실패했고, 구글링을 통해 발견한 &lt;a href=&quot;https://www.burndogfather.com/201&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;블로그 글&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;을 보고 ios 기기의 인앱브라우저에서는 이미지를 대체해서 보여주는 방식으로 해결할 수밖에 없었다. 지금 개인적으로 올린 데모에서는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;location.href=&quot;x-web-search://?&quot;&lt;/span&gt; 코드를 사용하여 사파리 앱 URL Scheme을 활용해 &lt;b&gt;인앱 브라우저에서를 사파리를 열어주는 방법&lt;/b&gt;으로 인앱 브라우저에서 탈출시키도록 했다.&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; ️ 이미지 성능 최적화&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;패럴랙스 스크롤링 랜딩 페이지는 원래 예정에 없었다. 그냥 애니메이션 제작하고 그 영상을 랜딩 페이지 최상단에서 플레이하기로 했는데, 계획이 바뀌면서 패럴랙스 스크롤링 기법을 적용해야 했다. 그래서 기존에 아트 팀에서 애니메이션을 위해 모든 아트 작업을 포토샵으로 작업했기 때문에 이미지 파일을 모두 PNG 파일로 변환해야 했다. 만약 벡터 기반 아트였으면 성능 이슈가 크지 않았을 텐데 PNG 포맷이었기 때문에 좀 더 성능에 큰 영향을 미쳤다. 이미지 용량이 큰 게 많이 들어가다 보니까 특히 모바일에서 스크롤하면 버벅거리는 경우가 많았다. 그래서 이미지를 최적화하는데 집중해야 했다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 이미지 용량 줄이기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이미지 최적화에서 사진 용량 줄이기를 빼놓을 수 없다. &lt;/span&gt;올바른 이미지 최적화를 위해서는, 사진 퀄리티는 그대로 유지하면서 용량은 최대한 줄여야 한다. &lt;span style=&quot;color: #333333;&quot;&gt;구글은 사진 용량줄이기를 위해 파일크기를 줄이는 도구 중 &lt;/span&gt;&lt;a href=&quot;https://imageoptim.com/mac&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ImageOptim&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #333333;&quot;&gt;을 사용할 것을 권장하는데, ImageOptim 은 맥용 무료 도구이며 이를 사용하면 실제로 JPEG 파일은 69 %, PNG 파일은 40 % 의 용량이 감소된다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 결과 이미지 리소스 사이즈를 &lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;총 18.5mb에서 13.2mb 까지 줄이는 데 성공&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;했다. &lt;/span&gt;&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;861&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2TL99/btrVVarYlUN/1MfZkKZnVrd6bKLkSzAqVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2TL99/btrVVarYlUN/1MfZkKZnVrd6bKLkSzAqVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2TL99/btrVVarYlUN/1MfZkKZnVrd6bKLkSzAqVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2TL99%2FbtrVVarYlUN%2F1MfZkKZnVrd6bKLkSzAqVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;861&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;861&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h4 data-ke-size=&quot;size20&quot;&gt; &lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. WebP 이미지 포맷 사용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;WebP는 무손실 및 손실 압축을 전부 지원하고, JPEG, PNG 및 GIF 이미지 포맷을 대체할 수 있는 웹에 최적화된 포맷이다. &lt;/span&gt;&lt;a href=&quot;https://web.dev/i18n/ko/serve-images-webp/#%EC%99%9C-%EC%8B%A0%EA%B2%BD%EC%9D%84-%EC%8D%A8%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 글&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #333333;&quot;&gt;에 따르면 WebP 이미지는 JPEG, PNG 보다 파일 크기가 일반적으로 25~35% 작아서 성능 향상에 중요한 역할을 할 수 있다고 한다. WebP 이미지 포맷은 현재 기준 IE를 제외한 브라우저에서 모두 지원하는 것으로 알려져 있다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기존에 사용하던 PNG 파일 중에 크기가 큰 파일을 모두 WebP 포맷으로 변환하고, WebP 파일을 지원하지 않는 경우에는 PNG파일을 사용하고 그 외에는 WebP 파일을 사용했다. 이를 구현하기 위해 다음과 같이 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;u&gt;picture 태그와 source태그, img태그&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;를 이용해야한다.&lt;/span&gt;&lt;br&gt; &lt;br&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;lt;picture&amp;gt; 태그는 &amp;lt;img&amp;gt; 태그의 다중 이미지 리소스를 위한 컨테이너를 정의할 때 사용한다. &lt;/span&gt;&lt;/span&gt;&amp;lt;picture&amp;gt; 태그는 0개 이상의 &amp;lt;source&amp;gt; 태그와 하나의 &amp;lt;img&amp;gt; 태그로 구성된다.&lt;br&gt;&amp;lt;source&amp;gt; 태그는 미디어 리소스를 지정한다. 브라우저가 지원하는 형식으로 나열된 첫 번째 소스를 사용하고, &amp;lt;source&amp;gt; 태그에 나열된 형식 또는 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;lt;picture&amp;gt; 태그를&lt;/span&gt; 브라우저가 지원하지 않으면 &amp;lt;img&amp;gt; 태그에 지정된 이미지를 로드하는 것으로 대체된다. 따라서 &amp;lt;img&amp;gt; 태그는 항상 가장 마지막에 나열해야 하고 보편적으로 지원하는 미디어 형식의 리소스여야 한다.&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예시 코드는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;source type=&quot;image/webp&quot; srcset=&quot;flower.webp&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;source type=&quot;image/png&quot; srcset=&quot;flower.png&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;img src=&quot;flower.png&quot; alt=&quot;flower&quot;&amp;gt;
&amp;lt;/picture&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 결과 이미지 리소스 사이즈를 &lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;총 13.2mb에서 3.5mb까지 줄이는 데 성공&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;했다.&lt;/span&gt;&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvdxDG/btrVU8OmJ2g/5jU6DqMmK7wHBwFzDcLj4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvdxDG/btrVU8OmJ2g/5jU6DqMmK7wHBwFzDcLj4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvdxDG/btrVU8OmJ2g/5jU6DqMmK7wHBwFzDcLj4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvdxDG%2FbtrVU8OmJ2g%2F5jU6DqMmK7wHBwFzDcLj4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;860&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; ️ CSS 애니메이션 성능 최적화&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 최적화 이후 피시에서 애니메이션 테스트를 마치고 모바일 테스트를 진행하는데, 굉장한 버벅거림 현상을 마주했다.&lt;br&gt;그래서 이미지 최적화뿐만 아니라 애니메이션 성능에도 문제가 있다는 것을 알게 됐고, 애니메이션 최적화를 시작했다.&lt;br&gt;애니메이션을 최적화하기 위해서는 우선 브라우저에서 렌더링이 어떻게 일어나는지를 먼저 파악해야 했다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duDotU/btrV75EodYm/CQPQzPBDp4zEor0yDeGzjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duDotU/btrV75EodYm/CQPQzPBDp4zEor0yDeGzjK/img.png&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;441&quot; style=&quot;width: 50.1908%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duDotU/btrV75EodYm/CQPQzPBDp4zEor0yDeGzjK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduDotU%2FbtrV75EodYm%2FCQPQzPBDp4zEor0yDeGzjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;865&quot; height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d31GoX/btrV84ZwpDB/AJhXLrVrMERGgZkOEWcZQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d31GoX/btrV84ZwpDB/AJhXLrVrMERGgZkOEWcZQK/img.png&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;455&quot; style=&quot;width: 48.6464%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d31GoX/btrV84ZwpDB/AJhXLrVrMERGgZkOEWcZQK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd31GoX%2FbtrV84ZwpDB%2FAJhXLrVrMERGgZkOEWcZQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;865&quot; height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;출처:&amp;amp;nbsp;https://developer.chrome.com/blog/inside-browser-part1/&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;크롬 기준으로 브라우저 아키텍처를 살펴보면 브라우저 프로세스, 렌더러 프로세스, 플러그인 프로세스, GPU 프로세스 등으로 구성되어 있다. 이 중에서 렌더러 프로세스가 브라우저 탭 안에서 일어나는 모든 일을 담당하게 된다. 렌더러 프로세스 안에서도 메인 스레드, 컴포지터 스레드, 래스터 스레드로 나뉘는데, 메인 스레드가 대부분의 스크립트 코드를 처리하고, 컴포지터/래스터 스레드는 화면을 부드럽게 렌더링 하는 것을 처리한다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOcIoY/btrVVJnyoXi/mH8kwRtwDE8DbSCDsceoo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOcIoY/btrVVJnyoXi/mH8kwRtwDE8DbSCDsceoo0/img.png&quot; data-alt=&quot; 출처: https://web.dev/howbrowserswork/ &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOcIoY/btrVVJnyoXi/mH8kwRtwDE8DbSCDsceoo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOcIoY%2FbtrVVJnyoXi%2FmH8kwRtwDE8DbSCDsceoo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;289&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 출처: https://web.dev/howbrowserswork/ &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;일반적으로 메인 스레드에서 Recalculate Style, Layout, Paint 과정이 실행되고 CSS 기반 애니메이션을 컴포지터 스레드에서 실행한다. 위에서 언급했듯이 자바스크립트 코드는 메인 스레드에서 실행되기 때문에 자바스크립트로 애니메이션을 구현할 경우 애니메이션의 우선순위가 밀릴 수 있다. 하지만 CSS 기반 애니메이션을 구현한다면 컴포지터 스레드에서 처리되기 때문에 애니메이션 성능을 최적화할 수 있다. &lt;br&gt;그리고 부드러운 애니메이션을 위해서는 렌더링 과정 중 &lt;u&gt;Composite Layers에 영향을 주는 속성만 사용&lt;/u&gt;하는 것이 적합하다. &lt;br&gt;웹 브라우저 렌더링 과정과 그 과정에 해당하는 CSS 속성을 간단히 살펴보면 다음과 같다.&lt;br&gt; &lt;br&gt;&lt;b&gt;1. &lt;/b&gt;&lt;b&gt;Recalculate Style&lt;/b&gt;&lt;b&gt; :&lt;/b&gt; 요소에 적용할 스타일을 계산한다.&lt;br&gt; &lt;br&gt;&lt;b&gt;2. Layout :&lt;/b&gt; 요소의 모양과 위치를 생성한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 126px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
 &lt;tbody&gt;
  &lt;tr style=&quot;height: 18px;&quot;&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;position&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;display&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;overflow&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;overflow-y&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;height: 18px;&quot;&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;width&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;height&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;min-width&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;min-height&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;height: 18px;&quot;&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;padding&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;margin&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;border&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;border-width&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;height: 18px;&quot;&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;top&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;bottom&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;left&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;right&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;height: 18px;&quot;&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;font&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;font-family&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;font-size&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;font-weight&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;height: 18px;&quot;&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;white-space&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;line-height&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;vertical-align&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;float&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;height: 18px;&quot;&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;clear&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
   &lt;td style=&quot;height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;&lt;b&gt;3. Paint :&lt;/b&gt; 생성된 모든 레이아웃에 픽셀을 추가한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;color&lt;/td&gt;
   &lt;td&gt;background&lt;/td&gt;
   &lt;td&gt;visibility&lt;/td&gt;
   &lt;td&gt;text-deocration&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;background-image&lt;/td&gt;
   &lt;td&gt;background-position&lt;/td&gt;
   &lt;td&gt;background-repeat&lt;/td&gt;
   &lt;td&gt;background-size&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;outline&lt;/td&gt;
   &lt;td&gt;outline-color&lt;/td&gt;
   &lt;td&gt;outline-style&lt;/td&gt;
   &lt;td&gt;outline-width&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;border-radius&lt;/td&gt;
   &lt;td&gt;border-style&lt;/td&gt;
   &lt;td&gt;box-shadow&lt;/td&gt;
   &lt;td&gt;&amp;nbsp;&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;&lt;b&gt;4. Composite Layers&lt;/b&gt; &lt;b&gt;:&lt;/b&gt; 레이어(z-index) 순서대로 레이어를 합성하고 화면에 그린다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;transform&lt;/td&gt;
   &lt;td&gt;opacity&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;위 순서에 따르면 Layout 관련 속성을 바꾸면 Paint, Composite Layers 순서를 거치기 때문에 성능 저하가 일어난다. 이를 Reflow라고 한다.&lt;br&gt;또한 Paint 관련 속성을 바꾸면 Composite Layers 순서를 거치기 때문에 성능 저하가 일어난다. 이를 Repaint라고 한다.&lt;br&gt;따라서 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;CSS 애니메이션 성능을 최대로 발휘하려면 Composite Layers 관련 속성(transform, opacity)만 바꾸는 것이 가장 좋다. &lt;/b&gt;&lt;/span&gt;&lt;br&gt; &lt;br&gt; &lt;br&gt;패럴랙스 스크롤 랜딩페이지를 만들어보면서 패럴랙스 스크롤링의 원리를 익힘과 동시에 이미지와 애니메이션 최적화에 대해 많이 배울 수 있었다. 다음에 기회가 된다면 라이브러리를 사용하지 않고 순수 CSS, JS로만 패럴랙스 스크롤링을 구현해보고 싶다!&lt;br&gt; &lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt; &lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;참고 문헌&lt;/span&gt;&lt;br&gt;&lt;a href=&quot;https://developer.chrome.com/blog/inside-browser-part1/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;https://developer.chrome.com/blog/inside-browser-part1/&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://developer.chrome.com/blog/inside-browser-part3/#style-calculation&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;https://developer.chrome.com/blog/inside-browser-part3/#style-calculation&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://greensock.com/react/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;https://greensock.com/react/&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://greensock.com/react-advanced&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;https://greensock.com/react-advanced&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://edidiongasikpo.com/using-gsap-scrolltrigger-plugin-in-react&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;https://edidiongasikpo.com/using-gsap-scrolltrigger-plugin-in-react&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://wit.nts-corp.com/2020/06/05/6134&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;https://wit.nts-corp.com/2020/06/05/6134&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study/Frontend</category>
      <category>css 애니메이션 최적화</category>
      <category>gsap</category>
      <category>parallax scroll</category>
      <category>Scroll trigger</category>
      <category>애플웹사이트</category>
      <category>이미지 최적화</category>
      <category>패럴랙스 스크롤</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/139</guid>
      <comments>https://xiubindev.tistory.com/139#entry139comment</comments>
      <pubDate>Fri, 13 Jan 2023 15:15:28 +0900</pubDate>
    </item>
    <item>
      <title>[컨퍼런스 리뷰 / Google I/O 2022] What's new for the web platform</title>
      <link>https://xiubindev.tistory.com/137</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca4QXy/btrCAxGgE2m/DwWGM2DFXJkA4rI66RJuQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca4QXy/btrCAxGgE2m/DwWGM2DFXJkA4rI66RJuQ0/img.png&quot; data-alt=&quot;제일 재밌었던 스피커 :)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca4QXy/btrCAxGgE2m/DwWGM2DFXJkA4rI66RJuQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca4QXy%2FbtrCAxGgE2m%2FDwWGM2DFXJkA4rI66RJuQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;301&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제일 재밌었던 스피커 :)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;구글이 개최하는 연례 개발자 컨퍼런스인 Goolge I/O 를 듣고 발표 내용 및 추가적으로 서칭하고 정리한 내용을 공유하려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;What's new for the web platform 세션은 프론트엔드 개발자라면 꼭 봐야 하는 세션이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;새로운 기능들을 많이 접해볼 수 있고, 발표 내용을 바로 실무에 적용해본 기능들도 있어서 굉장히 유용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=5b4YcLB4DVI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/byp0QD/hyOriSBt5d/dXyb7pr5nebFDmXRl0OwZ0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=912_78_994_500&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/5b4YcLB4DVI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;Google I/O 2022&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/accent-color/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CSS accent-color&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rGtzJ/btrCqV3FTX5/0SRVPONqAk6kMwxT4qiMrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rGtzJ/btrCqV3FTX5/0SRVPONqAk6kMwxT4qiMrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rGtzJ/btrCqV3FTX5/0SRVPONqAk6kMwxT4qiMrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrGtzJ%2FbtrCqV3FTX5%2F0SRVPONqAk6kMwxT4qiMrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;365&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;lt;input type=&quot;checkbox&quot;&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;lt;input type=&quot;radio&quot;&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;lt;input type=&quot;range&quot;&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;lt;progress&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;accent-color는 위 요소들에서 생성된 &lt;b&gt;사용자 인터페이스 컨트롤의&amp;nbsp;강조&amp;nbsp;색상을 설정&lt;/b&gt;해주는 속성이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이 속성은 color-scheme 속성과도 함께 작동하므로 light 모드와 dark 모드에 각각 색상을 설정해줄 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;PomBZdy&quot; data-user=&quot;web-dot-dev&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;See the Pen &lt;a href=&quot;https://codepen.io/web-dot-dev/pen/PomBZdy&quot;&gt; HTML elements with accent-color&lt;/a&gt; by web.dev (&lt;a href=&quot;https://codepen.io/web-dot-dev&quot;&gt;@web-dot-dev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/building-a-dialog-component/#accessibility&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot; data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;dialog&lt;/span&gt;&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVYx7Z/btrCuUQx7V7/PViykQsUFw9O09kk5k6kjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVYx7Z/btrCuUQx7V7/PViykQsUFw9O09kk5k6kjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVYx7Z/btrCuUQx7V7/PViykQsUFw9O09kk5k6kjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVYx7Z%2FbtrCuUQx7V7%2FPViykQsUFw9O09kk5k6kjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;851&quot; height=&quot;391&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;lt;dialog&amp;gt; 요소는 닫을 수 있는 경고, 창 등 &lt;b&gt;대화 상자 및 기타 다른 상호작용 가능한 컴포넌트&lt;/b&gt;를 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;u&gt;open 속성&lt;/u&gt;을 통해 활성 여부를 컨트롤 할 수 있고, method=&quot;dialog&quot; 특성을 사용한 form 요소는 제출 시 dialog를 닫는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;dialog를 닫을 때, dialog의 &lt;u&gt;returnValue 속성&lt;/u&gt;은 양식을 제출할 때 사용한 버튼의 value로 설정되며, returnValue에 따라 콜백 함수 내에서 조건문을 사용하여 다른 기능들을 처리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652928027386&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dialog id=&quot;MiniDialog&quot; modal-mode=&quot;mini&quot;&amp;gt;
  &amp;lt;form method=&quot;dialog&quot;&amp;gt;
    &amp;lt;article&amp;gt;
      &amp;lt;p&amp;gt;Are you sure you want to remove this user?&amp;lt;/p&amp;gt;
    &amp;lt;/article&amp;gt;
    &amp;lt;footer&amp;gt;
      &amp;lt;menu&amp;gt;
        &amp;lt;button autofocus type=&quot;reset&quot; value=&quot;cancle&quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
        &amp;lt;button type=&quot;submit&quot; value=&quot;confirm&quot;&amp;gt;Confirm&amp;lt;/button&amp;gt;
      &amp;lt;/menu&amp;gt;
    &amp;lt;/footer&amp;gt;
  &amp;lt;/form&amp;gt;
&amp;lt;/dialog&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652928016322&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MiniDialog = document.getElementById('MiniDialog');

MiniDialog.addEventListener('close', function onClose() {
	console.log(MiniDialog.returnValue)
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;input datetime-local&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WacNT/btrCwgzpkE2/EmEDKWQo6HGG9mLlJyKKc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WacNT/btrCwgzpkE2/EmEDKWQo6HGG9mLlJyKKc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WacNT/btrCwgzpkE2/EmEDKWQo6HGG9mLlJyKKc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWacNT%2FbtrCwgzpkE2%2FEmEDKWQo6HGG9mLlJyKKc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;867&quot; height=&quot;367&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;input 태그의 type 속성의 datetime-local을 사용하면, 유저가 &lt;b&gt;날짜와 시간을 쉽게 입력할 수 있도록 하는 입력 컨트롤&lt;/b&gt;을 만들어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652928074971&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;label&amp;gt;
	Start date &amp;amp;amp; time:
	&amp;lt;input type='datetime-local' /&amp;gt;
&amp;lt;/label&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCHv5d/btrCtwJfZN9/OjNjMxgKww1QzenWucs2P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCHv5d/btrCtwJfZN9/OjNjMxgKww1QzenWucs2P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCHv5d/btrCtwJfZN9/OjNjMxgKww1QzenWucs2P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCHv5d%2FbtrCtwJfZN9%2FOjNjMxgKww1QzenWucs2P1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;843&quot; height=&quot;481&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/i18n/ko/bfcache/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Back/forward cache&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3XgrQ/btrCvCCs0XA/2eXCLZTAgzOpOGUCFNQLkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3XgrQ/btrCvCCs0XA/2eXCLZTAgzOpOGUCFNQLkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3XgrQ/btrCvCCs0XA/2eXCLZTAgzOpOGUCFNQLkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3XgrQ%2FbtrCvCCs0XA%2F2eXCLZTAgzOpOGUCFNQLkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;847&quot; height=&quot;363&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;bfcahce(Back-Forward Cache)란 말 그대로 &lt;b&gt;뒤로/앞으로 캐시&lt;/b&gt;라고 할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;페이지 로드 성능 개선을 위해 브라우저에서 잠시 동안 캐시를 보유하고 있다가, &lt;b&gt;뒤로 또는 앞으로 탐색을 할 때 즉각적으로 페이지를 로드&lt;/b&gt;할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이 기능을 제대로 활용하기 위해서는 bfcahce에 맞게 페이지를 최적화를 진행해줘야한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;unload 이벤트 사용 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;window.opener 참조 피하기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;사용자가 다른 곳으로 이동하기 전에 항상 열려 있는 연결 닫기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;bfcache 복원 후 오래되거나 민감한 데이터 업데이트&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMi5ZD/btrCwhLUAsy/ANWInsKEKXvckeQOvkQ1bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMi5ZD/btrCwhLUAsy/ANWInsKEKXvckeQOvkQ1bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMi5ZD/btrCwhLUAsy/ANWInsKEKXvckeQOvkQ1bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMi5ZD%2FbtrCwhLUAsy%2FANWInsKEKXvckeQOvkQ1bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;873&quot; height=&quot;475&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;bfcahce는 chrome 개발자 도구의 application-cache 부분에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/browser-level-image-lazy-loading/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;img loading=&quot;lazy&amp;rdquo;&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mRaxw/btrCvWtXMLC/ltTSlPPCj3fBY7rDCAyJHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mRaxw/btrCvWtXMLC/ltTSlPPCj3fBY7rDCAyJHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mRaxw/btrCvWtXMLC/ltTSlPPCj3fBY7rDCAyJHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmRaxw%2FbtrCvWtXMLC%2FltTSlPPCj3fBY7rDCAyJHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;853&quot; height=&quot;392&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;JavaScript 라이브러리를 사용할 필요 없이 img 태그의&amp;nbsp;loading=lazy 속성을 사용하여 &lt;b&gt;이미지를 지연 로딩&lt;/b&gt;할 수 있다. lazy는 &lt;u&gt;뷰포트로부터&amp;nbsp;계산된 거리에 도달할 때까지&lt;/u&gt; 리소스 로딩을 지연시킨다. 이 속성은 img 요소뿐만 아니라 iframe 역시 가지고 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652928459577&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;image.jpg&quot; alt=&quot;...&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;iframe src=&quot;video-player.html&quot; title=&quot;...&quot; loading=&quot;lazy&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;layout shift를 방지하려면 모든&amp;nbsp;&amp;lt;img&amp;gt;&amp;nbsp;태그에&amp;nbsp;width&amp;nbsp;및&amp;nbsp;height&amp;nbsp;속성이 모두 포함되는 것이 좋다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;처음 뷰포트에 표시되는 이미지는 지연 로딩을 하지 않아야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/aspect-ratio/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CSS aspect-ratio&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbHLEY/btrCxJf2E7q/dODeSmvJezISvTFHoiOvAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbHLEY/btrCxJf2E7q/dODeSmvJezISvTFHoiOvAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbHLEY/btrCxJf2E7q/dODeSmvJezISvTFHoiOvAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbHLEY%2FbtrCxJf2E7q%2FdODeSmvJezISvTFHoiOvAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;858&quot; height=&quot;397&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;aspect-ratio 속성은&lt;b&gt; 요소의 크기를 비율대로 조정할 수 있도록 종횡비를 설정할&lt;/b&gt; 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;반응형 디자인의 출현으로 웹 개발자에게 종횡비를 유지하는 것이 점점 더 중요해졌다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;특히 이미지 크기가 다르고 사용 가능한 공간에 따라 요소 크기가 변하기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652928853237&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.container {
  width: 100%;
  aspect-ratio: 16 / 9;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;예전에는 이미지 요소의 종횡비를 세팅하기 위해서 padding-top을 계산해서 넣어주곤 했는데, 이제 그렇게 할 필요가 없어졌다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;단순히 요소에 aspect-ratio 속성만 넣어주면 반응형에 따라서도 종횡비를 잘 유지할 수가 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/priority-hints/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Priority hints&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Otcz/btrCyfsMEYF/KM2hral03Fr3Wej9RKrb50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Otcz/btrCyfsMEYF/KM2hral03Fr3Wej9RKrb50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Otcz/btrCyfsMEYF/KM2hral03Fr3Wej9RKrb50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Otcz%2FbtrCyfsMEYF%2FKM2hral03Fr3Wej9RKrb50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;851&quot; height=&quot;374&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Priority Hints를 통해 &lt;b&gt;리소스 로딩을 최적화할&lt;/b&gt; 수 있다. link, img, script 및 iframe 태그에&amp;nbsp;&lt;u&gt;fetchpriority 속성&lt;/u&gt;을 사용하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이 속성을 사용하면 리소스 로드의 우선순위를 지정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;high: 리소스를 높은 우선순위로 간주한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;low: 리소스를 낮은 우선 순위로 간주한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;auto: 브라우저가 적절한 우선순위를 결정하도록 하는 기본값이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1652929167201&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img fetchpriority='high' src='test.png' alt='...' /&amp;gt;
&amp;lt;img fetchpriority='low' src='test2.png' alt='...' /&amp;gt;

&amp;lt;script fetchpriority='high' src='test.js' async&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script fetchpriority='low' src='test2.js' async&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;iframe fetchpriority='high' src='/test'&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;iframe fetchpriority='low' src='/test2'&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcdVUI/btrCAv2KP7o/FGo2ObpAeCrKujf1TaBLO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcdVUI/btrCAv2KP7o/FGo2ObpAeCrKujf1TaBLO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcdVUI/btrCAv2KP7o/FGo2ObpAeCrKujf1TaBLO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcdVUI%2FbtrCAv2KP7o%2FFGo2ObpAeCrKujf1TaBLO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;887&quot; height=&quot;462&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;대용량 이미지일 경우 우선 순위를 높였을 때 시간 차이가 발생한다. script의 경우 async를 추가해 주면 우선 순위를 더욱 높일 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/css-size-adjust/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CSS size-adjust&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwIzD9/btrCyfGlqVH/wUfrDBKN4RNrZ5E99uKt90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwIzD9/btrCyfGlqVH/wUfrDBKN4RNrZ5E99uKt90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwIzD9/btrCyfGlqVH/wUfrDBKN4RNrZ5E99uKt90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwIzD9%2FbtrCyfGlqVH%2FwUfrDBKN4RNrZ5E99uKt90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;857&quot; height=&quot;391&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;size-adjust 속성을 사용하면 &lt;b&gt;웹 폰트가 로드되면 크기를 조정&lt;/b&gt;하여 폰트 크기를 표준화하고 폰트 간에 전환할 때 레이아웃 이동을 방지할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652929294694&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@font-face {
  font-family: &quot;Adjusted Regular Arial For Brand&quot;;
  src: local(Arial);
  size-adjust: 90%;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://developer.chrome.com/docs/privacy-sandbox/chips/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CHIPS&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blk4Jl/btrCAv2K1Gq/oqi0LdEA7wKVRsnkThrIH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blk4Jl/btrCAv2K1Gq/oqi0LdEA7wKVRsnkThrIH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blk4Jl/btrCAv2K1Gq/oqi0LdEA7wKVRsnkThrIH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblk4Jl%2FbtrCAv2K1Gq%2Foqi0LdEA7wKVRsnkThrIH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;851&quot; height=&quot;396&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;쿠키(Cookie)는 사용자가 어떤 웹사이트를 방문할 경우 사용자의 정보를 저장하기 위해서 사용자의 디바이스(컴퓨터, 스마트폰 등)에 저장하는 파일을 의미한다. 쿠키 종류 중에 서드파티 쿠키(Third-Party Cookie)라는 것이 있다. 서드파티 쿠키는 방문한 웹사이트에서 심는 쿠키인데, &lt;u&gt;서로 다른 사이트를 넘나들면서 사용자 정보를 추적&lt;/u&gt;할 수 있어서 개인 정보 문제가 화두가 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;구글은 &lt;b&gt;서드파티 쿠키로 인한 사이트 간 추적을 중단하여 사용자의 개인 정보를 개선&lt;/b&gt;하고자 &lt;b&gt;CHIPS라는&lt;/b&gt; 것을 제안했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;ldquo;&lt;b&gt;Cookies Having Independent Partitioned State (CHIPS)&lt;/b&gt; is a Privacy Sandbox proposal that will allow developers to opt a cookie into &quot;partitioned&quot; storage, with separate cookie jars per top-level site.&amp;rdquo;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2KGqF/btrCtvXV7h0/tKynPU0nyM77AA8Ouav0J1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2KGqF/btrCtvXV7h0/tKynPU0nyM77AA8Ouav0J1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2KGqF/btrCtvXV7h0/tKynPU0nyM77AA8Ouav0J1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2KGqF%2FbtrCtvXV7h0%2FtKynPU0nyM77AA8Ouav0J1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;468&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1652929424208&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Set-Cookie: __Host-example=34d8g; SameSite=None; Secure; Path=/; Partitioned;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;사용자가 사이트 A를 방문하고 임베디드 된 사이트 C가 &amp;nbsp;&lt;b&gt;Partitioned 속성으로 쿠키를 설정&lt;/b&gt;하면, &lt;b&gt;브라우저는 최상위 사이트가 A인 경우에만 해당 쿠키를 보낸다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;사용자가 사이트 B와 같은 새 사이트를 방문하면 &lt;u&gt;사이트 A에 방문할 때 설정된 쿠키를 공유하지 않고, 새로운 쿠키가 생성&lt;/u&gt;된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;즉, 특정 임베디드 콘텐츠가 A 사이트에 임베드될 때 쿠키가 설정되어 있으면, 사이트가 A에 임베드된 경우에만 쿠키를 사용할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이로 인해 여러 사이트에서 사용자를 추적하는 데 쿠키를 사용할 수 없지만, 쿠키로 세션은 계속 유지할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/structured-clone/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;structuredClone&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGrkxU/btrCwHDzj8q/lK3odjGwan1nh0p2mDpxC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGrkxU/btrCwHDzj8q/lK3odjGwan1nh0p2mDpxC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGrkxU/btrCwHDzj8q/lK3odjGwan1nh0p2mDpxC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGrkxU%2FbtrCwHDzj8q%2FlK3odjGwan1nh0p2mDpxC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;427&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1652929553963&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const myOriginal = {
  someProp: &quot;with a string value&quot;,
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존에는 deep-copy를 하기 위해서 Lodash의 cloneDeep과 같은 함수를 사용하거나, 위와 같이 JSON기반으로 파싱을 해서 사용했다. 하지만 JSON 기반의 방법은 재귀 데이터 구조를 처리하지 못하고, 함수를 버리는 등 문제점이 있었다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652929593588&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const clone = structuredClone(myOriginal);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존 JSON 기반 방법의 문제점을 해결하고 deep-copy를 쉽게 할 수 있도록 structuredClone 함수가 생겼다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이렇게 &lt;b&gt;간단하게 깊은 복사를 진행&lt;/b&gt;할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다만 아직 한계는 있다고 한다. 객체에 함수가 포함되어 있으면 복사를 진행할 때 함수는 버리게 된다. 또한, 객체의 값이 직렬화할 수 없다면 에러를 뱉는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;array.at&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;at 메서드는 &lt;b&gt;정수 값을 받아, 배열에서 해당 값에 해당하는 인덱스의 요소를 반환&lt;/b&gt;한다. 양수와 음수 모두 지정할 수 있고, 음수 값의 경우 배열의 뒤에서부터 인덱스를 세서 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652929754106&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const cart = ['사과', '바나나', '배'];

function returnLast(arr) {
  return arr.at(-1);
} 
// 마지막 요소인 '배'를 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;배열의 맨 마지막 요소를 가져오고 싶을 때 배열의 length&amp;nbsp;속성을 사용해&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;array[array.length - 1]&lt;/span&gt;&lt;/b&gt;을 하는 대신, 짧게&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;array.at(-1)&lt;/span&gt;&lt;/b&gt;을 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://developer.chrome.com/blog/cascade-layers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CSS cascade layers&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pF8Ka/btrCvZY00h1/qy6pKFuos71ehJOwtL4G30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pF8Ka/btrCvZY00h1/qy6pKFuos71ehJOwtL4G30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pF8Ka/btrCvZY00h1/qy6pKFuos71ehJOwtL4G30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpF8Ka%2FbtrCvZY00h1%2Fqy6pKFuos71ehJOwtL4G30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;954&quot; height=&quot;437&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CSS 속성의 우선순위를 결정하는 요인들이 몇 가지 있다. 나중에 선언할수록, 선택자가 더 구체적으로 명시되어있을수록 우선순위가 높은 것 등이 있다. cascade layers는 &lt;b&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;@layer를&lt;/span&gt; 사용해서 layer 단위로 우선순위를 지정&lt;/b&gt;해줄 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HBtlQ/btrCAp2sPMO/gqB69fGbKzbK6vgybgK1Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HBtlQ/btrCAp2sPMO/gqB69fGbKzbK6vgybgK1Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HBtlQ/btrCAp2sPMO/gqB69fGbKzbK6vgybgK1Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHBtlQ%2FbtrCAp2sPMO%2FgqB69fGbKzbK6vgybgK1Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;415&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652929888624&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@import 'theme.css' layer(utilities);

@layer base {
  .link {
    color: blue; /* ignored */
  }
}

@layer typography {
  a {
    color: green; /* styles *all* links */
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CSS 파일 자체를 레이어로 import 할 수도 있고, 나중에 선언될수록 우선순위가 높다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://web.dev/interop-2022/#new-viewport-units&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Interop 2022 - New viewport units&amp;nbsp;: dvh, lvh, svh&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Interop 2022 프로젝트의 목표는 이러한 표준을 기반으로 하는 웹 애플리케이션이 작동하고, 서로 다른 기기, 플랫폼, 운영체제로 구성된 전 세계 생태계에서 동일하게 보이도록 하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;현재 여러 가지 기능을 각각의 플랫폼에서 테스트하고 개발하는 중인데, 그중 눈에 띄는 기능 중 하나가 새로운 viewport 단위였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;701&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYYP7/btrCyxl8Prd/zlaBQUZdihgFqThrGuj2pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYYP7/btrCyxl8Prd/zlaBQUZdihgFqThrGuj2pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYYP7/btrCyxl8Prd/zlaBQUZdihgFqThrGuj2pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYYP7%2FbtrCyxl8Prd%2FzlaBQUZdihgFqThrGuj2pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;701&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;701&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위와 같이&lt;b&gt; dynamic, largest, smallest 세 가지 크기로 viewport 단위를 추가&lt;/b&gt;하여, &lt;b&gt;주소 표시줄을 고려하면서 모바일 기기에서 보이는 뷰포트 레이아웃을 세밀하게 다룰 수 있을 것&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Record/review</category>
      <category>CSS aspect-ratio</category>
      <category>Google I/O</category>
      <category>structuredClone</category>
      <category>What's new for the web platform</category>
      <category>구글 개발자 컨퍼런스</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/137</guid>
      <comments>https://xiubindev.tistory.com/137#entry137comment</comments>
      <pubDate>Thu, 19 May 2022 12:17:25 +0900</pubDate>
    </item>
    <item>
      <title>ESLint를 자동화해보자 (feat. husky &amp;amp; lint-staged)</title>
      <link>https://xiubindev.tistory.com/136</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/voi9U/btrzjHe4yHS/1tUFrkYNq42DmyIbaP3Mwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/voi9U/btrzjHe4yHS/1tUFrkYNq42DmyIbaP3Mwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/voi9U/btrzjHe4yHS/1tUFrkYNq42DmyIbaP3Mwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvoi9U%2FbtrzjHe4yHS%2F1tUFrkYNq42DmyIbaP3Mwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Lint&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;코드의 오류나 버그, 스타일 등을 점검&lt;/b&gt;하는 것을 lint라고 부른다. 린트는 코드의 가독성을 높이는 것뿐만 아니라 자바스크립트와 같은 동적 언어의 특성인 런타임 버그를 예방하는 역할도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ESLint는 ECMAScript 코드에서 문제점을 검사하고 더 나은 코드로 고쳐주는 lint tool이다. ESLint를 사용해서 개발을 하면 잠재적인 오류와 버그를 제거할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;협업을 하는 프로젝트에서 lint를 적용하는 것은 필수라고 할 수 있다. 왜냐하면 모든 개발자들이 &lt;b&gt;같은 규칙 내에서 개발해야 잠재적인 오류를 방지&lt;/b&gt;할 수 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;아무리 ESLint를 모든 개발자들이 사용한다고 해도 warning, error 등 lint 결과를 무시하고 바로 commit 하고 push 해버릴 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이러한 상황을 미연에 방지하기 위해서는 lint 에러가 있는 경우 commit을 하지 못하도록 하는 것이 최선의 방법일 것이다. 이를 구현하기 위해서는 git hook이라는 것을 사용하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Git Hook&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;프로그래밍에서 hook이란 특정 이벤트 또는 함수가 호출되기 전/후에 호출이 되는 코드를 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Git은 특정 상황에 특정 스크립트를 실행할 수 있도록 하는 Hook이라는 기능을 지원&lt;/b&gt;하고 있다. 별도로 설치할 것은 없고 모든 git repository에서 지원한다. git으로 관리하고 있는 폴더에서 cd .git/hooks/ 명령어를 치고 ls 명령어를 쳐보면 .sample 확장자로 되어있는 파일이 13개가 있다. 이는 git hook이 지원하는 특정 상황이 13개인 것을 알 수 있다. 해당 hook을 사용하려면 .sample 확장자만 지우면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkiOeU/btry67lAvXm/4HcJfL1LKbJB5rNqDAqEz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkiOeU/btry67lAvXm/4HcJfL1LKbJB5rNqDAqEz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkiOeU/btry67lAvXm/4HcJfL1LKbJB5rNqDAqEz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkiOeU%2Fbtry67lAvXm%2F4HcJfL1LKbJB5rNqDAqEz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;83&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이번에 사용해볼 git hook은 pre-commit이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;pre-commit&lt;/b&gt;은 단어 뜻 그대로 commit 직전에 실행되는 hook이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;husky&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;git hooks는 .git 디렉터리에 저장되기 때문에 git repository에 원격으로 저장하고 관리할 수 없다. 따라서 git hook을 공유하기 위한 방법에는 여러 가지가 있지만 그중 husky를 사용해보고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;husky란 Node.js 개발 환경에서 &lt;b&gt;git hook을 편리하게 사용&lt;/b&gt;할 수 있게 만들어주는 도구이다. husky를 사용하면 프로젝트 별로 commit, push 등과 관련된 정책을 관리하고 공유할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;lint-staged&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;git hook을 활용해서 commit 하기 전에 lint를 실행해서 코드를 검사할 수 있게 됐다. 하지만 검사해야하는 확장자에 해당하는 모든 파일을 검사하려면 굉장히 오랜 시간이 소요될 것이다. 이때 lint-staged를 함께 사용하면 변경된 파일만 검사할 수 있다. 즉 &lt;b&gt;git staging area에 올라온 파일만 검사&lt;/b&gt;할 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;지금부터 React 프로젝트에서 husky와 lint-staged를 활용해서 commit 전에 ESLint를 자동화해보도록 하겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1. 모듈 설치 : npm install husky lint-staged&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;모듈을 설치하기 전에 ESLint, Prettier 등 도구를 설치 및 세팅해놔야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;설치가 완료되면 .husky 폴더가 루트에 생긴다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;87&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etPb4L/btry8Qc0N6N/tIVEUnVgrHoIOsD1XgiVzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etPb4L/btry8Qc0N6N/tIVEUnVgrHoIOsD1XgiVzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etPb4L/btry8Qc0N6N/tIVEUnVgrHoIOsD1XgiVzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetPb4L%2Fbtry8Qc0N6N%2FtIVEUnVgrHoIOsD1XgiVzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;234&quot; height=&quot;87&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;87&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;그리고 package.json 파일에 아래와 같은 코드를 추가하고 해당 스크립트를 한 번 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;prepare&quot;: &quot;husky install&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Yarn2를 사용하고 있다면 아래와 같이 코드를 추가하고 해당 스크립트를 한 번 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1672925380782&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;postinstall&quot;: &quot;husky install&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;실행 후 .husky 폴더의 pre-commit 파일을 살펴보면 npx lint-staged라는 명령어가 있는데, commit 전에 이 명령어를 실행한다는 의미이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/sh
. &quot;$(dirname &quot;$0&quot;)/_/husky.sh&quot;

npx lint-staged&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. package.json의 lint-staged 설정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ESLint를 실행하여 검사하고 싶은 확장자를 설정하고 원하는 옵션도 세팅해서 명령어를 작성하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;나는 아래와 같이 ESLint의 cache, fix 옵션을 설정했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;lint-staged&quot;: {
    &quot;*.{ts,tsx}&quot;: &quot;eslint --cache --fix&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;3. git add , git commit 실행&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;코드를 수정하고 git add를 한 후에 git commit을 하면 우리가 세팅한 husky와 lint-staged에 의해 ESLint가 실행된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;lint 에러가 있다면 commit에 실패하고 어떤 에러가 발생했는지 터미널에 나타난다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSdRsw/btrzcP416l1/hSqkmi9hhm8qKlmtswlKkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSdRsw/btrzcP416l1/hSqkmi9hhm8qKlmtswlKkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSdRsw/btrzcP416l1/hSqkmi9hhm8qKlmtswlKkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSdRsw%2FbtrzcP416l1%2FhSqkmi9hhm8qKlmtswlKkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;291&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;lint 에러를 고치고 다시 commit을 실행하면 정상적으로 commit이 되는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB2vxN/btry8jT72tV/vefcSzjOfL8FGMuJIIbWz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB2vxN/btry8jT72tV/vefcSzjOfL8FGMuJIIbWz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB2vxN/btry8jT72tV/vefcSzjOfL8FGMuJIIbWz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB2vxN%2Fbtry8jT72tV%2FvefcSzjOfL8FGMuJIIbWz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;99&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ESLint를 자동화해보며..&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;git hook을 사용해서 ESLint를 자동화하는 과정을 알아봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이처럼 lint에 사용해도 되고 master 브랜치에 직접 push 하는 것을 막을 수도 있고 git hook은 다양한 정책에 활용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;필요한 정책을 셋업하고 유연하게 각자 프로젝트에 적용해보자!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;22년 6월 9일 추가 내용&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Next.js 프로젝트 적용 방법&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;package.json&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654744734275&quot; class=&quot;json&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;next&quot;,
  &quot;version&quot;: &quot;0.1.0&quot;,
  &quot;private&quot;: true,
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;next dev&quot;,
    &quot;build&quot;: &quot;next build&quot;,
    &quot;start&quot;: &quot;next start&quot;,
    &quot;lint&quot;: &quot;next lint --fix&quot;,
    &quot;prepare&quot;: &quot;husky install&quot;
  },
  &quot;dependencies&quot;: {
    &quot;next&quot;: &quot;12.1.6&quot;,
    &quot;react&quot;: &quot;18.1.0&quot;,
    &quot;react-dom&quot;: &quot;18.1.0&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@types/node&quot;: &quot;17.0.40&quot;,
    &quot;@types/react&quot;: &quot;18.0.12&quot;,
    &quot;@types/react-dom&quot;: &quot;18.0.5&quot;,
    &quot;@typescript-eslint/eslint-plugin&quot;: &quot;^5.27.1&quot;,
    &quot;eslint&quot;: &quot;8.17.0&quot;,
    &quot;eslint-config-next&quot;: &quot;12.1.6&quot;,
    &quot;eslint-config-prettier&quot;: &quot;^8.5.0&quot;,
    &quot;husky&quot;: &quot;&amp;gt;=6&quot;,
    &quot;lint-staged&quot;: &quot;&amp;gt;=10&quot;,
    &quot;prettier&quot;: &quot;^2.6.2&quot;,
    &quot;typescript&quot;: &quot;4.7.3&quot;
  },
  &quot;lint-staged&quot;: {
    &quot;*.{ts,tsx}&quot;: &quot;next lint --fix&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;create-next-app으로 프로젝트를 셋업 했을 때 기본적으로 eslint가 세팅되어있고, lint 실행은 next lint로 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;React 프로젝트와 비교했을 때 husky, lint-staged를 사용하는 방법은 크게 다르지 않은데, 중요한 한 가지가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;.lintstagedrc.js &lt;/b&gt;&lt;/span&gt;파일을 생성해야 한다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;u&gt;Next.js 프로젝트 루트 폴더에 파일을 생성&lt;/u&gt;하고 공식 문서에서 제공해준 아래와 같은 코드를 작성해주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.lintstagedrc.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654745172648&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const path = require('path')

const buildEslintCommand = (filenames) =&amp;gt;
  `next lint --fix --file ${filenames
    .map((f) =&amp;gt; path.relative(process.cwd(), f))
    .join(' --file ')}`

module.exports = {
  '*.{js,jsx,ts,tsx}': [buildEslintCommand],
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&amp;nbsp;.lintstagedrc.js&lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;파일을 생성하지 않으면 에러가 나면서 린트가 실행되지 않으므로 반드시 위와 같은 작업을 진행해주자!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;참고 문헌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://github.com/okonet/lint-staged&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/okonet/lint-staged&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://typicode.github.io/husky/#/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://typicode.github.io/husky/#/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://library.gabia.com/contents/8492/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://library.gabia.com/contents/8492/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://techblog.woowahan.com/2530/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/2530/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://nextjs.org/docs/basic-features/eslint#lint-staged&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nextjs.org/docs/basic-features/eslint#lint-staged&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Study/Frontend</category>
      <category>eslint</category>
      <category>ESLint 자동화</category>
      <category>git hook</category>
      <category>husky</category>
      <category>lint-staged</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/136</guid>
      <comments>https://xiubindev.tistory.com/136#entry136comment</comments>
      <pubDate>Thu, 14 Apr 2022 13:23:09 +0900</pubDate>
    </item>
    <item>
      <title>웹팩 성능 최적화를 통해 개발 효율성을 극대화 해보자 ✨</title>
      <link>https://xiubindev.tistory.com/135</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Izy4U/btryOS17cis/PSH86k1JfbwQMqq0oRrkjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Izy4U/btryOS17cis/PSH86k1JfbwQMqq0oRrkjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Izy4U/btryOS17cis/PSH86k1JfbwQMqq0oRrkjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIzy4U%2FbtryOS17cis%2FPSH86k1JfbwQMqq0oRrkjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt; &amp;nbsp; 이 글은 윈도우로 개발하는 동료로부터 시작되었습니다..!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;사실 m1 맥북 13 프로를 쓰는 나로서는 개발하는데 성능의 문제성을 크게 느끼지 못했다. 서버 사양도 나쁘지 않아서 배포하는데도 크게 느리다는 생각을 못했다. 하지만 윈도우 환경에서 개발하는 동료의 빌드 속도를 보고 경악을 금치 못했다...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;첫 순수 빌드 시간이 거의 30초에 가까웠으며, webpack dev server를 첫 구동하는 시간이 무려 90초나 걸리는 것이었다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;내 개발환경의 성능과 비교해보자면 첫 빌드는 약 3배, dev server 구동은 약 13배 차이가 났다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 동료 개발자의 웹팩 개선 시도를 기점으로, 정확하게 개발 환경의 문제점을 파악하고 고치기 위해 Webpack 공식 문서를 정독하면서 성능을 최적화해보기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;프로젝트 기술 스택&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Webpack5, React, Typescript&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uNQOl/btryLBgvKYn/I1e9Jhi5QUqJkEIHx6Xb6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uNQOl/btryLBgvKYn/I1e9Jhi5QUqJkEIHx6Xb6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uNQOl/btryLBgvKYn/I1e9Jhi5QUqJkEIHx6Xb6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuNQOl%2FbtryLBgvKYn%2FI1e9Jhi5QUqJkEIHx6Xb6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;615&quot; height=&quot;293&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;1. 최적의 devtool 옵션을 선택하자.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;웹팩으로 코드를 번들링 하게 되면 에러를 트래킹 하기 어려워진다. 하지만 source map을 사용하게 되면 번들링 된 파일의 코드를 소스 파일의 원래 위치로 다시 매핑해줘서, 어디서 에러가 발생했는지 알기 쉬워진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;가능하면 소스 맵을 생성하는 것이 좋은데, 어떤 source mapping 스타일을 선택하는지에 따라 빌드 및 리빌드 속도에 큰 영향을 미친다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;⚠️ 최적화 이전&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649232583568&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;devtool: !isDevelopment ? 'hidden-source-map' : 'inline-source-map',&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;최적화 이전에는 프로덕션 모드에서는 hidden-source-map이고, 개발 모드에서는 inline-source-map이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;devtool 옵션 중에 빌드와 리빌드 성능이 가장 느린 두 가지를 쓰고 있었다...ㅎㅎ;; 물론 코드 품질은 original 이어서 디버깅하기에는 최적이지만 성능은 최악인 옵션들이었다. 그래서 성능은 빠르면서 품질도 좋은 옵션으로 바꿔보고자 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649234068879&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;devtool: env.development ? 'eval-cheap-module-source-map' : false,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;웹팩 공식 문서 가이드를 보면 &lt;u&gt;eval-cheap-module-source-map&lt;/u&gt; 옵션이 가장 좋다고 기재돼있다. 빌드는 느림이지만 리빌드는 빠름이고, 코드 품질이 original lines 여서 트랜스파일하기 전의 코드를 볼 수 있어 개발 모드에서 디버깅하기 좋은 옵션이라고 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;프로덕션 모드에서는 굳이 소스 맵이 필요하지 않다고 생각해서 소스 맵을 생성하지 않는 옵션을 줬다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;더 많은 옵션을 보고 고르고 싶다면 &lt;a href=&quot;https://webpack.kr/configuration/devtool/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기서&lt;/a&gt; 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. 최적의 cache 타입을 선택하자.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;cache 타입을 설정하면 생성된 웹팩 모듈 및 청크를 캐시 하여 빌드 속도를 개선할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;cache는 개발 모드에서는 &lt;u&gt;memory&lt;/u&gt;, 프로덕션 모드에서는 비활성화되는 것이 디폴트로 세팅되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;최적화 이전에는 디폴트 상태로 내버려두었기 때문에 프로덕션 모드로 빌드할 때 비활성화 상태로 되어 있어서 캐싱이 전혀 안된 상태였기 때문에 빌드 성능이 좋지 않았다. 그래서 최적화 이후에는 개발 모드와 프로덕션 모드에서 별개로 타입을 설정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649236493102&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cache: { type: env.development ? 'memory' : 'filesystem' },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;타입을 &lt;u&gt;filesystem&lt;/u&gt;으로 설정하면 파일 시스템 캐시를 활성화하고&amp;nbsp;&lt;span style=&quot;color: #2b3a42;&quot;&gt;&lt;i&gt;node_modules/.cache/webpack&lt;/i&gt;&amp;nbsp;경로에 캐싱된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이와 같이 캐싱을 통해 빌드 속도를 개선할 수 있고 filesystem으로 설정하면 &lt;a href=&quot;https://webpack.kr/configuration/cache/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;더 많은 옵션&lt;/a&gt;을 설정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;3. output의 filename을 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;chunkhash로 저장하자.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;웹팩으로 빌드한 결과물인 파일 내용이 변경되지 않는 한 캐싱 상태로 유지하면 브라우저에서 불필요한 네트워크 트래픽을 줄여서 웹 성능을 개선할 수 있다. 파일을 캐싱할 수 있는 방법 중에 하나는 output의 filename 옵션의 &lt;span style=&quot;background-color: #ffffff; color: #2b3a42;&quot;&gt;&lt;u&gt;chunkhash를&lt;/u&gt; 사용하는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #2b3a42; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;chunkhash를 사용하게 되면 파일이 변경될 경우에만 해시 값을 생성해서 파일 이름을 저장하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;⚠️ 최적화 이전&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649240241243&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output: {
    path: path.join(__dirname, 'build'),
    filename: '[name].js',
    publicPath: '/build/',
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649240286024&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output: {
    pathinfo: false,
    path: path.join(__dirname, 'build'),
    filename: 'js/[name]-[chunkhash].js',
    assetModuleFilename: 'img/[hash][ext][query]',
    publicPath: '/',
    clean: true,
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;4. output의 pathinfo를 false로 설정하자.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;웹팩은 번들에 포함된 모듈에 대한 정보를 주석으로 번들에 포함하도록 생성한다. 그러나 수천 개의 모듈을 번들로 묶는 프로젝트에서는 가비지 컬렉션에 과부하를 주므로 &lt;u&gt;pathinfo를 false로&lt;/u&gt; 설정하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;5. 로더, 플러그인은 꼭 필요한 것만 사용하자.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;굳이 사용할 필요 없는 로더나 플러그인만 제거해도 빌드 성능이 개선된다. 그리고 개발 서버를 실행할 때 필요한 플러그인과 빌드할 때만 필요한 플러그인을 나눠서 실행하는 것도 성능을 개선하는데 큰 영향을 미친다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;  fork-ts-checker-webpack-plugin&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존에는 빌드할 때도 실행되도록 했는데, 굳이 빌드할 때 필요하지 않다는 생각이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 개발 모드일 때만 실행하도록 했고, &lt;u&gt;async: true&lt;/u&gt; 옵션을 줘서 컴파일을 빠르게 하고 별도의 프로세스에서 타입 체크도 할 수 있게 했다. 개발 모드일 때 async는 디폴트 값으로 true가 들어간다. 기존에는 false로 줘서 빌드도 느리고 컴파일도 느렸던 것으로 파악된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;eslint와 이 플러그인의 목적은 둘 다 컴파일 타임에 미리 에러를 잡아내는 것이다. 다만 eslint는 문법적으로 잘못된 부분을 잡아주고, type check 플러그인은 타입에 맞게 정확히 작성됐는지 검사해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;  webpack-bundle-analyzer&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존에는 개발 모드일 때도 실행되도록 했는데, 굳이 개발할 때 필요하지 않다는 생각이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 빌드할 때만 실행되도록 했고, &lt;u&gt;analyzerMode: 'static', openAnalyzer: false &lt;/u&gt;옵션들을 줘서 html 파일로 생성되도록 하고 브라우저에서 자동으로 열리지 않도록 설정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649301307286&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  if (env.WEBPACK_SERVE &amp;amp;&amp;amp; config.plugins) {
    config.plugins.push(new ForkTsCheckerWebpackPlugin());
    config.plugins.push(new ReactRefreshWebpackPlugin());
  }

  if (!env.WEBPACK_SERVE &amp;amp;&amp;amp; config.plugins) {
    config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false }));
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;  file-loader&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존에는 애셋을 처리할 때 file-loader를 사용했는데, Webpack5에서는 Asset modules가 새로 추가되어서 로더를 추가로 구성하지 않아도 애셋 파일을 사용할 수 있게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 file-loader를 대체하기 위해 &lt;b&gt;asset/resource&lt;/b&gt; 모듈을 새로 추가해서 에셋을 처리했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;파일을 출력 디렉터리로 내보낼 때 디렉터리 및 파일명을 정의할 수 있다. &lt;u&gt;output.assetModuleFilename &lt;/u&gt;에서 수정할 수 있다. 더 자세한 방법은 &lt;a href=&quot;https://webpack.kr/guides/asset-modules/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기서&lt;/a&gt; 확인 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;  clean-webpack-plugin&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존에는 웹팩으로 번들링 했던 결과물을 지우고 다시 번들링 하기 위해 clean-webpack-plugin 을 사용했었다. 하지만 Webpack5 에서는 &lt;b&gt;output에 clean이라는 옵션&lt;/b&gt;이 생겨서 이 플러그인을 대체할 수 있게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;u&gt;clean: true&lt;/u&gt; 옵션을 주게 되면 번들링 해서 출력하기 전에 output 디렉터리를 정리해주고 새로 번들링 된 결과물을 내보낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649301415409&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module: {
  rules: [
  ...
    {
      test: /\.(gif|jpg|png|webp|svg)$/,
      type: 'asset/resource',
    },
  ],
},

output: {
  ...
  assetModuleFilename: 'img/[hash][ext][query]',
  clean: true,
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;6. esbuild-loader를 사용하자.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;지금은 &lt;a href=&quot;https://2021.stateofjs.com/en-US/libraries/build-tools&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;자바스크립트 번들러의 춘추전국시대&lt;/a&gt;라고 해도 과언이 아니다. 원래는 한동안 webpack이 주름을 잡고 있었는데, 최근 들어 Vite, esbuild 등 다양한 번들러가 나왔고 웹팩보다 좋은 성능을 자랑하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존에는 webpack + babel-loader 조합으로 프로젝트를 구성했다. 하지만 이번에는 esbuild를 적용해서 성능을 높여보기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KDZlA/btryExjHqhg/5NzFWh6eVCk2k59y2fAkgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KDZlA/btryExjHqhg/5NzFWh6eVCk2k59y2fAkgk/img.png&quot; data-alt=&quot;https://esbuild.github.io/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KDZlA/btryExjHqhg/5NzFWh6eVCk2k59y2fAkgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKDZlA%2FbtryExjHqhg%2F5NzFWh6eVCk2k59y2fAkgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;131&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://esbuild.github.io/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;esbuild는 Go 언어로 작성됐고, 파싱, 프린팅, 소스 맵 추출 등 과정이 동시에 진행되며 빌드에 불필요한 단계를 줄여서 다른 번들러보다 속도를 확실히 빠르게 개선했다고 한다. 자세한 내용은 공식 문서에도 나와있으니 살펴보면 좋을 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기존 webpack 생태계를 그대로 유지하되 esbuild를 적용하기 위해서&amp;nbsp;babel-loader를 &lt;b&gt;esbuild-loader&lt;/b&gt;로 대체해보기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;대체하는 방법은 쉽다. 원래 babel-loader를 적용하던 곳에 esbuild-loader를 적용하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;⚠️ 최적화 이전&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649308725244&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                targets: {
                  browsers: ['last 2 versions', 'ie &amp;gt;= 11'],
                },
                useBuiltIns: 'usage',
                corejs: 3,
                shippedProposals: true,
                debug: isDevelopment,
              },
            ],
            '@babel/preset-react',
            '@babel/preset-typescript',
          ],
        },
      },
      ...
    ],
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649308820739&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module: {
      rules: [
        {
          test: /\.tsx?$/,
          loader: 'esbuild-loader',
          options: {
            loader: 'tsx',
            target: 'es2015',
          },
        },
        ...
      ],
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;7. optimization.minimizer 플러그인을 사용하자.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;기본적으로 프로덕션 모드로 빌드할 때 optimization.minimize 옵션은 true로 설정돼있다. 이 옵션이 true이면 minimizer에 지정된 플러그인을 사용해서 번들을 최소화한다. 웹팩 공식 문서 가이드에는 &lt;a href=&quot;https://webpack.kr/configuration/optimization/#optimizationminimizer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;terser-webpack-plugin&lt;/a&gt;을 사용하는 것이 예제로 나와있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 우리는 esbuild-loader를 사용할 것이기 때문에 이를 최대한으로 활용해보고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;esbuild-loader는 빌드 속도도 빠를 뿐만 아니라 Minification 툴도 함께 지원한다는 장점이 있다. 기존에 babel-loader를 사용했을 때는 파일 압축을 위해서 다양한 플러그인을 추가해야 했는데, esbuild-loader는 &lt;u&gt;ESBuildMinifyPlugin&lt;/u&gt;을 포함하고 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ESBuildMinifyPlugin을 사용하면 terser-webpack-plugin과 css-minimizer-plugin을 대체할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이 플러그인은 다른 플러그인에 비해 &lt;a href=&quot;https://github.com/privatenumber/minification-benchmarks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;속도가 10배 이상 빠르고 번들 사이즈도 정말 작다&lt;/a&gt;고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;minimizer에 플러그인을 추가하고 target, css 옵션을 설정해주면 끝이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649309730369&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;optimization: {
  minimizer: [
    new ESBuildMinifyPlugin({
      target: 'es2015',
      css: true,
    }),
  ],
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;8. devServer.static에 명확한 경로를 작성하자.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;u&gt;webpack-dev-server&lt;/u&gt;는 웹팩에서 제공해주는 개발용 웹서버이다. 웹서버를 실행시키면 코드가 변경될 때 자동으로 변경된 파일만 다시 번들링 하여 서버를 다시 구동해주는 역할도 한다. webpack-dev-server는 컴파일 후 출력 파일을 작성하지 않는다. 대신 번들링 한 결과물을 &lt;u&gt;메모리에 보관&lt;/u&gt;하고, 서버의 루트 경로에 마운트 된 실제 파일인 것처럼 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;devServer의 static 옵션은 번들링 된 정적 파일들을 담고 있는 디렉터리를 명시해준다. 즉 &lt;u&gt;서버에 콘텐츠를 제공할 위치&lt;/u&gt;를 알려주는 역할을 하는데, devServer는 static 옵션에 설정된 디렉터리를 브라우저에 띄워주게 된다. static 옵션을 설정하지 않으면 기본적으로 현재 작업 디렉터리 전체를 사용해서 콘텐츠를 제공하게 된다. 따라서 특정 디렉터리를 명시해주는 것이 번들링 속도를 높여줄 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;♻️ 최적화 이후&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649311018036&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;devServer: {
  historyApiFallback: true,
  port: 3090,
  static: { directory: path.resolve(__dirname, 'build') },
  hot: true,
  open: true,
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #8a3db6; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;웹팩 성능 최적화 이후 동료의 개발 속도 개선 결과&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;순수 빌드 시간: 30초 ➡️ &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;6초&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;dev server 첫 구동 시간: 90초&amp;nbsp;➡️&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;5초&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;웹팩 성능 최적화를 하나하나씩 해보면서 속도가 빨라지는 것을 보며 쾌감(?)을 느껴서 기분이 좋았다 :)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;맥북으로 개발해서 크게 체감하지 못했던 최적화의 필요성을 다시 한번 깨닫게 되는 좋은 경험이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그리고 웹팩 공식 문서 가이드만 읽어도 최적화 가이드가 정말 잘 나와있었는데, 이 부분을 놓친 게 아쉬웠다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이것만 제대로 읽었어도&amp;nbsp;동료 개발자의 개발 효율성을 진즉에 높여줄 수 있었을 텐데 하는 아쉬움이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다음 프로젝트에서는 또 어떤 것을 동료와 함께 헤쳐나갈지 기대가 된다! 회사 생활이 정말 즐겁다!(진심 )&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;참고 문헌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://webpack.kr/guides/build-performance/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://webpack.kr/guides/build-performance/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://webpack.kr/configuration/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://webpack.kr/configuration/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://esbuild.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://esbuild.github.io/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://techblog.woowahan.com/6465/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/6465/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://ui.toast.com/fe-guide/ko_BUNDLER&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ui.toast.com/fe-guide/ko_BUNDLER&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Study/Frontend</category>
      <category>esbuild-loader</category>
      <category>Webpack5</category>
      <category>웹팩</category>
      <category>웹팩 빌드 속도 개선</category>
      <category>웹팩 성능 최적화</category>
      <author>xiubin</author>
      <guid isPermaLink="true">https://xiubindev.tistory.com/135</guid>
      <comments>https://xiubindev.tistory.com/135#entry135comment</comments>
      <pubDate>Fri, 8 Apr 2022 16:25:47 +0900</pubDate>
    </item>
  </channel>
</rss>