<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>wonder-lee.log</title>
        <link>https://velog.io/</link>
        <description>원더리입니다.</description>
        <lastBuildDate>Sat, 25 Feb 2023 10:23:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. wonder-lee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wonder-lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[github actions > NPM package 자동 배포]]></title>
            <link>https://velog.io/@wonder-lee/github-actions-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-github-NPM-package-%EC%9E%90%EB%8F%99-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@wonder-lee/github-actions-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-github-NPM-package-%EC%9E%90%EB%8F%99-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Sat, 25 Feb 2023 10:23:43 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<blockquote>
<p>github main branch가 merge 되면, 자동으로 NPM publish를 하면 좋겠다.</p>
</blockquote>
<p>사내 npm package를 개발하고 팀원들과 개발 과정 공유 중, 위와 같은 이야기가 나와 <code>github acitons</code>을 이용하여 npm package 자동 배포 개발 과정을 정리한 포스트 입니다.</p>
<h1 id="내용">내용</h1>
<p>github actions를 이용하여 구현하고 싶은 자동화는 아래와 같습니다.</p>
<ol>
<li>main branch가 push event를 발생시킬 때 자동화가 이루어진다.</li>
<li>package의 version이 update되었을 때 npm publish를 실행한다.</li>
<li>github action의 시작, 종료, 에러를 slack message를 전송한다.</li>
<li>github action이 실행되고 package의 version 정보를 slack message로 전송한다.</li>
</ol>
<h2 id="github-actions-환경-설정">github actions 환경 설정</h2>
<p>우선 위의 목적 4가지를 실행할 경우 연동할 slack webhook url과 npm 배포시 필요한 npm token은 공개되어서는 안되는 key입니다.
그렇기 때문에 공개된 repository &gt; github actions file에 직접 작성하는 것이 아닌, <code>Repository secrets</code>로 설정을 해야합니다.
<img src="https://velog.velcdn.com/images/wonder-lee/post/15aed4c7-2d4e-4704-a4de-430845bd7e5b/image.png" alt="">
<code>Repository secrets</code> 설정은 Respository &gt; Settings &gt; Secrets and variables &gt; Actions 설정 page로 들어가면 위의 사진과 같은 설정 page가 보이게 됩니다. 여기서 우측 상단에 있는 <code>New repository secrets</code>라는 초록색 버튼을 눌러서 <code>Repository secrets</code> 등록합니다.
저는 위 사진처럼 <code>NPM_TOKEN</code>, <code>SLACK_WEBHOOK_URL</code>로 등록하였습니다.</p>
<ul>
<li><a href="https://blog.secuof.net/11">slack web hook url 얻는 방법</a></li>
<li><a href="https://runebook.dev/ko/docs/npm/creating-and-viewing-access-tokens">npm token 얻는 방법</a></li>
</ul>
<h2 id="gitbub-actions-개발">gitbub actions 개발</h2>
<h3 id="github-actions">github actions?</h3>
<p>자동으로 repository에서 어떤 event가 발생했을 때 특정 작업이 일어나게 하거나 주기적으로 어떤 작업들을 반복해서 실행시킬 수도 있습니다.
github actions에는 크게 2가지의 작업 단계가 있습니다. 
<code>workflow</code>는 github actions가 실행하게 될 가장 큰 틀의 자동화된 단계라 보시면 됩니다.
<code>action</code>는 workflow 안에서 실행하게될 하나 하나의 job들이라 보시면 됩니다.</p>
<h3 id="초기-설정">초기 설정</h3>
<p>자, 그럼 github actions의 가장 큰 틀인 workflow를 생성해 봅시다.</p>
<ol>
<li>github actions는 실행시킬 repository root folder에서 <code>.github</code> folder를 생성합니다.</li>
<li><code>.github</code> folder에서 실행시킬 <code>workflows</code> folder를 생성하여, <code>workflow</code> file을 생성합니다.<ul>
<li>저는 <code>on-push-check-and-publish-packages.yml</code> file로 생성하였습니다. (<a href="on-push-check-and-publish-packages.yml">참고</a>)</li>
</ul>
<ol start="3">
<li>저는 main branch에서 git push event가 발생할 때, 해당 workflow가 실행되게 아래처럼 설정하였습니다.<pre><code>name: &quot;on-push-check-and-publish-package&quot;
on:
push:
branches:
- main
jobs:
check-and-publish-package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
 with:
   node-version: 16.x</code></pre><h4 id="on-push-braches-main">on, push, braches, main</h4>
명령어는 직관적으로 main brach가 push event를 발생할 때 알 jobs를 실행 시킨다는 trigger 명령어입니다. 이 외에도 다양한 git event가 발생할 때 trigger를 연동시킬 수 있으며 가능한 event는 공식 homepage의 <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows">workflow event</a> 내용을 확인해보시면 됩니다. (ex : pull request, issue open, braches 이름 포함, 특정 경로의 file 등등)<h4 id="jobs-check-and-publish-package">jobs, check-and-publish-package</h4>
jobs는 work flow에서 실행될 행동들 이라고 생각하시면 됩니다. 그리고 모든 jobs를 통틀 이름을 다음 줄에 적어놓은 <code>check-and-publish-package</code>으로 설정하게 됩니다. 그러면 아래 image처럼 윗줄에서 설명했던 trigger가 발생되어 실행되는 jobs들을 통틀어 <code>check-and-publish-package</code>라고 불러지게 됩니다. 
<img src="https://velog.velcdn.com/images/wonder-lee/post/eb170d67-93ec-4bdf-9077-ae3e5bb5d192/image.png" alt=""><h4 id="runs-on">runs-on</h4>
jobs들을 실행할 환경을 설정하는 명령어 입니다. 저는 일반적으로 사용되는 <code>ubuntu-latest</code>로 설정하였습니다.<h4 id="steps">steps</h4>
steps는 jobs의 실행 단위라고 생각하시면 됩니다. 아래 image처럼 하나 하나 실행되는 단위 입니다. steps에서 실행될 행동들을 직접 작성할 때는 actions라는 file을 생성하거나, actions marketplace에서 다른 개발자들이 작성한 actions를 가져다 사용할 수 있습니다. 마치 다른 개발자가 만든 library를 install해서 사용하는 것과 같다고 생각하시면 됩니다.
저는 다른 개발자가 이미 만들어 놓은 actions인 <code>checkout@v3</code>와 <code>setup-node@v3</code>를 사용하였습니다. 
이 때 <code>uses</code>라는 명령어를 쓰고 뒤에 사용할 actions 명칭을 쓰면 됩니다. 또한, 해당 actions를 사용할 때 넣어줘야할 값이 있다면 with 명령어로 특정 값을 넣어주시면 됩니다. 마치 함수에 인자 값을 넣어주는 개념과 같습니다.</li>
</ol>
</li>
</ol>
<ul>
<li>checkout@v3
보통 GitHub Actions에서 작업을 수행하기 전에 Git 리포지토리에서 코드를 가져와야 합니다. 이 액션을 사용하면 원하는 리포지토리에서 소스 코드를 가져올 수 있습니다. 이 액션은 Git의 브랜치, 태그, 커밋을 가져올 수 있으며, 원하는 브랜치나 태그를 선택할 수 있습니다.
이렇게 작성하면, 현재 리포지토리의 브랜치를 가져와서 작업 폴더에 복사합니다. 이를 통해, 이후의 작업에서 리포지토리의 코드를 사용할 수 있습니다.</li>
<li>setup-node@v3
이렇게 가져온 우리의 project를 Node.js 환경 위에서 실행하게 도와주는 actions입니다.<h3 id="npm-publish-actions">NPM publish actions</h3>
<a href="https://github.com/marketplace/actions/npm-publish">NPM Publish</a> actions는 github market place에 올라와 있는 actions입니다. 우선 NPM 관련 actions 중 가장 stars가 많고, NPM,Github 관련 개발자들이 직접 참가한 actions로 가장 안정성이 좋다고 판단되어 사용하였습니다.</li>
</ul>
<p>NPM publish actions는 제가 원하는 기능을 완벽히 그리고 쉽게 이용할 수 있게 개발되어 있습니다.</p>
<pre><code>- name: Check &amp; Publish utils package
  uses: JS-DevTools/npm-publish@v1
  id: utils
  with:
    token: ${{ secrets.NPM_TOKEN }}
    package: &quot;./packages/utils/package.json&quot;</code></pre><ul>
<li>name : step의 이름을 설정합니다.</li>
<li>uses : 사용하는 actions를 설정합니다. 저는 npm-publish@v1을 사용하였습니다.</li>
<li>id : npm-publish actions의 unique하게 id를 입력해줍니다.</li>
<li>with : 사용하는 actions에 넣어줄 특정값을 설정합니다.<ul>
<li>token : NPM을 배포할 때, 필요한 NPM token 입니다.  github actions 환경 설정시 우리는 secrets key로 설정하였습니다.)</li>
<li>package : version을 확인하고 배포할 package의 경로를 설정합니다.</li>
</ul>
</li>
</ul>
<p>위 처럼 with에 package 경로를 설정하면 해당 <code>package.json</code> file에서 package 정보 참고하여, 자동적으로 이미 배포된 package version과 현재 push하려는 package version을 비교하여 version에 update가 발생하면 NPM token을 이용하여 배포를 해주게 됩니다.</p>
<h3 id="slack-연동">Slack 연동</h3>
<p>slack 연동 code는 <a href="https://fe-developers.kakaoent.com/2022/220106-github-actions/">kakao FE 기술 블로그</a>를 참고하여 작성하였습니다.</p>
<pre><code>- name: Send slack if completed
  if: ${{ success() }}
  uses: ./.github/actions/slack-noti
  with:
    status: success
    slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
    package_name: &quot;utils&quot;
    current_version: ${{steps.utils.outputs.version}}
    old_version: ${{steps.utils.outputs.old-version}}
    type: ${{steps.utils.outputs.type}}</code></pre><ul>
<li>uses : 직접 actions를 작성할 경우 위 처럼 경로를 설정하게 됩니다.</li>
<li>with :<ul>
<li>${{ secrets.SLACK_WEBHOOK_URL }} : 말 그대로 발송하게 될 slack channel의 webhook url입니다. 이 때, secrets로 설정한 값은 workflow file에서만 접근이 가능하여 with값으로 넘겨주고 있습니다.</li>
<li>${{steps.utils.outputs.version}} : 위 npm-publish actions를 사용할 때 설정한 step의 id값으로 해당 package의 현재 version 정보를 가져옵니다.</li>
<li>${{steps.utils.outputs.old-version}} : 동일하게 해당 id값의 이전 정보를 가져옵니다.</li>
<li>위 package에 대한 정보들은 Npm-publish actions에서 제공되는 값이며, output되는 정보는 <a href="https://github.com/marketplace/actions/npm-publish#output-variables">이 곳</a>에서 확인이 가능합니다.</li>
</ul>
</li>
</ul>
<p>위 처럼 workflow에서 제가 만든 <code>./.github/actions/slack-noti</code> file로 해당 값들을 넘겨 step으로 실행하게 되면 아래 file내용이 실행되어 집니다.</p>
<pre><code>Skip to content
Search or jump to…
Pull requests
Issues
Codespaces
Marketplace
Explore

@wonder-lee 
maxst-fe
/
max-at
Public
Fork your own copy of maxst-fe/max-at
Code
Issues
6
Pull requests
Actions
Projects
Security
Insights
max-at/.github/actions/slack-noti/action.yml
@lee-maxst
lee-maxst [NO-ISSUE] NPM 패키지 제목에 링크 추가
Latest commit f23d243 2 weeks ago
 History
 1 contributor
41 lines (34 sloc)  1.54 KB

name: &quot;slack-noti&quot;

inputs:
  status:
    required: false
    default: &quot;failure&quot;
  slack_webhook_url:
    required: true
  package_name:
    required: true
  current_version:
    required: false
  old_version:
    required: false
  process:
    required: false
  type:
    required: false
  emoji:
    required: false
runs:
  using: &quot;composite&quot;

  steps:
    - name: Send slack
      shell: bash
      run: |
        if [ &quot;${{ inputs.status }}&quot; = &quot;workflow&quot; ]; then
          CONTENT=&quot;${{inputs.emoji}} *&lt;https://github.com/maxst-fe/max-at|max-at&gt;* &gt; *&lt;https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}%7C${GITHUB_WORKFLOW}| NPM 버전 체크 및 배포 - 깃헙 액션&gt;* 이 ${{inputs.process}}되었습니다. ${{inputs.emoji}}\n&quot;
        elif [ &quot;${{ inputs.status }}&quot; = &quot;success&quot; ] &amp;&amp; [ ${{inputs.type}} != &#39;none&#39; ]; then
          CONTENT=&#39;&gt; *&lt;https://www.npmjs.com/package/@max-at/utils|@max-at/${{ inputs.package_name }}&gt;* ✅ \n&gt;성공적으로 ${{ inputs.old_version }} 버전에서 ${{ inputs.current_version }} 버전으로 업데이트 되었습니다.🥳&#39;

        elif [ &quot;${{ inputs.status }}&quot; = &quot;success&quot; ] &amp;&amp; [ ${{inputs.type}} = &#39;none&#39; ]; then
          CONTENT=&#39;&gt; *&lt;https://www.npmjs.com/package/@max-at/utils|@max-at/${{ inputs.package_name }}&gt;* ✅ \n&gt;버전 변경사항이 없으며, ${{ inputs.current_version }} 버전 유지 중 입니다.🏄🏻‍♂️&#39;
        fi
        MSG=&quot;{ \&quot;text\&quot;:\&quot;${CONTENT}\&quot;}&quot;
        curl -X POST -H &#39;Content-type: application/json&#39; --data &quot;${MSG}&quot; &quot;${{ inputs.slack_webhook_url }}&quot;</code></pre><ul>
<li>shell : linux bash로 설정합니다.</li>
<li>run : bash script로 동작시킬 내용을 작성합니다.<ul>
<li><code>curl -X POST -H &#39;Content-type: application/json&#39; --data &quot;${MSG}&quot; &quot;${{ inputs.slack_webhook_url }}&quot;</code></li>
<li>bash script에서 if 구문을 통하여 시작, 실패, 성공, 종료 상황 별로 slack 발송할 message를 설정하고, 마지막으로 slack webhook url로 message를 POST하는 구문입니다.</li>
</ul>
</li>
</ul>
<p>아래는 slack channel에 전송되는 모습입니다.</p>
<p><img src="https://velog.velcdn.com/images/wonder-lee/post/b4caafad-19e0-44ef-a771-a1e9819c31d3/image.png" alt="">
<img src="https://velog.velcdn.com/images/wonder-lee/post/bf88bd4d-9aaa-4dea-b632-7cc89073bd0f/image.png" alt=""></p>
<p>위 내용의 github file <a href="https://github.com/maxst-fe/max-at/blob/main/.github/actions/slack-noti/action.yml">원본</a>의 주소 입니다. 참고 바랍니다.</p>
<h2 id="마치며">마치며</h2>
<p>이렇게 <a href="https://velog.io/@wonder-lee/npm-package-%EA%B0%9C%EB%B0%9C-%EA%B3%BC%EC%A0%95">사내 package를 만들고</a> 또한, package가 update되었을 경우 github action을 통하여 자동적으로 package를 배포하는 과정을 기록해보았습니다. 
제가 작업을 한 사내 package도 완벽하지 않으며, github actions 또한 완벽하지는 않습니다. 
하지만 이 완벽하지 않은 package가 사내 FE개발자에게는 도움이 되었고, 완벽하지 않은 github actions 또한 package를 관리할 때 자동으로 배포되며 slack 상태를 알려주며 작은 도움이 되었습니다.
제가 미약한 시작을 하면 다른 FE개발자들도 동참하여 개선해나가며 완벽으로 가지 않을까 싶습니다. 
부족한 개발과정을 읽어주셔서 감사합니다. 🙇🏻‍♂️</p>
<h2 id="문제점">문제점</h2>
<ol>
<li>해당 workflow가 실행될 때 매번 build를 하는 부분 개선 필요</li>
<li>package가 추가될 때 마다 NPM publish actions step을 추가해야하는 부분</li>
<li>npm-publish actions 사용 시 <code>set-output</code> disabled warning issue 개선 여부 확인 필요
<a href="https://github.com/JS-DevTools/npm-publish/issues/67">Deprecation warning for &#39;set-output&#39; #67</a></li>
</ol>
<h1 id="오류">오류</h1>
<ul>
<li>Node.js 12 actions are deprecated.<ul>
<li><code>- uses: actions/checkout@v2</code><ul>
<li>생성한 actions &gt; step &gt; uses에 checkout@v2를 사용할 때, node 버전 문제가 발생하였고, chekcou@v3로 수정하였습니다.</li>
</ul>
</li>
<li><a href="https://github.com/actions/checkout/issues/1047">github actions issue 참고</a></li>
</ul>
</li>
<li>curl: (3) URL using bad/illegal format or missing URL<ul>
<li>slack webhooks url로 요청시 발생하였고, screts key 값을 echo로 출력하였지만 계속 빈 값이 나와 4시간 정도를 허비하였다.<pre><code>- `echo &quot;${{secrets.SLACK_WEBHOOK_URL}}&quot;`를 출력하면 빈값이 나왔다.</code></pre></li>
<li>해결방법은 아래 사진처럼 <code>Environment secrets</code>에 scret key를 저장하는 것이 아닌, 오른쪽 상단에 초록색 button의 <code>New create secret</code>을 눌러 <code>Repository secrets</code>에 추가하여야지 screts key에 접근할 수 있었다.
<img src="https://velog.velcdn.com/images/wonder-lee/post/639ac73e-1ae9-4ede-be84-d8be104824ee/image.png" alt=""></li>
</ul>
</li>
</ul>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://docs.github.com/en/actions">github actions 공식 homepage</a></li>
<li><a href="https://github.com/marketplace?type=">github marketplace</a></li>
<li><a href="https://github.com/marketplace/actions/npm-publish">NPM Publish</a></li>
<li><a href="https://fe-developers.kakaoent.com/2022/220106-github-actions/">카카오웹툰은 GitHub Actions를 어떻게 사용하고 있을까?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS > CloudFront <-> S3]]></title>
            <link>https://velog.io/@wonder-lee/CloudFront-S3</link>
            <guid>https://velog.io/@wonder-lee/CloudFront-S3</guid>
            <pubDate>Thu, 23 Feb 2023 03:48:31 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<h1 id="예제">예제</h1>
<h1 id="내용">내용</h1>
<p>S3에 file을 upload하고 해당 file을 cloudFront로 배포하는 과정입니다.</p>
<h2 id="s3">S3</h2>
<ol>
<li>S3 bucket 생성을 합니다.<ul>
<li>이 때, 지연 시간을 최적화하거나 비용을 최소화하려면 가까운 리전을 선택하는 것이 좋습니다. </li>
<li>우선 모두 default 값으로 생성합니다.</li>
</ul>
</li>
<li>생성한 bucke의 관리로 들어가 아래 image처럼 모든 사용자가 객체에 access할 수 있도록 설정 합니다.<image src="https://velog.velcdn.com/images/wonder-lee/post/534fa1de-4569-4b48-b3de-33806587d7e6/image.png" width="350">
<image src="https://velog.velcdn.com/images/wonder-lee/post/e22e454e-d0ba-480d-8731-42d332b4f8b3/image.png" width="350"></li>
<li>아래 image처럼 [액세스 제어 목록(ACL)(Access control list (ACL))] 섹션의 [객체(Objects)] 열에서 [모두(퍼블릭 액세스)(Everyone (public access)] 옆에 있는 [읽기(Read)]에 대한 확인란을 선택합니다. <image src="https://velog.velcdn.com/images/wonder-lee/post/85126a1f-70bb-4fe5-a82b-8ce328de75c4/image.png" width="350">
   <image src="https://velog.velcdn.com/images/wonder-lee/post/869ac22a-7b4f-4c69-bc61-134cb24b9f50/image.png" width="350">
     <image src="https://velog.velcdn.com/images/wonder-lee/post/ebd89370-c98c-46ec-bc7d-50c97134d29e/image.png" width="350"></li>
<li>bucket 정책 입력<image src="https://velog.velcdn.com/images/wonder-lee/post/4004d374-7605-4e19-aef3-22901b379b74/image.png" width="350">

</li>
</ol>
<pre><code>{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;Stmt1405592139000&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: &quot;s3:*&quot;,
            &quot;Resource&quot;: [
                &quot;arn:aws:s3:::버킷이름/*&quot;,
                &quot;arn:aws:s3:::버킷이름&quot;
            ]
        }
    ]
}</code></pre><ol start="5">
<li>접근 test 해보기<ul>
<li>bucket에 upload한 file을 누르면 <code>객체 URL</code>정보가 있습니다. 해당 내용을 click하면 해당 file을 볼 수 있습니다.<image src="https://velog.velcdn.com/images/wonder-lee/post/75c658f6-6464-4dd4-a623-85871abb7e43/image.png" wdith="350">

</li>
</ul>
</li>
</ol>
<h2 id="cludfront">CludFront</h2>
<ol>
<li>CloudFront를 배포 생성을 합니다.<ul>
<li>이 때, 원본 domain 선택란에서 위에서 만들 S3 Bucket을 선택합니다.</li>
</ul>
</li>
<li>생성된 cloudFront의 세부 정보의 domain을 확인합니다. <image src="https://velog.velcdn.com/images/wonder-lee/post/6c8e9093-ce16-4a9c-a697-dbe53bc76a63/image.png" width="350"></li>
<li>접근 test 해보기<ul>
<li>위에서 확인한 domain 뒤에 s3에 upload한 file이름을 입력합니다.<ul>
<li>ex:{배포 domain 이름}/{s3 file 이름}</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1 id="오류">오류</h1>
<ul>
<li>s3 upload file 접근 시 access denied error<ul>
<li><a href="https://velog.io/@chss3339/AWS-S3-Access-denied-%EC%97%90%EB%9F%AC">AWS S3 Access denied 에러 (+ 버킷 정책 했는데도 안될때)</a><h1 id="참고">참고</h1>
</li>
</ul>
</li>
<li><a href="https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.SimpleDistribution.html#GettingStartedUploadContent">간단한 CloudFront 배포 시작하기</a></li>
<li><a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html">서명된 URL 및 서명된 쿠키를 사용하여 비공개 콘텐츠 제공</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript > this]]></title>
            <link>https://velog.io/@wonder-lee/Javascript-this</link>
            <guid>https://velog.io/@wonder-lee/Javascript-this</guid>
            <pubDate>Fri, 10 Feb 2023 16:03:37 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>javascript에서 this에 대한 내용을 다룬 포스트입니다.</p>
<h1 id="예제">예제</h1>
<h1 id="내용">내용</h1>
<p>react에서 class component를 사용할 때나, vue를 사용할 때에는 javascipt의 <code>this</code>를 사용했습니다. 하지만 현재 next를 사용하면서 <code>this</code>를 사용한지가 오래되었습니다. 핑계가 길었지만, 결론적으로 솔직히 저는 <code>this</code>를 잘 모릅니다.(그게 자랑이니..?🤬)</p>
<h2 id="this는-무엇인가">this는 무엇인가?</h2>
<p><img src="https://velog.velcdn.com/images/wonder-lee/post/1cbff0f8-11bf-44aa-bffb-8f1a39250179/image.png" alt="">
<img src="https://velog.velcdn.com/images/wonder-lee/post/1561c969-c62b-4421-9f42-7e037e0d6116/image.png" alt="">
모를 때에는 일단 쳐보는 성향입니다. javascript 언어를 test할 때 주로 사용하는 <code>RunJS</code>, <code>JS Bin</code>에 한번 <code>this</code>를 출력해보았습니다. 위 이미지처럼 2곳 모두 <code>Window</code>라는 객체를 출력하였습니다.</p>
<h2 id="window-객체는-무엇인가">window 객체는 무엇인가?</h2>
<p>window라는 영어의 우리나라 말은 바로 <code>창</code> 입니다. 바로 browser창이 window객체입니다. browser 관련 객체인 <code>BOM(browser object model)</code>에는 몇가지 객체가 있는데 그 중 가장 최상위 객체가 바로 <code>window</code> 객체 입니다.
<img src="https://velog.velcdn.com/images/wonder-lee/post/38a0bedd-5d58-4299-91da-43a2c926f38a/image.png" alt="">
위 이미지 처럼 browser 창에 모든 것을 가지고 있는 window객체가 보이고, url 주소창은 location 객체이며, 개발자들이 작성한 code가 보이는 곳이 바로 document객체인 것을 알 수 있습니다. 그렇다면 <code>this</code>는 <code>window</code>객체를 출력하고 있으니까 뭔가 자신이 속해져있는 최상위 객체를 출력한다라고 이해가 됩니다.</p>
<p>책에서 잠깐 봤던 <code>this</code>의 정의가 조금은 이해가 갑니다. </p>
<blockquote>
</blockquote>
<ul>
<li>this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다. </li>
<li>this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.</li>
</ul>
<p>그러고보니 사용한지 좀 오래되어서 맞는지는 모르겠지만 vue를 사용할 때에도 자신의 component에 속해져 있는 method 혹은 data값을 가져올 때 <code>this</code>를 사용해서 접근했던 것 같습니다.</p>
<h2 id="this--window">this === window?</h2>
<p>위에서 제가 말씀드린 것 처럼 vue에서는 자신의 component안에 있던 method 혹은 data값을 가져올 때 <code>this</code>를 쓴 것 같은데... 그게 <code>window</code>객체에 들어간 걸까요? </p>
<p>포스트의 시작 부분에서 <code>RunJS</code>와 <code>JS Bin</code>에서 <code>this</code>를 출력하였을 때, <code>window</code>객체가 출력되었으니, <code>this</code>는 곧 <code>window</code>인가에 대한 물음이 생겼습니다.</p>
<p>즉, <code>this === window</code>가 매번 맞는지가 궁금해졌습니다.</p>
<p>google, velog에서 javascript <code>this</code>에 대해서 검색한 결과, 우선*<em><code>this === window</code>가 매번 맞지는 않습니다. 왜냐하면 <code>this</code>는 동적 binding이 되기 때문입니다. *</em></p>
<p>즉, javascript의 this는 바라보는 값이 변할 수 있기 때문에 <code>JS Bin</code>, <code>RunJs</code>에서 <code>this</code>를 출력하면 <code>window</code>가 나왔지만, 다른 상황에서는 <code>window</code>가 나오지 않습니다.</p>
<h2 id="window--object--undefined">window | object | undefined</h2>
<p>google, velog에서 <code>this</code>를 검색한 결과 친절한 문서들이 많았습니다. 아마 이 글을 보시는 여러분들도 javascript에서 <code>this</code>가 동적 binding되는 case들을 보셨을 겁니다. 아래처럼요.</p>
<pre><code>1. 전역 문맥에서의 this
2. 함수 내부에서의 this
3. method 안에서의 this
4. event hanlder 안에서의 this
5. 명시적 binding의 this
6. 화살표 함수에서의 this
7. 일반 함수에서의 this
8. 생성자에서의 this
...(더 있지 않을까 싶지만 여기까지 case를 나열하겠습니다...🤯)</code></pre><p><code>this</code>가 동적으로 biniding되는 걸 알겠지만, 그 case를 하나 하나 나열하는 것이 과연 <code>this</code>를 이해하는데 도움이 될까 싶습니다. 하지만 대부분 사람들의 포스트는 case를 하나 하나 나열했더군요. (그게 더 헷갈리는데😢... 나만 이상해씨 🐸)</p>
<p>case가 아닌 결과론적으로 어쩔때 <code>this</code>가 <code>window</code>, <code>object</code>, <code>undefined</code>가 나오는지 정리를 해보겠습니다.</p>
<h3 id="window--object">window | object</h3>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this">함수 문맥 &gt; 일반 함수</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%ED%99%94%EC%82%B4%ED%91%9C_%ED%95%A8%EC%88%98">함수 문맥 &gt; 화살표 함수</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%EA%B0%9D%EC%B2%B4%EC%9D%98_%EB%A9%94%EC%84%9C%EB%93%9C%EB%A1%9C%EC%84%9C">함수 문맥 &gt; 객체 method &gt; 일반 함수</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#dom_%EC%9D%B4%EB%B2%A4%ED%8A%B8_%EC%B2%98%EB%A6%AC%EA%B8%B0%EB%A1%9C%EC%84%9C">함수 문맥 &gt; inline event</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%EB%8B%A8%EC%88%9C_%ED%98%B8%EC%B6%9C">함수 문맥 &gt; call(), apply()</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%EB%8B%A8%EC%88%9C_%ED%98%B8%EC%B6%9C">함수 문맥 &gt; bind()</a></li>
</ul>
<h3 id="window">window</h3>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this">전역 문맥에서의 this</a></li>
</ul>
<h3 id="object">object</h3>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%EA%B0%9D%EC%B2%B4%EC%9D%98_%EB%A9%94%EC%84%9C%EB%93%9C%EB%A1%9C%EC%84%9C">함수 문맥 &gt; 객체 method &gt; 객체 prototype chain</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%EA%B0%9D%EC%B2%B4%EC%9D%98_%EB%A9%94%EC%84%9C%EB%93%9C%EB%A1%9C%EC%84%9C">함수 문맥 &gt; 객체 method &gt; 접근자, 설정자</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%EC%83%9D%EC%84%B1%EC%9E%90%EB%A1%9C%EC%84%9C">함수 문맥 &gt; 생성자</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#dom_%EC%9D%B4%EB%B2%A4%ED%8A%B8_%EC%B2%98%EB%A6%AC%EA%B8%B0%EB%A1%9C%EC%84%9C">함수 문맥 &gt; DOM event</a><h3 id="undefined">undefined</h3>
</li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this#%EB%8B%A8%EC%88%9C_%ED%98%B8%EC%B6%9C">함수 문맥 &gt; 단순 호출 &gt; 엄격 모드</a></li>
</ul>
<p>위 4가지로 나눈 내용들은 MDN에서 설명하고있는 <code>this</code>가 동적으로 binding되는 case들을 정리해보았습니다. 정확하게 window, object, undefined로 떨어지는 경우보다는, window 혹은 특정 object로 반환되는 케이스를 중점으로 깊게 확인하면 좋을 것 같습니다.</p>
<p>이 포스트에서는 따로 하나 하나 case를 구체적으로 설명하기에는 이미 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this">MDN-javascript-this</a>에 상세하게 예시를 들어주었기 때문에 참고하시면 좋을 것 같습니다.</p>
<h2 id="결론">결론</h2>
<p>정말 javascirpt의 this는 김춘수 시인의 &lt;꽃&gt;과 같다.</p>
<h1 id="오류">오류</h1>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://ossam5.tistory.com/224">[JS강좌] 14강 BOM - window객체 - 오쌤의 니가스터디</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this">this- javascript | MDN</a></li>
<li><a href="https://velog.io/@edie_ko/js-this#1-%EA%B7%B8%EB%A0%87%EB%8B%A4%EB%A9%B4-this%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%83%90">자바스크립트의 this는 김춘수의 〈꽃〉이다</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[npm package 개발 과정]]></title>
            <link>https://velog.io/@wonder-lee/npm-package-%EA%B0%9C%EB%B0%9C-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@wonder-lee/npm-package-%EA%B0%9C%EB%B0%9C-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Sun, 05 Feb 2023 12:45:26 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>frontend 개발자들이 사용하게될 util 함수들을 package를 개발하고 배포하였던 과정의 포스트 입니다.</p>
<pre><code>참여자 : 1명
기여도 : 100%
기술 : javascript, typescript, rollup.js
배운 점 : 
    - npm package 개발 과정
    - 공통 참여를 위한 package 개발 조직화 방법
    - javascript module system</code></pre><h1 id="예제">예제</h1>
<ul>
<li><a href="https://www.npmjs.com/package/@max-at/utils">npm package 주소</a>입니다.</li>
<li><a href="https://github.com/maxst-fe/max-at/tree/main/packages/utils">github 주소</a>입니다.<h1 id="내용">내용</h1>
<h2 id="배경">배경</h2>
회사에서 운영중인 주요 service는 여러개의 service를 하나로 모아놓은 구
조입니다. 사용자 입장에서는 하나의 service로 보여지겠지만, 운영자 입장에서는 하나의 service가 아니라 여러개의 repository로 나누어져 있는 상태입니다.
이러한 배경 아래에서 <strong>service 전체에 동일하게 적용해야할 기능이 생길 때 마다 모든 repositroy에 code를 수정 혹은 추가해야하는 비효율적인 문제점이 자주 발생했습니다.</strong></li>
</ul>
<p>해당 문제점을 해결하기 위해서는 2가지 해결법이 보였습니다.</p>
<ol>
<li>monorepo로 통일</li>
<li>공통 기능 package 개발</li>
</ol>
<p>결국에는 공수가 적으면서 안정성을 보장하는 package를 생성하기로 결정하였고, 해당 작업을 자원하면서 처음으로 npm package를 개발하게 되었습니다.</p>
<h2 id="개발-과정">개발 과정</h2>
<h3 id="1-npm-계정-및-organization-생성">1. NPM 계정 및 organization 생성</h3>
<p>npm package를 생성을 위해 npm 공식 homepage에서 계정을 생성하고, 회사의 frontend 개발자들과 같이 package를 관리하기 위하여 npm organization을 생성하였습니다.max-at 이라는 조직을 생성하면, 자동으로 @ scope가 생성됩니다.</p>
<h3 id="2-folder-구조-설정">2. folder 구조 설정</h3>
<p>package의 전체 구조는 <a href="https://github.com/toss/slash">toss의 library</a> 구조와 동일하게 설계하였습니다.
아래는 utils함수가 추가된 folder 구조입니다. packages라는 folder에 생성할 package를 추가해 나가면 됩니다.</p>
<pre><code>├── README.md
└── packages
    └── utils // 라이브러리 명(@max-at/utils)
        ├── README.md
        ├── package.json
        ├── rollup.config.js
        ├── src
        │   ├── helpers // export되는 함수는 아니나 내부적으로 사용되는 함수를 생성하시면 됩니다.
        │   │   ├── factory // 내부적으로 사용하게 될 팩토리 함수를 생성하시면 됩니다.
        │   │   │   └── cookie.ts
        │   │   └── unit // 내부적으로 사용하게 될 단일 함수를 생성하시면 됩니다.
        │   │       ├── deleteCookiesByToken.ts
        │   │       ├── getDateTimeExpiresByToken.ts
        │   │       └── setCookiesByTokens.ts
        │   ├── index.ts // 최종적으로 export되는 함수들을 export 해주시면 됩니다.
        │   └── utils // 최종적으로 export되는 library의 기능을 file 단위로 생성하시면 됩니다.
        │       ├── deleteCookiesByToken.ts
        │       └── updateCookiesByToken.ts
        └── tsconfig.json
    └── new library...</code></pre><h3 id="3-packagejson-설정">3. package.json 설정</h3>
<p>아래에서 생성하게 될 package 이름은 utils입니다.
위에서 생성한 repositoryd에서 packages 폴더 하위에 우리가 생성할 utils라는 folder를 생성합니다. 그 다음 생성한 utils folder로 들어와 <code>npm init -y</code> 명령어로 package의 기본 설정값으로 빠르게 package를 생성합니다. <code>npm i</code> 명령어를 통해, typescript, rollup.js 설치합니다.
<a href="https://github.com/maxst-fe/max-at/blob/main/packages/utils/package.json">package.json 세부 내용</a></p>
<h3 id="4-typescript-설정">4. typescript 설정</h3>
<p>tsconfig.json file을 생성하여 typescript의 세부 설정을 합니다.
<a href="https://github.com/maxst-fe/max-at/blob/main/packages/utils/tsconfig.json">tsconfig.json 세부 내용</a></p>
<h3 id="5-rollupjs-설정">5. rollup.js 설정</h3>
<h4 id="51-rollupjs">5.1. rollup.js?</h4>
<p>rollup.js는 library 제작 용도에 특화된 bundler 입니다. frontend 개발자라면 webpack을 아실 겁니다. rollup도 webpack과 비슷한 역할로 javascript의 bundler의 한 종류 입니다. rollup은 webpack과 다르게 build의 결과물을 ES6 module 형태로 추출할 수 있습니다.
ES6 module로 추출할 수 있다는 뜻은, module의 전부를 불러오는 것이 아닌, module 중에서 개발자가 필요한 기능만 import해서 사용할 수 있습니다. 이렇게 되면, code를 build하는 단계에서 treeshaking을 통해 사용하지 않는 code는 제거가 가능합니다. 또한, rollup library는 CJS, ESM으로 bundle이 가능합니다.</p>
<p>rollup.config.js file을 생성하여 bundle 세부 설정을 합니다.
<a href="https://github.com/maxst-fe/max-at/blob/main/packages/utils/rollup.config.js">rollup.config.js 세부 내용</a></p>
<h3 id="6-package-test">6. package test</h3>
<p>아마 위에 설정들의 세부 내용을 참고하여 초기 환경 설정을 마치게 되면 build와 publish는 아마도 정상적으로 실행이 될 겁니다. 그렇다면 우리가 package에 추가할 함수를 추가하면 어떻게 test를 하게 될까요?
저는 아래 3가지 과정의 test방법을 시도 했었고, 결론적으로는 symbol link를 이용한 test가 가장 유용합니다.</p>
<ol>
<li>직접 배포 후 install</li>
<li>local에서 경로를 통해 직접 install </li>
<li>symbol link 이용<h4 id="61-symbol-link-이용">6.1. symbol link 이용</h4>
위에 방법 중 1번의 단점은 수정 후 매 번 build, publish, install의 단계가 필요했고, 2번의 단점은 수정 후 매 번 build, install의 단계가 필요했습니다.
하지만 <strong>symbol link를 이용하여 test할 경우에는 build, publish, install 단계가 모두 필요없이 수정된 source가 즉각적으로 install된 project에 반영되어 실시간으로 수정된 사항을 적용</strong>할 수 있었습니다!
아래는 symbol link를 이용하는 방법입니다.<pre><code>###### symbol link 이용 시작 #####
# 1. package 경로 위치에서 아래 명령어를 실행하여, symbol link를 생성합니다.
# 1.1. 이 때 package.json에서 설정한 name으로 symbol link가 생성되어집니다.
sudo npm link
</code></pre></li>
</ol>
<h1 id="2-package를-test할-project-경로에서-아래-명령어를-실행하여-package를-연동합니다">2. package를 test할 project 경로에서 아래 명령어를 실행하여 package를 연동합니다.</h1>
<p>npm link @max-at/utils</p>
<h6 id="symbol-link-이용-종료">symbol link 이용 종료</h6>
<h1 id="1-package를-test한-project에서-아래-명령어를-실행하여-package를-제거합니다">1. package를 test한 project에서 아래 명령어를 실행하여 package를 제거합니다.</h1>
<p>npm unlink --no-save @max-at/utils</p>
<h1 id="2-package-경로에서-아래-명령어를-실행하여-symbol-link를-제거합니다">2. package 경로에서 아래 명령어를 실행하여, symbol link를 제거합니다.</h1>
<p>npm uninstall @max-at/utils</p>
<p>```</p>
<h3 id="7-배포-과정">7. 배포 과정</h3>
<p>작업한 package foloder의 package.json &gt; version 을 0.0.1을 올리고 저장합니다.
작업한 라이브러리 폴더에서 <code>npm publish</code> 명령어를 실행하면 배포가 되어집니다.
만약 권한 에러가 발생할 경우 터미널에 npm login 명령어를 실행하여 npm이 로그인을 진행합니다.</p>
<h1 id="오류">오류</h1>
<p>기록을 하지 못하였습니다. 😢</p>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://github.com/toss/slash">toss/slash</a></li>
<li><a href="https://wormwlrm.github.io/2021/11/07/Rollup-React-TypeScript.html">Rollup 기반 라이브러리 개발 환경 구성하기</a></li>
<li><a href="https://docs.npmjs.com/">npm Docs</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[글또 신청 > 삶의 지도]]></title>
            <link>https://velog.io/@wonder-lee/%EA%B8%80%EB%98%90-%EC%8B%A0%EC%B2%AD-%EC%82%B6%EC%9D%98-%EC%A7%80%EB%8F%84</link>
            <guid>https://velog.io/@wonder-lee/%EA%B8%80%EB%98%90-%EC%8B%A0%EC%B2%AD-%EC%82%B6%EC%9D%98-%EC%A7%80%EB%8F%84</guid>
            <pubDate>Sun, 05 Feb 2023 12:36:21 GMT</pubDate>
            <description><![CDATA[<h1 id=""></h1>
<h1 id="개요">개요</h1>
<p>글또 8기 신청시 제출해야하는 <code>삶의 지도</code>라는 주제의 글 입니다.</p>
<h1 id="내용">내용</h1>
<h2 id="요약">요약</h2>
<ul>
<li>사건 : 고등학교 (중국 → 말레이시아) → 대학교 (중국) → 군대 (단기 장교) → 직장(워커힐 파라다이스 → 개발자)</li>
<li>역량 : 도전, 목표 달성, 책임감</li>
<li>성격 : 내향적, 호기심, 긍정적, 몽상가, 게으름</li>
<li>개발 : 브랜디(백, 프론트), 맥스트(프론트)</li>
</ul>
<h2 id="본문">본문</h2>
<blockquote>
<p>사막에서 그는 너무도 외로워 때로는 뒷걸음질로 걸었다. 자기 앞에 찍힌 발자국을 보려고.
Hortense Vlou </p>
</blockquote>
<p>저를 어떻게 설명하면 좋을지 고민하다 자연스레 글또 신청 주제처럼 삶의 지도에서 잠시 뒷걸음질로 걸으며 제가 걸어온 발자국을 보게 되었습니다. 제가 걸어온 발자국을 크게 나눈다면, 위 요약에서 사건 정도로 볼 수 있습니다.</p>
<p>객관적으로 제 발자국을 부정적으로 본다면 <strong>방황하며 시간을 낭비한 인생</strong>을 산 것 같습니다. 왜냐하면 <strong>속해있던 조직과 단체가 일관성이 없기 때문</strong>입니다.</p>
<p>객관적으로 제 발자국을 긍정적으로 본다면 <strong>도전적으로 올바른 방향의 인생을 탐색</strong>하며 산 것 같습니다. 왜냐하면 당시 저 자신이 <strong>올바르다고 설정한 목표들을 달성</strong>해 왔기 때문입니다. 지금 역시도 제가 걸어온 경험을 비교하며 <strong>저 자신이 좋아하고 만족했던 분야 범위를 좁혀 나가고 있습니다.</strong></p>
<p>타임머신이 발명되지 않는 한, <strong>제가 걸어온 발자국은 변하지 않는 고정값</strong>입니다. 하지만 그 변하지 않는 고정값을 <strong>어떤 시각으로 보는 것은 정말 다행히도 제가 선택 할 수 있습니다.</strong> 저는 굉장히 부정적이면서도 굉장히 긍정적입니다. 그렇기 때문에 우선 값을 긍정적 시각으로 설정하고 다음 값으로는 적게 참조하는 부정적 시각을 설정하며 살아갑니다.</p>
<p>그래서 그런지 아무리 생각해도 <strong>저는 제 인생이 낭비한 인생이라 생각하지 않고, 제가 제 삶의 주권을 가지고 올바른 방향을 탐색하며 살아가고 있다고 자신합니다.</strong></p>
<p>위 요약에서 제 역량과 성격이 거짓이 아니라는 것을 제가 걸어온 발자국을 보면 조금은 납득이 갈 것 같습니다. 호기심이 많고 긍정적이어서 남들이 달성하지 못할 것 같은 목표를 설정하고, 혼자 소심하게 행복한 상상을 자주 합니다. 그리고 다행히 현재는 개발자라는 직업을 달성하게 되었습니다. 경험했던 직업들도 모두 좋았지만, 그중에서도 개발자라는 직업의 만족도가 가장 높습니다.</p>
<p>심플하디심플한 회사의 하얀 책상에 앉아서 턱에 손을 괴고, 코드를 어떻게 작성할지 상상의 나래를 그려나가다 머릿속에서 로직이 깨끗하게 결합하고 손으로 직접 쓰고 그 <strong>결과물이 머릿속에서 상상했던 것과 동일한 순간 ‘나는 몽상가가 아니구나!’라는 느낌이 좋습니다.</strong> 😊 왜냐하면 전 저 자신이 몽상가라고 생각하므로 그걸 부정하기 위해 노력을 나름 하는 편 입니다.</p>
<p>오늘, 글또 덕분에 삶의 지도 속에서 잠시 뒷걸음질로 걸으며 제 앞에 찍힌 발자국들을 돌이켜보는 시간을 가져 매우 뜻깊었습니다. 이제 다시 몸을 앞으로 돌려, 발자국이 채워져 있지 않는 깨끗한 길에 제 발자국을 찍어 나가려고 합니다. 글또와 같이 찍어나가도 너무 좋고, 만약 제가 글또와 맞지 않아 같이 채워나가지 않아도 괜찮습니다. 잠시 제 삶을 돌이켜 생각할 수 있는 시간을 준 것만으로도 저는 오늘 하루 많은 것을 얻었다고 생각합니다.</p>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://www.notion.so/ac5b18a482fb4df497d4e8257ad4d516">글또</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>