<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>WANZARGEN</title>
    <link>https://wanzargen.tistory.com/</link>
    <description>Wanjin&amp;rsquo;s tech blog</description>
    <language>ko</language>
    <pubDate>Thu, 16 Apr 2026 17:04:55 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>WANJIN</managingEditor>
    <image>
      <title>WANZARGEN</title>
      <url>https://tistory1.daumcdn.net/tistory/2741907/attach/5d9e7f28b15d4e7daf066f1de63870b7</url>
      <link>https://wanzargen.tistory.com</link>
    </image>
    <item>
      <title>npm workspaces 번역해드려요</title>
      <link>https://wanzargen.tistory.com/44</link>
      <description>&lt;div class=&quot;markdown-body&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 npm 공식 문서를 이해할 목적으로, 제가 이해할 수 있는 언어로 바꾸어 쓴 글입니다.&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;TMI&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 개발하고 있는 멀티 클라우드 플랫폼, &lt;a href=&quot;https://spaceone.megazone.io/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;스페이스원(SpaceONE)&lt;/b&gt;&lt;/a&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;/li&gt;
&lt;li&gt;스페이스원 콘솔 서비스 내에서 전반적으로 사용되는 코어 라이브러리&lt;/li&gt;
&lt;/ul&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;a href=&quot;https://blog.wanzargen.me/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;우리 팀 FE 파트에서는 멀티레포가 왜 벅찰까&lt;/a&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;그래서 모노레포를 PoC 로 적용하려고 찾아다니다가, 간편하게 해결할 수 있는 npm workspaces를 찾아냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;설명&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;워크스페이스(workspaces)&lt;/b&gt;는 npm cli의 단일 최상위 루트 패키지 내에서 로컬 파일 시스템의 여러 패키지 관리를 지원하는 기능을 나타내는 용어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 로컬 파일 시스템에서 연결(link)된 패키지를 처리하는 훨씬 더 간소화된 워크플로우를 구성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;npm install&lt;/code&gt;의 일부로 연결(link) 프로세스를 자동화합니다.&lt;/li&gt;
&lt;li&gt;현재 &lt;code&gt;node_modules&lt;/code&gt; 폴더에 심볼릭 링크되어야 하는 패키지에 대한 참조를 추가하기 위해 &lt;code&gt;npm link&lt;/code&gt;를 수동으로 사용하지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크스페이스는 &lt;code&gt;package.json&lt;/code&gt;의 &lt;code&gt;workspaces&lt;/code&gt;에 명시적으로 정의된 현재 로컬 파일 시스템 내의 중첩 패키지들이며, &lt;code&gt;npm install&lt;/code&gt; 을 하는 동안 자동으로 심볼릭 링크되어 마치 단일 워크스페이스인 것처럼 여겨집니다.&lt;/p&gt;
&lt;h1&gt;workspace &lt;b&gt;정의&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크스페이스는 &lt;code&gt;package.json&lt;/code&gt; 파일의 &lt;code&gt;workspaces&lt;/code&gt;속성을 통해 정의됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;my-workspaces-powered-project&quot;,
  &quot;workspaces&quot;: [
    &quot;packages/a&quot;
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 작업 디렉토리(&lt;code&gt;.&lt;/code&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;code&gt;package.json&lt;/code&gt;)이 있고&lt;/li&gt;
&lt;li&gt;Node.js 패키지를 정의하는 &lt;code&gt;package.json&lt;/code&gt; 파일을 가지고 있는 &lt;code&gt;packages/a&lt;/code&gt; 폴더가 있는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 표현할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;.
+-- package.json
`-- packages
   +-- a
   |   `-- package.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 현재 작업 디렉토리(&lt;code&gt;.&lt;/code&gt;) 에서 &lt;code&gt;npm install&lt;/code&gt;을 실행했을 때 예상되는 결과는, 폴더 &lt;code&gt;packages/a&lt;/code&gt;가 현재 작업 디렉토리의 &lt;code&gt;node_modules&lt;/code&gt; 폴더에 심볼릭 링크된다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상되는 결과를 아래와 같이 표현할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;.
+-- node_modules
|  `-- a -&amp;gt; ../packages/a
+-- package-lock.json
+-- package.json
`-- packages
   +-- a
   |   `-- package.json&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;workspace &lt;b&gt;시작하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v7/commands/npm-init&quot;&gt;npm init&lt;/a&gt; 를 사용하여 새 워크스페이스를 정의하는 데 필요한 단계를 자동화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이미 &lt;code&gt;package.json&lt;/code&gt; 가 정의된 프로젝트에서 다음을 실행할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;npm init -w ./packages/a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령은 필요한 경우 &lt;code&gt;package.json&lt;/code&gt;이나 누락된 폴더들을 생성하는 동시에, 루트 프로젝트의 &lt;code&gt;package.json&lt;/code&gt;에 &lt;code&gt;workspaces&lt;/code&gt; 속성을 적절하게 구성하는지 확인합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;workspace&lt;b&gt;에 종속성 추가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v7/using-npm/config#workspace&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;workspace&lt;/code&gt;config&lt;/a&gt;를 사용하여 워크스페이스의 종속성을 직접 추가/제거/업데이트하는 것이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 구조로 예를 들어볼게요.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;.
+-- package.json
`-- packages
   +-- a
   |   `-- package.json
   `-- b
       `-- package.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;workspace&lt;/code&gt; config를 사용하여 npm installer에게 어느 워크스페이스의 종속성으로 해당 패키지가 추가되어야 하는지를 알릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;code&gt;a&lt;/code&gt; 워크스페이스에 &lt;code&gt;abbrev&lt;/code&gt; 패키지 종속성을 추가하려는 경우에 대한 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;npm install abbrev -w a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;code&gt;uninstall&lt;/code&gt;, &lt;code&gt;ci&lt;/code&gt; 등과 같은 다른 명령도 동일하게 동작합니다.&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;a href=&quot;https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_all_together&quot;&gt;Node.js가 모듈 확인을 처리하는 방법&lt;/a&gt;을 감안할 때, 정의된 &lt;code&gt;package.json&lt;/code&gt;의 &lt;code&gt;name&lt;/code&gt;을 워크스페이스의 이름으로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 정의한 예제에 이어, &lt;code&gt;a&lt;/code&gt; 워크스페이스 모듈이 필요한 Node.js 스크립트도 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ./packages/a/index.js
module.exports = 'a'

// ./lib/index.js
const moduleA = require('a')
console.log(moduleA) // -&amp;gt; a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 실행: &lt;code&gt;node lib/index.js&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;node_modules&lt;/code&gt; resolution의 특성은, 워크스페이스들이 개별 워크스페이스를 요구(require)할 수 있는 portable한 워크플로우가 가능하게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 중첩된(nested) 워크스페이스들을 다른 곳에서도 사용할 수 있도록 &lt;a href=&quot;https://docs.npmjs.com/cli/v7/commands/npm-publish&quot;&gt;배포할&lt;/a&gt; 수 있게 합니다.&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;code&gt;workspace&lt;/code&gt; 설정 옵션을 사용하여, 구성된 워크스페이스의 컨텍스트에서 명령을 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;npm run&lt;/code&gt;다음은 중첩된 워크스페이스의 컨텍스트에서 명령 을 사용하는 방법에 대한 간단한 예입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 워크스페이스를 포함하는 프로젝트의 경우, 예:&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;.
+-- package.json
`-- packages
   +-- a
   |   `-- package.json
   `-- b
       `-- package.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;workspace&lt;/code&gt; 옵션을 사용하여 명령을 실행하면, 해당하는 특정 워크스페이스의 컨텍스트에서 지정된 명령을 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;npm run test --workspace=a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &lt;code&gt;./packages/a/package.json&lt;/code&gt; 파일 내에 정의된 &lt;code&gt;test&lt;/code&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;npm run test --workspace=a --workspace=b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;workspaces&lt;/code&gt; (복수형) 옵션을 사용하여, 구성된 &lt;b&gt;모든&lt;/b&gt; 워크스페이스의 컨텍스트에서 해당 명령을 실행할 수도 있습니다. 예:&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run test --workspaces&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;./packages/a&lt;/code&gt;및 &lt;code&gt;./packages/b&lt;/code&gt; 모두에서 &lt;code&gt;test&lt;/code&gt; 스크립트를 실행합니다 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령은 각 &lt;code&gt;package.json&lt;/code&gt;에서 나타나는 워크스페이스 순서대로 실행됩니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;workspaces&quot;: [ &quot;packages/a&quot;, &quot;packages/b&quot; ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 케이스의 실행 순서는 위 케이스의 실행 순서와 다릅니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;workspaces&quot;: [ &quot;packages/b&quot;, &quot;packages/a&quot; ]
}&lt;/code&gt;&lt;/pre&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;모든 workspace가 &lt;code&gt;npm run&lt;/code&gt; 명령으로 실행되는 스크립트를 구현할 필요는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;--if-present&lt;/code&gt; 플래그 와 함께 명령을 실행하면 npm은 대상 스크립트가 없는 workspace는 무시합니다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run test --workspaces --if-present&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v7/using-npm/workspaces&quot;&gt;https://docs.npmjs.com/cli/v7/using-npm/workspaces&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 423px; top: 139.5px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming</category>
      <category>monorepo</category>
      <category>npm workspaces</category>
      <category>spaceone</category>
      <category>멀티 클라우드 플랫폼</category>
      <category>모노레포</category>
      <category>스페이스원</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/44</guid>
      <comments>https://wanzargen.tistory.com/44#entry44comment</comments>
      <pubDate>Mon, 29 Aug 2022 21:48:48 +0900</pubDate>
    </item>
    <item>
      <title>타입스크립트의 &amp;quot;프로젝트 참조(reference)&amp;quot;</title>
      <link>https://wanzargen.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발하고 있는 &lt;a href=&quot;https://spaceone.megazone.io/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스페이스원(SpaceONE)&lt;/a&gt; 콘솔 프로젝트에 모노레포를 도입하면서 TypeScript의 reference 기능을 알게되었습니다.&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;a href=&quot;https://www.typescriptlang.org/docs/handbook/project-references.html&quot;&gt;Typescript 의 공식 문서에서 References 와 관련된 부분&lt;/a&gt;을 개인적으로 이해할 목적으로 의역한 글입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 부분에 당장 불필요해서(이해가 안되어서) 빼먹은게 있긴 한데&amp;hellip;. 언젠가 업데이트 할 날이 오겠죠 ㅠㅠ&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입스크립트의 프로젝트 참조가 뭔가요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 더 작은 조각으로 구성할 수 있는 TypeScript 3.0의 새로운 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;구성 요소(compoents) 간의 논리적 분리가 가능하고&lt;/li&gt;
&lt;li&gt;새롭고 더 나은 방식으로 코드를 구성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsc용 &lt;code&gt;--build&lt;/code&gt; 플래그도 함께 작동합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 필요하죠? 어떨 때 좋나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 참조(references)가 프로그램을 더 잘 구성하는 데 어떻게 도움이 되는지 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 모듈(converter 및 unit)과 각각에 해당하는 테스트 파일이 있는 프로젝트가 있다고 합시다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;/
├── src/
│   ├── converter.ts
│   └── units.ts
├── test/
│   ├── converter-tests.ts
│   └── units-tests.ts
└── tsconfig.json&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;// converter-tests.ts
import * as converter from &quot;../converter&quot;;
assert.areEqual(converter.celsiusToFahrenheit(0), 32);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 단일 tsconfig 파일을 사용하는 경우, 이 구조를 사용하는 것이 다소 어색했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현 파일에서 테스트 파일을 가져올 수 있었습니다.&lt;/li&gt;
&lt;li&gt;output 폴더 이름에 src를 표시하지 않고(아마도 이렇게 해주기 싫었을 것) test와 src를 동시에 빌드하는 것은 불가능했습니다.&lt;/li&gt;
&lt;li&gt;구현 파일의 내부만 변경하면 새로운 오류가 발생하지 않더라도 테스트를 다시 type check해야 했습니다.&lt;/li&gt;
&lt;li&gt;테스트만 변경하면 아무 것도 변경되지 않았더라도 구현 파일도 다시 type check해야 했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 tsconfig 파일을 사용하여 이러한 문제 중 일부를 해결할 수 있지만 새로운 문제가 나타났을 겁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내장된 최신 확인(built-in up-to-date checking) 기능이 없으므로 항상 tsc를 두 번 실행하게 됩니다.&lt;/li&gt;
&lt;li&gt;tsc를 두 번 호출하면 더 많은 시작 시간 오버헤드가 발생합니다.&lt;/li&gt;
&lt;li&gt;tsc -w는 한 번에 여러 config 파일에 대하여 실행할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 참조는 이러한 모든 문제 등을 해결할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 쓰나요? 어떻게 동작하죠?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig.json 파일에는 새로운 최상위 속성인 references가 있습니다. 참조할 프로젝트를 지정하는 객체의 배열입니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;compilerOptions&quot;: {
        // The usual
    },
    &quot;references&quot;: [
        { &quot;path&quot;: &quot;../src&quot; }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;references의 각 path 속성은 tsconfig.json 파일이 포함된 디렉토리 또는 tsconfig 파일 자체(이름이 있을 수 있음)를 가리킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 참조하면 다음과 같은 새로운 일이 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조된 프로젝트에서 모듈을 가져오면, 대신 출력 선언 파일(.d.ts)이 로드됩니다.&lt;/li&gt;
&lt;li&gt;참조된 프로젝트가 outFile을 생성하는 경우, 출력 파일 .d.ts 파일의 선언이 이 프로젝트에서 표시됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빌드 모드&lt;/b&gt;(아래 참조)는 필요한 경우 참조된 프로젝트를 자동으로 빌드합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로젝트로 분리하면 type check 및 컴파일 속도를 크게 향상시키고 편집기를 사용할 때 메모리 사용량을 줄이고 프로그램의 논리적 그룹화 시행을 개선할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조되는 프로젝트에 동작하도록 설정해줄게 있나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조된 프로젝트에는 composite 설정이 활성화되어 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정은 TypeScript가 참조된 프로젝트의 출력을 찾을 위치를 빠르게 결정할 수 있도록 하는 데 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 플래그를 활성화하면 몇 가지 사항이 변경됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적으로 설정하지 않은 경우 rootDir 설정은 기본적으로 tsconfig 파일이 포함된 디렉토리로 설정됩니다.&lt;/li&gt;
&lt;li&gt;모든 구현 파일은 include 패턴과 일치하거나 files 배열에 나열되어야 합니다. 이 제약 조건을 위반하면 tsc는 지정되지 않은 파일을 알려줍니다.&lt;/li&gt;
&lt;li&gt;declaration 도 켜야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조된 프로젝트들도 IDE의 탐색기능이 지원되나요?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;declarationMap&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언 소스 맵(&lt;a href=&quot;https://github.com/Microsoft/TypeScript/issues/14479&quot;&gt;declaration source map&lt;/a&gt;)에 대한 지원도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/tsconfig#declarationMap&quot;&gt;declarationMap&lt;/a&gt; 을 활성화하면 &quot;Go to Definition&quot; 및 이름 바꾸기와 같은 IDE의 기능을 사용할 수 있어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;종속된 파일들을 앞에 추가하려면?(이거 아직 이해가 안됨 ㅠㅠ)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;references에서 에서 prepend 옵션을 사용하여 종속성(dependency)의 출력 파일들(output)을 앞에 추가할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;&quot;references&quot;: [
   { &quot;path&quot;: &quot;../utils&quot;, &quot;prepend&quot;: true }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 앞에 추가하면 현재 프로젝트의 출력 위에 프로젝트의 출력이 포함됩니다. 모든 출력 파일(.js, .d.ts, .js.map, .d.ts.map)이 올바르게 내보내집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsc는 이 프로세스를 수행하기 위해, 디스크의 기존 파일만 사용합니다.&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;pre class=&quot;nginx&quot;&gt;&lt;code&gt;   A
  ^ ^
 /   \\
B     C
 ^   ^
  \\ /
   D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상황에서 각 참조 앞에 추가하지 않는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D의 출력에 A의 두 복사본이 있게 되기 때문입니다. 이는 예기치 않은 결과를 초래할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조한 프로젝트를 빌드하려면 어떻게 하는게 좋죠?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종속 프로젝트(다른 프로젝트들을 참조한 프로젝트)는 참조 프로젝트의 빌드된 .d.ts 파일을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 특정 빌드 output으로 확인해야 합니다. 혹은 복제한 후 프로젝트를 빌드해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가짜 오류를 보지 않고 편집기에서 프로젝트를 탐색할 수 있게 하려면요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 빌드 워크플로우와의 호환성을 유지하기 위해 --build 스위치를 사용하여, 호출하지 않는 한 tsc가 종속성을 자동으로 빌드하지 않도록 할 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--build에 대해 자세히 알아보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가된 --build 알아보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript 프로젝트를 위한 스마트한 증분 빌드(incremental builds)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.0에서는 tsc와 함께 --build 플래그를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 단순한 컴파일러보다 빌드 오케스트레이터처럼 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsc --build(줄여서 tsc -b)를 실행하면 다음이 수행됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조된 모든 프로젝트 찾기&lt;/li&gt;
&lt;li&gt;최신 상태인지 감지&lt;/li&gt;
&lt;li&gt;올바른 순서로 오래된 프로젝트 빌드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsc -b에 여러 config 파일의 경로를 제공할 수 있습니다(예: tsc -b src test).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 수만큼 config 파일을 구성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt; &amp;gt; tsc -b                            # Use the tsconfig.json in the current directory
 &amp;gt; tsc -b src                        # Use src/tsconfig.json
 &amp;gt; tsc -b foo/prd.tsconfig.json bar  # Use foo/prd.tsconfig.json and bar/tsconfig.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령줄에서 전달하는 config 파일의 순서는 중요하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsc는 종속성이 항상 먼저 빌드되도록, 필요한 경우 파일을 재정렬합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tsc -b와 관련된 몇 가지 플래그도 있습니다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;--verbose: 진행 상황을 설명하기 위해 자세한 로깅을 출력합니다(다른 플래그와 결합될 수 있음).&lt;/li&gt;
&lt;li&gt;--dry: 수행할 작업을 표시하지만 실제로는 아무 것도 빌드하지 않습니다.&lt;/li&gt;
&lt;li&gt;--clean: 지정된 프로젝트의 출력을 삭제합니다(--dry와 결합될 수 있음).&lt;/li&gt;
&lt;li&gt;--force: 모든 프로젝트가 오래된 것(out of date)처럼 작동합니다.&lt;/li&gt;
&lt;li&gt;--watch: 감시 모드(--verbose를 제외한 플래그와 결합할 수 없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의 사항이 있습니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 tsc는 noEmitOnError가 켜져 있지 않는 한, 구문 또는 타입 에러가 있는 경우 출력물(.js 및 .d.ts)을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증분 빌드 시스템에서 이 작업을 수행하는 것은 매우 나쁩니다. 그렇기 때문에, 오래된 종속성 중 하나에 새로운 오류가 있는 경우 한 번만 볼 수 있었을 겁니다. 후속 빌드가 현재의 최신 프로젝트 빌드를 건너뛰기 때문이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 tsc -b는 모든 프로젝트에 대해 noEmitOnError가 활성화된 것처럼 효과적으로 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 출력(.js, .d.ts, .d.ts.map 등)을 확인하려는 경우, 소스 제어 도구(IDE)가 로컬 사본과 원격 사본 사이의 타임스탬프를 보존하는지 여부에 따라, 특정 소스 제어 작업 후에 --force 빌드를 실행해야 할 수도 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSBuild (이건 걍 번역만&amp;hellip;)이렇게 하면 자동 증분 빌드와 cleaning이 활성화됩니다.일부 팀은 tsconfig 파일이 짝을 이루어 관리되는 프로젝트와 동일한 암시적 그래프 순서를 갖는 msbuild 기반 워크플로를 설정했습니다. 솔루션이 이와 같으면 프로젝트 참조와 함께 tsc -p와 함께 msbuild를 계속 사용할 수 있습니다. 이들은 완전히 상호 운용 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt; &amp;lt;TypeScriptBuildMode&amp;gt;참&amp;lt;/TypeScriptBuildMode&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;tsconfig.json / -p와 마찬가지로 기존 TypeScript 프로젝트 속성은 존중되지 않습니다. 모든 설정은 tsconfig 파일을 사용하여 관리해야 합니다.&lt;/li&gt;
&lt;li&gt;msbuild 프로젝트가 있는 경우, 프로젝트 파일에 다음을 추가하여 빌드 모드를 활성화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 246px; top: -3.5px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming</category>
      <category>Project Reference</category>
      <category>reference</category>
      <category>spaceone</category>
      <category>Typescript</category>
      <category>멀티클라우드 플랫폼</category>
      <category>스페이스원</category>
      <category>타입스크립트</category>
      <category>프로젝트 참조</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/43</guid>
      <comments>https://wanzargen.tistory.com/43#entry43comment</comments>
      <pubDate>Fri, 5 Aug 2022 19:08:29 +0900</pubDate>
    </item>
    <item>
      <title>우리 팀 FE 파트에서는 멀티레포가 왜 벅찰까</title>
      <link>https://wanzargen.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저는 &lt;a href=&quot;https://spaceone.megazone.io?utm_source=wjblog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=content-mkt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;멀티 클라우드 플랫폼, 스페이스원(SpaceONE)&lt;/a&gt;의 웹 Console FE(프론트엔드) 개발을 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 SpaceONE Console을 개발하는 우리 팀 프론트엔드 개발자들의 개발 경험(DX)를 높이기 위해, 멀티레포 방식을 두고 무엇을 고민했는지 그 흔적을 남겨두기 위한 포스트입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스페이스원 프론트엔드의 멀티레포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpaceONE Console 프로젝트는 멀티레포로 관리되고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아&amp;hellip; 물론 이전에는 모놀리틱하게 관리되고 있었습니다. 네, 태초에는요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;뭐 하나 리팩토링을 하거나, 설계를 할 때에 그 범위 자체가 거대해서 비효율적이고&lt;/li&gt;
&lt;li&gt;전혀 다른 프로젝트를 PoC로 진행해보려고 해도, 공통적으로 사용되는 코어 모듈들을 재사용할 수 없어 copy and paste를 해야 하는&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spaceone-dev/console&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;spaceone console&lt;/a&gt; (main application)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spaceone-dev/spaceone-design-system&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;spaceone design-system&lt;/a&gt; (하위 package)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spaceone-dev/console-core-lib&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;spaceone console-core-lib&lt;/a&gt; (하위 package)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 방식은 개발인력이 4명 남짓인 상황에서, 빠르게 개발하기에 벅차다는 생각이 점점 커지기 시작합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멀티레포, 이상과 현실&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 빠르게 발전하고 있기 때문에, 3 개의 프로젝트에 모두 기능의 추가 및 변경이 일어나야 하는 상황은 저희 팀에게 매우 빈번하게 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이럴 때 이상적인 것은, 하위의 패키지들이 변경되고 충분히 테스트된 후 application 에 반영되는 것이겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래의 패키지들이 배포되고 나면, 그것을 바탕으로 application 에서도 필요한 기능 변경 및 추가가 일어나는 것입니다.&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;ldquo;구현 단계에서만 보이는 어색한 기능이나 UX&amp;rdquo;가 분명히 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업들이 더 나은 UI/UX로 개선하기 위해 끊임없이 노력하는 프론트엔드 개발자를 원하는 이유겠죠.&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;h3 data-ke-size=&quot;size23&quot;&gt;우리 팀 FE 파트에서 개발 프로세스에 멀티레포가 걸림돌이 되는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 레포에 변경사항이 있을 때 빠른 대응을 해주기 위해, 우리는 로컬에서 패키지들을 간단하게 &lt;a href=&quot;https://docs.npmjs.com/cli/v8/commands/npm-link&quot;&gt;npm link&lt;/a&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;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분리되어있는 레포를 개발할 때에는 함께 개발해야 효율적이고&lt;/li&gt;
&lt;li&gt;하위 패키지의 배포는 따로 이뤄져야 하며&lt;/li&gt;
&lt;li&gt;어플리케이션이 동작할 때에는 패키지 의존성을 통하여 또다시 함께 동작해야 한다&lt;/li&gt;
&lt;/ol&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;이렇게 개발하기 위해서는 우리는 아래의 프로세스를 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm link 로 로컬에서 개발한 후 &amp;rarr; 하위 패키지로 사용하는 레포(design-system or console-core-lib)가 배포된 후 &amp;rarr; link 를 끊고 &amp;rarr; 배포된 패키지를 버전 업데이트한 다음 &amp;rarr; 로컬에서 잘 돌아가는지 확인하고 &amp;rarr; 푸시&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;물론 멀티레포로 사용하더라도 이 방식이 best practice 는 아니겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 패키지의 버전 관리 전략을 잘 세워두고 CI 과정과 적절히 믹스할 수 있을테지만, 더 쉽고 빠르게 해결할만한 다른 전략과 방식에 대한 고민이 생기기 시작했습니다.&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;&quot;우리 팀의 규모와 프로세스에 fit한 다른 방식은 없을까?  &quot;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서브모듈은 어떨까&lt;/h2&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;종속성으로 설치되어 사용되는 core-lib&lt;/b&gt;을 서브모듈 방식으로 관리한다면 어떻게 할까를 두고 고민해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메인 어플리케이션인 console 레포&lt;/b&gt;에 core-lib 레포를 서브모듈로 두어 사용하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;우리 팀은 핫픽스가 필요한 경우, 마스터에서 체리픽하여 릴리즈 브랜치에 반영합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메인 어플리케이션 레포를 제외하고는 마스터 브랜치만 관리합니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특징으로 인해 생길 수 있는 문제를 한번 예를 들어 추적해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;console(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;v1.1.0&lt;/span&gt;) 에서 core-lib의 헤드 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;aaa&lt;/span&gt;에 대하여 최종적으로 배포되었다고 가정해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;console(v1.1.0) &amp;gt; core-lib.aaa&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 새로운 스프린트(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;v1.2.0&lt;/span&gt;)가 시작됩니다. core-lib에 기능의 변경이 생겼고, 이것을 console에 반영하기 위해, console 의 마스터 브랜치에는 core-lib의 헤드가 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;bbb&lt;/span&gt;로 바뀌어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;console(master) &amp;gt; core-lib.bbb&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 스프린트 중간에, 이미 배포된 console(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;v1.1.0&lt;/span&gt;) 버전에 대하여 core-lib 에 크리티컬한 문제가 있는 것을 발견하여 핫픽스 이슈가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경사항을 반영하여 core-lib 마스터 브랜치에 새로운 커밋이 올라가고, 마스터의 최신 브랜치는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ccc&lt;/span&gt;가 됩니다. 그리고 console &lt;span style=&quot;background-color: #dddddd;&quot;&gt;v.1.1.1&lt;/span&gt; 은 바로 core-lib 의 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ccc&lt;/span&gt; 헤드를 바라보죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;console(v1.1.1) &amp;gt; core-lib.ccc&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 core-lib 의 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ccc&lt;/span&gt; 헤드는 console의 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;v1.2.0&lt;/span&gt; 버전에 변경되어야 하는 core-lib의 스펙이 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;bbb&lt;/span&gt;에 이미 들어가있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은, 핫픽스인 console(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;v1.1.1&lt;/span&gt;)에 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;v1.2.0&lt;/span&gt; 버전에 추가되어야 할 core-lib 의 스펙이 포함되어 심각한 문제를 초래할 수 있다는 것이죠.&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;core-lib도 메인 어플리케이션인 console과 동일한 버저닝 및 브랜치 전략이 필요할 수 있습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;모노레포는 어떨까&lt;/h2&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;a href=&quot;https://docs.npmjs.com/cli/v7/using-npm/workspaces&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;npm workspaces&lt;/a&gt; 를 이용하여 스페이스원 console 프로젝트에 적용해보려고 합니다.&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;후속 포스트들을 통해 모노레포로의 첫 경험과 Trouble Shooting 한 내용 등을 다루도록 하겠습니다.&lt;/p&gt;</description>
      <category>Programming</category>
      <category>DX</category>
      <category>DXDX</category>
      <category>frontnend</category>
      <category>spaceone</category>
      <category>멀티 클라우드</category>
      <category>멀티레포</category>
      <category>모노레포</category>
      <category>서브모듈</category>
      <category>스페이스원</category>
      <category>프론트엔드</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/42</guid>
      <comments>https://wanzargen.tistory.com/42#entry42comment</comments>
      <pubDate>Fri, 15 Jul 2022 11:08:32 +0900</pubDate>
    </item>
    <item>
      <title>Front-end 클린 아키텍처</title>
      <link>https://wanzargen.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.to/bespoyasov/clean-architecture-on-frontend-4311&quot;&gt;https://dev.to/bespoyasov/clean-architecture-on-frontend-4311&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 글의 일부를 번역 &amp;amp; 정리한 글입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;아키텍처와 설계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 설계는 &lt;u&gt;나중에 다시 조립할 수 있도록 시스템을 분리하는 것&lt;/u&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 너무 &lt;u&gt;많은 작업 없이 쉽게 조립할 수 있어야 한다&lt;/u&gt;는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처의 또 다른 목표는 &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 data-ke-size=&quot;size16&quot;&gt;프로그램에 대한 요구사항은 지속적으로 변경되며, 새로운 요구사항을 충족하기 위해 쉽게 변경할 수 있어야 하는데, 클린 아키텍처는 이러한 목표를 달성하는 데에 도움을 줍니다.&lt;/p&gt;
&lt;h1&gt;클린 아키텍처&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 &lt;u&gt;도메인에 대한 근접성에 따라, 책임과 기능 부분을 분리하는 방법&lt;/u&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;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이것은 실제 세계의 변환을 반영하는 데이터 변환입니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 제품 이름을 업데이트한 경우 이전 이름을 새 이름으로 바꾸는 것은 도메인 변환입니다.&lt;/li&gt;
&lt;li&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;b&gt;3계층 아키텍처&lt;/b&gt;라고도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot; data-filename=&quot;CleanArchitecture.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZT2Sj/btrewxxLDDd/jMkNDkQ1wbC6Ecjevo8Y6K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZT2Sj/btrewxxLDDd/jMkNDkQ1wbC6Ecjevo8Y6K/img.jpg&quot; data-alt=&quot;Clean Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZT2Sj/btrewxxLDDd/jMkNDkQ1wbC6Ecjevo8Y6K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZT2Sj%2FbtrewxxLDDd%2FjMkNDkQ1wbC6Ecjevo8Y6K%2Fimg.jpg&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot; data-filename=&quot;CleanArchitecture.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Clean Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인 레이어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중심에는 도메인 레이어가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔터티(entities)와 데이터는 응용 프로그램의 주제 영역과 해당 데이터를 변환하는 코드입니다.&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;도메인은 React에서 Angular로 변경하거나, 어떤 시나리오를 변경한다고 해서 변경되는 것이 아닙니다.&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;도메인 엔터티의 데이터 구조와 변환의 본질은 외부 세계와 독립적입니다.&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;예를 들면, 장바구니에 물건을 추가하는 기능은 정확히 어떤 방식으로 물건이 담기는지를 서술하지는 않습니다.&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;rarr; 그 아이템을 장바구니에 담아 &amp;rarr; 변경된 장바구니를 반환할 뿐입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애플리케이션(응용 프로그램) 계층&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 주변에는 응용 프로그램 계층이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 계층은 사용 사례(Use Cases), 즉 사용자 시나리오입니다.&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;예를 들어, &quot;장바구니에 추가&quot; 시나리오는 &quot;버튼을 클릭한 후 취해야 하는 조치&quot;에 대한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버로 이동하여 요청을 보내고 &amp;rarr; 도메인 변환을 수행하고 &amp;rarr; 응답 데이터를 사용해 UI 그리기&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/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;h2 data-ke-size=&quot;size26&quot;&gt;어댑터 레이어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 바깥쪽 레이어에는 외부 서비스에 대한 어댑터가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호환되지 않는 외부 서비스 API를 응용 프로그램에 호환되도록 변환하기 위해 필요합니다.&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;/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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;driving - 우리 응용 프로그램에 신호를 보냄&lt;/li&gt;
&lt;li&gt;driven - 우리 응용 프로그램으로부터 신호를 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;driving 어댑터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;driving 어댑터는 주로 &lt;u&gt;사용자 상호 작용&lt;/u&gt; 부분을 맡습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, UI 프레임워크의 버튼 클릭 처리는 driving 어댑터의 작업입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 API를 통해 발생한 이벤트를 우리 응용 프로그램이 이해할 수 있는 신호로 이벤트를 변환합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;driven 어댑터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;driven 이벤트는 주로 인프라와 상호 작용 합니다.&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;중심에서 멀어질수록 코드 기능이 &quot;서비스 지향적&quot;이고, 응용 프로그램의 도메인 지식에서 멀어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이것은 나중에 어떤 모듈이 어느 계층에 와야하는지 결정할 때 중요하게 작용&lt;/u&gt;합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;종속성 규칙(Dependency Rule)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3계층 아키텍처에는 종속성 규칙이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;외부 계층만 내부 계층에 종속될 수 있다&lt;/u&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;/li&gt;
&lt;li&gt;응용 프로그램 계층은 도메인에 종속될 수 있음&lt;/li&gt;
&lt;li&gt;외부 레이어는 무엇이든 의존할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;602&quot; data-filename=&quot;080-explicit-architecture-svg.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNuFK8/btreAJxrLqe/zMZ6pSL97Jav2p1bWs2xsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNuFK8/btreAJxrLqe/zMZ6pSL97Jav2p1bWs2xsK/img.png&quot; data-alt=&quot;dependency rule&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNuFK8/btreAJxrLqe/zMZ6pSL97Jav2p1bWs2xsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNuFK8%2FbtreAJxrLqe%2FzMZ6pSL97Jav2p1bWs2xsK%2Fimg.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;602&quot; data-filename=&quot;080-explicit-architecture-svg.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;dependency rule&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;pre id=&quot;code_1631175866426&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// domain/order.ts

import { currentDatetime } from &quot;../lib/datetime&quot;; // 종속성이 없는 라이브러리 같은 코드 
import { Product, totalPrice } from &quot;./product&quot;;

export function createOrder(user: User, cart: Cart): Order {
  return {
    user: user.id,
    cart,
    created: currentDatetime(), // 그대로 사용하면 매우 편함
    status: &quot;new&quot;,
    total: totalPrice(products),
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 currentDatetime 에 의존합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 종속성을 제거하여, 완전(pure)한 형태를 취하는 것이 좋습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1631175925679&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// domain/order.ts

import { currentDatetime } from &quot;../lib/datetime&quot;; // 종속성이 없는 라이브러리 같은 코드 
import { Product, totalPrice } from &quot;./product&quot;;

export function createOrder(user: User, cart: Cart): Order {
  return {
    user: user.id,
    cart,
    created: currentDatetime(), // 그대로 사용하면 매우 편함
    status: &quot;new&quot;,
    total: totalPrice(products),
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이렇게 created 가 라이브러리에 의존하지 않도록하여, 의존성 규칙을 위반하지 않습니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순환 종속성. 모듈 A는 B에 종속, B는 C에, C는 다시 A에 종속되는 문제&lt;/li&gt;
&lt;li&gt;작은 부분을 테스트하기 위해 전체 시스템을 시뮬레이션 하는 문제&lt;/li&gt;
&lt;li&gt;너무 높은 결합으로 결과적으로 모듈 간의 취약한 상호 작용&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;클린 아키텍처의 장점&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;별도의 도메인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 주요 응용 프로그램의 기능이 도메인 한 곳에 격리되고 모여있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;모듈 종속성이 적을수록 테스트에 필요한 인프라가 줄어들어, 필요한 mock 개수도 줄어듭니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;독립적인 사용 사례(use case)&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;교체 가능한 타사 서비스&lt;/h2&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;다른 사람의 코드 변경이, 우리 자신의 코드에 직접적인 영향을 미치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑터는 또한 런타임에서 버그 전파를 제한합니다.&lt;/p&gt;
&lt;h1&gt;클린 아키텍처의 비용&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시간이 걸린다&lt;/h2&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;모든 모듈의 상호작용을 미리 생각하는 것도 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계할 때 시스템이 어떻게 변경될 수 있는지 염두에 두고, 확장의 여지를 남겨두어야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;때로는 지나치게 장황하다&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;온보딩을 더 어렵게 만들 수 있다&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;코드 양을 늘릴 수 있다&lt;/h2&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;따라서, 어디에서 무엇을 잘라내야 하는지에 대한 결정을 내려야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용 사례를 좀 더 간단하게 설명&lt;/li&gt;
&lt;li&gt;사용 사례를 우회하여 어댑터에서 직접 도메인 기능에 액세스&lt;/li&gt;
&lt;li&gt;코드 분할 정도 조정&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;비용 줄이는 방법&lt;/h1&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 확실히 투자할 가치가 있는 최소 요구사항이 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인 추출&lt;/h2&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;다른 계층은 건너 뛰더라도, 추출된 도메인을 가지고 작업 및 리팩토링 하는 것이 더 쉬울 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 계층들은 필요에 따라 추가하는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;종속성 규칙 준수&lt;/h2&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;외부 서비스는 우리의 필요에 맞게 조정되지 않으면 제대로 동작하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 API를 호출할 수 있도록 코드를 &quot;미세 조정&quot;하고 있다고 생각되면 문제가 있는 것입니다.&amp;nbsp;(외부서비스에 종속되어 돌아가고 있다는 뜻)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 확산되기 전에 어댑터를 작성하는 것이 좋습니다.&lt;/p&gt;
&lt;h1&gt;실제 프로젝트에서 더 복잡할 수 있는 것&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비지니스 로직 나누기&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&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;코드의 copy &amp;amp; paste 가 항상 나쁜 것은 아닙니다. 이것은 도구입니다.&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;그래도 상속해야 한다면...&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공분산(covariance), 반공변(contravariance), 불변(invariance)를 항상 염두에 두어, &lt;u&gt;실수로 해야할 것보다 더 많은 작업을 수행하지 않도록&lt;/u&gt; 하세요.&lt;/li&gt;
&lt;li&gt;다른 엔티티를 만들 것인지 아니면 확장할 것인지를 선택할 때, &lt;u&gt;BEM의 블록 및 수정자(modifier)를 통해 유추&lt;/u&gt;하세요. BEM의 맥락에서 생각한다면, 별도의 엔터티가 있는지 혹은 modifier 확장 코드가 있는지 확인하는 데에 많은 도움이 됩니다.위 글의 일부를 번역 &amp;amp; 정리한 글입니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming</category>
      <category>Clean Architecture</category>
      <category>Front-End</category>
      <category>비지니스 로직 나누기</category>
      <category>클린 아키텍처</category>
      <category>프론트엔드</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/41</guid>
      <comments>https://wanzargen.tistory.com/41#entry41comment</comments>
      <pubDate>Thu, 9 Sep 2021 17:30:04 +0900</pubDate>
    </item>
    <item>
      <title>디렉토리(패키지) 구조 - 계층보다는 기능에 의한 분류</title>
      <link>https://wanzargen.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.javapractices.com/topic/TopicAction.do?Id=205&quot;&gt;http://www.javapractices.com/topic/TopicAction.do?Id=205&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;위 글은 자바의 package 를 어떤 기준으로 나눌지에 대한 글입니다.&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;br /&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;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;응용 프로그램을 빌드할 때 첫 번째 질문은 &quot;&lt;b&gt;어떻게 패키지로 나눌 수 있습니까?&lt;/b&gt;&quot;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 비즈니스 애플리케이션의 경우 이 질문에 답하는 두 가지 방법이 있는 것 같습니다.&lt;/p&gt;
&lt;h1&gt;기능별 패키지&lt;/h1&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;예를 들어, 약물 처방 응용 프로그램에는 다음과 같은 패키지가 있을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;doctor&lt;/li&gt;
&lt;li&gt;drug&lt;/li&gt;
&lt;li&gt;patient&lt;/li&gt;
&lt;li&gt;presription&lt;/li&gt;
&lt;li&gt;report&lt;/li&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;li&gt;webmaster&lt;/li&gt;
&lt;li&gt;util&lt;/li&gt;
&lt;li&gt;and so on...&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 패키지는 일반적으로 특정 기능과 관련된 항목만 포함하고 다른 기능은 포함하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지에는 해당 기능이 작동하는 데에 필요한 모든 항목이 있습니다. 인터페이스, 상수, js 코드 등 주어진 기능과 관련된 모든 항목이 해당 기능 전용 단일 디렉토리에 있어야 합니다.&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;h1&gt;레이어별 패키지&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 패키지는 다음과 같이 기능 대신 다양한 애플리케이션 &quot;계층&quot;을 반영합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;action&lt;/li&gt;
&lt;li&gt;model&lt;/li&gt;
&lt;li&gt;util&lt;/li&gt;
&lt;/ul&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;h1&gt;권장: 기능별 패키지 사용&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;더 높은 모듈성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급한 바와 같이 기능별 패키지는 응집도가 높고 모듈성이 높으며 결합도가 낮습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;더 쉬운 코드 탐색&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;더 높은 수준의 추상화&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;기능과 레이어를 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능별 패키지 스타일은 여전히 레이어를 분리하는 아이디어를 존중하지만, 해당 분리는 별도의 클래스를 사용하여 구현됩니다. &amp;rarr; 디렉토리는 기능별 분리, 말단의 파일들은 계층으로 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 레이어별 패키지 스타일은 별도의 클래스와 별도의 패키지를 모두 사용하여 분리를 구현하므로 바람직하지 않습니다. &amp;rarr; 디렉토리, 말단의 파일 모두 계층으로 분리&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;범위 최소화&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;더 나은 성장 스타일&lt;/h2&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;</description>
      <category>Programming</category>
      <category>디렉토리 구조</category>
      <category>패키지 구조</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/40</guid>
      <comments>https://wanzargen.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 6 Sep 2021 19:15:00 +0900</pubDate>
    </item>
    <item>
      <title>[Vue] Vue Router - chunk load fail 로 인한 삽질기</title>
      <link>https://wanzargen.tistory.com/39</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TL;DR&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;솔루션  &amp;zwj;♀️: router.onError 콜백으로 chunk load 에러 핸들링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1661779725619&quot; class=&quot;moonscript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let nextPath: string;
router.onError((error) =&amp;gt; {
    console.error(error);

    if (error.name === 'ChunkLoadError') {
        window.location.href = nextPath || '/';
    }
});&lt;/code&gt;&lt;/pre&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;현재 Vue로 개발하고 있는 멀티 클라우드 플랫폼, &lt;a href=&quot;https://spaceone.megazone.io/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;SpaceONE(스페이스원)&lt;/b&gt;&lt;/a&gt;을 개발하면서 vue router chunk load fail 이슈를 만났다.&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;1. 나와 같은 문제를 겪고 있는 모두에게 시간을 절약하게 돕고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Vue Router의 동작 방식을 재미난(?) 방식으로 이해하도록 돕고자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성해본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Trouble Shooting&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제  : &lt;span style=&quot;color: #ee2323;&quot;&gt;Uncaught SyntaxError: Unexpected token '&amp;lt;'&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1171&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cidGTc/btq9IZlPdTo/z04xkIk1j7Wv0RXj2Ai5R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cidGTc/btq9IZlPdTo/z04xkIk1j7Wv0RXj2Ai5R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cidGTc/btq9IZlPdTo/z04xkIk1j7Wv0RXj2Ai5R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcidGTc%2Fbtq9IZlPdTo%2Fz04xkIk1j7Wv0RXj2Ai5R0%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;1171&quot; height=&quot;304&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1171&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&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;span style=&quot;color: #ee2323;&quot;&gt; &quot;도저히 못읽겠어. 어떻게좀 해봐.&quot; &lt;/span&gt;할 때 내뿜는 에러이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리팀의 경우는, 이 에러가 굉장히 간헐적으로 나타나서 '무엇 때문에 이 에러가 나는가'를 명확히 하기 어려웠는데, &lt;span style=&quot;color: #000000;&quot;&gt;재배포시 일어나는 이슈로 우선 추측&lt;/span&gt;하였다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 발생 상황: 재배포 &amp;rarr; 메뉴의 이동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 언제 발생하느냐면, 메뉴의 이동(url 변경 &amp;rarr; 새로운 chunk 파일 요청) 시에 나타난다.&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;span style=&quot;color: #000000;&quot;&gt;&quot;재배포 후, 다른 페이지에서 &lt;b&gt;서버 메뉴로 진입&lt;/b&gt;을 하는 경우에 발생하는 문제&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 정해놓고 문제를 추적해 나가보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 추적   1. 브라우저는 파일을 왜 해석하지 못할까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa;&quot;&gt;참고로, 우리의 route config 는 아래와 같이 작성되어 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1626510572478&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Server = () =&amp;gt; import(/* webpackChunkName: &quot;Server&quot; */ '@/views/inventory/server/pages/ServerPage.vue');

{
    path: 'inventory',
    name: INVENTORY_ROUTE._NAME,
    redirect: 'inventory/cloud-service',
    meta: { label: 'Inventory' },
    component: { template: '&amp;lt;router-view /&amp;gt;' },
    children: [
        {
            path: 'server',
            meta: {
                label: 'Server',
            },
            component: { template: '&amp;lt;router-view /&amp;gt;' },
            children: [
                {
                    path: '/',
                    name: INVENTORY_ROUTE.SERVER._NAME,
                    component: Server,
                },
            ],
        },
			...
		]
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;background-color: #dddddd;&quot;&gt;Server.2937218a.js&lt;/span&gt; 파일이 존재하지 않다면 &quot;응 그런 파일 없어~&quot; 하고 네트워크 에러가 나야하는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWnigx/btq9NsgByGD/v5DstXSq9AAHdjXk5boHL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWnigx/btq9NsgByGD/v5DstXSq9AAHdjXk5boHL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWnigx/btq9NsgByGD/v5DstXSq9AAHdjXk5boHL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWnigx%2Fbtq9NsgByGD%2Fv5DstXSq9AAHdjXk5boHL1%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;659&quot; height=&quot;84&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&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-filename=&quot;3.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;23&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rufaX/btq9LrJdg6B/KpkOWtkQfDXPyCIynaC6TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rufaX/btq9LrJdg6B/KpkOWtkQfDXPyCIynaC6TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rufaX/btq9LrJdg6B/KpkOWtkQfDXPyCIynaC6TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrufaX%2Fbtq9LrJdg6B%2FKpkOWtkQfDXPyCIynaC6TK%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;1172&quot; height=&quot;23&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;23&quot;/&gt;&lt;/span&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;&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;/p&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-filename=&quot;4.png&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi88PM/btq9MEn6ZTC/AKeIWP3NQ9LZ05ADWSmK8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi88PM/btq9MEn6ZTC/AKeIWP3NQ9LZ05ADWSmK8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi88PM/btq9MEn6ZTC/AKeIWP3NQ9LZ05ADWSmK8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi88PM%2Fbtq9MEn6ZTC%2FAKeIWP3NQ9LZ05ADWSmK8K%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;918&quot; height=&quot;146&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1626510695328&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;base href=/ &amp;gt;&amp;lt;meta charset=utf-8&amp;gt;&amp;lt;meta http-equiv=X-UA-Compatible content=&quot;IE=edge&quot;&amp;gt;&amp;lt;meta name=viewport content=&quot;width=device-width,initial-scale=1&quot;&amp;gt;&amp;lt;link rel=&quot;shortcut icon&quot; type=image/x-icon href=/favicon.ico&amp;gt;&amp;lt;link rel=manifest type=image/x-icon href=/manifest.json&amp;gt;&amp;lt;link rel=apple-touch-icon href=/images/icons/icon-192x192.png&amp;gt;&amp;lt;link rel=stylesheet type=text/css href=/reset.css&amp;gt;&amp;lt;link rel=stylesheet type=text/css href=/site-loader.css&amp;gt;&amp;lt;script src=lottie.js&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;link rel=preload href=/fonts/roboto/roboto-v27-latin-regular.woff2 as=font type=font/woff2 crossorigin=anonymous&amp;gt;&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;&amp;lt;link href=/AddEventRulePage.2443967e.js rel=prefetch&amp;gt;&amp;lt;link href=/AddNotificationPage.423f99ad.js rel=prefetch&amp;gt;&amp;lt;link href=/AddNotificationPage~ManageNotificationPage~ProjectNotificationsPage~UserNotificationPage.262ec027.js rel=prefetch&amp;gt;&amp;lt;link href=/AddServiceAccount.dd2b033d.js rel=prefetch&amp;gt;&amp;lt;link href=/AddSpotGroupPage.25309be0.js rel=prefetch&amp;gt;&amp;lt;link href=/AddSpotGroupPage~AlertDashboardPage~AlertListPage~CloudService~CloudServicePage~CollectorHistory~Col~1fe78708.a45199fe.js rel=prefetch&amp;gt;&amp;lt;link href=/AddSpotGroupPage~SpotGroupDetailPage.4c1b9c62.js rel=prefetch&amp;gt;&amp;lt;link href=/AlertDashboardPage.4e22d8b6.js rel=prefetch&amp;gt;&amp;lt;link href=/AlertDashboardPage~CloudServicePage~CollectorHistory~Dashboard~ProjectDashboardPage~Server~SpotDashb~225d570e.3ef4eef0.js rel=prefetch&amp;gt;&amp;lt;link href=/AlertDetailPage.6dd37180.js rel=prefetch&amp;gt;&amp;lt;link href=/AlertDetailPage~EscalationPolicyPage~ProjectSettings.f647fded.js rel=prefetch&amp;gt;&amp;lt;link href=/AlertListPage.5c40c793.js rel=prefetch&amp;gt;&amp;lt;link href=/AlertListPage~ProjectAlert.600d25ee.js rel=prefetch&amp;gt;&amp;lt;link href=/CloudService.56a70266.js rel=prefetch&amp;gt;&amp;lt;link href=/CloudServicePage.0f8b0712.js rel=prefetch&amp;gt;&amp;lt;link href=/CloudServicePage~PowerSchedulerPage~Server~ServiceAccount.de259d30.js rel=prefetch&amp;gt;&amp;lt;link href=/CloudServiceSearch.682a59e6.js rel=prefetch&amp;gt;&amp;lt;link href=/CloudServiceTypeSearch.01c8bd65.js rel=prefetch&amp;gt;&amp;lt;link href=/CloudService~ProjectPage~SpotAutomationMainPage.ac5686f6.js rel=prefetch&amp;gt;&amp;lt;link href=/CollectorHistory.e1b75a33.js rel=prefetch&amp;gt;&amp;lt;link href=/CollectorPage.60229ab2.js rel=prefetch&amp;gt;&amp;lt;link href=/CollectorPlugin.ce9943a8.js rel=prefetch&amp;gt;&amp;lt;link href=/CreateCollector.fe3bbe46.js rel=prefetch&amp;gt;&amp;lt;link href=/Dashboard.5b60298a.js rel=prefetch&amp;gt;&amp;lt;link href=/Dashboard~ProjectDashboardPage.12a8dfb0.js rel=prefetch&amp;gt;&amp;lt;link href=/Dashboard~ProjectDashboardPage~SpotGroupDetailPage.5594adf1.js rel=prefetch&amp;gt;&amp;lt;link href=/DomainAdminSignIn.6aba32d3.js rel=prefetch&amp;gt;&amp;lt;link href=/EscalationPolicyPage.fd9b6910.js rel=prefetch&amp;gt;&amp;lt;link href=/EscalationPolicyPage~ProjectSettings.3e13a324.js rel=prefetch&amp;gt;&amp;lt;link href=/KeycloakPage.81a0cd20.js rel=prefetch&amp;gt;&amp;lt;link href=/ManageNotificationPage.8c7a9cc9.js rel=prefetch&amp;gt;&amp;lt;link href=/ManageNotificationPage~ProjectNotificationsPage~UserNotificationPage.8e793b36.js rel=prefetch&amp;gt;&amp;lt;link href=/MonitoringMainPage.8a7d7a69.js rel=prefetch&amp;gt;&amp;lt;link href=/NoResource.ba963788.js rel=prefetch&amp;gt;&amp;lt;link href=/PowerSchedulerLanding.af0c625f.js rel=prefetch&amp;gt;&amp;lt;link href=/PowerSchedulerPage.e2700908.js rel=prefetch&amp;gt;&amp;lt;link href=/PowerSchedulerPage~ResourceGroup.cccb9af0.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectAlert.3320370f.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectAlertPage.12f4777b.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectDashboardPage.4e5d56f8.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectDetailPage.7548f76f.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectDetailPage~ProjectMaintenanceWindowPage.379f8f0c.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectMaintenanceWindowPage.1efe29a0.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectMemberPage.823e92e3.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectMemberPage~ProjectPage.4d8fe64a.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectNotificationsPage.861e77e0.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectPage.08327029.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectSettings.06976158.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectTagPage.8f9d0731.js rel=prefetch&amp;gt;&amp;lt;link href=/ProjectWebhook.0dba36f9.js rel=prefetch&amp;gt;&amp;lt;link href=/ResourceGroup.4cb9fcc0.js rel=prefetch&amp;gt;&amp;lt;link href=/Server.2cf4907a.js rel=prefetch&amp;gt;&amp;lt;link href=/ServiceAccount.57e4165d.js rel=prefetch&amp;gt;&amp;lt;link href=/ServiceAccountSearchPage.3593fb1d.js rel=prefetch&amp;gt;&amp;lt;link href=/SignIn.e6fa5cdb.js rel=prefetch&amp;gt;&amp;lt;link href=/SpotAutomationMainPage.1fc3a4bd.js rel=prefetch&amp;gt;&amp;lt;link href=/SpotDashboardPage.c4780986.js rel=prefetch&amp;gt;&amp;lt;link href=/SpotGroupDetailPage.38ea0f92.js rel=prefetch&amp;gt;&amp;lt;link href=/SpotGroupPage.071a8566.js rel=prefetch&amp;gt;&amp;lt;link href=/SupervisorPlugin.0ab663f4.js rel=prefetch&amp;gt;&amp;lt;link href=/User.73334390.js rel=prefetch&amp;gt;&amp;lt;link href=/UserAPIKey.61c8765e.js rel=prefetch&amp;gt;&amp;lt;link href=/UserAPIKey~UserManagement.3dfa8abf.js rel=prefetch&amp;gt;&amp;lt;link href=/UserAccount.2c09bb9c.js rel=prefetch&amp;gt;&amp;lt;link href=/UserManagement.50749944.js rel=prefetch&amp;gt;&amp;lt;link href=/UserNotificationPage.d0308613.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-0ba3f7fd.994916e4.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-1bc4056e.486766a9.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-1be89e71.b5868b40.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-2d0c95ba.41fa8489.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-2d0f0b9f.a2c22464.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-2d22c0b4.64ad6b33.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-2e0b8c86.205aa870.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-44e90382.9851c8f0.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-461a2683.42df45c5.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-4aaaf80d.a48cd1a6.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-55bc86f8.4b05952e.js rel=prefetch&amp;gt;&amp;lt;link href=/chunk-f5f99b42.ebace51f.js rel=prefetch&amp;gt;&amp;lt;link href=/css/AddEventRulePage.bd08c668.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AddNotificationPage.7d8db5de.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AddNotificationPage~ManageNotificationPage~ProjectNotificationsPage~UserNotificationPage.43013aec.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AddServiceAccount.9a85fb46.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AddSpotGroupPage.ed6ed8b6.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AddSpotGroupPage~SpotGroupDetailPage.6276a2de.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AlertDashboardPage.b6c5bca9.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AlertDetailPage.312f4992.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AlertDetailPage~EscalationPolicyPage~ProjectSettings.7289a2eb.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AlertListPage.b4992990.css rel=prefetch&amp;gt;&amp;lt;link href=/css/AlertListPage~ProjectAlert.9c01661b.css rel=prefetch&amp;gt;&amp;lt;link href=/css/CloudService.d79778b1.css rel=prefetch&amp;gt;&amp;lt;link href=/css/CloudServicePage.9d676694.css rel=prefetch&amp;gt;&amp;lt;link href=/css/CloudService~ProjectPage~SpotAutomationMainPage.03f9a2b0.css rel=prefetch&amp;gt;&amp;lt;link href=/css/CollectorHistory.978dc00e.css rel=prefetch&amp;gt;&amp;lt;link href=/css/CollectorPage.52d7bc4e.css rel=prefetch&amp;gt;&amp;lt;link href=/css/CollectorPlugin.db59d594.css rel=prefetch&amp;gt;&amp;lt;link href=/css/CreateCollector.ed5a306e.css rel=prefetch&amp;gt;&amp;lt;link href=/css/Dashboard.48fa125d.css rel=prefetch&amp;gt;&amp;lt;link href=/css/Dashboard~ProjectDashboardPage.11f5c19f.css rel=prefetch&amp;gt;&amp;lt;link href=/css/DomainAdminSignIn.815cc6d2.css rel=prefetch&amp;gt;&amp;lt;link href=/css/EscalationPolicyPage.c882677f.css rel=prefetch&amp;gt;&amp;lt;link href=/css/EscalationPolicyPage~ProjectSettings.5d111922.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ManageNotificationPage.1180520a.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ManageNotificationPage~ProjectNotificationsPage~UserNotificationPage.8ebb6cfb.css rel=prefetch&amp;gt;&amp;lt;link href=/css/MonitoringMainPage.3630c535.css rel=prefetch&amp;gt;&amp;lt;link href=/css/NoResource.a102c29a.css rel=prefetch&amp;gt;&amp;lt;link href=/css/PowerSchedulerLanding.d670e43d.css rel=prefetch&amp;gt;&amp;lt;link href=/css/PowerSchedulerPage.15b06258.css rel=prefetch&amp;gt;&amp;lt;link href=/css/PowerSchedulerPage~ResourceGroup.f616876f.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectAlert.111e2064.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectAlertPage.5545d2a1.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectDashboardPage.73985304.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectDetailPage.33c7bbd9.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectDetailPage~ProjectMaintenanceWindowPage.5588b8cb.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectMaintenanceWindowPage.bdc07193.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectMemberPage~ProjectPage.e4cd3c16.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectPage.ae16d886.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectSettings.f73a4e4f.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectTagPage.77cd1e1e.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ProjectWebhook.244e7aaa.css rel=prefetch&amp;gt;&amp;lt;link href=/css/Server.25f42c1f.css rel=prefetch&amp;gt;&amp;lt;link href=/css/ServiceAccount.c600b0fa.css rel=prefetch&amp;gt;&amp;lt;link href=/css/SignIn.07f21294.css rel=prefetch&amp;gt;&amp;lt;link href=/css/SpotAutomationMainPage.25a5327b.css rel=prefetch&amp;gt;&amp;lt;link href=/css/SpotDashboardPage.de09b2d3.css rel=prefetch&amp;gt;&amp;lt;link href=/css/SpotGroupDetailPage.bebff5b1.css rel=prefetch&amp;gt;&amp;lt;link href=/css/SpotGroupPage.cb642963.css rel=prefetch&amp;gt;&amp;lt;link href=/css/SupervisorPlugin.ace392a4.css rel=prefetch&amp;gt;&amp;lt;link href=/css/User.401d7e26.css rel=prefetch&amp;gt;&amp;lt;link href=/css/UserAPIKey.65e3cb4e.css rel=prefetch&amp;gt;&amp;lt;link href=/css/UserAPIKey~UserManagement.43c226e9.css rel=prefetch&amp;gt;&amp;lt;link href=/css/UserAccount.3d6f3f4f.css rel=prefetch&amp;gt;&amp;lt;link href=/css/UserManagement.cac7e050.css rel=prefetch&amp;gt;&amp;lt;link href=/css/UserNotificationPage.57b523f0.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-0ba3f7fd.cfd75d8e.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-1bc4056e.ea70c3f0.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-1be89e71.d34b8ff7.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-2e0b8c86.afb7cc4a.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-44e90382.45ef29c4.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-461a2683.c3a06ffd.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-4aaaf80d.5eb1f8ea.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-55bc86f8.5a3dab04.css rel=prefetch&amp;gt;&amp;lt;link href=/css/chunk-f5f99b42.a608ca90.css rel=prefetch&amp;gt;&amp;lt;link href=/app.d406ce09.js rel=preload as=script&amp;gt;&amp;lt;link href=/css/vendor.a107d60e.css rel=preload as=style&amp;gt;&amp;lt;link href=/js/runtime~app.c8b73606.js rel=preload as=script&amp;gt;&amp;lt;link href=/vendor.9fa6f766.js rel=preload as=script&amp;gt;&amp;lt;link href=/css/vendor.a107d60e.css rel=stylesheet&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;noscript&amp;gt;&amp;lt;strong&amp;gt;We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.&amp;lt;/strong&amp;gt;&amp;lt;/noscript&amp;gt;&amp;lt;script src=page-initializer.js&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;div id=site-loader-wrapper&amp;gt;&amp;lt;div id=site-loader&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div id=site-loader-text&amp;gt;Loading SpaceONE&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;script src=site-loader.js&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;div id=app&amp;gt;&amp;lt;script src=/js/runtime~app.c8b73606.js&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;script src=/vendor.9fa6f766.js&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;script src=/app.d406ce09.js&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;js 파일에 웬... html 이 자리잡고 있나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것도 이해가 안가지만, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;href=/site-loader.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;size16&quot;&gt;개발자 도구의 network &amp;gt; preview 탭을 봤더니.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;55.png&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNMdAX/btq9NQ2DZo1/1lX6OnEDqyG4MujA50qiCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNMdAX/btq9NQ2DZo1/1lX6OnEDqyG4MujA50qiCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNMdAX/btq9NQ2DZo1/1lX6OnEDqyG4MujA50qiCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNMdAX%2Fbtq9NQ2DZo1%2F1lX6OnEDqyG4MujA50qiCk%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;763&quot; height=&quot;160&quot; data-filename=&quot;55.png&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;띠용 상황 2.&lt;/p&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-filename=&quot;5.png&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/45avi/btq9NsAWcwB/G59yQONeCsyp13Vik3lM5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/45avi/btq9NsAWcwB/G59yQONeCsyp13Vik3lM5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/45avi/btq9NsAWcwB/G59yQONeCsyp13Vik3lM5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F45avi%2Fbtq9NsAWcwB%2FG59yQONeCsyp13Vik3lM5K%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;1162&quot; height=&quot;219&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&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;&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 추적   2. 정상적인 경우와 비교하기&lt;/h2&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCsZp9%2Fbtq9NsHHwDM%2F54t8Utqm0XIXk7UzHMkcZ1%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;465&quot; height=&quot;118&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 페이지로 갔다고 해서 서버페이지만 로드한게 아니라, &lt;s&gt;아마도&lt;/s&gt; 그 라우트 정보를 가지고 있는 것으로 추정되는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;CloudServicePage~....js&lt;/span&gt; 파일을 먼저 로드한 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 상황에서는 아래에 있는 파일, 즉 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;Server.2937218a.js&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;br /&gt;흠... 않이. 서버 페이지로 연결되는&amp;nbsp;a&amp;nbsp;태그 클릭 했는데, 왜. 도대체 저런 것들을 부르지?엇, 그러고보니&amp;nbsp;a&amp;nbsp;태그네? 그러면&amp;nbsp;href&amp;nbsp;를 보면 되잖아?&lt;br /&gt;그런데 우리 서비스는 SPA 서비스이고, a 태그로 페이지 로드 없이 이동한다고?&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;background-color: #dddddd;&quot;&gt;a&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;7.png&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;27&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgPtj9/btq9R5Egh0J/82k6dz5U9uYTirzyFXJHw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgPtj9/btq9R5Egh0J/82k6dz5U9uYTirzyFXJHw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgPtj9/btq9R5Egh0J/82k6dz5U9uYTirzyFXJHw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgPtj9%2Fbtq9R5Egh0J%2F82k6dz5U9uYTirzyFXJHw0%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;554&quot; height=&quot;27&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;27&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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-filename=&quot;6.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCsZp9%2Fbtq9NsHHwDM%2F54t8Utqm0XIXk7UzHMkcZ1%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;465&quot; height=&quot;118&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;118&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;style3&quot;&gt;  아... 바보...ㅠㅠ 이거 훼이크겠구나 ㅠㅠ&lt;br /&gt;그래 말이 안되지.&amp;nbsp;a&amp;nbsp;태그는 그 태생이 페이지 연결인데.그저 해시뱅으로 책갈피 정도인데...&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;background-color: #dddddd;&quot;&gt;a&lt;/span&gt; 태그는 Vue Router 의 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router-link&lt;/span&gt;를 이용해 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;s&gt;아마도&lt;/s&gt; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router-link&lt;/span&gt;는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;a&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 추적   3. Vue Router 의 router-link 파헤치기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;router-link&lt;/span&gt; 는 렌더 결과는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;a&lt;/span&gt; 태그에 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;href&lt;/span&gt; 속성이 우리가 보내고 싶은 url로 올바르게 반영된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 그 태그를 클릭했을 때의 동작은 일반적인 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;a&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;Vue Router 의 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router-link&lt;/span&gt;가 내부적으로 어떻게 동작하는지를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1626511189747&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// link.js
export default {
  name: 'RouterLink',
props: {
	to: {
      type: toTypes,
      required: true
    },
}
	render (h: Function) {
		...

		const { location, href } = router.resolve(this.to)

		const handler = e =&amp;gt; {
			router.push(location)
		}

		const on = { click: handler }

		const attrs =  { href } 

		return h('a', { on, attrs }, this.$slots.default)
	}
}

// index.js
export default class VueRouter {
	constructor (options: RouterOptions = {}) {
		this.history = new HTML5History(this, options.base)
	}
	
	push (location: RawLocation) {  
    return new Promise((resolve, reject) =&amp;gt; {
      this.history.push(location, resolve, reject)
    })
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오리지널 코드는 넘나 복잡해서, 요점만 간단히 요약해보았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;props로 주입 받은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;to&lt;/span&gt; 객체를 통해 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router.resolve()&lt;/span&gt; 함수로 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;href&lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;location&lt;/span&gt; 을 알아낸다.&lt;/li&gt;
&lt;li&gt;알아낸 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;location&lt;/span&gt;으로 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router.push()&lt;/span&gt; 해주는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;handler()&lt;/span&gt; 함수를 만든다.&lt;/li&gt;
&lt;li&gt;이벤트 리스너 객체 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;on&lt;/span&gt; 을 만들어, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;click&lt;/span&gt; 이벤트에 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;handler()&lt;/span&gt;함수를 바인딩 해준다.&lt;/li&gt;
&lt;li&gt;속성 객체 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;attrs&lt;/span&gt; 를 만들어, 알아낸 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;href&lt;/span&gt; 를 바인딩 해준다.&lt;/li&gt;
&lt;li&gt;이벤트 리스너와 속성이 바인딩 된 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;a&lt;/span&gt; 태그를 만들어준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 보이는건 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;href&lt;/span&gt; 속성이 부여된 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;a&lt;/span&gt; 태그, 클릭하여 동작하는 것은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;handler()&lt;/span&gt; 함수, 즉 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router.push(location)&lt;/span&gt; 이라는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://router.vuejs.org/kr/guide/essentials/navigation.html&quot;&gt;공식 문서&lt;/a&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 style=&quot;width: 842px;&quot;&gt;&lt;span&gt;다른 URL로 이동하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;router.push&lt;/b&gt;를 사용하십시오. 이 메소드는 새로운 항목을 히스토리 스택에 넣기 때문에 사용자가 브라우저의 뒤로 가기 버튼을 클릭하면 이전 URL로 이동하게된다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;이것은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;lt;router-link&amp;gt;를 클릭 할 때 내부적으로 호출되는 메소드&lt;/b&gt;이므로 &amp;lt;router-link :to=&quot;...&quot;&amp;gt;를 클릭하면 router.push(...)를 호출하는 것과 같습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌아 돌아서 여기까지 왔는데 버젓이 나와있다. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서를 씹어먹어야 하는 이유. &lt;s&gt;알지만 항상 놓치는 나.&lt;/s&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;span style=&quot;background-color: #dddddd;&quot;&gt;a&lt;/span&gt;&amp;nbsp;태그를 누르면, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router.push(location)&lt;/span&gt; 가 동작하면서 내부적으로 저 2가지 파일을 요청할 것이라는 점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;location&lt;/span&gt;에 매치되는 파일(컴포넌트)을 route config 에서 찾아서 요청할 것이라는 점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 요청하여 받아온 파일 내부에서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;router-view&lt;/span&gt;를 만나, 그 다음 파일을 요청할 것이라는 점!&lt;/p&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-filename=&quot;6.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CsZp9/btq9NsHHwDM/54t8Utqm0XIXk7UzHMkcZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCsZp9%2Fbtq9NsHHwDM%2F54t8Utqm0XIXk7UzHMkcZ1%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;465&quot; height=&quot;118&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&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;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재배포를 해서 없다고 404가 와야하는 애가, 왜 버젓이 200으로 들어오는걸까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고. 왜 js를 요청했는데 html 로 날아오는걸까.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 추적   4. 왜 js 를 요청했는데 html 로 날아오는걸까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 웹 서버로 nginx 를 쓰고있다.&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: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에 특정 url로 브라우저에 진입한다. nginx 는 모든 요청에 대하여 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;index.html&lt;/span&gt;을 리턴하도록 되어 있는데, 거기에 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;script&lt;/span&gt; 태그로 entry 파일이 로드된다.&lt;/li&gt;
&lt;li&gt;로드된 entry 파일이 실행되면서, router 훅도 실행되고 다양한 일이 일어날 것이다.&lt;/li&gt;
&lt;li&gt;그리고 라우트 정보에서 현재 브라우저의 url과 매치되는 컴포넌트들을 요청한다. (Vue Router 코드를 살펴봤는데 matched 정보를 이용하더라.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 이동하는 경우를 생각해보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;router.push&lt;/span&gt;가 특정 url로 컴포넌트를 GET 요청한다. 그 파일이 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;Server.aaa.js&lt;/span&gt; 라고 해보자.&lt;/li&gt;
&lt;li&gt;그 파일을 받아 와 잘 로드한다....면 좋겠지만, 여기서 로드를 못해온다. 왜? 파일이 변경되어서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;Server.aaa.js&lt;/span&gt;는 더이상 없다. nginx 는 파일이 있으면 그대로 돌려주지만, 없으면 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;index.html&lt;/span&gt; 을 보내주도록 설정되어 있다.&lt;br /&gt;
&lt;pre id=&quot;code_1626511559469&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location / {
  try_files $uri /index.html;
}​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;nginx 는 영문도 모르고 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;index.html&lt;/span&gt; 를 돌려보내준다.&lt;/li&gt;
&lt;li&gt;브라우저는 nginx 로부터 무언가를 받긴 했는데, 그 포맷이 html 이라는 것을 모른다. 이를 실행 가능한 javascript 라고 생각하고 실행한다. 그리고... &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt; 이걸 만나는 순간부터 에러가 난다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 원인  : 잘못된 파일 요청을 index.html로 보내주는 nginx의 신박함&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 알아버렸다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 chunk file을 요청하였으나, 이를 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;index.html&lt;/span&gt; 로 되돌려준 nginx 설정으로부터 이 모든 것이 시작되었다!&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;저 설정은 SPA 에는 필수적인 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 url의 변경사항을 웹 어플리케이션이 관장하여야, 마치 하나의 어플리케이션처럼 동작하도록 만들 수 있으니까.&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;b&gt;router 가 어떤 파일을 달라고 요청&lt;/b&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;b&gt;처리하는 정도의 구멍&lt;/b&gt;은 파놓지 않았을까?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;솔루션  &amp;zwj;♀️: router.onError 콜백으로 chunk load 에러 핸들링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 원래 아래처럼 에러 핸들링을 해주고 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1626511661541&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;router.onError((error) =&amp;gt; {
    if (/loading chunk \d* failed./i.test(error.message)) {
        window.location.reload();
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 무엇이냐하면, 이게 안먹고 있었다는 것...ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;if&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;s&gt;수많은 빌드와, 문제상황을 억지로 만들어내가며&lt;/s&gt; &lt;br /&gt;에러는 정확히 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ChunkLoadError&lt;/span&gt; 라는 이름으로 판별할 수 있다는걸 찾아내었고,&lt;/p&gt;
&lt;pre id=&quot;code_1626511695866&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let nextPath: string;
router.onError((error) =&amp;gt; {
    console.error(error);

    if (error.name === 'ChunkLoadError') {
        window.location.href = nextPath || '/';
    }
});&lt;/code&gt;&lt;/pre&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;참고로, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;nextPath&lt;/span&gt; 이 친구는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;beforeEach&lt;/span&gt; 훅에서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;to.fullPath&lt;/span&gt; 를 넣어주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;onError&lt;/span&gt; 콜백 함수의 인자로는 그 어떤 라우트 정보가 넘어오지 않기 때문에.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며  &lt;/h2&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;</description>
      <category>Programming/Lib, Frameworks</category>
      <category>chunk error</category>
      <category>chunk load error</category>
      <category>spaceone</category>
      <category>uncaught syntax error</category>
      <category>unexpected token</category>
      <category>vue</category>
      <category>Vue Router</category>
      <category>멀티 클라우드 플랫폼</category>
      <category>뷰 라우터</category>
      <category>스페이스원</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/39</guid>
      <comments>https://wanzargen.tistory.com/39#entry39comment</comments>
      <pubDate>Sat, 17 Jul 2021 17:49:44 +0900</pubDate>
    </item>
    <item>
      <title>Micro Frontends, 글로 배워보자구요.</title>
      <link>https://wanzargen.tistory.com/38</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1096&quot; data-filename=&quot;11.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xvcbb/btq9ID2ZY6X/AHM5fW923tmDE7Tht3SpgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xvcbb/btq9ID2ZY6X/AHM5fW923tmDE7Tht3SpgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xvcbb/btq9ID2ZY6X/AHM5fW923tmDE7Tht3SpgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxvcbb%2Fbtq9ID2ZY6X%2FAHM5fW923tmDE7Tht3SpgK%2Fimg.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1096&quot; data-filename=&quot;11.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;1154&quot; data-filename=&quot;22.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rpmyA/btq9DusIVfO/XFcKLMs2MYxQj7kLuCd1zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rpmyA/btq9DusIVfO/XFcKLMs2MYxQj7kLuCd1zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rpmyA/btq9DusIVfO/XFcKLMs2MYxQj7kLuCd1zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrpmyA%2Fbtq9DusIVfO%2FXFcKLMs2MYxQj7kLuCd1zk%2Fimg.png&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;1154&quot; data-filename=&quot;22.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;h1&gt;Main Concept&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;각 작동 단위에 사용된 프론트앤드 기술(React, Vue, Vanilla JS 등)에 상관 없이 조합이 가능해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;독립적인 애플리케이션을 자체적으로 구축해야 하고, 상태 공유나 전역 변수에 의존해서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네임스페이스를 활용한 분리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 작동 단위의 격리가 불가능한 경우, 네이밍 컨벤션에 따라 prefix 등으로 네임스페이스를 활용한다.&lt;/li&gt;
&lt;li&gt;CSS, 로컬 스토리지, 이벤트, 쿠키에 네임스페이스를 부여하여 충돌을 방지하고 명확히 분리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;만약 정말로 작동 단위 간 커스텀 API가 필요한 경우, 가능한 한 간단하게 유지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;범용 렌더링(Universal Rendering)과 점진적 향상(Progressive Enhancement)을 통해 성능을 향상시킬 수 있다.&lt;/li&gt;
&lt;li&gt;Universal Rendering&lt;/li&gt;
&lt;li&gt;Progressive Enhancement&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작고, 응집력 있고 유지보수에 용이한 코드베이스를 가질 수 있다. 따라서 디커플링이라는 소프트웨어 개발 목표를 달성한다.&lt;/li&gt;
&lt;li&gt;각 마이크로 프런트 엔드는 고유한 기술 및 프레임 워크를 선택할 수 있다. 독립적으로 구현, 테스트, 업그레이드, 업데이트 및 배포 할 수 있어, 팀에 유연성을 제공한다.&lt;/li&gt;
&lt;li&gt;프론트앤드 개발을 점진적 업그레이드 또는 재작성이 수월해진다.&lt;/li&gt;
&lt;li&gt;마이크로 프런트 엔드는 수직 팀을 장려한다. 수직 팀에는 일반적으로 기능 소유자, UX 디자이너, 제품 관리자, 백엔드 개발자, 프런트 엔드 개발자 및 품질 보증 엔지니어가 포함된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로 프런트 엔드는 분리되거나 느슨하게 결합 된 구성 요소를 위해 설계되었다. 그들 사이에 너무 많은 종속성을 넣으려고하면 디버깅 악몽이 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;마이크로 프런트 엔드를 가능하게하는 파이프라인으로 인해 복잡도가 증가한다. 외부로드 문제를 해결하려면 기술적 전문 지식이 필요하며 디버깅 프로세스에는 시간이 많이 걸린다. 또한 SSO (Single Sign-On), 글로벌 CSS 등과 관련된 문제에 직면 할 수 있다.&lt;/li&gt;
&lt;li&gt;각 마이크로 프런트 엔드에는 중복 된 코드 또는 기능이 있을 수 있다. 예를 들어, React 라이브러리는 각 마이크로 프런트 엔드에 포함될 수 있어, 번들 크기와 메모리 소비를 증가시킨다.&lt;/li&gt;
&lt;li&gt;런타임 시 마이크로 프런트 엔드를 동적 또는 지연로드하는 데 추가 시간이 걸린다.&lt;/li&gt;
&lt;li&gt;사용자 인터페이스는 여러 팀에서 설계되었으므로 UX 설계는 마이크로 프런트 엔드에서 일관되지 않을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9353716323969604&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h1&gt;Micro Frontend 통합 방법&lt;/h1&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-origin-width=&quot;703&quot; data-origin-height=&quot;244&quot; data-filename=&quot;33.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oKRMU/btq9DwquqpU/ZrxvrCMIxFzWIXGAK16dq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oKRMU/btq9DwquqpU/ZrxvrCMIxFzWIXGAK16dq1/img.png&quot; data-alt=&quot;독립적인 개발 및 배포&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oKRMU/btq9DwquqpU/ZrxvrCMIxFzWIXGAK16dq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoKRMU%2Fbtq9DwquqpU%2FZrxvrCMIxFzWIXGAK16dq1%2Fimg.png&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;244&quot; data-filename=&quot;33.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;/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;단일 컨테이너 애플리케이션은 다음과 같은 것들을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공통 페이지 요소를 렌더링한다.&lt;/li&gt;
&lt;li&gt;인증 및 탐색과 같은 공통적으로 고려되어야 하는 문제들을 해결한다.&lt;/li&gt;
&lt;li&gt;다양한 마이크로 앱들을 페이지에 모으고, 각 앱들에게 언제 어디서 렌더링할지 알려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버 사이드 템플릿 통합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서버로 html 템플릿을 요청하고, 최종 응답서버에서 각 템플릿을 조합해서 응답을 보낸다. 서버측에서 최종 화면을 조합한다. (SSR 에 적용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 범용적인 페이지 요소들을 포함하는 index.html 파일을 만든다. 이 파일에는 HTML fragment 파일들이 서버사이드에서 포함되어진다(넣어진다).&lt;/p&gt;
&lt;pre id=&quot;code_1626359206238&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Feed me&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;  Feed me&amp;lt;/h1&amp;gt;
    &amp;lt;!--# include file=&quot;$PAGE.html&quot; --&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx를 사용하여 아래 파일을 제공하고, 요청되는 URL이 $PAGE 변수에 대입되도록 구성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1626359236156&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 8080;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;
    ssi on;

    # Redirect / to /browse
    rewrite ^/$ http://localhost:8080/browse redirect;

    # Decide which HTML fragment to insert based on the URL
    location /browse {
      set $PAGE 'browse';
    }
    location /order {
      set $PAGE 'order';
    }
    location /profile {
      set $PAGE 'profile'
    }

    # All locations should render through index.html
    error_page 404 /index.html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 방식은 상당히 표준적인 서버측 구성 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기엔 어떻게 여러 개의 HTML 파일들이 웹 서버에 배치되는지는 나와있지 않지만, 각 파일들은 다른 페이지들을 고려하거나 다른 페이지들에게 영향을 미치지 않는다고 가정한다. 또한, 각각이 독립적인 배포 파이프라인을 가진다고 가정한다.&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-origin-width=&quot;724&quot; data-origin-height=&quot;226&quot; data-filename=&quot;44.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bza9OA/btq9EOxasXD/JJ2LWOMVuVQzLC26ilS7V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bza9OA/btq9EOxasXD/JJ2LWOMVuVQzLC26ilS7V1/img.png&quot; data-alt=&quot;각 서버는 독립적으로 빌드 및 배포할 수 있음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bza9OA/btq9EOxasXD/JJ2LWOMVuVQzLC26ilS7V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbza9OA%2Fbtq9EOxasXD%2FJJ2LWOMVuVQzLC26ilS7V1%2Fimg.png&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;226&quot; data-filename=&quot;44.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;h2 data-ke-size=&quot;size26&quot;&gt;빌드타임 통합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로 프론트엔드를 패키지로 배포하고, 컨테이너 앱이 그것들을 라이브러리 종속성(library dependencies)으로 포함하도록 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 살펴보면, 아래와 같은 package.json이 작성될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1626359299050&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@feed-me/container&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;A food delivery web app&quot;,
  &quot;dependencies&quot;: {
    &quot;@feed-me/browse-restaurants&quot;: &quot;^1.2.3&quot;,
    &quot;@feed-me/order-food&quot;: &quot;^4.5.6&quot;,
    &quot;@feed-me/user-profile&quot;: &quot;^7.8.9&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 배포 가능한 단일 Javascript 번들을 생성한다. 그리고 다양한 공통 종속성을 제거할 수 있다.&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;h2 data-ke-size=&quot;size26&quot;&gt;iframe을 통한 런타임 통합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 방식이면서 가장 쉬운 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iframe을 사용하면 각 마이크로 페이지들을 독립적인 하위 페이지로 쉽게 만들 수 있다. 스타일과 글로벌 변수가 서로 간섭하지 않는다는 측면에서 상당한 수준의 고립성을 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1626359347958&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Feed me!&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Welcome to Feed me!&amp;lt;/h1&amp;gt;

    &amp;lt;iframe id=&quot;micro-frontend-container&quot;&amp;gt;&amp;lt;/iframe&amp;gt;

    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
      const microFrontendsByRoute = {
        '/': 'https://browse.example.com/index.html',
        '/order-food': 'https://order.example.com/index.html',
        '/user-profile': 'https://profile.example.com/index.html',
      };

      const iframe = document.getElementById('micro-frontend-container');
      iframe.src = microFrontendsByRoute[window.location.pathname];
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Iframe 방식을 선택하는 것을 꺼리는 경우가 종종 있는데, 사실 그런 데에는 타당한 이유가 있다. 이 방식을 통한 분리는 쉽지만, 다른 옵션들보다 유연성이 떨어지는 경향이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 어플리케이션의 서로 다른 부분들을 통합 구축하는 것이 어렵다. 따라서 라우팅, 히스토리, deep linking이 더욱 복잡해지고, 반응형 페이지 개발에도 추가적인 어려움들이 따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UX가 iframe안에 갇히기 때문에 어색한 UI 표현을 가질 수 있다.&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;Javascript를 통한 런타임 통합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iframe과 달리 유연한 통합이 가능하다. 현실적으로 가장 많이 사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 마이크로 앱들은 &amp;lt;script&amp;gt; 태그를 통해 페이지에 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 애플리케이션에서는 마이크로 앱 번들을 &amp;lt;script&amp;gt; 태그를 통합 다운로드 받고, 약속된 초기화 메소드를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 앱에서는 mount 되어야 할 마이크로 앱을 결정하고, 마이크로 앱들에게 언제 어디에 렌더링할지 알려주기 위한 함수를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626359457928&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Feed me!&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Welcome to Feed me!&amp;lt;/h1&amp;gt;

    &amp;lt;!-- 이 스크립트는 즉시 무언가를 렌더링 하지 않는다. --&amp;gt;
    &amp;lt;!-- 대신, window에 entry-point 함수를 추가한다. --&amp;gt;
    &amp;lt;script src=&quot;https://browse.example.com/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://order.example.com/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://profile.example.com/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

    &amp;lt;div id=&quot;micro-frontend-root&quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
      // 이 전역 함수들은 위의 script들에서 window에 추가된 것들이다.
      const microFrontendsByRoute = {
        '/': window.renderBrowseRestaurants,
        '/order-food': window.renderOrderFood,
        '/user-profile': window.renderUserProfile,
      };
      const renderFunction = microFrontendsByRoute[window.location.pathname];

			/* 
				entry-point 함수를 선언한 후, 그것들을 호출한다.
				엘리먼트의 id를 넘겨서 어디에 렌더링할지를 알려준다.
			*/
      renderFunction('micro-frontend-root');
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드타임 통합과는 달리, 각 번들.js 파일들을 독립적으로 배치할 수 있고, iframe 통합과는 달리, 원하는대로 마이크로 앱들 간의 통합이 가능하도록 유연성을 제공한다. 필요한 경우에만 번들을 다운로드하거나, 마이크로 앱이 렌더링될 때 데이터를 주고받는 등 다양한 방법으로 코드를 확장할 수 있기 때문이다.&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://martinfowler.com/articles/micro-frontends.html#TheExampleInDetail&quot;&gt;Full Examples&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Web Components를 통한 런타임 통합&lt;/h2&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;u&gt;컨테이너 앱에서 인스턴스화할 HTML Custom Element를 정의하는 방식&lt;/u&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1626359515979&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Feed me!&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Welcome to Feed me!&amp;lt;/h1&amp;gt;

    &amp;lt;!-- 이 스크립트는 즉시 무언가를 렌더링 하지 않는다. --&amp;gt;
    &amp;lt;!-- 대신,이 스크립트를 통해 custom element들이 정의된다. --&amp;gt;
    &amp;lt;script src=&quot;https://browse.example.com/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://order.example.com/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://profile.example.com/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

    &amp;lt;div id=&quot;micro-frontend-root&quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
      // 이 엘리먼트 타입들은 위의 스크립트들을 통해서 정의되었다.
      const webComponentsByRoute = {
        '/': 'micro-frontend-browse-restaurants',
        '/order-food': 'micro-frontend-order-food',
        '/user-profile': 'micro-frontend-user-profile',
      };
      const webComponentType = webComponentsByRoute[window.location.pathname];

			/*
				적합한 웹 컴포넌트 custom element 타입을 선언한 후, 인스턴스를 생성하여 document에 추가한다.
			*/
      const root = document.getElementById('micro-frontend-root');
      const webComponent = document.createElement(webComponentType);
      root.appendChild(webComponent);
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 앱과 마이크로 앱들 사이에 고유한 인터페이스를 정의하여 사용하려면, 이전 방식이 적합하다.&lt;/p&gt;
&lt;h1&gt;Mirco Frontend 통합 고려사항&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스타일링
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BEM과 같은 엄격한 네이밍 규칙 사용&lt;/li&gt;
&lt;li&gt;SASS와 같은 전처리기를 통해 상위 선택자 추가 등을 통해 독립성 유지&lt;/li&gt;
&lt;li&gt;모든 스타일을 CSS-in-JS 방식으로 처리&lt;/li&gt;
&lt;li&gt;Shadow DOM: 메인 document DOM 으로부터 독립적으로 렌더링 되는 캡슐화된 Shadow DOM 트리를 엘리먼트에 추가하고, 연관된 기능을 제어하기 위한 JavaScript API 의 집합. 이 방법으로 엘리먼트의 기능을 프라이빗하게 유지할 수 있어, 다큐먼트의 다른 부분과의 충돌에 대한 걱정 없이 스크립트와 스타일을 작성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;component library 공유
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로 앱들 간의 시각적 일관성을 유지하기 위해 재사용 가능한 UI 컴포넌트 라이브러리를 개발하여 공유한다. 코드 재사용성과 시각적 일관성을 유지할 수 있다.&lt;/li&gt;
&lt;li&gt;너무 일찍 만드는 것은 좋지 않다. 실제로 사용하기 전에 컴포넌트의 API가 무엇이어야 하는지 추측하기는 쉽지 않다. 그러므로 많은 변동이 발생하게 되므로, 초기에는 일부 중복이 발생하더라도 각 팀이(각 마이크로 앱 내에서) 코드베이스에서 필요에 따라 자체 컴포넌트를 만들어두는 것이 좋다. 패턴이 자연스럽게 나타나도록 허용하고, 컴포넌트의 API가 명확해지면 그 때 만드는 것을 권장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 간 커뮤니케이션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가능한 적은 소통을 하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;Custom events를 사용하면 마이크로 앱들이 간접적으로 통신할 수 있다. 이는 직접 결합을 최소화하는 좋은 방법이지만, 마이크로 앱들 간 약속을 정하고 구현해야 하므로 더 어려울 수 있다.&lt;/li&gt;
&lt;li&gt;콜백과 데이터를 아래쪽으로 전달하는 React 모델로 약속을 명확하게 정하는 것도 좋은 솔루션이다.&lt;/li&gt;
&lt;li&gt;URL 표시 줄을 통신 메커니즘으로 사용하는 것도 좋은 방법이다.&lt;/li&gt;
&lt;li&gt;어떤 방식을 선택하든, 서로 메시지 또는 이벤트를 전송하여 통신하는 것이 좋으며, 공유 상태를 갖는 것은 좋은 선택지가 아니다. 마이크로 서비스에서 데이터베이스를 공유하는 것이 좋지 않은 것처럼, 데이터 구조와 도메인 모델을 공유하자마자 엄청난 양의 결합이 생기게 되고, 변경에 많은 제약이 따르기 때문이다.&lt;/li&gt;
&lt;li&gt;vuex는 일반적으로 하나의 응용 프로그램에 대해 단일 전역 공유 저장소를 기본으로 한다. 그러나 각 마이크로 앱들이 자체 저장소를 갖는 것이 더 합리적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;백엔드 통신
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BFF(Backend for Frontend Pattern) 패턴으로 프론트앤드 전용 API를 갖는다. 각 마이크로 앱들은 해당 프론트 엔드의 요구사항만 충족하는 목적을 가진 백엔드만을 가진다.&lt;/li&gt;
&lt;li&gt;별도의 데이터베이스를 가질 수도 있다.&lt;/li&gt;
&lt;li&gt;로그인 인증 정보는 통합하는 Container가 소유한다. 초기화 시 인증을 통해 얻은 토큰을 각 마이크로 프론트엔드에 주입되며, 마이크로 프론트엔드는 서버에 대한 요청과 함께 토큰을 보내고, 해당 마이크로 앱의 백엔드에서는 필요한 모든 유효성 검사를 수행하는 방식으로 구성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;324&quot; data-filename=&quot;55.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu51yI/btq9IZSbLe0/LDeVCgxa4Mffzg6bAEcNGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu51yI/btq9IZSbLe0/LDeVCgxa4Mffzg6bAEcNGk/img.png&quot; data-alt=&quot;프론트앤드와 백앤드의 구조화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu51yI/btq9IZSbLe0/LDeVCgxa4Mffzg6bAEcNGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu51yI%2Fbtq9IZSbLe0%2FLDeVCgxa4Mffzg6bAEcNGk%2Fimg.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;324&quot; data-filename=&quot;55.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;프론트앤드와 백앤드의 구조화&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;통합 테스팅&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리틱과 마이크로 방식은 테스팅에서 모두 동일하지만 한 가지 명확한 차이는, 다양한 마이크로 프론트엔드와 컨테이너 애플리케이션의 통합 테스트이다. 이 작업은 기능 테스트나 e2e 테스트 도구(selenium, cypress 등)를 사용하여 수행할 수 있으나, 그렇게 멀리 갈 것 까진 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합 기능 테스트는 low level에서 다룰 수 없는 테스트만 다루면 된다. low level 에서는 비지니스 로직, 렌더링 로직 정도를 커버하는 수준의 단위 테스트를 수행하고, 통합 기능 테스트에서는 각 페이지들이 잘 조합되었는지만 확인하면 된다. 예를 들면, 특정 URL 에 완전히 통합된 애플리케이션이 로드되었는지 확인하기 위해, 마이크로 프론트엔드의 하드코딩 된 특정 텍스트가 페이지에 존재하는지를 assert 하는 것이 될 수 있다.&lt;/p&gt;
&lt;h1&gt;References&lt;/h1&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://mobicon.tistory.com/572&quot;&gt;https://mobicon.tistory.com/572&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://micro-frontends.org/&quot;&gt;https://micro-frontends.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ichi.pro/ko/maikeulo-peuleonteu-endeu-jeobgeun-bangsig-eul-wihan-10-gaji-gyeoljeong-sahang-5410395627328&quot;&gt;https://ichi.pro/ko/maikeulo-peuleonteu-endeu-jeobgeun-bangsig-eul-wihan-10-gaji-gyeoljeong-sahang-5410395627328&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.xenonstack.com/insights/micro-frontend-architecture&quot;&gt;https://www.xenonstack.com/insights/micro-frontend-architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://soobing.github.io/micro-frontends/&quot;&gt;https://soobing.github.io/micro-frontends/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://martinfowler.com/articles/micro-frontends.html&quot;&gt;https://martinfowler.com/articles/micro-frontends.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Web, HTML</category>
      <category>Architecture</category>
      <category>Frontend</category>
      <category>micro frontend</category>
      <category>micro service</category>
      <category>마이크로 프론트엔드</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/38</guid>
      <comments>https://wanzargen.tistory.com/38#entry38comment</comments>
      <pubDate>Thu, 15 Jul 2021 23:34:10 +0900</pubDate>
    </item>
    <item>
      <title>husky로 git hook에 conventional commit 적용하기</title>
      <link>https://wanzargen.tistory.com/37</link>
      <description>&lt;h1&gt;Conventional Commits?&lt;/h1&gt;
&lt;p&gt;커밋 메세지에 사용자와 기계 모두가 이해할 수 있는 의미를 부여하기 위한 스펙&lt;/p&gt;
&lt;p&gt;명확한 커밋 히스토리를 생성하기 위한 간단한 규칙을 제공&lt;/p&gt;
&lt;p&gt;커밋 히스토리를 이용하여 더 쉽게 자동화된 도구를 만듦&lt;/p&gt;
&lt;p&gt;이 컨벤션은 커밋 메세지에 신규 기능 추가, 문제 수정, 커다란 변화가 있음을 기술함으로써 유의적 버전(Sementic Versioning)과 일맥상통&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;git 으로 commit 시에 일괄된 양식을 유지 → 그 양식을 바탕으로 버전 관리나 Change Log 를 자동으로 만들 수 있음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;커밋 메시지 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;타입&amp;gt;[적용 범위(선택 사항)]: &amp;lt;설명&amp;gt;

[본문(선택 사항)]

[꼬리말(선택 사항)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://www.notion.so/20bea107d508488598acdf568ca0273c&quot;&gt;커밋 메시지 구조적 요소&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Git hook에 Conventional Commit 적용하기&lt;/h1&gt;
&lt;p&gt;Commit 메시지를 아름답고 정갈하게 유지하기 위해 conventional commit을 git hook에 적용하는 과정을 소개하려고 한다.&lt;/p&gt;
&lt;h2&gt;1. 패키지에 husky 적용하기&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://typicode.github.io/husky/#/&quot;&gt;husky&lt;/a&gt; 가 뭔가요?&lt;/h3&gt;
&lt;p&gt;husky는 git hook을 손쉽게 제어하도록 도와주는 npm 라이브러리이다.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://git-scm.com/book/ko/v2/Git%EB%A7%9E%EC%B6%A4-Git-Hooks&quot;&gt;git hook&lt;/a&gt;?&lt;/h3&gt;
&lt;p&gt;git을 쓰다가 특정 이벤트(커밋할 때, 푸시할 때 등등)가 벌어졌을 때, 그 순간에 ‘갈고리’를 걸어서 특정 스크립트가 실행되도록 도와주는 것!&lt;/p&gt;
&lt;p&gt;물론 husky를 쓰지 않더라도 git hook을 설정할 수 있는 공식적인 방법은 따로 있다.&lt;br&gt;&lt;code&gt;.git/hooks&lt;/code&gt; 폴더에 들어가서 스크립트를 작성하면 된다.&lt;/p&gt;
&lt;p&gt;그러나 &lt;code&gt;.git/hooks&lt;/code&gt; 폴더 안에 스크립트 파일을 넣게 되면 그 파일은 &lt;code&gt;git&lt;/code&gt;에 기록되지 않아서 따로 관리해야 한다는 단점이 있다.&lt;/p&gt;
&lt;p&gt;또 &lt;code&gt;git hook&lt;/code&gt;으로 &lt;code&gt;npm scripts&lt;/code&gt;를 제어하고 싶을 때, 예컨대 &lt;code&gt;npm test&lt;/code&gt; 등의 명령어를 써야 한다면 스크립트를 작성하는 게 번거롭다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;husky&lt;/code&gt;는 굳이 &lt;code&gt;.git/hooks&lt;/code&gt; 폴더를 건드리지 않고도 &lt;code&gt;git hook&lt;/code&gt; 스크립트를 제어할 수 있게 도와준다.&lt;/p&gt;
&lt;h3&gt;husky 설치하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npx husky-init &amp;amp;&amp;amp; npm install&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 파일에는 아래와 같은 script가 추가된 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;prepare&amp;quot;: &amp;quot;husky install&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;그리고, &lt;code&gt;.husky&lt;/code&gt; 디렉토리가 생성된 것도 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.husky/pre-commit&lt;/code&gt; 파일이 생성되었는데, 기존에 &lt;code&gt;test&lt;/code&gt; 명령어가 있었기 때문에 이를 감지하여 아래와 같이 추가되었다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
. &amp;quot;$(dirname &amp;quot;$0&amp;quot;)/_/husky.sh&amp;quot;

npm test&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2. 패키지에 commitlint 적용하기&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://commitlint.js.org/#/&quot;&gt;commitlint&lt;/a&gt; 는 뭔가요?&lt;/h3&gt;
&lt;p&gt;commit 에 대한 lint를 확인하여 성공/실패를 리턴해주는 도구이다.&lt;/p&gt;
&lt;h3&gt;commitlint 설치하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install -D @commitlint/cli @commitlint/config-conventional&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;commitlint config 추가하기&lt;/h3&gt;
&lt;p&gt;루트 디렉토리에 &lt;code&gt;commitlint.config.js&lt;/code&gt; 파일을 추가해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = { extends: [&amp;#39;@commitlint/config-conventional&amp;#39;] };&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;commitlint의 기본 컨벤션&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
    parserPreset: &amp;#39;conventional-changelog-conventionalcommits&amp;#39;,
    rules: {
        &amp;#39;body-leading-blank&amp;#39;: [1, &amp;#39;always&amp;#39;],
        &amp;#39;body-max-line-length&amp;#39;: [2, &amp;#39;always&amp;#39;, 100],
        &amp;#39;footer-leading-blank&amp;#39;: [1, &amp;#39;always&amp;#39;],
        &amp;#39;footer-max-line-length&amp;#39;: [2, &amp;#39;always&amp;#39;, 100],
        &amp;#39;header-max-length&amp;#39;: [2, &amp;#39;always&amp;#39;, 100],
        &amp;#39;subject-case&amp;#39;: [
            2,
            &amp;#39;never&amp;#39;,
            [&amp;#39;sentence-case&amp;#39;, &amp;#39;start-case&amp;#39;, &amp;#39;pascal-case&amp;#39;, &amp;#39;upper-case&amp;#39;],
        ],
        &amp;#39;subject-empty&amp;#39;: [2, &amp;#39;never&amp;#39;],
        &amp;#39;subject-full-stop&amp;#39;: [2, &amp;#39;never&amp;#39;, &amp;#39;.&amp;#39;],
        &amp;#39;type-case&amp;#39;: [2, &amp;#39;always&amp;#39;, &amp;#39;lower-case&amp;#39;],
        &amp;#39;type-empty&amp;#39;: [2, &amp;#39;never&amp;#39;],
        &amp;#39;type-enum&amp;#39;: [
            2,
            &amp;#39;always&amp;#39;,
            [
                &amp;#39;build&amp;#39;,
                &amp;#39;chore&amp;#39;,
                &amp;#39;ci&amp;#39;,
                &amp;#39;docs&amp;#39;,
                &amp;#39;feat&amp;#39;,
                &amp;#39;fix&amp;#39;,
                &amp;#39;perf&amp;#39;,
                &amp;#39;refactor&amp;#39;,
                &amp;#39;revert&amp;#39;,
                &amp;#39;style&amp;#39;,
                &amp;#39;test&amp;#39;,
            ],
        ],
    },
};&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3. commit-msg hook 적용하기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;commit-msg&lt;/code&gt; 훅은 최종적으로 커밋이 완료되기 전에, 프로젝트 상태나 커밋 메시지를 검증하기 위해 사용한다.&lt;/p&gt;
&lt;h3&gt;husky hook 에 commit-msg 추가하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npx husky add .husky/commit-msg &amp;#39;npx --no-install commitlint --edit $1&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이렇게 하면, commitlint 의 컨벤션으로 husky가 commit-msg 훅을 실행한다.&lt;/p&gt;
&lt;h3&gt;commit-msg hook 동작 확인하기&lt;/h3&gt;
&lt;p&gt;그럼 어디한번 동작하는지 확인해볼까.&lt;/p&gt;
&lt;p&gt;우선 잘못된 커밋 메시지를 넣어보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &amp;#39;hello-world&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;270&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNB8Ho/btq4WFTdCGg/qT4A6MGl7K0TNcCjFm7FM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNB8Ho/btq4WFTdCGg/qT4A6MGl7K0TNcCjFm7FM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNB8Ho/btq4WFTdCGg/qT4A6MGl7K0TNcCjFm7FM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNB8Ho%2Fbtq4WFTdCGg%2FqT4A6MGl7K0TNcCjFm7FM0%2Fimg.png&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;270&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;두둥탁- 짠-.&lt;/p&gt;
&lt;p&gt;subject 가 비어있으면 안되고, type 도 비어있으면 안된다고 지적해준다.&lt;/p&gt;
&lt;p&gt;이번엔 제대로된 메시지를 넣어보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &amp;#39;test: hello world&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;264&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7Xb3g/btq4X6a12ku/SCs9KCiPqtw8K5WmU58ES1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7Xb3g/btq4X6a12ku/SCs9KCiPqtw8K5WmU58ES1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7Xb3g/btq4X6a12ku/SCs9KCiPqtw8K5WmU58ES1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7Xb3g%2Fbtq4X6a12ku%2FSCs9KCiPqtw8K5WmU58ES1%2Fimg.png&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;264&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;아주아주 잘되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h3&gt;git kraken 에서 동작 확인하기&lt;/h3&gt;
&lt;p&gt;야심차게 git kraken 에서도 잘못된 커밋메시지로 커밋을 시도해봤다.&lt;/p&gt;
&lt;p&gt;그런데 웬걸  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;270&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blw39C/btq4Y44N692/ULwk0LjX2nhlpTAHVKX191/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blw39C/btq4Y44N692/ULwk0LjX2nhlpTAHVKX191/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blw39C/btq4Y44N692/ULwk0LjX2nhlpTAHVKX191/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblw39C%2Fbtq4Y44N692%2FULwk0LjX2nhlpTAHVKX191%2Fimg.png&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;270&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;커밋이 너무 잘된다... 젠장.&lt;/p&gt;
&lt;p&gt;commit-msg 훅 로그를 들여다보니.&lt;/p&gt;
&lt;p&gt;별 말도 없다. 어쩌란 건지..&lt;/p&gt;
&lt;p&gt;그래서 issue tracking 을 해보니, 아래와 같은 문제점이 있다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/typicode/husky/issues/875&quot;&gt;Husky v5 and Gitkraken #875&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;버전 7.5부터 Gitkraken은 &lt;code&gt;core.hookspath&lt;/code&gt;를 지원하지 않으며 &lt;code&gt;.git/hooks&lt;/code&gt;을 사용할 것입니다.이 문제를 해결하는 방법은 매우 간단합니다.&lt;br&gt;&lt;code&gt;.git/hooks&lt;/code&gt; 에서 허스키의 디렉토리로의 심볼릭 링크를 만듭니다.&lt;br&gt;최선의 방법은 아니지만 내가 찾은 유일한 방법입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  $ rm -rf .git/hooks &amp;amp;&amp;amp; ln -s ../.husky .git/hooks&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You may add this command to your package.json &lt;code&gt;postinstall&lt;/code&gt; script, alongside &lt;code&gt;husky install&lt;/code&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;{ &amp;quot;scripts&amp;quot;: { &amp;quot;postinstall&amp;quot;: &amp;quot;husky install &amp;amp;&amp;amp; rm -rf .git/hooks &amp;amp;&amp;amp; ln -s ../.husky .git/hooks&amp;quot; } }&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From you project root:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;참고로, &lt;a href=&quot;https://git-scm.com/docs/githooks&quot;&gt;git 공식 문서&lt;/a&gt;에 이런 글이 있다.&lt;br&gt;By default the hooks directory is &lt;code&gt;$GIT_DIR/hooks&lt;/code&gt;, but that can be changed via the &lt;code&gt;core.hooksPath&lt;/code&gt; configuration variable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;husky v5부터는 기본 git의 경로 ( &lt;code&gt;.git/hooks&lt;/code&gt;)가 아닌 사용자 지정 후크 경로 ( &lt;code&gt;.husky&lt;/code&gt;) 가 사용됩니다 . 이것은 로컬 git 구성 &lt;code&gt;core.hookspath&lt;/code&gt;키 설정을 통해 수행됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하...&lt;/p&gt;
&lt;p&gt;&lt;del&gt;이렇게까지 해서 맥 Big Sur OS 에도 최적화되지 못한 채, 커밋 날릴때마다 7초 이상 기다리게 하더니, 이제는 깃훅 path 커스텀도 막아버리는 무례한 깃크라켄의 만행을, 돈을 내가면서 참아줘야 하는걸까.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;결국 했다.&lt;/p&gt;
&lt;p&gt;gitkraken 을 사용하지 않는 컨트리뷰터들을 위해 아래 postinstall script 는 추가하지 않고, 로컬에서만 .git/hooks 에 심볼링 링크를 걸어주는 것으로 끝냈다.&lt;/p&gt;
&lt;h2&gt;4. 컨벤션에 맞게 커밋하기&lt;/h2&gt;
&lt;p&gt;컨벤션에 맞게 커밋하는 것을 도와주는 &lt;a href=&quot;https://www.npmjs.com/package/commitizen&quot;&gt;commitizen&lt;/a&gt; cli 도구를 사용하려고 한다.&lt;/p&gt;
&lt;h3&gt;commitizen 설치하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm i -D commitizen&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;commitizen 어댑터 구성하기&lt;/h3&gt;
&lt;p&gt;commitizen으로 커밋을 만들기 위한 방식은 어떤 어댑터를 사용하느냐에 따라 달라진다.&lt;/p&gt;
&lt;p&gt;여기에서는 cz-conventional-changelog 어댑터 사용할 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx commitizen init cz-conventional-changelog --save-dev --save-exact&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 명령어는 아래 3가지를 수행한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cz-conventional-changelog&lt;/code&gt; 어댑터를 설치한다.&lt;/li&gt;
&lt;li&gt;그리고 dev dependency에 추가한다.&lt;/li&gt;
&lt;li&gt;아래처럼 &lt;code&gt;config.commitizen&lt;/code&gt; 을 &lt;code&gt;package.json&lt;/code&gt; 에 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;config&amp;quot;: {
    &amp;quot;commitizen&amp;quot;: {
      &amp;quot;path&amp;quot;: &amp;quot;cz-conventional-changelog&amp;quot;
    }
  }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이렇게 설정하면, 커밋하려고 할 때 사용할 어댑터를 commitizen에게 알려준다.&lt;/p&gt;
&lt;h3&gt;commitizen script 추가하기&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;에 아래와 같은 script 를 추가한다.&lt;/p&gt;
&lt;p&gt;husky와 함께 쓰는 경우는 commit 명령어로 쓰지 말라는 경고가 있으니 주의하자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;scripts&amp;quot;: {
...
    &amp;quot;cm&amp;quot;: &amp;quot;cz&amp;quot;
  }&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;npx cz

or

npm run cm&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 명령어를 통해 commitizen으로 커밋을 정형화된 포맷으로 할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, git commit 명령어를 실행시켰을 때, commitizen 을 먼저 실행시킬 수 있도록 설정한다면, 굳이 이 명령어를 쓰지 않아도 된다.&lt;/p&gt;
&lt;h3&gt;커밋 시 commitzen 실행하도록 husky에 훅 추가하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npx husky add .husky/prepare-commit-msg &amp;#39;exec &amp;lt; /dev/tty &amp;amp;&amp;amp; node_modules/.bin/cz --hook || true&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;commitizen 으로 커밋하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;원하는 type 선택하기&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;246&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zK6kU/btq4ZfLUIM2/he12wKKksLRff9qnCQo44k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zK6kU/btq4ZfLUIM2/he12wKKksLRff9qnCQo44k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zK6kU/btq4ZfLUIM2/he12wKKksLRff9qnCQo44k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzK6kU%2Fbtq4ZfLUIM2%2Fhe12wKKksLRff9qnCQo44k%2Fimg.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;246&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;필요한 설명 입력하기&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;186&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BMwMV/btq4ZgKNmCd/6YKHWQ6clKsCEVUevXEqw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BMwMV/btq4ZgKNmCd/6YKHWQ6clKsCEVUevXEqw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BMwMV/btq4ZgKNmCd/6YKHWQ6clKsCEVUevXEqw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBMwMV%2Fbtq4ZgKNmCd%2F6YKHWQ6clKsCEVUevXEqw0%2Fimg.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;186&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;이슈에 연결하기(선택사항)&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;80&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/usld0/btq4WWAqIEG/loLJAKou7OlDw6TgsSZVr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/usld0/btq4WWAqIEG/loLJAKou7OlDw6TgsSZVr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/usld0/btq4WWAqIEG/loLJAKou7OlDw6TgsSZVr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fusld0%2Fbtq4WWAqIEG%2FloLJAKou7OlDw6TgsSZVr1%2Fimg.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;80&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;결과 확인&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;32&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R4DJb/btq4XxUruQW/KwmmBWsTzCzGT78AM809ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R4DJb/btq4XxUruQW/KwmmBWsTzCzGT78AM809ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R4DJb/btq4XxUruQW/KwmmBWsTzCzGT78AM809ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR4DJb%2Fbtq4XxUruQW%2FKwmmBWsTzCzGT78AM809ak%2Fimg.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;32&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;5. commitizen 어댑터 변경하기 (선택사항)&lt;/h2&gt;
&lt;p&gt;엇, 그런데 commitizen 에서 제공하는 어댑터 중에 commitlint 를 사용하는 것이 있다는 것을 뒤늦게 알아버렸다.&lt;/p&gt;
&lt;p&gt;위에 commit-msg 훅에서 commitlint를 사용하고 있어서, 일관성 유지에는 commitlint를 사용하는 것이 좋을 것 같아서 commitlint 어댑터로 바꿔보겠다.&lt;/p&gt;
&lt;h3&gt;기존 어댑터 삭제하기&lt;/h3&gt;
&lt;p&gt;위에서 추가해준 cz-conventional-changelog 어댑터를 삭제해주자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm uninstall -D cz-conventional-changelog&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;새로운 어댑터 추가하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm i -D @commitlint/prompt&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;commitizen 어댑터 설정 변경하기&lt;/h3&gt;
&lt;p&gt;아래처럼 &lt;code&gt;package.json&lt;/code&gt; 에서 &lt;code&gt;config.commitizen&lt;/code&gt; 을 변경해주자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;script&amp;quot;: {
...
&amp;quot;cm&amp;quot;: &amp;quot;git-cz&amp;quot;
},
&amp;quot;config&amp;quot;: {
    &amp;quot;commitizen&amp;quot;: {
       &amp;quot;path&amp;quot;: &amp;quot;./node_modules/@commitlint/prompt&amp;quot;
    }
  }&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;변경된 어댑터 동작 확인하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;624&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FP1UG/btq4XYRPYnp/j4kkt7kkrLELujzgFomcKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FP1UG/btq4XYRPYnp/j4kkt7kkrLELujzgFomcKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FP1UG/btq4XYRPYnp/j4kkt7kkrLELujzgFomcKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFP1UG%2Fbtq4XYRPYnp%2Fj4kkt7kkrLELujzgFomcKk%2Fimg.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;624&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;전혀 다른 방식으로 커밋이 진행되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h1&gt;마치며.&lt;/h1&gt;
&lt;p&gt;마지막에 바꾼 어댑터.&lt;br&gt;써보니까 너무 별로여서 다시 기존 어댑터로 롤백했다.&lt;br&gt;ㅎㅎ..&lt;/p&gt;
&lt;p&gt;그로부터 며칠 뒤.&lt;br&gt;&lt;code&gt;prepare-commit-msg&lt;/code&gt; 훅을 뻈다.&lt;br&gt;이제 대충 커밋 컨벤션이 머리에 있는데 저 과정으로 커밋하는게 영 불편했다.&lt;br&gt;그래서 필요한 경우에는 &lt;code&gt;npm run cm&lt;/code&gt; 으로 실행시키는 것으로.&lt;/p&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.huskyhoochu.com/npm-husky-the-git-hook-manager/&quot;&gt;https://www.huskyhoochu.com/npm-husky-the-git-hook-manager/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://git-scm.com/book/ko/v2/Git%EB%A7%9E%EC%B6%A4-Git-Hooks&quot;&gt;https://git-scm.com/book/ko/v2/Git맞춤-Git-Hooks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cookapps.io/guide/conventional-commits/&quot;&gt;https://blog.cookapps.io/guide/conventional-commits/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.conventionalcommits.org/ko/v1.0.0/&quot;&gt;https://www.conventionalcommits.org/ko/v1.0.0/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines&quot;&gt;https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming/Git</category>
      <category>commit convention</category>
      <category>commitizen</category>
      <category>commitlint</category>
      <category>conventional commits</category>
      <category>git hook</category>
      <category>Husky</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/37</guid>
      <comments>https://wanzargen.tistory.com/37#entry37comment</comments>
      <pubDate>Sat, 15 May 2021 10:44:02 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 실행 컨텍스트(Execution Context) 정리!!</title>
      <link>https://wanzargen.tistory.com/36</link>
      <description>&lt;h1&gt;실행 컨텍스트 정의&lt;/h1&gt;
&lt;p&gt;&lt;b&gt;실행 가능한 코드를 형상화하고 구분하는 추상적인 개념&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;= 실행 가능한 코드가 실행되기 위해 필요한 환경&lt;/p&gt;
&lt;p&gt;자바스크립트 엔진은 실행 가능한 코드를 실행하기 위해 필요한 정보를 형상화하고 구분하기 위해 실행 컨텍스트를 물리적 객체의 형태로 관리한다.&lt;/p&gt;
&lt;h2&gt;실행 가능한 코드&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;전역 코드 : 전역 영역에 존재하는 코드&lt;/li&gt;
&lt;li&gt;함수 코드 : 함수 내에 존재하는 코드&lt;/li&gt;
&lt;li&gt;Eval 코드 :&amp;nbsp;eval 함수로 실행되는 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Eval 함수? &lt;br /&gt;문자로 표현 된 JavaScript 코드를 실행하는 함수.&lt;br /&gt;인자로 받은 코드를 caller의 권한으로 수행하므로, 제 3자 코드가 eval()이 호출된 위치의 스코프를 볼 수 있으며, 비슷한 함수인 Function으로는 실현할 수 없는 공격이 가능하여 절대 쓰지 않는 것이 좋다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;실행에 필요한 정보&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;변수 : 전역변수, 지역변수, 매개변수, 객체의 프로퍼티&lt;/li&gt;
&lt;li&gt;함수 선언&lt;/li&gt;
&lt;li&gt;변수의 유효범위(Scope)&lt;/li&gt;
&lt;li&gt;this&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;정리하면&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;변수, 함수 선언, 스코프, this는 ****전역, 함수 코드를 실행하기 위해 필요한 정보이다.&lt;/li&gt;
&lt;li&gt;실행 컨텍스트는 전역 코드, 함수 코드를 실행하기 위한 환경이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;실행 컨텍스트 스택&lt;/h1&gt;
&lt;p&gt;앞으로 실행 가능한 코드를 편의상 '함수'라고 하겠다.&lt;/p&gt;
&lt;p&gt;함수를 실행하면, 실행 컨텍스트가 생성된다.&lt;/p&gt;
&lt;p&gt;그 안에서 또 다른 함수가 실행되면 그 위에 실행 컨텍스트가 생성되어, 스택 구조로 콜스택 메모리에 쌓인다.&lt;/p&gt;
&lt;p&gt;그리고 함수 실행이 종료되면, 실행 컨텍스트가 파기되는 LIFO 구조다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1620725060631&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var x = 'xxx';

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z);
  }
  bar();
}
foo();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드의 실행 컨텍스트 스택은 아래 그림과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lHzZN/btq4HHWymbL/OB1dz9FEaImZaCYhJdCU7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lHzZN/btq4HHWymbL/OB1dz9FEaImZaCYhJdCU7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lHzZN/btq4HHWymbL/OB1dz9FEaImZaCYhJdCU7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlHzZN%2Fbtq4HHWymbL%2FOB1dz9FEaImZaCYhJdCU7k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;전역 컨텍스트(Global EC)로 컨트롤 이동 (전역 컨텍스트는 그냥 가장 먼저 실행되는 컨텍스트라고 이해하면 된다)&lt;/li&gt;
&lt;li&gt;foo() 함수 실행으로 foo() 실행 컨텍스트 스택이 생성 &amp;rarr; foo() 실행 컨텍스트로 컨트롤 이동&lt;/li&gt;
&lt;li&gt;foo() 함수 내에서 bar() 함수 실행으로 bar() 실행 컨택스트 스택이 생성 &amp;rarr; bar() 실행 컨텍스트로 컨트롤 이동&lt;/li&gt;
&lt;li&gt;bar() 함수 실행 종료 &amp;rarr; bar() 실행 컨텍스트 파기 &amp;rarr; 이전 컨텍스트인 foo()로 컨트롤 이동&lt;/li&gt;
&lt;li&gt;foo() 함수 실행 종료 &amp;rarr; foo() 실행 컨텍스트 파기 &amp;rarr; 이전 컨텍스트인 전역 컨텍스트로 컨트롤 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;실행 컨텍스트의 3가지 프로퍼티&lt;/h1&gt;
&lt;p&gt;실행 컨텍스트는 물리적으로는 객체의 형태를 가지며 아래의 3가지 프로퍼티를 소유한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;활성 객체(변수 객체, Variable Object, VO)&lt;/li&gt;
&lt;li&gt;스코프 체인(Scope Chain)&lt;/li&gt;
&lt;li&gt;this&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;실행 컨텍스트 생성 및 실행 과정&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;활성 객체(변수 객체) 생성&lt;/li&gt;
&lt;li&gt;arguments 객체 생성&lt;/li&gt;
&lt;li&gt;스코프 정보 생성&lt;/li&gt;
&lt;li&gt;변수 생성&lt;/li&gt;
&lt;li&gt;this 바인딩&lt;/li&gt;
&lt;li&gt;코드 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1620725112080&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function execute(param1, param2) {
	var a = 1, b = 2;

	function func() {
		return a + b;
	};

	return param1 + param2 + func();
};

execute(3, 4);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;활성 객체(변수 객체, Variable Object, VO) 생성&lt;/h2&gt;
&lt;p&gt;활성 객체, 즉 변수 객체는 실행에 필요한 어려가지 정보를 담을 객체이다. 이 객체에는 변수, 매개변수(parameter), 전달인자(arguments), 함수 선언(함수 표현식은 제외)이 저장된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;339&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctBgoc/btq4CjCMNNG/b6Wvv9jHNwlQW0xEVnlqjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctBgoc/btq4CjCMNNG/b6Wvv9jHNwlQW0xEVnlqjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctBgoc/btq4CjCMNNG/b6Wvv9jHNwlQW0xEVnlqjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctBgoc%2Fbtq4CjCMNNG%2Fb6Wvv9jHNwlQW0xEVnlqjk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;339&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;arguments 객체 생성&lt;/h2&gt;
&lt;p&gt;자바스크립트에서는 함수를 호출할 때 암묵적으로 arguments 객체가 함수 내부로 전달된다. 넘긴 인자들이 배열 형태로 저장된 객체이다.&lt;/p&gt;
&lt;p&gt;특이한 점은, arguments 객체는 배열이 아니라 유사 배열 객체이다. 유사배열 객체는 객체임에도 불구하고, apply() 를 통해서 자바스크립트의 표준 배열 메소드를 사용하는게 가능하다. 그러나 배열 메소드를 바로 사용하는 것은 불가능하다.&lt;/p&gt;
&lt;p&gt;arguments 객체는 다음과 같이 세 부분으로 구성되어 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;함수 호출 시 넘겨진 인자 (배열 형태)&lt;/li&gt;
&lt;li&gt;length 프로퍼티&lt;/li&gt;
&lt;li&gt;callee 프로퍼티: 현재 실행중인 함수의 참조값&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;337&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vOApr/btq4H11x0Dl/6NS7dFHdhcethgnNlx8Ov0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vOApr/btq4H11x0Dl/6NS7dFHdhcethgnNlx8Ov0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vOApr/btq4H11x0Dl/6NS7dFHdhcethgnNlx8Ov0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvOApr%2Fbtq4H11x0Dl%2F6NS7dFHdhcethgnNlx8Ov0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;337&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;스코프 정보 생성&lt;/h2&gt;
&lt;p&gt;스코프 정보는 현재 컨텍스트의 유효 범위를 나타낸다. 스코프 정보는 현재 실행 중인 실행 컨텍스트 안에서 linked list와 유사한 형식으로 만들어진다.&lt;/p&gt;
&lt;p&gt;이 리스트를 '스코프 체인'이라고 한다.&lt;/p&gt;
&lt;h3&gt;스코프 체인&lt;/h3&gt;
&lt;p&gt;[[scope]] 프로퍼티로 참조되는 스코프 정보들의 linked list이다.&lt;/p&gt;
&lt;p&gt;스코프 체인은 현재 컨텍스트의 변수 뿐만 아니라, 상위 실행 컨텍스트의 변수도 접근이 가능하다. 이 리스트에서 찾지 못한 변수는 결국 정의되지 않은 변수에 접근하는 것으로 판단하여, 에러를 검출한다.&lt;/p&gt;
&lt;p&gt;현재 생성된 활성 객체(변수 객체)가 스코프 체인의 가장 앞에 추가된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;353&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/93OdL/btq4Csmn42v/I3hFrRkKk3Q1C8PQSx4Pok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/93OdL/btq4Csmn42v/I3hFrRkKk3Q1C8PQSx4Pok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/93OdL/btq4Csmn42v/I3hFrRkKk3Q1C8PQSx4Pok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F93OdL%2Fbtq4Csmn42v%2FI3hFrRkKk3Q1C8PQSx4Pok%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;353&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;변수 생성&lt;/h2&gt;
&lt;p&gt;현재 실행 컨텍스트 내부에서 사용되는 지역 변수들이 생성된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;호출된 함수 인자, param1, param2 프로퍼티가 만들어지고 그 값이 할당된다. 만약 값이 넘겨지지 않았다면, undefined 가 할당된다.&lt;/li&gt;
&lt;li&gt;함수 내부에 정의된 변수 a, b와, 함수 func가 생성된다. 이 과정에서는 변수나 내부 함수를 단지 메모리에 생성할 뿐이다. 즉, 변수 a, b에는 각각 undefined 가 할당된다. 그렇다면 초기화는 언제 이루어질까. 각 변수나 함수에 해당하는 표현식이 실행되면 초기화가 이뤄진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;576&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfD8T9/btq4HOH3GK9/tpSg2GgRNysgEOcJl0DfqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfD8T9/btq4HOH3GK9/tpSg2GgRNysgEOcJl0DfqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfD8T9/btq4HOH3GK9/tpSg2GgRNysgEOcJl0DfqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfD8T9%2Fbtq4HOH3GK9%2FtpSg2GgRNysgEOcJl0DfqK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;576&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;왜 함수는 초기화까지 이뤄진거죠? &lt;br /&gt;이는 func 함수가 함수 선언식으로 작성되었기 때문에 그렇다.&amp;nbsp;&lt;br /&gt;함수 표현식은 변수 객체가 모두 만들어진 이후에 코드가 실행되는 시점에 읽힌다. 그러나 함수 선언식은 초기화 단계에서 읽힌다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;this 바인딩&lt;/h2&gt;
&lt;p&gt;this 키워드를 사용하는 값이 할당된다. 여기서 this가 참조하는 객체가 없으면 전역 객체를 참조한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;코드 실행&lt;/h2&gt;
&lt;p&gt;하나의 실행 컨텍스트가 생성되고, 변수 객체가 만들어진 후에 드디어 코드에 있는 여러 표현식의 실행이 이루어진다.&lt;/p&gt;
&lt;p&gt;이 과정에서 변수의 초기화 및 연산, 또 다른 함수 실행 등이 이뤄지는 것이다. 예제 코드의 변수 a, b 에 각각 1, 2 값이 할당된다.&lt;/p&gt;
&lt;p&gt;참고로, 전역 실행 컨텍스트는 일반적인 실행 컨텍스트와는 약간 다르다. arguments 객체가 없으며, 전역 객체 하나만을 포함하는 스코프 체인이 있다. 또한 전역 실행 컨텍스트에서는 변수 객체가 곧 전역 객체이다. 따라서, 전역적으로 선언된 함수와 변수가 전역 객체의 프로퍼티가 된다.&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://poiemaweb.com/js-execution-context&quot;&gt;https://poiemaweb.com/js-execution-context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@modolee/javascript-eval-is-evil&quot;&gt;https://velog.io/@modolee/javascript-eval-is-evil&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/Javascript</category>
      <category>execution context</category>
      <category>javascript</category>
      <category>실행 컨텍스트</category>
      <category>자바스크립트</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/36</guid>
      <comments>https://wanzargen.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 11 May 2021 18:28:05 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] 프로토타입(Prototype) 뽀개기</title>
      <link>https://wanzargen.tistory.com/35</link>
      <description>&lt;h1&gt;프로토타입(Prototype) 뽀개버리기!!&amp;nbsp;&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;함수도 객체다&lt;/h2&gt;
&lt;p&gt;함수의 기본 기능인 코드 실행뿐만 아니라, 함수 자체가 일반 객체처럼 프로퍼티들을 가질 수 있다.&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;function add(x, y) { 
return x+y; 
}

add.status = 'OK';
console.log(add.status)&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;add( ) 함수를 생성할 때 함수 코드는 함수 객체의 [[Code]] 내부 프로퍼티에 자동으로 저장된다(이것은 ECMAScript 명세서를 참조한 것이다* ).&lt;/li&gt;
&lt;li&gt;add() 함수에 마치 일반 객체처럼 status 프로퍼티를 생성하고 저장한 것을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;status 프로퍼티도 일반 객체에서의 접근 방식처럼 add.status를 이용해 접근 가능하다. 이처럼 자바스크립트에서 함수는 특정 기능의 코드를 수행하는 역할뿐만 아니라, 일반 객체처럼 자신의 프로퍼티를 가질 수 있는 특별한 객체라고 볼 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;242&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H3N4K/btqZP3dIpbU/EhEAt8kmekdeHTkgWuc9o1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H3N4K/btqZP3dIpbU/EhEAt8kmekdeHTkgWuc9o1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H3N4K/btqZP3dIpbU/EhEAt8kmekdeHTkgWuc9o1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH3N4K%2FbtqZP3dIpbU%2FEhEAt8kmekdeHTkgWuc9o1%2Fimg.png&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;242&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;함수는 객체이기 때문에, 일반 객체처럼 취급될 수 있다.&lt;/p&gt;
&lt;p&gt;따라서 다음과 같은 동작이 가능하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리터럴에 의해 생성&lt;/li&gt;
&lt;li&gt;변수나 배열의 요소,객체의 프로퍼티 등에 할당 가능&lt;/li&gt;
&lt;li&gt;함수의 인자로 전달 가능&lt;/li&gt;
&lt;li&gt;함수의 리턴값으로 리턴 가능&lt;/li&gt;
&lt;li&gt;동적으로 프로퍼티를 생성 및 할당 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이와 같은 특징 때문에 함수를 &lt;b&gt;일급 객체&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;일급 객체(First Class)&lt;/h4&gt;
&lt;p&gt;일급 객체라는 말은 컴퓨터 프로그래밍 언어 분야에서 쓰이는 용어로서, 위에 나열한 기능이 모두 가능한 객체를 일급 객체라고 부른다. 자바스크립트 함수가 가지는 이러한 일급 객체의 특성으로 함수형 프로그래밍이 가능하다.&lt;/p&gt;
&lt;p&gt;정리하면,&lt;/p&gt;
&lt;p&gt;자바스크립트 함수의 기능은 C나 자바와 같은 다른 언어 함수의 기능과 거의 비슷하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 기본적인 기능 외에도, 자바스크립트에서 함수를 제대로 이해하려면, 함수가 일급 객체라는 것을 아는 것이다.&lt;/p&gt;
&lt;p&gt;즉, 함수가 일반 객체처럼 값으로 취급된다는 것을 이해해야 한다. 이 때문에 함수를 변수나 객체, 배열 등에 값으로도 저장할 수 있으며, 다른 함수의 인자로 전달한다거나 함수의 리턴값으로도 사용 가능하다는 것을 알아야 한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;하지만 일반 객체와는 조금 다르다&lt;/h2&gt;
&lt;p&gt;무엇이 다를까?&lt;/p&gt;
&lt;p&gt;&lt;b&gt;함수 객체만의 표준 프로퍼티&lt;/b&gt;가 정의되어 있다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;function add(x, y) { 
    return x+y; 
}

console.dir(add);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;185&quot; data-filename=&quot;Untitled 1.png&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KRa7Y/btqZRsqBuD2/ayCfwBLdxMd4nbOK2CwYQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KRa7Y/btqZRsqBuD2/ayCfwBLdxMd4nbOK2CwYQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KRa7Y/btqZRsqBuD2/ayCfwBLdxMd4nbOK2CwYQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKRa7Y%2FbtqZRsqBuD2%2FayCfwBLdxMd4nbOK2CwYQ1%2Fimg.png&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;185&quot; data-filename=&quot;Untitled 1.png&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;arguments, caller, length 등과 같은 다양한 프로퍼티가 기본적으로 생성된다. 이러한 프로퍼티들이 함수를 생성할 때 포함되는 표준 프로퍼티다.&lt;/p&gt;
&lt;p&gt;참고로 ECMA5 스크립트 명세서에는 모든 함수가 length와 prototype 프로퍼티를 가져야 한다고 기술하고 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;arguments: 호출 시 전달되는 인자 값&lt;/li&gt;
&lt;li&gt;caller: 자신을 호출한 함수를 나타낸다&lt;/li&gt;
&lt;li&gt;name: 함수의 이름&lt;/li&gt;
&lt;li&gt;length: 함수가 정상적으로 실행될 때 기대되는 인자의 개수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;__proto__&lt;/b&gt;: 부모 역할을 하는 프로토타입 객체를 가리킨다. = [[Prototype]] = &lt;b&gt;__proto__&lt;/b&gt;&amp;nbsp;&amp;rarr; Function.prototype&lt;/li&gt;
&lt;li&gt;prototype: 함수가 생성될 때 만들어지며, 단지 constructor 프로퍼티 하나만 있는 객체를 가리킨다. 그리고 prototype 프로퍼티가 가리키는 프로토타입 객체의 유일한 constructor 프로퍼티는 자신과 연결된 함수를 가리킨다. 즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하며, 이 둘은 각각 prototype 과 constructor 라는 프로퍼티로 서로를 참조하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;함수 객체는 항상 부모 역할을 하는 객체를 가리킨다: 암묵적 프로토타입 링크&lt;/h2&gt;
&lt;p&gt;모든 자바스크립트 객체는 자신의 프로토타입을 가리키는 &lt;b&gt;[[Prototype]]&lt;/b&gt; 이라는 내부 프로퍼티를 가진다.크롬 브라우저는 이 프로퍼티가 &lt;b&gt;__proto__&lt;/b&gt;&amp;nbsp;프로퍼티로 구현되어 있다.&lt;/p&gt;
&lt;p&gt;이것이 가리키는 부모 역할을 하는 프로토타입 객체는 &lt;b&gt;Function.prototype&lt;/b&gt; &lt;b&gt;객체&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 모든 함수는 Function Prototype 객체의 프로퍼티나 메소드를 마치 자신의 것처럼 상속받아 그대로 사용할 수 있다. - constructor, toString(), apply(), call(), bind()&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 ECMAScript 명세서에는 Function.prototype은 함수라고 정의하고 있다.&lt;br /&gt;그리고 함수 역시 객체이므로, 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다.&lt;br /&gt;그렇다면 이러한 규칙에 의해 Function.prototype이 함수니까, 이것도 Function.prototype 객체, 즉, 자기 자신을 부모가 갖는 것인가?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;ECMAScript 명세서에는 &lt;b&gt;예외적으로 Function.prototype 함수 객체의 부모&lt;/b&gt;는 자바스크립트의 모든 객체의 조상격인 &lt;b&gt;Object.prototype 객체&lt;/b&gt;라고 설명하고 있다.&lt;br /&gt;때문에 Function Prototype 객체의 &lt;b&gt;proto&lt;/b&gt; 프로퍼티는 Object.prototype 객체를 가리킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 2.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;399&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mChKd/btqZVlYhXPa/mTygMt40d2k98OoZ1CynZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mChKd/btqZVlYhXPa/mTygMt40d2k98OoZ1CynZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mChKd/btqZVlYhXPa/mTygMt40d2k98OoZ1CynZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmChKd%2FbtqZVlYhXPa%2FmTygMt40d2k98OoZ1CynZ0%2Fimg.png&quot; data-filename=&quot;Untitled 2.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;399&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;함수는 Prototype 객체랑 같이 다닌다: prototype 프로퍼티&lt;/h2&gt;
&lt;p&gt;함수 객체의 prototype 프로퍼티는 함수가 생성될 때 만들어진다.&lt;/p&gt;
&lt;p&gt;이 프로퍼티는 단지 constructor 프로퍼티 하나만 있는 객체를 가리킨다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 prototype 프로퍼티가 가리키는 프로토타입 객체의 유일한 constructor 프로퍼티는 자신과 연결된 함수를 가리킨다.&lt;/p&gt;
&lt;p&gt;즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하며, 이 둘은 각각 prototype 과 constructor 라는 프로퍼티로 서로를 참조하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예제로 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;function myFunction() { 
    return true;
}

console.dir(myFunction.prototype);
console.dir(myFunction.prototype.constructor);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 3.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;354&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4GD63/btqZYmvn4ef/GiELHWiuYimwZnRv75UXTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4GD63/btqZYmvn4ef/GiELHWiuYimwZnRv75UXTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4GD63/btqZYmvn4ef/GiELHWiuYimwZnRv75UXTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4GD63%2FbtqZYmvn4ef%2FGiELHWiuYimwZnRv75UXTk%2Fimg.png&quot; data-filename=&quot;Untitled 3.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;354&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;myFunction() 이라는 함수를 생성했다. 함수가 생성됨과 동시에 myFunction() 함수의 prototype 프로퍼티에는 이 함수와 연결된 프로토타입 객체가 생성된다.&lt;/li&gt;
&lt;li&gt;myFunction.prototype은 myFunction() 함수의 프로토타입 객체를 의미한다. constructor, &lt;b&gt;__proto__&lt;/b&gt; 라는 두 개의 프로퍼티를 가진다.&lt;br /&gt;이 객체는 myFunction() 함수의 프로토타입 객체이므로 constructor 프로퍼티가 있다.&lt;br /&gt;이 객체 역시 자바스크립트 객체이므로, 예외 없이 자신의 부모 역할을 하는 &lt;b&gt;__proto__&lt;/b&gt; 가 있다.&lt;/li&gt;
&lt;li&gt;myFunction.prototype.constructor 의 결과 값을 보면, myFunction() 함수를 가리키고 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;함수 객체와 프로토타입 객체는 prototype 과 constructor 라는 프로퍼티를 통해 서로를 참조하는 관계이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 4.png&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;239&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b06EDP/btqZP20bKMB/rK1XkmwTFNqBtO7AnjMHW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b06EDP/btqZP20bKMB/rK1XkmwTFNqBtO7AnjMHW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b06EDP/btqZP20bKMB/rK1XkmwTFNqBtO7AnjMHW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb06EDP%2FbtqZP20bKMB%2FrK1XkmwTFNqBtO7AnjMHW0%2Fimg.png&quot; data-filename=&quot;Untitled 4.png&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;239&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;프로토타입의 두 가지 의미&lt;/h2&gt;
&lt;p&gt;자바스크립트는 다른 언어와는 다르게 &lt;b&gt;프로토타입 기반의 객체지향 프로그래밍&lt;/b&gt;을 지원한다.&lt;/p&gt;
&lt;p&gt;객체지향 프로그래밍에서는 클래스를 정의하고 이를 통해 객체를 생성하지만, 자바스크립트에는 이런 개념이 없다.대신에 객체 리터럴이나 생성자 함수로 객체를 생성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 생성된 &lt;b&gt;객체의 부모 객체가 바로 '프로토타입' 객체&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;모든 객체에는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티([[Prototype]] 프로퍼티)가 있는데, 이러한 링크를 &lt;b&gt;암묵적 프로토타입 링크(Implicit prototype link)&lt;/b&gt; 라고 부른다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;주의할 점은, &lt;b&gt;암묵적 프로토타입 링크와 prototype 프로퍼티를 구분해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;모든 객체는&lt;br /&gt;자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를&lt;br /&gt;자신의 부모 객체로 설정하는 암묵적 프로토타입 링크로 연결한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;예제를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Person(name) {
    this.name = name;
};

var foo = new Person('foo');

console.dir(Person);
console.dir(foo);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 5.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;356&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/excQBv/btqZP3x0bri/8TxF9o2689FSb8wGHTJ0R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/excQBv/btqZP3x0bri/8TxF9o2689FSb8wGHTJ0R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/excQBv/btqZP3x0bri/8TxF9o2689FSb8wGHTJ0R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexcQBv%2FbtqZP3x0bri%2F8TxF9o2689FSb8wGHTJ0R1%2Fimg.png&quot; data-filename=&quot;Untitled 5.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;356&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Person() 생성자 함수로 생성된 foo 객체는 Person() 함수의 프로토타입 객체를 암묵적 프로토타입 링크로 연결한다.&lt;/p&gt;
&lt;p&gt;따라서&lt;br /&gt;Person 함수 객체의 prototype 프로퍼티 = foo 객체의 &lt;b&gt;__proto__&lt;/b&gt;&amp;nbsp;프로퍼티&lt;br /&gt;가 성립한다.&lt;/p&gt;
&lt;p&gt;prototype 프로퍼티는 함수 입장에서 자신과 링크된 프로토타입 객체를 가리키는 것이고,&lt;br /&gt;&lt;b&gt;__proto__&lt;/b&gt;(암묵적 프로토타입 링크) 프로퍼티는 객체의 입장에서 자신의 부모 객체인 프로토타입 객체를 가리키는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 6.png&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;394&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M40B4/btqZTUUoWp2/j64Kspg5n7k2wD89NWjOW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M40B4/btqZTUUoWp2/j64Kspg5n7k2wD89NWjOW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M40B4/btqZTUUoWp2/j64Kspg5n7k2wD89NWjOW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM40B4%2FbtqZTUUoWp2%2Fj64Kspg5n7k2wD89NWjOW1%2Fimg.png&quot; data-filename=&quot;Untitled 6.png&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;394&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;프로토타입 체이닝&lt;/h2&gt;
&lt;p&gt;객체는 자기 자신의 프로퍼티 뿐만 아니라, 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티 또한 자신의 것처럼 접근하는 것이 가능하다.&lt;/p&gt;
&lt;p&gt;이것을 가능하게 하는 것이 바로 프로토타입 체이닝이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;자바스크립트에서는 특정 객체의 프로퍼티나 메서드에 접근하려고 할 때,&lt;br /&gt;해당 객체에 접근하려는 프로퍼티 또는 메서드가 없으면&lt;br /&gt;암묵적 프로토타입 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 차례대로 검색한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이것을 프로토타입 체이닝이라고 한다.&lt;/p&gt;
&lt;h3&gt;객체 리터럴 프로토타입 체이닝&lt;/h3&gt;
&lt;p&gt;리터럴로 생성된 객체는 내부적으로 Object() 함수 객체를 통해 생성된다.&lt;/p&gt;
&lt;p&gt;Object() 함수는 암묵적 프로토타입 링크로 Object.prototype 객체를 가리킨다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이에 따라 어떻게 프로토타입 체이닝이 이뤄지는지 예제를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;var myObject = {
    name: 'foo',
    sayName: function() {
        console.log('My Name is ' + this.name);
    }
}

myObject.sayName();
console.log(myObject.hasOwnProperty('name'));
console.log(myObject.hasOwnProperty('nickName'));
myObject.sayNickName();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 7.png&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;185&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnS4IR/btqZRrLZdgB/aIGvC8oxwqoyKPksi8WbSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnS4IR/btqZRrLZdgB/aIGvC8oxwqoyKPksi8WbSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnS4IR/btqZRrLZdgB/aIGvC8oxwqoyKPksi8WbSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnS4IR%2FbtqZRrLZdgB%2FaIGvC8oxwqoyKPksi8WbSk%2Fimg.png&quot; data-filename=&quot;Untitled 7.png&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;185&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;왜 myObject 객체가 hasOwnProperty() 메서드를 호출할 때는 에러가 발생하지 않았을까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;객체 리터럴로 생성한 객체는 Object()라는 내장 생성자 함수로 생성된 것이다.&lt;/p&gt;
&lt;p&gt;Object() 생성자힘수도힘수객체이므로prototype이라는프로퍼티 속성이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 Object() 함수의 prototype 프로퍼티가 가리키는 Object.prototype 객체를 자신의 프로토타입 객체로 연결한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 8.png&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;408&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K9X2D/btqZYkYDY5n/sSffiJlJTtyQoL0ioop7N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K9X2D/btqZYkYDY5n/sSffiJlJTtyQoL0ioop7N0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K9X2D/btqZYkYDY5n/sSffiJlJTtyQoL0ioop7N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK9X2D%2FbtqZYkYDY5n%2FsSffiJlJTtyQoL0ioop7N0%2Fimg.png&quot; data-filename=&quot;Untitled 8.png&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;408&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;myObject.sayName();&lt;/code&gt; : 해당 객체 내에 메서드가 있어서 바로 수행된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;myObject.hasOwnProperty('name')&lt;/code&gt; : myObject 내에는 hasOwnProperty() 라는 메서드가 없다.&lt;br /&gt;따라서 암묵적 프로토타입 링크를 따라, 그것의 부모 역할을 하는 Object.prototpye 프로토타입 객체에서 hasOwnProperty() 메서드가 있는지 검색한다.&lt;br /&gt;hasOwnProperty() 메서드는 자바스크립트 표준 API 로 Object.prototype 객체에 포함되어 있다.&lt;br /&gt;따라서 코드가 정상적으로 수행된다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;생성자 함수로 객체를 생성하는 경우의 프로토타입 체이닝&lt;/h3&gt;
&lt;p&gt;생성자 함수로 객체를 생성하는 경우는 객체 리터럴 방식과 약간 다른 프로토타입 체이닝이 이뤄진다.&lt;/p&gt;
&lt;p&gt;예제를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Person(name, age, hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
};

var foo = new Person('foo', 30, 'tennis');

console.log(foo.hasOwnProperty('name')); // true
console.dir(Person.prototype);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 9.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;133&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6RyXy/btqZUWRY4Fu/7EqJ8Dswx1KSxaUfmfEN6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6RyXy/btqZUWRY4Fu/7EqJ8Dswx1KSxaUfmfEN6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6RyXy/btqZUWRY4Fu/7EqJ8Dswx1KSxaUfmfEN6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6RyXy%2FbtqZUWRY4Fu%2F7EqJ8Dswx1KSxaUfmfEN6k%2Fimg.png&quot; data-filename=&quot;Untitled 9.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;133&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;foo.hasOwnProperty('name')&lt;/code&gt; 를 실행하면 먼저 foo 객체에서 hasOwnProperty() 메서드를 찾는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;foo 객체에는 없으므로, 암묵적 프로토타입 링크를 통해 부모 객체인 Person.prototype 에서 hasOwnProperty() 메서드를 찾는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Person.prototype 에도 없으므로, Person.prototype 의 프로토타입 객체에서 찾는다. Person.prototype 객체도 역시 자바스크립트 객체이므로 Object.prototype 을 프로토타입 객체로 가진다. 따라서 Object.prototype 에서 hasOwnProperty() 메서드를 찾는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Object.prototype 객체의 hasOwnProperty() 메서드를 실행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;393&quot; data-filename=&quot;Untitled 10.png&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p1MK9/btqZV7laIy1/VKab4MF129oebjFsbIfTpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p1MK9/btqZV7laIy1/VKab4MF129oebjFsbIfTpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p1MK9/btqZV7laIy1/VKab4MF129oebjFsbIfTpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp1MK9%2FbtqZV7laIy1%2FVKab4MF129oebjFsbIfTpk%2Fimg.png&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;393&quot; data-filename=&quot;Untitled 10.png&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위의 예제들을 통해 알 수 있듯이 &lt;b&gt;Object.prototype 객체&lt;/b&gt;는 &lt;b&gt;프로토타입 체이닝의 종점&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이와 같은 방식으로 자바스크립트의 숫자, 문자열, 배열 등에서 사용되는 표준 메서드들의 경우, Number.prototype, String.prototype, Array.prototype 등에 정의되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;프로토타입 체이닝 활용하기&lt;/h2&gt;
&lt;h3&gt;빌트인 프로토타입에 메서드 추가하기&lt;/h3&gt;
&lt;p&gt;자바스크립트는 Object.prototype, String.prototype 등과 같이 표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메서드들을 추가하는 것을 허용한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;Object.prototype.testMethod = function() {
    console.log('test method');
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;프로토타입 객체에 동적으로 프로퍼티 추가/삭제하기&lt;/h3&gt;
&lt;p&gt;프로토타입 객체 역시 자바스크립트 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는 것이 가능하다.&lt;/p&gt;
&lt;p&gt;그리고 이렇게 변경된 프로퍼티는 실시간으로 프로토타입 체이닝에 반영된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Person(name) {
    this.name = name;
};

var foo = new Person('foo');

foo.sayHello(); // Uncaught TypeError: foo.sayHello is not a function

Person.prototype.sayHello = function() {
    console.log('Hello');
};

foo.sayHello(); // Hello&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;프로토타입 객체의 this 바인딩&lt;/h3&gt;
&lt;p&gt;프로토타입 객체는 메서드를 가질 수 있다.&lt;/p&gt;
&lt;p&gt;만약 프로토타입 메서드 내부에서 this 를 사용한다면 이는 어디에 바인딩 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;객체의 메서드 호출 시 this 바인딩 규칙이 그대로 적용된다.&lt;/p&gt;
&lt;p&gt;즉, 메서드 호출 패턴대로, this는 그 메서드를 호출한 객체에 바인딩된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Person(name) {
    this.name = name;
};

var foo = new Person('foo');

Person.prototype.getName = function() {
    return this.name;
};

Person.prototype.name = 'person';

console.log(foo.getName()); // foo

console.log(Person.prototype.getName()); // person&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;foo.getName()&lt;/code&gt; 실행 시 getName() 메서드는 foo 객체에서 찾을 수 없으므로, 프로토타입 체이닝이 발생한다.&lt;br /&gt;결과적으로 Person.prototype의 getName() 이 실행된다.&lt;br /&gt;그런데 getName() 메서드를 호출한 객체는 foo 객체이므로, this 는 foo 객체에 바인딩이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Person.prototype.getName()&lt;/code&gt; 실행 시 Person.prototype 객체의 getName() 이 실행된다.&lt;br /&gt;getName()을 호출한 객체는 Person.prototype 이므로 this는 Person.prototype 객체에 바인딩된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 11.png&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;492&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boyYJ6/btqZUW5wOtg/IKSi7akyXbspCi2KjaWOak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boyYJ6/btqZUW5wOtg/IKSi7akyXbspCi2KjaWOak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boyYJ6/btqZUW5wOtg/IKSi7akyXbspCi2KjaWOak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboyYJ6%2FbtqZUW5wOtg%2FIKSi7akyXbspCi2KjaWOak%2Fimg.png&quot; data-filename=&quot;Untitled 11.png&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;492&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;디폴트 프로토타입 객체를 다른 객체로 변경하기&lt;/h3&gt;
&lt;p&gt;디폴트 프로토타입 객체는 함수가 생성될 때 같이 생성되는 프로토타입 객체를 말한다.&lt;br /&gt;이 객체를 다른 객체로 변경하는 것이 가능하다.&lt;/p&gt;
&lt;p&gt;이러한 특징을 이용해서 객체지향의 상속을 구현하는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 주의할 점이 있다.&lt;/p&gt;
&lt;p&gt;생성자 함수의 프로토타입 객체가 변경되면, 변경된 시점 이후에 생성된 객체들은 변경된 프로토타입 객체로 암묵적 프로토타입 링크가 이뤄진다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;반면, 생성자 함수의 프로토타입이 변경되기 이전에 생성된 객체들은 기존 프로토타입 객체로의 링크를 유지한다.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;function Person(name) {
    this.name = name;
};

console.log(Person.prototype.constructor); // Person 함수 객체

var foo = new Person('foo');

console.log(foo.country); // undefined

Person.prototype = {
    country: 'korea'
};

console.log(Person.prototype.constructor); // Object 함수 객체

var bar = new Person('bar');

console.log(foo.country); // undefined
console.log(bar.country); // korea

console.log(foo.constructor); // Person 함수 객체
console.log(bar.constructor); // Object 함수 객체&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 12.png&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;325&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO8Btb/btqZV58Jm4h/yEUtjgJ9GqDOAUpmBPcuqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO8Btb/btqZV58Jm4h/yEUtjgJ9GqDOAUpmBPcuqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO8Btb/btqZV58Jm4h/yEUtjgJ9GqDOAUpmBPcuqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO8Btb%2FbtqZV58Jm4h%2FyEUtjgJ9GqDOAUpmBPcuqk%2Fimg.png&quot; data-filename=&quot;Untitled 12.png&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;325&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Person.prototype 을 리터럴 객체로 변경한 후, Person.prototype.constructor 값이 Object() 생성자 함수로 출력된 것을 주목하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;변경한 프로토타입 객체는 리터럴로 생성하여 단지 country 프로퍼티만 있다. 즉, 디폴트 프로토타입 객체처럼 constructor 프로퍼티가 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 경우에도 암묵적 프로토타입 링크는 존재하기 때문에 프로토타입 체이닝은 발생한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 자바스크립트의 리터럴 객체는 프로토타입 객체로 Object.prototype 을 가진다. 따라서 Object.prototype 의 constructor 가 참조하고 있는 Object() 함수를 연결하여, Object() 생성자 함수가 출력되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 13.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;551&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4RWku/btqZQJlJa1T/0MbTzkBZ0HPGgvo5tKs581/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4RWku/btqZQJlJa1T/0MbTzkBZ0HPGgvo5tKs581/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4RWku/btqZQJlJa1T/0MbTzkBZ0HPGgvo5tKs581/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4RWku%2FbtqZQJlJa1T%2F0MbTzkBZ0HPGgvo5tKs581%2Fimg.png&quot; data-filename=&quot;Untitled 13.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;551&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Javascript</category>
      <category>javascript</category>
      <category>prototype</category>
      <category>자바스크립트</category>
      <category>프로토타입</category>
      <author>WANJIN</author>
      <guid isPermaLink="true">https://wanzargen.tistory.com/35</guid>
      <comments>https://wanzargen.tistory.com/35#entry35comment</comments>
      <pubDate>Fri, 12 Mar 2021 11:03:59 +0900</pubDate>
    </item>
  </channel>
</rss>