<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sungmin.log</title>
        <link>https://velog.io/</link>
        <description>Share everything that I like</description>
        <lastBuildDate>Mon, 08 Jan 2024 11:21:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. sungmin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/eddie_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[리액트 테스팅 라이브러리 - toBeInTheDocument() 에러]]></title>
            <link>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8C%85-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-toBeInTheDocument-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8C%85-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-toBeInTheDocument-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Mon, 08 Jan 2024 11:21:54 GMT</pubDate>
            <description><![CDATA[<p>expect(<code>html 엘리먼트</code>).toBeInTheDocument() 사용하려는데 <code>toBeInTheDocument()</code>에 에러가 발생했고 그 에러 메시지는 다음과 같다. </p>
<blockquote>
<p>Property <code>toBeInTheDocument</code> does not exist on type <code>JestMatchers&lt;HTMLElement&gt;</code></p>
</blockquote>
<h2 id="에러-발생-원인">에러 발생 원인</h2>
<p>이러한 에러가 발생하는 이유는 처음 리액트+타입스크립트 프로젝트를 만들었을 때 타입스크립트에 <code>@tesing-library/jest-dom</code>의 타입 정의(Type Definition)를 안 해주었기 때문이다. 타입스크립트에 Type definition을 주면 된다. 해결방법은 간단하다.</p>
<h2 id="해결책">해결책</h2>
<ol>
<li><code>npm install --save-dev @tesing-library/jest-dom</code> 을 실행하여 <code>@tesing-library/jest-dom</code>을 설치한다.</li>
<li><code>tsconfig.json</code>의 <code>compilerOptions</code>에 다음을 추가한다.</li>
</ol>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    // ...
    &quot;types&quot;: [&quot;@testing-library/jest-dom&quot;],
    // ...
  }
}</code></pre>
<p>[출처]
<a href="https://github.com/testing-library/jest-dom/issues/546">https://github.com/testing-library/jest-dom/issues/546</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git 잘못 써서 오늘 했던 코드를 날려 먹었어요 ㅠㅠ]]></title>
            <link>https://velog.io/@eddie_kim/git-%EB%95%8C%EB%AC%B8%EC%97%90-%EC%A7%80%EA%B8%88%EA%B9%8C%EC%A7%80-%ED%95%B4-%EC%99%94%EB%8D%98-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%82%A0%EB%A0%A4-%EB%A8%B9%EC%9D%80-%EC%A0%81%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@eddie_kim/git-%EB%95%8C%EB%AC%B8%EC%97%90-%EC%A7%80%EA%B8%88%EA%B9%8C%EC%A7%80-%ED%95%B4-%EC%99%94%EB%8D%98-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%82%A0%EB%A0%A4-%EB%A8%B9%EC%9D%80-%EC%A0%81%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80</guid>
            <pubDate>Mon, 08 Jan 2024 07:49:16 GMT</pubDate>
            <description><![CDATA[<h2 id="git-명령어를-잘못-써서-지금까지-해-왔던-코드를-날려-먹은-적이-있는가">git 명령어를 잘못 써서 지금까지 해 왔던 코드를 날려 먹은 적이 있는가?</h2>
<p>그게 나다. 오늘 그랬다. 오늘 작업을 완료 했는데(심지어 파일이 10개나 되었다) 실수로 작업한 것들을 날렸다. 스터디 카페에 있었는데 울고 싶었다.
이전 커밋으로 되돌릴 때 이전 커밋 앞에 있는 모든 커밋들은 사라지는 <code>git reset --hard</code> 명령어 때문이었다.</p>
<p>멘탈이 나갔지만, 정신을 가다듬고 사라진 커밋을 복구할 해결 방법을 구글에 검색해보았다.</p>
<h2 id="git-reflog란">git reflog란?</h2>
<p>Reflog는 <strong>HEAD의 상태</strong>가 변하거나 <strong>HEAD가 참조한 branch</strong>의 상태가 변할 때를 추적하고 기록으로 남긴다. 그렇다면 무엇이 HEAD의 상태와 HEAD가 참조한 branch의 상태를 변화시키는 것인가?</p>
<h2 id="git-reflog가-추적하고-기록으로-저장하는-명령어들">git reflog가 추적하고 기록으로 저장하는 명령어들</h2>
<p>다음 명령어들이 git reflog가 추적하는 것들이다.</p>
<ul>
<li><code>git commit</code></li>
<li><code>git reset</code> </li>
<li><code>git checkout</code></li>
<li><code>git merge</code> </li>
<li><code>git rebase</code></li>
<li><code>git cherrypick</code></li>
<li><code>git pull</code></li>
</ul>
<h2 id="git-reflog-사용법">git reflog 사용법</h2>
<p>git reflog와 사용하기 위한 명령어는 다음과 같다</p>
<pre><code class="language-bash">git reflog show HEAD </code></pre>
<p><code>git reflog show HEAD</code> 명령어와 같은 역할을 하지만 짧은 명령어는 다음과 같다.</p>
<pre><code class="language-bash">git reflog</code></pre>
<p><code>git reflog</code> 명령어를 실행시키면 커밋 기록 뿐만 아니라 브랜치 업데이트 되는 상태까지 출력이 되는데 다음과 같은 화면이 나온다.
<img src="https://velog.velcdn.com/images/eddie_kim/post/57c4b313-bf43-45f7-8265-2ec7683d530f/image.png" alt=""></p>
<ul>
<li><code>HEAD@{숫자}</code> (0부터 시작) - 가장 작은 숫자가 가장 최신의 기록이다.</li>
<li><code>reset</code>, <code>commit</code>, <code>checkout</code> - git reflog가 추적하고 기록한 git 명령어들이다.</li>
</ul>
<p>내가 오늘 겪었던 일은 다음과 같다.</p>
<p><code>HEAD@{5}</code> - 내가 작업하고 커밋한 내역이다.
<code>HEAD@{4}</code> - 착오로 <code>git reset --hard</code>를 써서 작업한 커밋을 날려먹었다.</p>
<p>날려먹은 커밋을 복구하기 다음과 같은 명령어를 실행했다.</p>
<pre><code class="language-bash">git reflog # 사라진 커밋 기록 가져옴
git reset --hard &lt;SHA hash&gt; # 사라진 커밋 복구</code></pre>
<p><code>HEAD@{0}</code> - <code>git reset --hard 70b4011</code>로 사라진 커밋을 복구하였고 없어져버린 10개의 파일도 그대로 복구가 되었다..</p>
<p>git reset --hard를 잘못 쓴 덕분에 git reflog라는 것을 처음 알게 되었고 정말 유용하게 쓰게 될 것 같다.</p>
<p>[출처]</p>
<ul>
<li><a href="https://seosh817.tistory.com/297">https://seosh817.tistory.com/297</a></li>
<li><a href="https://www.atlassian.com/git/tutorials/rewriting-history/git-reflog">https://www.atlassian.com/git/tutorials/rewriting-history/git-reflog</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Craco Alias 설정을 통해 relative path hell 벗어나기]]></title>
            <link>https://velog.io/@eddie_kim/Craco-Alias-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@eddie_kim/Craco-Alias-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 18 Dec 2023 02:48:45 GMT</pubDate>
            <description><![CDATA[<p><strong>Craco</strong>는 Create Reacrt App Configuration override 약어로, CRA에 Webpack 설정을 eject할 필요없이 설정 파일을 새로 만들어서 웹팩 설정을 덮어쓰는데에 도움을 주는 패키지이다.
그 중, <a href="https://www.npmjs.com/package/craco-alias">craco-alias</a>는 <code>import App from &quot;../../../../App.js</code> 같은 alias를 막기 위해 사용되는 node module 패키지이다.</p>
<h2 id="1-craco-관련-패키지-설치">1. craco 관련 패키지 설치</h2>
<pre><code class="language-bash">yarn add -D @craco/craco craco-alias</code></pre>
<h2 id="2-tsconfigpathsjson-파일-만들기">2. tsconfig.paths.json 파일 만들기</h2>
<pre><code class="language-json">{
    &quot;compilerOptions&quot;: {
        &quot;baseUrl&quot;: &quot;.&quot;,
        &quot;paths&quot;: {
            &quot;@/*&quot;: [&quot;src/*&quot;],
            &quot;@components/*&quot;: [&quot;src/components/*&quot;],
          // alias를 주고 싶은 폴더를 추가하면 된다.
        }
    }
}</code></pre>
<h2 id="3-cracoconfigjs-파일-만들기">3. craco.config.js 파일 만들기</h2>
<pre><code class="language-js">const CracoAlias = require(&#39;craco-alias&#39;)

module.exports = {
  plugins: [
    {
      plugin: CracoAlias,
      options: {
        source: &#39;tsconfig&#39;,
        tsConfigPath: &#39;tsconfig.paths.json&#39;,
      },
    },
  ]
}</code></pre>
<h2 id="4-tsconfigjson에-tsconfigpathsjs의-존재를-알게-해주기">4. tsconfig.json에 tsconfig.paths.js의 존재를 알게 해주기</h2>
<p><code>&quot;extends&quot;: &quot;./tsconfig.paths.json&quot;</code>와 <code>&quot;tsconfig.paths.json&quot;</code>를 추가한다.</p>
<pre><code class="language-json">// tsconfig.json
{
  &quot;extends&quot;: &quot;./tsconfig.paths.json&quot;, // 추가
  &quot;compilerOptions&quot;: {
    ...
  },
  &quot;include&quot;: [
    ...
    &quot;tsconfig.paths.json&quot; // 추가
  ]
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ESLint, Prettier 설정하기]]></title>
            <link>https://velog.io/@eddie_kim/ESLint-Prettier-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@eddie_kim/ESLint-Prettier-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 18 Dec 2023 02:25:49 GMT</pubDate>
            <description><![CDATA[<h2 id="1-vscode-extenstion에서-eslint-prettier-설치">1. vscode extenstion에서 eslint, prettier 설치</h2>
<h2 id="2-eslint-prettier-패키지-설치">2. eslint, prettier 패키지 설치</h2>
<p>다음 패키지 모듈을 설치한다.</p>
<pre><code class="language-bash">yarn add -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-react eslint-config-react-app</code></pre>
<h2 id="3-eslintrcjson파일을-새로-만들고-config-설정-분리">3. .eslintrc.json파일을 새로 만들고 Config 설정 분리</h2>
<p>Create React App의 경우 package.json에 보면 eslint 설정이 이미 되어 있다.</p>
<pre><code class="language-bash">// package.json
{
   ...
  &quot;eslintConfig&quot;: {
      &quot;estends&quot;: [
          &quot;react-app&quot;,
          &quot;react-app/jest&quot;
      ]
  },
  ...
 }</code></pre>
<p>가장 최상단 디렉토리에 <code>.eslintrc.json</code> 파일을 새로 만들고 위의 <code>&quot;estends&quot;:[]</code>를 <code>.eslintrc.json</code>에 옮긴다.</p>
<h2 id="4-eslintrcjson-룰-설정">4. .eslintrc.json 룰 설정</h2>
<p><code>.eslintrc.json</code> 에 <code>&quot;estends&quot;:[]</code>를 포함해서 다음 룰을 적는다</p>
<pre><code class="language-json">// .eslintrc.json
{
    &quot;extends&quot;: [
        &quot;react-app&quot;,
        &quot;react-app/jest&quot;,
        &quot;plugin:prettier/recommended&quot;
    ],
    &quot;plugins&quot;: [&quot;prettier&quot;],
    &quot;rules&quot;: {
        &quot;prettier/prettier&quot;: &quot;error&quot;
    }
}</code></pre>
<h2 id="5-prettierrc-설정">5. .prettierrc 설정</h2>
<pre><code class="language-bash">{
    &quot;useTabs&quot;: false,
    &quot;printWidth&quot;: 80,
    &quot;tabWidth&quot;: 2,
    &quot;singleQuote&quot;: true,
    &quot;trailingComma&quot;: &quot;all&quot;,
    &quot;endOfLine&quot;: &quot;lf&quot;,
    &quot;semi&quot;: false,
    &quot;arrowParens&quot;: &quot;always&quot;
}</code></pre>
<h2 id="6-settingjson-설정">6. setting.json 설정</h2>
<p><code>command</code> + <code>,</code>를 눌러서 설정창을 연 후 종이를 뒤집는 듯한 아이콘을 클릭하면 <code>setting.json</code>이 연다.
<img src="https://velog.velcdn.com/images/eddie_kim/post/3c7530cd-1f10-4c8e-9f4d-8704d7a8c31c/image.png" alt=""></p>
<p><code>setting.json</code>에서 아래의 <code>&quot;editor.codeActionsOnSave&quot;</code>를 추가한다.</p>
<pre><code class="language-json">{
  &quot;editor.codeActionsOnSave&quot;: {
    &quot;source.fixAll.eslint&quot;: true
  }
  ...
}</code></pre>
<blockquote>
<p>Mac이 아닌 Window를 사용하는 경우 <code>setting.json</code>에 <code>&quot;files.eol&quot;: &quot;\n&quot;</code>도 추가한다.</p>
</blockquote>
<h2 id="7-vscode에-eslint와-prettier-적용하기">7. vscode에 eslint와 prettier 적용하기</h2>
<p>vscode가 eslint와 prettier를 인식해야하기 때문에 다음과 같은 명령어를 실행한다.</p>
<pre><code class="language-bash">yarn dlx @yarnpkg/sdks vscode</code></pre>
<h2 id="8-packagejson에-eslint-명령어-추가하기">8. package.json에 eslint 명령어 추가하기</h2>
<p>.eslintrc.json을 통해 lint룰이 정해져서 lint 에러가 나는 파일들이 있을 것이다. 모든 파일 하나씩 lint 에러를 고치면 되겠지만, 파일이 많을 경우에는 귀찮으니 모든 파일을 eslint 룰에 맞춰서 수정해주는 명령어를 <code>package.json</code> 추가한다</p>
<pre><code class="language-bash">// package.json
{
    &quot;scripts&quot;: {
        &quot;lint&quot;: &quot;eslint \&quot;src/**/*.{js,jsx,ts,tsx}\&quot;&quot;,
        &quot;lint:fix&quot;: &quot;eslint --fix \&quot;src/**/*.{js,jsx,ts,tsx}\&quot;&quot;
    }
}
</code></pre>
<ul>
<li><code>&quot;eslint \&quot;src/**/*.{js,jsx,ts,tsx}\&quot;&quot;</code> - src 폴더 안에 있는 모든 js,jsx,ts,tsx파일들의 lint를 검사하겠다는 의미이다.</li>
<li><code>&quot;lint:fix&quot;: &quot;eslint --fix \&quot;src/**/*.{js,jsx,ts,tsx}\&quot;&quot;</code> - src 폴더 안에 있는 모든 js,jsx,ts,tsx파일들의 lint를 검사하고 lint 에러나면 고치겠다는 의미이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Testing Crash Course 유튜브 강의 정리]]></title>
            <link>https://velog.io/@eddie_kim/React-Testing-Crash-Course-%EC%9C%A0%ED%8A%9C%EB%B8%8C-%EA%B0%95%EC%9D%98-%EB%85%B8%ED%8A%B8</link>
            <guid>https://velog.io/@eddie_kim/React-Testing-Crash-Course-%EC%9C%A0%ED%8A%9C%EB%B8%8C-%EA%B0%95%EC%9D%98-%EB%85%B8%ED%8A%B8</guid>
            <pubDate>Thu, 14 Dec 2023 04:08:45 GMT</pubDate>
            <description><![CDATA[<p>유튜브 강의 링크 - <a href="https://www.youtube.com/watch?v=OVNjsIto9xM&amp;t=360s">React Testing Crash Course</a></p>
<h1 id="리액트-테스팅-관련-강의를-보게된-동기">리액트 테스팅 관련 강의를 보게된 동기</h1>
<ul>
<li>테스팅 코드가 거의 없는(커버리지가 10%도 안되었다) 회사 솔루션에 새로운 기능을 넣을 때마다 기존 기능에서 발생하는 예상치 못한 에러, 버그의 원인을 찾고 고치는데 정말 힘이 많이 들었다.<ul>
<li>QA 팀에서 하나씩 입력하고 눌러가면서 테스트를 할 때 처음에는 잘 작동 되었던 기능이 새로운 기능을 추가하고 나서부터는 작동이 잘 안되었던 기억이 있다.</li>
</ul>
</li>
<li>테스트 코드를 작성한다는 것이 중요하다는 것을 인지하면서도 정작 리액트 테스팅 코드를 어떻게 작성하는지 하나도 몰랐다.</li>
</ul>
<h1 id="why-should-you-test">Why should you test?</h1>
<ul>
<li>우리가 만든 애플리케이션이 예상했던 대로 작동이 되는지 체크하기 위해</li>
<li>새로운 기능을 넣을 때마다 기존 기능에서 발생하는 예상치 못한 에러, 버그 원인을 바로 알아내려고 -&gt; 이거는 너무나 공감한다. 테스트 코드 작성 안하게 되면 에러, 버그 원인 찾는데 너무나 많은 시간을 낭비하게 된다.</li>
</ul>
<h1 id="why-do-i-have-test-priorities">Why do I have Test Priorities?</h1>
<ul>
<li>프로젝트에 시간과 돈이 할당된 만큼만 소비 되어야 하기 때문이다.
예를 들어, 자동차 손 세차장에 왔다고 하자. 당장 돈은 2000원 밖에 없는데 내일 당장 자동차를 끌고 클라이언트와의 첫 미팅을 가야한다. 이렇게 주어진 시간과 돈이 부족할 때 자동차를 세차를 해야하는 부분은 차 유리랑 차체를 먼저 닦는 것이 급선무 일 것이다.</li>
</ul>
<h1 id="what-should-i-testtest-priorities">What should I test?(Test Priorities)</h1>
<p>프로젝트의 목표와 할당된 시간, 예산에 따라 테스트를 해야하는 대상이 달라지고 테스트 우선 순위가 달라지는 것이 당연하다.
하지만 강의자는 다음 우선 순위가 잘 맞았다고 한다.</p>
<ol>
<li>프로젝트에서 회사의 수익이 가장 많이 나는 기능들 (High value features)<ul>
<li>Amazon은 수익이 나기 위해 필수적인 기능은 상품들의 정보 페이지 그리고 결제 기능이다.</li>
<li>Spotify는 음악을 재생하는 기능과 구독 서비스 기능이 가장 중요하다.</li>
</ul>
</li>
<li>극한의 상황에서도, 즉 최대/최소의 값을 넣어도 서비스 정상 작동 여부(Edge cases in high value features)<ul>
<li>Edge case는 극한의 상황을 테스트하는 케이스이다.</li>
<li>예를 들어, 이커머스의 상품 100만개를 만들어 본래 기능이 정상 작동하는지 테스트한다.</li>
</ul>
</li>
<li>자주 망가지는 기능들(Things that are easy to break)</li>
<li>기본적인 리액트 컴포넌트 테스팅 (Basic React component testing)<ul>
<li>User interactions</li>
<li>Conditional rendering</li>
<li>Utils/Hooks</li>
</ul>
</li>
</ol>
<h1 id="types-of-testing">Types of Testing</h1>
<p>테스트에도 3가지 종류가 있다. 단위 테스트(Unit Test), 통합 테스트(Integration Test), E2E(End to End)가 있다.</p>
<h2 id="단위-테스트unit-test">단위 테스트(Unit Test)</h2>
<p>단위 테스트는 <strong>하나의 모듈</strong>을 기준으로 독립적으로 테스트 하는 것이다. 모듈은 하나의 함수 혹은 메소드를 각각 테스트를 하는 것이다. 예를 들어, 더하기 함수 <code>function add(a, b){}</code> 같이 가장 작은 단위를 테스팅 하는 것을 의미한다.</p>
<h2 id="통합-테스트integration-test">통합 테스트(Integration Test)</h2>
<p>통합 테스트는 여러 개의 단위 테스트(모듈)를 모아서 더 큰 단위의 기능이 정상적으로 작동 되는지 확인하는 테스트이다. 예를 들어 로그인 기능을 테스트 할 때 아이디, 비밀번호의 validation에 대한 단위 테스트, 아이디, 비밀번호를 입력 후 로그인 버튼을 눌렀을 때 정상적으로 로그인이 되는 단위 테스트를 하나의 테스트로 묶어서 테스트 하는 것이 있다.</p>
<figure style="display: flex; flex-direction: column; width: 75%;">
    <img src="https://velog.velcdn.com/images/eddie_kim/post/c8141278-d6f6-4fa7-9fe4-b3e41caf03f2/image.png" />
  <figcaption style="text-align: center">위의 이미지는 단위 테스트와 통합 테스트의 차이를 보여주는 이미지인 것 같다.</figcaption>
</figure>


<h2 id="e2e-테스트-end-to-end-test">E2E 테스트 (End to End test)</h2>
<p><em>프론트엔드</em>에서의 E2E 테스트는 사용자의 입장이 되어 실제 웹페이지에서 원하는 행동이 나오는지를 테스트하는 것이다. 예를 들어, 로그인 화면에 아이디, 비밀번호를 입력하고, 로그인 버튼 눌렀을 때 로그인이 되어 있는 상태에서 메인 화면으로 이동으로 되는지를 테스트 하는 것이다.</p>
<hr>
<h1 id="단위-테스트-통합-테스트-e2e-코드-작성">단위 테스트, 통합 테스트, E2E 코드 작성</h1>
<h2 id="단위-테스트">단위 테스트</h2>
<p>리액트에서 단위 테스트, 통합 테스트를 할 때 주로 <code>react-testing-library</code>를 사용한다. <code>react-testing-library</code>를 많이 쓰는 이유에는 <code>Enzyme</code>라는 테스트 라이브러리와 비교해야 한다.</p>
<h3 id="enzyme-vs-react-testing-library">Enzyme vs react-testing-library</h3>
<p><code>Enzyme</code>는 컴포넌트 상태 변화 이전과 이후의 props, state의 변화를 비교하는 테스트라 props와 state를 조회해야 해서 테스트 코드를 작성에 복잡함이 추가된다.
반면, <code>react-testing-library</code>는 props, state 값의 변화보다는 사용자의 관점에서 컴포넌트의 동작/행위에 초점을 두는 라이브러리라서 테스트 코드를 작성할 때 좀 더 직관적으로 생각하면서 작성할 수 있는 것이 장점이다.</p>
<hr>
<p>다음은 작은 단위(모듈 단위)로 테스트 코드를 작성한 단위 테스트 코드이다.</p>
<p><em>모듈 1: Pay 버튼은 비활성화</em>
<em>모듈 2: 조건에 부합되면 Pay 버튼은 활성화</em></p>
<pre><code class="language-javascript">test(&#39;모듈 1 - 처음 페이지를 렌더링할 때,  Pay 버튼은 비활성화 되어 있어야 한다&#39;, async () =&gt; {

    render(&lt;TransactionCreateStepTwo sender={{ id: &#39;5&#39; }} receiver={{ id: &#39;5&#39; }} /&gt;);

    expect(screen.getByRole(&#39;button&#39;, { name: /pay/i }).toBeDisabled();
});

test(&#39;모듈 2 - amount와 note가 입력이 되었으면,  Pay 버튼은 활성화 되어 있어야 한다&#39;, async () =&gt; {
     // Arrange
    render(&lt;TransactionCreateStepTwo sender={{ id: &#39;5&#39; }} receiver={{ id: &#39;5&#39; }} /&gt;);

    // Act
    userEvent.type(screen.getByPlaceholderText(/amount/i, &quot;50&quot;);
    userEvent.type(screen.getByPlaceholderText(/add a note/i, &quot;dinner&quot;);

    // Assert
    expect(screen.getByRole(&#39;button&#39;, { name: /pay/i }).toBeEnabled();
});</code></pre>
<blockquote>
<p><strong>단위 테스트를 작성하기 위한 3단계(Three Test Phase)</strong>
<strong>Arrange</strong> - 테스트를 판을 깔아주는 단계. 예를 들어 테스트하고 싶은 컴포넌트를 렌더링부터 먼저 해야 한다.
<strong>Act</strong> - 유저가 직접 이벤트를 실행하는 단계 (User clicking button, User typing email and password)
<strong>Assert</strong> - 유저가 어떤 이벤트를 하고 나서 결과가 예상되는 결과와 일치하는지를 검증하는 단계</p>
</blockquote>
<h2 id="통합-테스트">통합 테스트</h2>
<p><strong>Pay 페이지 시나리오</strong>는 Amount, Add a note 인풋을 입력 안하면 Pay 버튼은 비활성화 되고 Amount 인풋을 입력하고 Add a note 인풋을 입력을 하고 나면 Pay 버튼이 활성화 되는 것이다.
결국은, 위 두 개의 단위 테스트들은 Pay 페이지 시나리오에서 바라봤을 때 하나의 테스트로 합쳐도 된다. </p>
<pre><code class="language-javascript">test(&#39;모듈 1 - 처음 페이지를 렌더링할 때,  Pay 버튼은 비활성화 되어 있어야 하고, amount와 note가 입력이 되어 있을 때 pay 버튼은 활성화 되어있어야 한다&#39;, async () =&gt; {

    render(&lt;TransactionCreateStepTwo sender={{ id: &#39;5&#39; }} receiver={{ id: &#39;5&#39; }} /&gt;);

    expect(screen.getByRole(&#39;button&#39;, { name: /pay/i }).toBeDisabled();

    userEvent.type(screen.getByPlaceholderText(/amount/i, &quot;50&quot;);
    userEvent.type(screen.getByPlaceholderText(/add a note/i, &quot;dinner&quot;);

    expect(screen.getByRole(&#39;button&#39;, { name: /pay/i }).toBeEnabled();
});</code></pre>
<p>이처럼, 단위 테스트들을 작성하다가 테스트 시나리오 상 하나로 합쳐도 된다는 생각이 들면 같은 테스트 단위에 넣으면 된다.</p>
<h2 id="e2e-테스트">E2E 테스트</h2>
<p>리액트에서 E2E 테스트는 주로 <code>cypress</code>를 이용한다.</p>
<p>다음은 결제하는 과정을 cypress로 작성한 코드이다</p>
<pre><code class="language-javascript">const { v4: uuidv4 } = require(&#39;uuid&#39;);

describe(&#39;payment&#39;, () =&gt; {
    it(&#39;user can make payment&#39;, () =&gt; {
        //  login
        cy.visit(&#39;/&#39;);
        cy.findByRole(&#39;textbox&#39;, { name: /username/i }).type(&#39;johndoe&#39;);
        cy.findByLabelText(/password/i).type(&#39;s3cret&#39;);
        cy.findByRole(&#39;checkbox&#39;, { name: /remember me/i }).check();
        cy.findByRole(&#39;button&#39;, { name: /sign in/i }).click();

        // check account balance
        let oldBalance;
        cy.get(&#39;[data-test=sidenav-user-balance]&#39;).then($balance =&gt; oldBalance = $balance.text());

        // click on new button
        cy.findByRole(&#39;button&#39;, { name: /new/i }).click();

        // search for user
        cy.findByRole(&#39;textbox&#39;).type(&#39;devon becker&#39;);
        cy.findByText(/devon becker/i).click();

        // add amount and note and click pay
        const paymentAmount = &quot;5.00&quot;;
        cy.findByPlaceholderText(/amount/i).type(paymentAmount);
        const note = uuidv4();
        cy.findByPlaceholderText(/add a note/i).type(note);
        cy.findByRole(&#39;button&#39;, { name: /pay/i }).click();

        // return to transactions
        cy.findByRole(&#39;button&#39;, { name: /return to transactions/i }).click();

        // go to personal payments
        cy.findByRole(&#39;tab&#39;, { name: /mine/i }).click();

        // click on payment
        cy.findByText(note).click({ force: true });

        // verify if payment was made
        cy.findByText(`-$${paymentAmount}`).should(&#39;be.visible&#39;);
        cy.findByText(note).should(&#39;be.visible&#39;);

        // verify if payment amount was deducted
        cy.get(&#39;[data-test=sidenav-user-balance]&#39;).then($balance =&gt; {
            const convertedOldBalance = parseFloat(oldBalance.replace(/\$|,/g, &quot;&quot;));
            const convertedNewBalance = parseFloat($balance.text().replace(/\$|,/g, &quot;&quot;));
            expect(convertedOldBalance - convertedNewBalance).to.equal(parseFloat(paymentAmount));
        });
    });
});</code></pre>
<h1 id="마치면서">마치면서</h1>
<p>개인적인 생각으로 <code>react-testing-library</code>와 <code>cypress</code> 둘 다 사용자 관점에서 컴포넌트를 테스트하는 것이라 한 프로젝트에 둘 다 쓰기에는 중복되는 점이 너무 많다.</p>
<p>회사에서의 프로젝트들은 고유의 규칙, 철학에 따라 둘 다 쓰거나, 둘 중 하나를 선택하겠지만, 내 개인 프로젝트에서는 리액트 컴포넌트를 테스트하는 목적에서는 둘 중에 하나만 선택해서 쓸 생각이다.</p>
<p>왜냐하면 프로덕션 코드를 서포트 하기 위해 테스트 코드를 작성하는 것이지 사용자 관점에서 컴포넌트를 테스트하는 측면에서 중복되는 지점이 많은 두 테스트 라이브러리를 시간을 낭비하면서 동시에 쓸 필요가 없기 때문이다.</p>
<p><strong>출처</strong>
Edge Case - <a href="https://daryeou.tistory.com/203">https://daryeou.tistory.com/203</a>
단위 테스트 - <a href="https://mangkyu.tistory.com/143">https://mangkyu.tistory.com/143</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 프로젝트를 Yarn Berry(PnP)로 설정 하는 법]]></title>
            <link>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-Yarn-BerryPnP%EB%A1%9C-%EC%84%A4%EC%A0%95-%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-Yarn-BerryPnP%EB%A1%9C-%EC%84%A4%EC%A0%95-%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Wed, 13 Dec 2023 12:10:37 GMT</pubDate>
            <description><![CDATA[<p>패스트 캠퍼스의 프론트 엔드 강의 <code>고성능 대규모 프론트엔드 10개 프로젝트: 최적화부터 유지보수까지 한 번에 끝내는 초 격차 패키지 Online</code>를 참고하였습니다.</p>
<h2 id="1-리액트-프로젝트-만들기">1. 리액트 프로젝트 만들기</h2>
<pre><code class="language-bash">// 타입스크립트가 탑재된 리액트 앱
yarn create react-app my-app --template typescript</code></pre>
<h2 id="2-node_modules-폴더-삭제하기">2. node_modules 폴더 삭제하기</h2>
<p><code>node_modules</code> 폴더를 삭제한다.</p>
<h2 id="3-yarn-berry-버전-세팅">3. Yarn Berry 버전 세팅</h2>
<p>터미널 열어서</p>
<pre><code class="language-bash">yarn set version berry</code></pre>
<p>를 입력한다</p>
<h2 id="4-node-linker-설정">4. Node Linker 설정</h2>
<p><code>node_modules</code>를 사용하지 않고 PnP 방식을 사용하기 위해 PnP 설정을 해줘야 한다.
폴더 트리에서 <code>.yarnrc.yml</code>을 열고 <code>yarnPath</code> 밑에 다음을 추가한다.</p>
<pre><code class="language-yaml">yarnPath: ...
nodeLinker: pnp &lt;- 추가하기</code></pre>
<h2 id="5-패키지-설치">5. 패키지 설치</h2>
<p>패키지 설치를 위해 <code>yarn install</code> 명령어를 실행한다.</p>
<pre><code class="language-bash">➤ YN0000: · Yarn 4.0.2
➤ YN0000: ┌ Resolution step
➤ YN0082: │ @types/jest@npm:*: No candidates found # 에러 메시지
➤ YN0000: └ Completed in 1s 344ms
➤ YN0000: · Failed with errors in 1s 358ms</code></pre>
<p>만약 이러한 에러 메시지가 나왔다면 <code>package.json</code>에서</p>
<pre><code class="language-bash">...
&quot;@types/jest&quot;: &quot;버전&quot;, &lt;- 지우기
...</code></pre>
<p><code>&quot;@types/jest&quot;: &quot;버전&quot;</code>을 지운다.</p>
<h2 id="6-zipfs-익스텐션을-설치한다">6. &#39;ZipFS&#39; 익스텐션을 설치한다.</h2>
<p><img src="https://velog.velcdn.com/images/eddie_kim/post/bd77d109-cf30-47e8-bf8e-ea7e8812a0c6/image.png" alt=""></p>
<ul>
<li>vscode를 사용하는 경우 ZipFS 익스텐션을 사용해야 한다.
프로젝트 내에 .ts, .tsx 파일을 열어보면 빨간 밑줄이 그려지면서 깨지는 현상이 보인다. 그 이유는 타입스크립트 관련 패키지가 <code>.yarn/cache</code> 폴더 아래에 zip파일들로 만들어져 있는데 vscode는 zip파일들을 인식 못하기 때문이다. 따라서 vscode가 zip 파일들을 인식시키기 위해 ZipFS 익스텐션을 사용한다.</li>
</ul>
<h2 id="7-명령어-yarn-dlx-yarnpkgsdks-vscode-실행">7. 명령어 &#39;yarn dlx @yarnpkg/sdks vscode&#39; 실행</h2>
<pre><code class="language-bash">yarn dlx @yarnpkg/sdks vscode</code></pre>
<p>vscode가 <code>.yarn/cache</code> 폴더 아래에 있는 타입스크립트 관련 패키지들을 알아차리게 되었다.
<code>.vscode/settings.json</code>을 열어보면 <code>&quot;typescript.tsdk&quot;: &quot;.yarn/sdks/typescript/lib&quot;</code>가 입력이 되어있는데 이는 .yarn 폴더 안에 있는 타입스크립트 패키지들을 읽어오겠다는 의미이다.</p>
<blockquote>
<p>주의! 프로젝트 디렉토리에 공백 문자가 있으면 에러가 발생해서 명령어가 작동이 안되니 공백문자를 꼭 없앤 상태에서 명령어를 실행해야 한다.
예를 들어, 프로젝트 디렉토리가 &#39;chelsoo-macbook-pro/React Projects/...&#39;에서 &#39;React Projects&#39;에 공백 문자가 있으면 &#39;ReactProjects&#39;나 &#39;React_Projects&#39;로 바꿔주어야 한다!</p>
</blockquote>
<h2 id="8-워크스페이스-버전에-맞는-타입스크립트-버전-허용">8. 워크스페이스 버전에 맞는 타입스크립트 버전 허용</h2>
<p>.ts, .tsx 파일에서 여전히 빨간색 줄이 그려져 있는 것이 보일 것이다. 이를 없애기 위해 vscode에서 <code>cmd + p</code> 누른 다음 <code>&gt;</code> 를 입력해서 <code>Typescript: Select TypeScript Version</code>을 입력해서 선택해준다.
<img src="https://velog.velcdn.com/images/eddie_kim/post/bd7072ec-a4c1-428d-9439-564755f4b3ec/image.png" alt="">
그런 다음, <code>Use Workspace Version</code>을 선택한다.
<img src="https://velog.velcdn.com/images/eddie_kim/post/8e7df1bb-b858-48eb-a64e-548f0b45aa75/image.png" alt=""></p>
<h2 id="9-apptesttsx-에러-고치기">9. App.test.tsx 에러 고치기</h2>
<p>App.test.tsx에서 타입 에러가 뜨는데 이를 고치기 위해서</p>
<ol>
<li><code>yarn remove @testing-library/jest-dom</code>을 입력해서 @testing-library/jest-dom을 지운다.</li>
<li>그런 다음 다시 설치한다.<pre><code class="language-bash">yarn add -D @types/testing-library__jest-dom @testing-library/jest-dom</code></pre>
</li>
</ol>
<h2 id="10-gitignore에-git이-무시해야할-파일들-추가">10. .gitignore에 git이 무시해야할 파일들 추가</h2>
<p>yarn berry로 세팅하면서 생긴 dependency들을 git에서 제외시킨다.</p>
<pre><code class="language-bash"># yarn zero-install
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sequelize-cli와 PostgreSQL를 이용해서 데이터 Migration 및 Seed하기]]></title>
            <link>https://velog.io/@eddie_kim/Sequelize-cli%EC%99%80-PostgreSQL%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-Migration-%EB%B0%8F-Seed%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@eddie_kim/Sequelize-cli%EC%99%80-PostgreSQL%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-Migration-%EB%B0%8F-Seed%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 30 Apr 2020 21:24:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 <a href="https://dev.to/nedsoft/getting-started-with-sequelize-and-postgres-emp">Getting Started with Sequelize and Postgres</a>를 참고해서 작성하였습니다.</p>
</blockquote>
<hr>
<h2 id="sequelize가-뭐지">Sequelize가 뭐지?</h2>
<ul>
<li>Sequelize는 관계형 데이터베이스(MySQL, Postgres, MS SQL)을 위한 Node.js용 <a href="#what-is-orm?">ORM</a>입니다</li>
<li>Promise 기반입니다</li>
</ul>
<blockquote>
<ul>
<li>ORM(Object Relational Mapping)은 SQL 대신에 데이터베이스와 소통할 수 있게 해주는 프로그래밍 언어입니다. 즉 SQL 대신에 코드로 쿼리문을 작성할 수 있게 해줍니다.</li>
</ul>
</blockquote>
<h2 id="sequelize-cli는-뭐지">Sequelize-cli는 뭐지?</h2>
<ul>
<li>cli에서 알 수 있듯이 윈도우의 cmd나 Mac의 Terminal에서 Sequelize 관련 명령어를 쓸 수 있게 해주는 도구입니다.</li>
<li>대표적으로 <strong>Model</strong>을 만들거나 <strong>Migration</strong>이나 <strong>Seed</strong>를 할 때 사용합니다.</li>
</ul>
<h2 id="model은-뭐고-migration은-뭐고-seed는-뭐지">Model은 뭐고 Migration은 뭐고 Seed는 뭐지?</h2>
<ul>
<li>Model은 Column 이름, 데이터 타입, Foreign Key 설정, 제약 조건 등 테이블의 스키마를 정의한 것입니다.</li>
<li>Migration은 Model에서 정의된 스키마를 바탕으로 실제 DB 서버에 테이블을 만들어주는 과정입니다.</li>
<li>Seed는 실제 데이터를 데이터베이스에 넣는 것을 과정입니다. </li>
</ul>
<hr>
<h5 id="개발환경은-nodejs를-사용하고-postgresql을-사용합니다-npm-혹은-yarn-pgadmin을-설치했다는-것을-가정합니다">개발환경은 node.js를 사용하고, PostgreSQL을 사용합니다. npm 혹은 yarn, pgAdmin을 설치했다는 것을 가정합니다.</h5>
<h3 id="1-dependency-설치">1. Dependency 설치</h3>
<pre><code class="language-bash">npm install sequelize sequelize-cli pg pg-hstore dotenv</code></pre>
<p>혹은</p>
<pre><code class="language-bash">yarn add sequelize sequelize-cli pg pg-hstore dotenv</code></pre>
<p>을 입력해서 dependency들을 설치해주세요</p>
<ul>
<li><code>sequelize</code>는 sequelize 라이브러리 사용 및 PostgreSQL DB서버와 연결하게 해줌</li>
<li><code>sequelize-cli</code>는 sequelize 관련 명령어로 데이터베이스 작업을 해주는 툴</li>
</ul>
<h3 id="2-sequelizerc를-통해서-sequelize-config-만들기">2. .sequelizerc를 통해서 sequelize config 만들기</h3>
<p>새로 <code>.sequelizerc</code>라는 파일을 만들고 다음과 같이 코드를 작성합니다.</p>
<pre><code class="language-javascript">const path = require(&#39;path&#39;)

module.exports = {
  &#39;config&#39;: path.resolve(&#39;./database/config&#39;, &#39;config.js&#39;),
  &#39;models-path&#39;: path.resolve(&#39;./database/models&#39;),
  &#39;migrations-path&#39;: path.resolve(&#39;./database/migrations&#39;),
  &#39;seeders-path&#39;: path.resolve(&#39;./database/seeders&#39;)
}</code></pre>
<p><code>&#39;config&#39;</code>: PostgreDB 서버와 연결할 connection string를 설정하는 폴더를 sequelize-cli가 생성하도록 해줌
<code>&#39;models-path&#39;</code>:  테이블의 스키마를 정의해준 디렉토리. <code>sequelize-cli</code>가 이 파일을 바탕으로 마이그레이션 파일을 생성.
<code>&#39;migrations-path&#39;</code>: 실제로 cli가 DB서버에 테이블을 만들도록 도와주는 디렉토리.
<code>&#39;seeders-path&#39;</code>: 실제 데이터들을 넣는 폴더. cli가 데이터들을 보고 DB의 테이블에 데이터들을 넣음.</p>
<hr>
<p>다음 명령어를 실행합니다.
<code>npx sequelize init</code></p>
<blockquote>
<p><code>./node_modules/.bin/sequelize</code> 쓰는 대신에 npx가 <code>./node_modules/.bin</code>을 가리키게 해줍니다.
<code>./node_modules/.bin/sequelize</code> 를 실행해야 하는 이유는 sequelize-cli가 global하게 설치 하지 않고 로컬에 설치했기 때문입니다. Global하게 설치하면 <code>sequelize init</code>으로 실행시킬 있습니다. <a href="https://velog.io/@jeff0720/Sequelize-CLI%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B0%84%EB%8B%A8%ED%95%9C-User-API-%EB%A7%8C%EB%93%A4%EA%B8%B0-vdjpb8nl0k#2-sequelize-%EC%84%A4%EC%B9%98-%EB%B0%8F-sequelize-cli-%EC%84%A4%EC%B9%98">Sequelize CLI를 사용하여 User API 만들기</a> 참고
<br/></p>
</blockquote>
<div align="center">
  <img src="https://images.velog.io/images/eddie_kim/post/50d2cd78-3629-4fe2-8c03-8086756f4b59/image.png" height="200" />
</div>
그러면 위의 사진 같이 `./database/` 디렉토리 안에 `config`, `models`, `migrations`, `seeders` 폴더가 생성됩니다.

<hr>
<p><code>database/config/</code>안에 <code>config.js</code>를 밑의 코드로 대체해줍니다 </p>
<pre><code class="language-javascript">require(&#39;dotenv&#39;).config()

module.exports = {
  development: {
    url: process.env.DEV_DATABASE_URL,
    dialect: &#39;postgres&#39;,
  },
  test: {
    url: process.env.TEST_DATABASE_URL,
    dialect: &#39;postgres&#39;,
  },
  production: {
    url: process.env.DATABASE_URL,
    dialect: &#39;postgres&#39;,
  },
}</code></pre>
<h3 id="3-db서버에-데이터베이스-만들기">3. DB서버에 데이터베이스 만들기</h3>
<p><em>방법 1)</em> pgAdmin에 들어가서 왼쪽에 <code>Servers-PostgreSQL 12-Databases</code>에 마우스 우클릭 하고 <code>my_db</code>라는 이름으로 Database를 만듭니다
<img src="https://images.velog.io/images/eddie_kim/post/f7f9664d-c841-4016-845c-a5f954d682e3/image.png" /></p>
<p><em>방법 2)</em> cli를 이용해서 Database를 만듭니다. Database를 만들기 위해 아래의 명령어를 입력합니다.</p>
<pre><code>npx createdb my_db -U &lt;db_user&gt;</code></pre><blockquote>
<p><db_user>는 Datases의 properties에서 볼 수 있습니다.
<img src="https://images.velog.io/images/eddie_kim/post/2a880eec-4099-4896-8f1d-2dd399a7c6ce/image.png" /></p>
</blockquote>
<hr>
<p><code>npm install dotenv</code> 혹은 <code>yarn add dotenv</code>를 입력하여 <code>dotenv</code>를 설치하고 <code>.dotenv</code> 파일을 생성하여 아래와 같이 입력합니다.</p>
<pre><code>DEV_DATABASE_URL=postgres://&lt;db_user&gt;:&lt;db_password&gt;@127.0.0.1:5432/my_db</code></pre><blockquote>
<p><em>방법2</em> 에서 <db_user>가 어떤 것인지 확인해주세요</p>
</blockquote>
<h3 id="4-model-migration-만들기">4. Model, Migration 만들기</h3>
<h5 id="테이블-시나리오">테이블 시나리오</h5>
<p>예를 들어 레딧 같은 커뮤니티 웹사이트를 만들어 본다고 가정해봅시다. 우리는 User, Post, Comment라는 테이블이 필요합니다. 그러면 다음과 같은 관계가 생깁니다.</p>
<blockquote>
<ul>
<li>User는 여러개의 Post를 가집니다. (One To Many)</li>
<li>User는 여러개의 Comment를 가집니다. (One To Many)</li>
<li>Post는 여러개의 Comment를 가집니다. (One To Many)</li>
</ul>
</blockquote>
<p>이러한 테이블 관계를 만들기 위해 계속해서 봅시다.</p>
<hr>
<h5 id="user-post-comment-모델-만들기">User, Post, Comment 모델 만들기</h5>
<p>sequelize-cli로 User, Post, Comment 모델을 만들어 볼까요? </p>
<pre><code class="language-bash">npx sequelize model:generate --name User --attributes name:string,email:string

npx sequelize model:generate --name Post --attributes title:string,content:text,userId:integer

npx sequelize model:generate --name Comment --attributes postId:integer,comment:text,userId:integer</code></pre>
<p>위의 명령어들을 통해 <code>/database/migration</code>과 <code>/database/model</code> 디렉토리에 Migration과 Model을 생성해줍니다.</p>
<blockquote>
<p>주의 : attirbute를 입력할 때 space 없이 입력합니다. ex) <code>name:string,email:string</code></p>
</blockquote>
<p>Foreign Key(userId, postId)에 NOT NULL이라는 제약조건을 넣습니다. 모델을 Eager loading 하기 위해서 입니다</p>
<pre><code class="language-javascript">userId: {
      type: Sequelize.INTEGER,
      allowNull: false,
    },

postId: {
      type: Sequelize.INTEGER,
      allowNull: false,
    },</code></pre>
<hr>
<p><code>/database/models/</code>에 다음과 같은 코드를 추가합니다. </p>
<pre><code class="language-javascript">// database/models/user.js

module.exports = (sequelize, DataTypes) =&gt; {
  const User = sequelize.define(&#39;User&#39;, {
    name: DataTypes.STRING,
    email: DataTypes.STRING
  }, {});
  User.associate = function(models) {
    // associations can be defined here
    User.hasMany(models.Post, {
      foreignKey: &#39;userId&#39;,
      as: &#39;posts&#39;,
      onDelete: &#39;CASCADE&#39;,
    });

    User.hasMany(models.Comment, {
      foreignKey: &#39;userId&#39;,
      as: &#39;comments&#39;,
      onDelete: &#39;CASCADE&#39;,
    });
  };
  return User;
};</code></pre>
<pre><code class="language-javascript">// database/models/post.js

module.exports = (sequelize, DataTypes) =&gt; {
  const Post = sequelize.define(&#39;Post&#39;, {
    title: DataTypes.STRING,
    content: DataTypes.TEXT,
    userId: DataTypes.INTEGER
  }, {});
  Post.associate = function(models) {
    // associations can be defined here
    Post.hasMany(models.Comment, {
      foreignKey: &#39;postId&#39;,
      as: &#39;comments&#39;,
      onDelete: &#39;CASCADE&#39;,
    });

    Post.belongsTo(models.User, {
      foreignKey: &#39;userId&#39;,
      as: &#39;author&#39;,
      onDelete: &#39;CASCADE&#39;,
    })
  };
  return Post;
};</code></pre>
<pre><code class="language-javascript">// database/models/comment.js

module.exports = (sequelize, DataTypes) =&gt; {
  const Comment = sequelize.define(&#39;Comment&#39;, {
    postId: DataTypes.INTEGER,
    comment: DataTypes.TEXT,
    userId: DataTypes.INTEGER
  }, {});
  Comment.associate = function(models) {
    // associations can be defined here
    Comment.belongsTo(models.User, {
      foreignKey: &#39;userId&#39;,
      as: &#39;author&#39;
    });
    Comment.belongsTo(models.Post, {
      foreignKey: &#39;postId&#39;,
      as: &#39;post&#39;
    });
  };
  return Comment;
};</code></pre>
<h5 id="마이그레이션-하기">마이그레이션 하기</h5>
<p>테이블의 스키마도 다 정의 했으니(모델링) DB에 테이블을 만들어 봅시다.
<code>npx sequelize db:migrate</code>
성공했다면 DB에 User, Post, Comment 테이블이 만들어졌을 겁니다.</p>
<hr>
<h3 id="5-데이터-seed-하기">5. 데이터 Seed 하기</h3>
<p>sequelize-cli를 이용하여 seed 파일을 만들어 봅시다.</p>
<pre><code class="language-bash">npx sequelize seed:generate --name User

npx sequelize seed:generate --name Post

npx sequelize seed:generate --name Comment</code></pre>
<p>위의 명령어를 실행하면 <code>/database/seeders/</code> 디렉토리 안에 YYYYMMDD-User.js, YYYYMMDD-Post.js, YYYYMMDD-Comment.js 가 만들어집니다.</p>
<p>실제 데이터를 테이블에 넣어 봅시다. 아래와 같이 작성하여 DB에 넣을 데이터를 만듭니다.</p>
<pre><code class="language-javascript">// database/seeds/YYYYMMDD-User.js

module.exports = {
  up: (queryInterface, Sequelize) =&gt; queryInterface.bulkInsert(
    &#39;Users&#39;,
    [
      {
        name: &#39;Jane Doe&#39;,
        email: &#39;janedoe@example.com&#39;,
        createdAt: new Date(),
        updatedAt: new Date(),
      },
      {
        name: &#39;Jon Doe&#39;,
        email: &#39;jondoe@example.com&#39;,
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    ],
    {},
  ),

  down: (queryInterface, Sequelize) =&gt; queryInterface.bulkDelete(&#39;Users&#39;, null, {}),
};</code></pre>
<pre><code class="language-javascript">// database/seeds/YYYYMMDD-Post.js

module.exports = {
  up: (queryInterface, Sequelize) =&gt;
    queryInterface.bulkInsert(
      &quot;Posts&quot;,
      [
        {
          userId: 1,
          title: &quot;hispotan de nu&quot;,
          content:
            &quot;Nulla mollis molestie lorem. Quisque ut erat. Curabitur gravida nisi at nibh.&quot;,
          createdAt: new Date(),
          updatedAt: new Date()
        },
        { 
          userId: 2,
          title: &#39;some dummy title&#39;,
          content:
            &quot;Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque. Quisque porta volutpat erat.&quot;,
          createdAt: new Date(),
          updatedAt: new Date()
        }
      ],

      {}
    ),

  down: (queryInterface, Sequelize) =&gt;
    queryInterface.bulkDelete(&quot;Posts&quot;, null, {})
};</code></pre>
<pre><code class="language-javascript">// database/seeds/YYYYMMDD-Comment.js

module.exports = {
  up: (queryInterface, Sequelize) =&gt;
    queryInterface.bulkInsert(
      &quot;Comments&quot;,
      [
        {
          userId: 1,
          postId: 2,
          comment:
            &quot;Nulla mollis molestie lorem. Quisque ut erat. Curabitur gravida nisi at nibh.&quot;,
          createdAt: new Date(),
          updatedAt: new Date()
        },
        {
          userId: 2,
          postId: 1,
          comment:
            &quot;Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque. Quisque porta volutpat erat.&quot;,
          createdAt: new Date(),
          updatedAt: new Date()
        }
      ],
      {}
    ),

  down: (queryInterface, Sequelize) =&gt;
    queryInterface.bulkDelete(&quot;Comments&quot;, null, {})
};</code></pre>
<p>sequelize-cli를 이용하여 데이터를 DB에 Seed 해봅시다
<code>npx sequleize db:seed:all</code></p>
<p>성공했으면 데이터가 실제 테이블에 들어간 것입니다. 여기까지 Sequelize와 Sequelize-cli를 이용하여 Model, Migration 그리고 Seed까지 해봤습니다. 끝! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 네이티브 + 타입스크립트로 프로젝트 만들기]]></title>
            <link>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 18 Feb 2020 07:32:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>출처 - <a href="https://www.youtube.com/watch?v=ZcWZUSuD_jM">How to setup a Typescript React Native App</a> by Ben Awad</p>
</blockquote>
<h3 id="들어가기-전">들어가기 전</h3>
<p>node.js가 설치되어 있어야 하고 yarn 환경에서 진행할 예정할 예정이다.</p>
<ul>
<li><a href="https://nodejs.org/ko/">node.js 설치하기</a></li>
<li>npm에서 yarn 패키지 모듈 관리 도구 설치하기 - <code>npm install -g yarn</code></li>
</ul>
<p>리액트 네이티브 프로젝트를 expo 라는 도구를 설치해야 한다. expo는 React의 &#39;create-react-app&#39;처럼 새로운 리액트 네이티브 프로젝트를 만들때 쉽고 빠르게 해주는 도구이다. 또한 안드로이드의 안드로이드 스튜디오나 아이폰의 XCode 설치 없이 실제 Device에서 앱을 실행하기 쉽게 해주는 도구이다.</p>
<h3 id="프로젝트-만드는-순서">프로젝트 만드는 순서</h3>
<p><strong>1. expo-cli를 설치한다</strong>
<code>yarn global add expo-cli</code></p>
<p><strong>2. cli를 열어서 <code>expo init myReactNative</code>를 실행한다.</strong>
expo init은 리액트 네이티브 프로젝트를 만드는 명령어이다. 프로젝트 이름은 myReactNative 말고 다른 것을 사용해도 된다.</p>
<p><strong>3. 다음 dependencies를 설치한다.</strong>
<code>yarn add -D react-native-typescript-transformer typescript @types/react @types/react-native</code></p>
<ul>
<li><em>react-native-typescript-transformer</em>
리액트 네이티브 프로젝트의 .ts|.tsx 파일들을 .js로 transpiling 해주는 도구</li>
<li><em>typescript</em>
타입스크립트를 지원해주는 도구</li>
<li><em>@types/react</em>
처음 리액트 프로젝트를 만들 때는 리액트의 타입이 지원되지 않은 채로 만들어진다. 그렇게 되면 타입스크립트 컴파일러가 리액트가 무슨 타입인지 몰라서 에러를 발생한다. 타입을 지원하기 위해 설치한다.</li>
<li><em>@types/react-native</em>
마찬가지로  타입을 지원하기 위해 설치한다.</li>
</ul>
<p><strong>4. ./myReactNative/App.json을 열고 가장 하단에 다음 옵션을 추가한다.</strong>
<img src="https://images.velog.io/images/eddie_kim/post/bf5a638d-2898-489d-9918-d0478aef0b72/image.png" alt=""></p>
<p><strong>5. 타입스크립트를 컴파일 하기 위해 tsconfig.json 파일을 생성한다.</strong>
<code>npx tsc --init</code></p>
<p><strong>6. tsconfig.json에서 &quot;jsx&quot;: &#39;preserve&quot;을 &quot;jsx&quot;: &quot;react-native&quot;로 변경한다.</strong>
<img src="https://images.velog.io/images/eddie_kim/post/524a567c-e1fd-4676-a894-1dd2d63d7fa9/image.png" alt=""></p>
<p><strong>7. cli을 열고 리액트 네이티브 프로젝트를 실행한다.</strong>
<code>yarn start</code><img src="https://images.velog.io/images/eddie_kim/post/71b9441c-f2cb-4dbc-a5bb-8c1c42f99b9d/image.png" alt="">
프로젝트를 실행하면 다음과 같이 엑스포 dev tool이 나온다.</p>
<p><strong>8. 개발하는 동안 핸드폰에서 어플을 개발되는 과정을 볼 수 수 있어야 한다. 따라서 핸드폰에서 Expo 앱을 설치한다. Expo 앱은 다음과 같이 생겼다.</strong>
<img src="https://images.velog.io/images/eddie_kim/post/b5ef1a10-ca7a-43a7-8a7c-53253cd99272/image.png" alt=""></p>
<p><em>*9. 설치를 했으면 Expo에 *필수로</em> 회원가입하고 로그인을 해야한다. 로그인을 안하면 휴대폰에서 프로젝트 빌드가 안된다. **</p>
<p>*<em>10. 안드로이드 유저는 엑스포 앱을 켜서 QR코드로 스캔하면 앱이 휴대폰에서 빌드가 된다. (아이폰 유저는... 아이폰이 없어서 잘 모르겠다.. 댓글로 알려주면 감사하겠다.) *</em></p>
<p><strong>11. 휴대폰 화면에 &quot;Open up App.js to start working on your app!&quot;이 나오면 리액트 네이티브 프로젝트를 성공적으로 만든 것이다. 이제부터 개발을 시작하면 된다.</strong></p>
<h3 id="못다한-말">못다한 말</h3>
<p>실제 기기로 개발하면 편리하고 좋지만 안드로이드 혹은 아이폰이 없을 수도 있을 것이다. Expo에서 아이폰/안드로이드 에뮬레이터를 제공하여 실제 컴퓨터에서도 아이폰/안드로이드 환경에서 개발을 할 수 있다고 한다. 다음 기회에 에뮬레이터를 설치하는 방법을 찾아서 알리도록 하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ES2016+코드를 ES2015 코드로 컴파일하기]]></title>
            <link>https://velog.io/@eddie_kim/ES2016%EC%BD%94%EB%93%9C%EB%A5%BC-ES2015-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%ED%95%98%EA%B8%B0-u6k6a5bl75</link>
            <guid>https://velog.io/@eddie_kim/ES2016%EC%BD%94%EB%93%9C%EB%A5%BC-ES2015-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%ED%95%98%EA%B8%B0-u6k6a5bl75</guid>
            <pubDate>Thu, 06 Feb 2020 03:47:49 GMT</pubDate>
            <description><![CDATA[<h3 id="note-e2016코드로-작성했다는-것을-가정하고-글을-진행한다">Note: E2016+코드로 작성했다는 것을 가정하고 글을 진행한다.</h3>
<h2 id="0-사전준비">0. 사전준비</h2>
<p>다음 2개의 패키지를 설치한다.</p>
<blockquote>
<p><code>npm install @babel/core @babel/cli --save-dev</code>
혹은
<code>yarn add @babel/core @babel/cli --dev</code></p>
</blockquote>
<h2 id="1-해당-프로젝트에-src-폴더를-만든다">1. 해당 프로젝트에 src 폴더를 만든다.</h2>
<p>src 폴더를 만드는 이유는 </p>
<blockquote>
<p>ES2015로 변환시키기 위한 .js파일들만 모으기 위해서이다. 만약 현재 진행하고 있는 프로젝트에 src 폴더가 없으면 새로 생성해서 옮겨도 무방하다.</p>
</blockquote>
<h2 id="2-es2016코드로-간단한-expressjs-웹앱을-만든다">2. ES2016+코드로 간단한 Express.js 웹앱을 만든다.</h2>
<pre><code class="language-javascript">import express from &quot;express&quot;      // ES2016 코드
import morgan from &quot;morgan&quot;        // ES2016 코드
const app = express();

app.get(&#39;/&#39;, (req, res) =&gt; {
  res.send(&#39;Hello World!&#39;);
});


const PORT = 8080;
app.listen(PORT, () =&gt; {
  console.log(`Server listening on port ${PORT}...`);
});</code></pre>
<h4 id="note-es2015-코드로-컴파일-하기-위한-예시코드이다-코드를-이해할-필요없다"><strong>Note</strong>: ES2015 코드로 컴파일 하기 위한 예시코드이다. 코드를 이해할 필요없다.</h4>
<h2 id="3-packagejson을-열어-script-부분을-다음과-같은-명령어를-추가한다">3. package.json을 열어 &quot;script&quot; 부분을 다음과 같은 명령어를 추가한다.</h2>
<p><img src="https://images.velog.io/post-images/eddie_kim/c7c71af0-488b-11ea-b2e5-df61347add39/image.png" alt="image.png"></p>
<ul>
<li><strong>&quot;build:server&quot;: &quot;babel src --out-dir build&quot;</strong></li>
</ul>
<ol>
<li><p><code>&quot;babel src --out-dir build&quot;</code> : src 폴더 안에 있는 모든 .js 파일들을 ES2015 코드로 컴파일한다. 그리고 src 폴더의 상위 디렉토리에 build라는 폴더를 만들고 컴파일된 파일들을 build 폴더 안에 넣는다. 폴더이름을 build말고 lib이나 dist 같이 다른 이름을 써도 괜찮다.</p>
<blockquote>
<p>Note:  <code>&quot;babel src --out-dir build&quot;</code>을 여러번 실행해도 덮어쓰기가 안된다.
This doesn&#39;t overwrite any other files or directories in lib. - <a href="https://babeljs.io/docs/en/babel-cli">babel 문서</a></p>
</blockquote>
</li>
<li><p><code>&quot;build:server&quot;</code> 말고 <code>&quot;complile:server&quot;</code>, <code>&quot;transpile:server&quot;</code> 등등 자기가 알아보기 쉬운 명령어로 적어도 된다.</p>
</li>
</ol>
<ul>
<li><strong>&quot;build&quot;: &quot;npm run build:server&quot;</strong>
위에서 추가한 <code>&quot;build:server&quot;</code> 를 실행시키기 위한 명령어이다. 없어도 괜찮지만 컴파일하려면 PowerShell이나 cmd에 <code>&quot;npm run build:server&quot;</code> 라는 명령어를 직접 입력해야 한다. React.js에서 build할 때 명령어가 <code>npm run build</code>라서 명령어를 통일하고 싶었다.</li>
</ul>
<ol>
<li><strong>&quot;prebuild&quot;: &quot;rd /s &quot;build&quot;&quot;</strong>
업데이트 된 코드를 다시 컴파일 하려면 build 폴더를 지워야 하는데 직접 마우스 클릭해서 지우기 귀찮다.
또한 <code>babel src --out-dir build</code> 은 덮어쓰기가 안된다고 했다.
따라서 컴파일된 코드를 build 폴더에 넣기 전 기존에 있었던 build 폴더 및 build폴더에 있었던 모든 파일들을 삭제시킨다.
따로 <code>npm run prebuild</code> 를 입력할 필요 없이 <code>npm run build</code> 명령어를 입력하면 알아서 <code>npm run prebuild</code> 를 하게 된다. prebuild가 말 그대로 빌드하기 전에 하는 것이니까.</li>
</ol>
<blockquote>
<p>주의</p>
</blockquote>
<ol>
<li><code>rd /s</code>는 윈도우의 cmd에서 쓰는 명령어이다. 리눅스를 쓰는 사용자라면 <code>&quot;prebuild&quot;: &quot;rm -rf build&quot;</code>를 쓰자.</li>
<li>빈 build 폴더를 우선 만들자. 만들지 않으면 build라는 폴더를 못찾아서 실행이 되지 않는다.</li>
</ol>
<h3 id="es2015로-컴파일-결과">#ES2015로 컴파일 결과..</h3>
<ul>
<li><p>컴파일 되기 전..
<img src="https://images.velog.io/post-images/eddie_kim/9def6b40-4892-11ea-be6e-c787a98d866e/image.png" alt="컴파일 되기 전"></p>
</li>
<li><p>컴파일 후..
<img src="https://images.velog.io/post-images/eddie_kim/87ddda30-4892-11ea-9ce4-1d17cba700ba/Capture5.PNG" alt="Capture5.PNG"></p>
</li>
</ul>
<h3 id="에필로그">에필로그</h3>
<p><code>node app.js</code>으로 실행이 되서 서버가 작동이 되어야 실제 웹앱을 구글 클라우드나 AWS에 배포할 때도 작동이 되는데 <code>node app.js</code>는 ES2016+ 문법을 이해못한다는 사실을 알았다. 그래서 ES2016+ 문법을 컴파일 하는 방법을 찾았고 의외로 간단하게 컴파일 할 수 있다는 것도 깨달았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Storybook에서 CDN을 이용해서 부트스트랩 CSS 모듈 가져오기]]></title>
            <link>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%9D%98-%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81Storybook%EC%97%90%EC%84%9C-CDN%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%B6%80%ED%8A%B8%EC%8A%A4%ED%8A%B8%EB%9E%A9-CSS-%EB%AA%A8%EB%93%88-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-itk5kca18q</link>
            <guid>https://velog.io/@eddie_kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%9D%98-%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81Storybook%EC%97%90%EC%84%9C-CDN%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%B6%80%ED%8A%B8%EC%8A%A4%ED%8A%B8%EB%9E%A9-CSS-%EB%AA%A8%EB%93%88-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-itk5kca18q</guid>
            <pubDate>Sun, 19 Jan 2020 02:02:02 GMT</pubDate>
            <description><![CDATA[<ul>
<li><h4 id="velopert님의-typescript와-storybook을-이용한-리액트-디자인-시스템-구축하기---httpsvelogiovelopertseries">@velopert님의 Typescript와 Storybook을 이용한 리액트 디자인 시스템 구축하기 - <a href="https://velog.io/@velopert/series">https://velog.io/@velopert/series</a></h4>
</li>
<li><h4 id="스토리북-공식-문서---httpsstorybookjsorgdocsconfigurationsadd-custom-head-tags">스토리북 공식 문서 - <a href="https://storybook.js.org/docs/configurations/add-custom-head-tags/">https://storybook.js.org/docs/configurations/add-custom-head-tags/</a></h4>
</li>
<li><h4 id="스토리북-버전-v53">스토리북 버전: v5.3</h4>
</li>
</ul>
<h2 id="0-들어가기-전">0) 들어가기 전..</h2>
<p>웹팩 설정, npm package 설치 그리고 어떠한 configuration이 전혀 없다! 정말 쉽다.</p>
<h2 id="1-storybook-디렉토리에-preview-headhtml-파일을-생성한다">1) .storybook 디렉토리에 preview-head.html 파일을 생성한다</h2>
<p><img src="https://images.velog.io/post-images/eddie_kim/9b50c190-3a5a-11ea-af24-0f5acf69d7a2/image.png" alt="image.png"></p>
<p>웹팩 설정 없이 preview-head.html파일을 만들어 준다. preview-head.html 파일은 마치 리액트 프로젝트의 public/index.html에서 부트스트랩을 CDN을 통해(<link />를 통해) 가져올 때 처럼 스토리북 프로젝트에도 CDN을 통해 가져올 때 쓰도록 해주는 파일이다.</p>
<h2 id="2-부트스트랩-cdn을-복사해서-preview-headhtml에-붙여넣는다">2) 부트스트랩 CDN을 복사해서 preview-head.html에 붙여넣는다.</h2>
<h4 id="bootstrap-css-cdn-링크-httpsgetbootstrapcomdocs44getting-startedintroduction">Bootstrap CSS CDN 링크: <a href="https://getbootstrap.com/docs/4.4/getting-started/introduction/">https://getbootstrap.com/docs/4.4/getting-started/introduction/</a></h4>
<p><img src="https://images.velog.io/post-images/eddie_kim/07af4270-3a5c-11ea-90c2-d71c7bab0238/image.png" alt="image.png"></p>
<p>위의 스크린샷 처럼 단순히 CDN을 복사+붙여넣기 한다. <strong>스토리북 프로젝트가 실행되어 있는 상태에서 CDN을 적용했다면</strong> CLI에서 ctrl+c를 눌러 종료시킨 후 다시 실행시켜야 Bootstrap의 스타일이 적용된 스토리북 프로젝트를 볼 수 있다.</p>
<h2 id="3-bootstrap-cdn-적용-전--적용-후">3) Bootstrap CDN 적용 전 / 적용 후</h2>
<ul>
<li><h5 id="적용-전--">적용 전 -</h5>
<p><img src="https://images.velog.io/post-images/eddie_kim/eb8d9c30-3a5c-11ea-90c2-d71c7bab0238/image.png" alt="image.png"></p>
</li>
<li><h5 id="적용-후--">적용 후 -</h5>
<p><img src="https://images.velog.io/post-images/eddie_kim/4800c140-3a5d-11ea-bca8-41d78be01e63/image.png" alt="image.png"></p>
</li>
<li><p>또한 Bootstrap JS의 CDN을 가져와서 적용이 가능하다. 이때는 <body></body>태그의 가장 아랫부분에 Bootstrap JS의 CDN을 import를 해야할 필요가 있을 것이다. 스토리북 공식 문서에 <a href="https://storybook.js.org/docs/configurations/add-custom-body/">Add Custom Body</a> 를 참고하면 된다.</p>
</li>
<li><p>부트스트랩 뿐만 아니라 다른 CSS Framework 또한 CDN을 가져와서 스토리북에 적용할 수 있다.</p>
</li>
</ul>
<h2 id="4-여러분의-시간은-소중합니다">4) 여러분의 시간은 소중합니다...</h2>
<p>how to import bootstrap in storybook 으로 구글링 하면서 찾아보니까 대부분 하는 말들이 &#39;웹팩 설정을 다시해야 한다&#39;, &#39;어떤 npm package를 설치해야한다&#39; 이었다. 설정하느라고 고생은 다했지만 제대로 된 것은 하나도 없었고 개발자에게 <del>삽질</del>이 중요한 미덕이라고 생각하지만 <del>삽질</del>도 각 개발자마다 우선순위가 있다고 생각한다. 이 글을 보는 분들의 <del>덜 삽질</del>을 위해 이 글을 쓴다.</p>
]]></description>
        </item>
    </channel>
</rss>