<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>HEROPY</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://heropy.blog/"/>
  <updated>2020-11-30T08:21:06.744Z</updated>
  <id>https://heropy.blog/</id>
  
  <author>
    <name>HEROPY</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Snowpack, 더 빠른 웹 개발을 위한 새로운 방식의 프론트엔드 빌드 도구</title>
    <link href="https://heropy.blog/2020/10/31/snowpack/"/>
    <id>https://heropy.blog/2020/10/31/snowpack/</id>
    <published>2020-10-30T15:00:00.000Z</published>
    <updated>2020-11-30T08:21:06.744Z</updated>
    
    <content type="html"><![CDATA[<h1 id="변경사항"><a href="#변경사항" class="headerlink" title="변경사항"></a>변경사항</h1><h2 id="2020년-11월"><a href="#2020년-11월" class="headerlink" title="2020년 11월"></a>2020년 11월</h2><ul><li>Pika.dev를 Skypack.dev로 수정했습니다.</li><li>다음 파트를 추가했습니다.<ul><li>구성 옵션</li><li>devOptions &lt;구성 옵션&gt;</li><li>buildOptions &lt;구성 옵션&gt;</li><li>installOptions &lt;구성 옵션&gt;</li><li>Svelte template &lt;템플릿&gt;</li></ul></li></ul><div class="toc"><ul><li><a href="e6552cce-33fa-4878-af87-15d76b2800bd">개요</a></li><li><a href="778592cd-71af-44b8-8849-072ec1d91da4">주요 개념</a><ul><li><a href="92449c0d-5a1e-40d9-a509-12d84757cca0">번들 없는 개발(Unbundled Development)</a></li><li><a href="1ce6d374-86d5-4b30-9792-91f6fd460ed4">의존성 모듈(Using NPM Dependencies)</a><ul><li><a href="2dbddf88-163d-468d-a11f-a47d386a9ef0">Skypack.dev</a></li></ul></li></ul></li><li><a href="67716756-9b76-49e8-a292-870208c6bffe">기본 구조 이해</a><ul><li><a href="16f6d9a2-6440-4f1d-9dfe-b941f2840463">snowpack.config.js</a></li><li><a href="ed1a8484-69c0-454f-b002-b7c088e4c979">package.json</a></li><li><a href="a5623cb5-6398-46ba-96e0-a1bdeb67dbcc">public/index.html</a></li><li><a href="f198d0c0-0000-4b25-9c58-26e0cc44425d">build/</a></li></ul></li><li><a href="0a84ba8e-bdfc-49da-891f-af9b838396ba">구성 옵션</a><ul><li><a href="0064e38b-0819-4764-9e07-a39a822a7dc1">devOptions</a></li><li><a href="6299b5f7-738d-4019-9e7c-3c45cd0b51b4">buildOptions</a></li><li><a href="b2dc104f-942a-4f7d-a7d5-1fdec753f690">installOptions</a></li></ul></li><li><a href="348cb1b0-c286-4852-b606-d922f5dd01d9">템플릿</a><ul><li><a href="a5723640-a011-4575-9097-cd8c447d36f5">공식 템플릿</a></li><li><a href="f5450b1e-ec5d-4926-8b8f-a525ee899aa9">Svelte template</a></li></ul></li><li><a href="84a0f2b2-1a29-4fc1-bf52-95ce79dd2688">참고자료</a></li></ul></div><h1><span id="e6552cce-33fa-4878-af87-15d76b2800bd">개요</span><a href="#e6552cce-33fa-4878-af87-15d76b2800bd" class="header-anchor"></a></h1><p><a href="https://www.snowpack.dev" target="_blank" rel="noopener">Snowpack</a>은 더 빠른 웹 개발을 위한 최신의 프론트엔드 빌드 도구입니다.<br>기존의 <a href="https://webpack.js.org" target="_blank" rel="noopener">Webpack</a>, <a href="https://rollupjs.org/guide/en" target="_blank" rel="noopener">Rollup</a> 그리고 <a href="https://parceljs.org" target="_blank" rel="noopener">Parcel</a> 같은 무겁고 복잡한 번들러의 번들 소요 시간을 획기적으로 절약할 수 있습니다.<br>Snowpack은 번들러가 아닙니다! 단순히 더 빨리 번들하는 것이 아니라 웹 빌드 시스템에 대한 새로운 접근 방식을 제공합니다.<br>JavaScript의 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import" target="_blank" rel="noopener">ESM(ES Modules)</a>을 활용하여 동일 파일을 다시 빌드하지 않는 최초의 빌드 시스템을 생성해 변경사항을 브라우저에 즉시 적용할 수 있습니다.</p><p><img src="/images/screenshot/snowpack/snowpack-benchmarks.jpg" alt="Snowpack Benchmarks"></p><div class="image-caption">Benchmarks by Pika</div><ul><li>개발 서버를 50ms 이내에 시작할 수 있습니다.</li><li>변경 사항이 브라우저에 즉시 반영됩니다.</li><li>선호하는 번들러(Webpack, Rollup..)와 통합할 수 있습니다.</li><li>TypeScript, JSX, CSS 모듈 등을 기본적으로 지원합니다.</li><li>React, Vue, Svelte 같은 다양한 프레임워크(라이브러리)를 지원합니다.</li></ul><h1><span id="778592cd-71af-44b8-8849-072ec1d91da4">주요 개념</span><a href="#778592cd-71af-44b8-8849-072ec1d91da4" class="header-anchor"></a></h1><h2><span id="92449c0d-5a1e-40d9-a509-12d84757cca0">번들 없는 개발(Unbundled Development)</span><a href="#92449c0d-5a1e-40d9-a509-12d84757cca0" class="header-anchor"></a></h2><p>번들 없는 개발(Unbundled Development)은 개별 파일을 브라우저에 전달하는 아이디어입니다.<br>Babel, TypeScript, Sass 같은 자주 사용하는 라이브러리로 파일을 빌드하고,<br>ESM import/export 구문으로 브라우저에서 개별적으로 로드합니다.<br>Snowpack은 파일이 변경되면 해당 단일 파일만 다시 빌드합니다.</p><p>반면에 Webpack, Rollup 같은 번들러들은 번들 개발(Bundled Development)에 중점을 둡니다.<br>번들러로 애플리케이션을 실행하면 개발에 추가 작업과 복잡성이 발생합니다.<br>저장할 때마다 모든 변경사항을 나머지 애플리케이션과 함께 다시 번들해야만 변경사항이 브라우저에 반영됩니다.<br>이제 ESM이 널리 지원되므로 이런 방식은 불필요합니다.</p><p><img src="/images/screenshot/snowpack/snowpack-unbundled-example-3.png" alt="Webpack vs Snowpack"></p><p>‘번들 없는 개발’은 기존 방식에 비해 몇 가지 장점이 있습니다.</p><ul><li>빠릅니다.</li><li>예측한 대로 동작합니다.</li><li>디버깅이 더 쉽습니다.</li><li>개별 파일 캐시가 더 좋습니다.</li><li>프로젝트 크기가 개발 속도에 영향을 주지 않습니다.</li></ul><p>모든 파일은 개별적으로 빌드되고 지속해서 캐시됩니다.<br>파일에 변경사항이 없으면 파일을 다시 빌드하지 않고 브라우저에서 다시 다운로드하지 않습니다.<br>이것이 번들 없는 개발의 핵심입니다.</p><p>또한 Snowpack은 즉시(50ms 미만) 시작하여 속도 저하 없이 무한히 큰 프로젝트로 확장할 수 있습니다.<br>반대로 기존 번들러로 대규모 앱을 빌드할 때는 개발 시작 시간이 30초 이상 걸리는 것이 일반적입니다.</p><blockquote><p>회사에서 관리하는 Webpack 기반의 일부 프론트엔드 개발은 최초 빌드가 4분 정도 걸립니다.</p></blockquote><h2><span id="1ce6d374-86d5-4b30-9792-91f6fd460ed4">의존성 모듈(Using NPM Dependencies)</span><a href="#1ce6d374-86d5-4b30-9792-91f6fd460ed4" class="header-anchor"></a></h2><p>NPM 패키지는 대부분 웹에서 직접 실행할 수 없는 CommonJS를 사용합니다.<br>브라우저에서 직접 실행할 수 있는 ESM import/export 구문을 사용하여 애플리케이션을 작성하더라도,<br>NPM 패키지를 가져올 때 번들러가 동작해야 합니다.</p><p>Snowpack은 다른 접근 방식을 취합니다.<br>전체 애플리케이션을 번들로 묶는 대신 Snowpack은 의존성을 별도로 처리합니다.<br>작동 방식은 다음과 같습니다.</p><ol><li>Snowpack이 애플리케이션을 스캔합니다.</li><li>node_modules 디렉토리에 설치된 의존성 모듈을 확인합니다.</li><li>모든 의존성 모듈을 개별 JavaScript 파일로 변환합니다.(E.g. react =&gt; react.js)</li><li>변환된 각 파일은 브라우저에서 직접 실행하고 ESM import 구문을 통해 가져옵니다.</li><li>의존성 모듈은 따로 변경하지 않는 이상 변경사항이 없으니 다시 빌드할 필요가 없습니다.</li></ol><pre><code class="plaintext">node_modules/react  =&gt;  http://localhost:8080/web_modules/react.jsnode_modules/vue    =&gt;  http://localhost:8080/web_modules/vue.js</code></pre><pre><code class="html">&lt;body&gt;  &lt;script type=&quot;module&quot;&gt;    import React from &#39;react&#39;    import Vue from &#39;vue&#39;    console.log(React)    console.log(Vue)  &lt;/script&gt;&lt;/body&gt;</code></pre><h3><span id="2dbddf88-163d-468d-a11f-a47d386a9ef0">Skypack.dev</span><a href="#2dbddf88-163d-468d-a11f-a47d386a9ef0" class="header-anchor"></a></h3><p><a href="https://www.skypack.dev/" target="_blank" rel="noopener">Skypack</a>를 통해 Snowpack에서 사용할 ESM 지원 패키지를 쉽게 검색하고 테스트할 수 있습니다.</p><p><img src="/images/screenshot/snowpack/skypack.dev.jpg" alt="Skypack.dev"></p><h1><span id="67716756-9b76-49e8-a292-870208c6bffe">기본 구조 이해</span><a href="#67716756-9b76-49e8-a292-870208c6bffe" class="header-anchor"></a></h1><p>Snowpack의 기본 구조를 이해하기 위해서,<br>Vue.js 템플릿(<a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-vue" target="_blank" rel="noopener">@snowpack/app-template-vue</a>)을 최대한 간소화하고 핵심 내용만 정리했습니다.</p><p><img src="/images/screenshot/snowpack/snowpack-test-vue-directory-structure.jpg" alt="Snowpack vue example"></p><h3><span id="16f6d9a2-6440-4f1d-9dfe-b941f2840463">snowpack.config.js</span><a href="#16f6d9a2-6440-4f1d-9dfe-b941f2840463" class="header-anchor"></a></h3><ul><li><code>mount</code>: Snowpack 빌드에 사용할 ‘<em>현재 경로</em>‘와 빌드 결과로 연결할 ‘<em>빌드 경로</em>‘를 지정합니다.</li><li><code>plugins</code>: Snowpack에서 사용할 플러그인 목록을 지정합니다.</li></ul><blockquote><p>연결 옵션은 <a href="https://www.snowpack.dev/#config.mount" target="_blank" rel="noopener">CONFIG.MOUNT</a> 파트에서 확인할 수 있습니다.<br>기타 모든 옵션은 <a href="https://www.snowpack.dev/#config" target="_blank" rel="noopener">CONFIG</a> 파트에서 확인할 수 있습니다.</p></blockquote><pre><code class="plaintext">mount: {  현재_경로: 빌드_경로}</code></pre><p>현재 프로젝트 구조에서 <code>public/</code>(디렉토리)을 루트 경로로 사용하고,<br><code>src/</code>를 지정한(<code>/_dist_</code>) 경로로 사용하도록 지정합니다.</p><pre><code class="js">module.exports = {  mount: {     public: &#39;/&#39;,    src: &#39;/_dist_&#39;  },  plugins: [    &#39;@snowpack/plugin-vue&#39;  ]};</code></pre><h3><span id="ed1a8484-69c0-454f-b002-b7c088e4c979">package.json</span><a href="#ed1a8484-69c0-454f-b002-b7c088e4c979" class="header-anchor"></a></h3><p><code>snowpack dev</code> 명령으로 개발 서버를 준비하고,<br><code>snowpack build</code> 명령으로 제품을 빌드를 할 수 있습니다.<br>설치할 개발 의존성은 <strong>snowpack</strong>과 Vue 구성을 위한 <strong>@snowpack/plugin-vue</strong>입니다.<br>설치할 일반 의존성으론 실제 사용할 <strong>vue</strong>만 있으면 충분합니다.</p><pre><code class="json">{  &quot;scripts&quot;: {    &quot;dev&quot;: &quot;snowpack dev&quot;,    &quot;build&quot;: &quot;snowpack build&quot;  },  &quot;devDependencies&quot;: {    &quot;@snowpack/plugin-vue&quot;: &quot;^2.2.4&quot;,    &quot;snowpack&quot;: &quot;^2.15.1&quot;  },  &quot;dependencies&quot;: {    &quot;vue&quot;: &quot;^3.0.2&quot;  }}</code></pre><h3><span id="a5623cb5-6398-46ba-96e0-a1bdeb67dbcc">public/index.html</span><a href="#a5623cb5-6398-46ba-96e0-a1bdeb67dbcc" class="header-anchor"></a></h3><p><code>snowpack.config.js</code>에서 <code>src: &#39;/_dist_&#39;</code>을 통해 어느 경로에 빌드 결과가 저장되는지 지정했습니다.<br>이 Vue 프로젝트의 진입점은 <code>src/main.js</code>입니다.</p><pre><code class="html">&lt;body&gt;  &lt;script type=&quot;module&quot; src=&quot;/_dist_/main.js&quot;&gt;&lt;/script&gt;&lt;/body&gt;</code></pre><p>만약 진입점이 <code>src/index.js</code>인 경우, 경로를 다음과 같이 수정해야 합니다.</p><pre><code class="html">&lt;script type=&quot;module&quot; src=&quot;/_dist_/index.js&quot;&gt;&lt;/script&gt;</code></pre><h3><span id="f198d0c0-0000-4b25-9c58-26e0cc44425d">build/</span><a href="#f198d0c0-0000-4b25-9c58-26e0cc44425d" class="header-anchor"></a></h3><p>프로젝트를 다음과 같이 빌드하면, <code>build/</code> 디렉토리가 생성됩니다.</p><pre><code class="bash">$ npm run build## or$ snowpack build</code></pre><p><img src="/images/screenshot/snowpack/snowpack-test-vue-build-directory.jpg" alt="Snowpack vue example"></p><ul><li><code>__snowpack__</code>: Snowpack의 메타 데이터(HMR, ENV..)가 출력되는 디렉토리입니다. <code>buildOptions.metaDir</code> 옵션으로 이름을 수정할 수 있습니다.</li><li><code>_dist_</code>: <code>src/</code>의 파일이 빌드 후 출력되는 디렉토리입니다.</li><li><code>web_modules</code>: 사용할 웹 모듈의 디렉토리입니다. 이 프로젝트에서는 <strong>vue</strong>가 웹 모듈로 사용됩니다. <code>buildOptions.webModulesUrl</code> 옵션으로 이름을 수정할 수 있습니다.</li></ul><blockquote><p>빌드 옵션은 <a href="https://www.snowpack.dev/#config.buildoptions" target="_blank" rel="noopener">CONFIG.BUILDOPTIONS</a> 파트에서 확인할 수 있습니다.</p></blockquote><h1><span id="0a84ba8e-bdfc-49da-891f-af9b838396ba">구성 옵션</span><a href="#0a84ba8e-bdfc-49da-891f-af9b838396ba" class="header-anchor"></a></h1><table><thead><tr><th>옵션</th><th>설명</th><th>타입</th></tr></thead><tbody><tr><td>mount</td><td>빌드에 포함될 디렉토리와 연결될 URL을 지정</td><td><code>object</code></td></tr><tr><td>plugins</td><td>기능을 확장할 플러그인을 연결</td><td><code>string[] &vert; [string, object][]</code></td></tr><tr><td>alias</td><td>앱에서 사용할 경로 별칭 지정</td><td><code>object</code></td></tr><tr><td>devOptions</td><td>개발 서버의 작동 방식을 지정</td><td><code>object</code></td></tr><tr><td>buildOptions</td><td>최종 빌드를 처리하는 방법을 지정</td><td><code>object</code></td></tr><tr><td>installOptions</td><td>외부 모듈을 처리하는 방법을 지정</td><td><code>object</code></td></tr><tr><td>testOptions</td><td>테스트 환경의 작동 방식을 지정</td><td><code>object</code></td></tr><tr><td>proxy</td><td>개발 서버의 프록시 동작을 지정</td><td><code>object</code></td></tr><tr><td>exclude</td><td>지정된 파일을 빌드에서 제외</td><td><code>string[]</code></td></tr><tr><td>install</td><td>자동으로 감지되지 않는 설치할 모듈을 지정</td><td><code>string[]</code></td></tr><tr><td>extends</td><td>지정된 별도의 기본 구성 옵션을 상속해 병합</td><td><code>string</code></td></tr></tbody></table><h2><span id="0064e38b-0819-4764-9e07-a39a822a7dc1">devOptions</span><a href="#0064e38b-0819-4764-9e07-a39a822a7dc1" class="header-anchor"></a></h2><p><code>snowpack dev</code>를 통한 개발 서버의 작동 방식을 지정합니다.</p><div class="filename">snowpack.config.js</div><pre><code class="js">module.exports = {  devOptions: {    port: 8080, // 개발 서버를 실행할 포트 번호    fallback: &#39;index.html&#39;, // SPA인 경우 제공할 모든 사용자 경로    open: &#39;default&#39;, // 새 브라우저 탭에 개발 서버를 열기, &quot;default&quot; | &quot;none&quot; | &quot;BROWSER_NAME&quot;    output: &#39;dashboard&#39;, // 콘솔의 출력 모드를 지정, &quot;stream&quot; | &quot;dashbaord&quot;    hostname: &#39;localhost&#39;, // 브라우저가 열리는 호스트 이름    hmr: true, // Hot Module Replacement(HMR, 수정사항을 즉시 반영) 활성화    hmrErrorOverlay: true, // HMR 활성화시 자바스크립트 오류 표시 여부    secure: false, // HTTP2 활성화 상태에서 HTTPS 사용 여부    // out: &#39;build&#39; // Deprecated!   }}</code></pre><h2><span id="6299b5f7-738d-4019-9e7c-3c45cd0b51b4">buildOptions</span><a href="#6299b5f7-738d-4019-9e7c-3c45cd0b51b4" class="header-anchor"></a></h2><p><code>snowpack build</code>를 통해 최종 빌드를 처리하는 방법을 지정합니다.</p><div class="filename">snowpack.config.js</div><pre><code class="js">module.exports = {  buildOptions: {    out: &#39;build&#39;, // 최종 빌드를 출력하는 로컬 디렉토리 이름    baseUrl: &#39;/&#39;, // 제품 모드의 기본 URL 지정, 현재 앱이 하위 디렉토리로 배포되는 경우 유용    clean: false, // 빌드 전 기존 데이터 제거    metaDir: &#39;__snowpack__&#39;, // HMR 및 ENV 등의 정보를 출력할 디렉토리 이름    sourceMap: false, // 소스 맵 사용 여부    webModulesUrl: &#39;web_modules&#39; // 사용하는 웹 모듈이 저장될 디렉토리 이름  }}</code></pre><h2><span id="b2dc104f-942a-4f7d-a7d5-1fdec753f690">installOptions</span><a href="#b2dc104f-942a-4f7d-a7d5-1fdec753f690" class="header-anchor"></a></h2><p>설치하는 외부 모듈을 처리하는 방법을 지정합니다.</p><div class="filename">snowpack.config.js</div><pre><code class="js">module.exports = {  installOptions: {    dest: &#39;web_modules&#39;, // 사용하는 웹 모듈이 저장될 디렉토리 이름    sourceMap: false, // 설치된 모듈의 소스맵 사용    env: {}, // 환경 변수 지정    treeshake: false, // 의존성 모듈의 트리쉐이킹 여부(제품 모드만 동작)    installType: false, // 모듈 설치 시 타입 정의를 함께 설치(타입스크립트)    namedExports: [], // `Uncaught SyntaxError: The requested module &#39;/web_modules/XX.js&#39; does not provide an export named &#39;YY&#39;` 에러가 발생하는 경우, `namedExports: [&#39;XX&#39;]`    externalPackage: [], // 무시할 외부 모듈의 목록, E.g. &#39;fs&#39;    packageLookupFields: [], // ??    rollup: { // Snowpack이 내부적으로 사용하는 Rollup을 더욱 세부적으로 제어하기 위한 옵션      plugins: [], // Rollup 플러그인을 지정      dedupe: [], // 중복 번들을 방지하기 위한 외부 모듈 이름을 지정(rollup-plugin-node-resolve)      context: [] // 최상위 `this`를 지정, 최상위 변수를 참조하는 레거시 CJS 모듈의 오류 해결에 유용     },  }}</code></pre><h1><span id="348cb1b0-c286-4852-b606-d922f5dd01d9">템플릿</span><a href="#348cb1b0-c286-4852-b606-d922f5dd01d9" class="header-anchor"></a></h1><h2><span id="a5723640-a011-4575-9097-cd8c447d36f5">공식 템플릿</span><a href="#a5723640-a011-4575-9097-cd8c447d36f5" class="header-anchor"></a></h2><p>주요 프레임워크의 공식 템플릿을 지원합니다.<br>Snowpack을 이해하는 데 많은 도움이 됩니다.<br><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/cli#featured-community-templates" target="_blank" rel="noopener">더 많은 템플릿</a>을 확인하세요!</p><ul><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-blank" target="_blank" rel="noopener">@snowpack/app-template-blank</a></li><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-blank-typescript" target="_blank" rel="noopener">@snowpack/app-template-blank-typescript</a></li><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-react" target="_blank" rel="noopener">@snowpack/app-template-react</a></li><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-react-typescript" target="_blank" rel="noopener">@snowpack/app-template-react-typescript</a></li><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-svelte" target="_blank" rel="noopener">@snowpack/app-template-svelte</a></li><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-svelte-typescript" target="_blank" rel="noopener">@snowpack/app-template-svelte-typescript</a></li><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-vue" target="_blank" rel="noopener">@snowpack/app-template-vue</a></li><li><a href="https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/app-template-vue-typescript" target="_blank" rel="noopener">@snowpack/app-template-vue-typescript</a></li></ul><h2><span id="f5450b1e-ec5d-4926-8b8f-a525ee899aa9">Svelte template</span><a href="#f5450b1e-ec5d-4926-8b8f-a525ee899aa9" class="header-anchor"></a></h2><p><a href="https://github.com/ParkYoungWoong/svelte-snowpack-template" target="_blank" rel="noopener">Snowpack 기반의 Svelte 템플릿</a>을 만들었습니다.<br>별도의 구성 없이 바로 프로젝트를 시작할 수 있습니다.<br>템플릿에서 지원하는 내용은 크게 다음과 같습니다.</p><ul><li>Svelte</li><li>TypeScript</li><li>SCSS</li><li>Autoprefixer/PostCSS</li><li>Web test runner</li><li>Chai</li><li>Reset.css</li></ul><p>다음과 같이 설치합니다.</p><pre><code class="bash">## Install template$ npx degit ParkYoungWoong/svelte-snowpack-template DIR_NAME## Change directory$ cd DIR_NAME## Install dependencies$ npm i## Start dev server$ npm run dev</code></pre><p>타입스크립트를 사용하는 경우 다음과 같이 작성할 수 있습니다.</p><pre><code class="svelte">&lt;script lang=&quot;ts&quot;&gt;  let count: number = 0&lt;/script&gt;</code></pre><p>SCSS를 사용하는 경우 다음과 같이 작성할 수 있습니다.</p><pre><code class="svelte">&lt;style lang=&quot;scss&quot;&gt;  $color--primary: royalblue;  h1 {    color: $color--primary;  }&lt;/style&gt;</code></pre><h1><span id="84a0f2b2-1a29-4fc1-bf52-95ce79dd2688">참고자료</span><a href="#84a0f2b2-1a29-4fc1-bf52-95ce79dd2688" class="header-anchor"></a></h1><p><a href="https://www.snowpack.dev/" target="_blank" rel="noopener">https://www.snowpack.dev/</a></p>]]></content>
    
    <summary type="html">
    
      Snowpack은 번들러가 아닙니다! 더 빠른 웹 개발을 위한 최신의 프론트엔드 빌드 도구로, JavaScript의 ESM(ES Modules)을 활용해 기존의 Webpack, Rollup 그리고 Parcel 같은 무겁고 복잡한 번들러의 번들 소요 시간을 절약할 수 있습니다.
    
    </summary>
    
    
      <category term="webpack" scheme="https://heropy.blog/tags/webpack/"/>
    
      <category term="parcel" scheme="https://heropy.blog/tags/parcel/"/>
    
      <category term="snowpack" scheme="https://heropy.blog/tags/snowpack/"/>
    
      <category term="rollup" scheme="https://heropy.blog/tags/rollup/"/>
    
  </entry>
  
  <entry>
    <title>PixiJS와 Depth map으로 3D 이미지 만들기</title>
    <link href="https://heropy.blog/2020/09/27/generate-3d-photo-by-pixijs/"/>
    <id>https://heropy.blog/2020/09/27/generate-3d-photo-by-pixijs/</id>
    <published>2020-09-26T15:00:00.000Z</published>
    <updated>2020-10-30T06:01:57.944Z</updated>
    
    <content type="html"><![CDATA[<p>PixiJS 라이브러리와 Depth map(깊이 정보 이미지)을 사용해 3D 이미지를 만들어 봅니다.<br>이 포스트에서 사용된 이미지 출처는 각 이미지 하단에 표기했으며,<br>핵심 코드는 <a href="https://redstapler.co/" target="_blank" rel="noopener">Red Stapler</a>의 <a href="https://redstapler.co/3d-photo-from-image-javascript-tutorial/" target="_blank" rel="noopener">Create 3D Photo from Image JavaScript Tutorial</a>을 참고했습니다.<br>예제를 더 쉽게 적용할 수 있는 NPM 모듈(<a href="https://github.com/ParkYoungWoong/pixi-3d-photo" target="_blank" rel="noopener">pixi-3d-photo</a>)을 만들어 배포했으니 사용해 보세요 :D</p><pre><code class="bash">$ npm i pixi-3d-photo</code></pre><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/github.png" alt="pixi-3d-photo"></p><h1 id="PixiJS란"><a href="#PixiJS란" class="headerlink" title="PixiJS란?"></a>PixiJS란?</h1><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/pixijs-homepage.jpg" alt="PixiJS Homepage"></p><p><a href="https://www.pixijs.com/" target="_blank" rel="noopener">PixiJS</a>는 WebGL을 사용하는 고속 HTML5 2D 렌더링 라이브러리입니다.<br>Canvas 혹은 WebGL API를 Flash API와 유사하게 사용할 수 있습니다.</p><h2 id="WebGL"><a href="#WebGL" class="headerlink" title="WebGL"></a>WebGL</h2><p>WebGL은 별도 플러그인 없이도 자바스크립트를 사용해 브라우저에서 그래픽을 렌더링합니다.<br>쉽게 OpenGL의 웹 버전으로, 실제 OpenGL ES 2.0 기반 API를 사용합니다.<br>대부분의 최신 브라우저에서 지원하며, GPU 가속을 사용해 다양한 이미지 처리와 효과를 구현할 수 있습니다.<br>주의할 점은, 브라우저 옵션에서 GPU 가속을 여부를 확인해야 합니다.</p><p>크롬의 경우,<br>‘고급’ 설정에서 해당 옵션(<code>가능한 경우 하드웨어 가속 사용</code>)을 제어할 수 있습니다.</p><pre><code class="plaintext">chrome://settings</code></pre><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/hardware-acceleration.jpg" alt="Hardware Acceleration in Chrome"></p><p>Graphics Feature Status(그래픽 기능 상태)에서 WebGL 항목을 체크할 수 있습니다.</p><pre><code class="plaintext">chrome://gpu</code></pre><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/chrome-gpu.jpg" alt="GPU options in Chrome"></p><h1 id="Depth-map이란"><a href="#Depth-map이란" class="headerlink" title="Depth map이란?"></a>Depth map이란?</h1><p>‘Depth map’은 주어진 시점에서 물체 표현의 거리에 대한 정보를 가진 이미지를 말합니다.<br>일반적으로 회색 톤(Gray scale)으로 표현하며, 색이 밝을수록 더 가까운 거리를, 어두울수록 더 먼 거리를 의미합니다.</p><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/cubic.jpg" alt="Cubic"></p><div class="image-caption">Image credit: <a href="https://en.wikipedia.org/wiki/Depth_map" target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Depth_map</a></div><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/cubic.map.jpg" alt="Cubic Map"></p><div class="image-caption">Image credit: <a href="https://www.pvsm.ru/programmirovanie/309027" target="_blank" rel="noopener">https://www.pvsm.ru/programmirovanie/309027</a></div><h1 id="예제"><a href="#예제" class="headerlink" title="예제"></a>예제</h1><h2 id="피카츄"><a href="#피카츄" class="headerlink" title="피카츄"></a>피카츄</h2><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/sample.gif" alt="Sample" width="500"></p><p>PixiJS를 사용해서 3D 피카츄 이미지를 만들어 봅시다.<br>필요한 준비물은 피카츄 원본 이미지와 Depth map(이미지)입니다.<br>두 이미지의 크기 및 깊이 정보가 일치해야 제대로 동작할 수 있습니다.</p><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/pikachu.jpg" alt="Pikachu" width="260"></p><div class="image-caption">pikachu.jpg<br>Image credit: <a href="https://redstapler.co/3d-photo-from-image-javascript-tutorial/" target="_blank" rel="noopener">https://redstapler.co/3d-photo-from-image-javascript-tutorial/</a></div><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/pikachu.map.jpg" alt="Pikachu" width="260"></p><div class="image-caption">pikachu.map.jpg<br>Image credit: <a href="https://redstapler.co/3d-photo-from-image-javascript-tutorial/" target="_blank" rel="noopener">https://redstapler.co/3d-photo-from-image-javascript-tutorial/</a></div><p>코드는 다음과 같습니다.</p><pre><code class="directory">your_project├── pikachu.jpg├── pikachu.map.jpg└── index.html</code></pre><pre><code class="html">&lt;!-- index.html --&gt;&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js&quot;&gt;&lt;/script&gt;&lt;div id=&quot;pikachu&quot;&gt;&lt;/div&gt;&lt;script&gt;  // 원본 이미지 경로와 Depth map 경로.  let src = &#39;pikachu.jpg&#39;  let srcMap = &#39;pikachu.map.jpg&#39;  // 출력될 이미지 크기.  let width = 768  let height = 432  // HTML 요소.  const $target = document.querySelector(&#39;#pikachu&#39;);  $target.style.width = `${width}px`;  $target.style.height = `${height}px`;  // 출력될 이미지 크기와 동일하게 PixiJS 앱을 정의.  let app = new PIXI.Application({    width,    height  });  $target.appendChild(app.view);  // 화면에 렌더링 될 원본 이미지(Sprite 객체)를 정의.  let img = new PIXI.Sprite.from(src);  img.width = width;  img.height = height;  // 화면에 렌더링 될 Depth map(Sprite 객체)을 정의.  let depthMap = new PIXI.Sprite.from(srcMap);  depthMap.width = width;  depthMap.height = height;  // 지정된 Depth map(깊이 정보)을 사용해, 이미지의 위치 정보를 변화시킬 수 있는 필터를 등록.  let displacementFilter = new PIXI.filters.DisplacementFilter(depthMap);  // 앱 스테이지에 각 이미지를 등록.  app.stage.addChild(img);  app.stage.addChild(depthMap);  // 앱 스테이지에 필터 정보를 등록.  app.stage.filters = [displacementFilter];  // HTML 요소 내에서 마우스를 움직이면,  // 그 정보로 필터 정보를 수정해 원하는 효과를 구현합니다.  $target.onmousemove = function(e) {    displacementFilter.scale.x = (width / 2 - e.clientX) / 20;    displacementFilter.scale.y = (height / 2 - e.clientY) / 20;  };&lt;/script&gt;</code></pre><h2 id="자동차"><a href="#자동차" class="headerlink" title="자동차"></a>자동차</h2><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/car-sample.gif" alt="Sample" width="340"></p><p>필요한 준비물은 역시 원본 이미지와 Depth map입니다.<br>이번에는 제가 배포한 모듈로 좀 더 간단하게 만들어 보겠습니다.</p><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/car.jpg" alt="Car" width="200"></p><div class="image-caption">car.jpg<br>Image credit: <a href="https://www.oculus.com/blog/introducing-new-features-for-3d-photos-on-facebook/?locale=en_US" target="_blank" rel="noopener">https://www.oculus.com/blog/introducing-new-features-for-3d-photos-on-facebook/?locale=en_US</a></div><p><img src="/images/screenshot/generate-3d-photo-by-pixijs/car.map.jpg" alt="Car" width="200"></p><div class="image-caption">car.map.jpg<br>Image credit: <a href="https://www.oculus.com/blog/introducing-new-features-for-3d-photos-on-facebook/?locale=en_US" target="_blank" rel="noopener">https://www.oculus.com/blog/introducing-new-features-for-3d-photos-on-facebook/?locale=en_US</a></div><pre><code class="html">&lt;div id=&quot;car&quot;&gt;&lt;/div&gt;</code></pre><pre><code class="js">import { generate3dPhoto } from &#39;pixi-3d-photo&#39;generate3dPhoto({  el: &#39;#car&#39;,  src: &#39;car.jpg&#39;,  map: &#39;car.map.jpg&#39;,  scale: 1.3})</code></pre><p>간단한 프로젝트는 <a href="https://parceljs.org/" target="_blank" rel="noopener">Parcel bundler</a>가 정말 편리합니다!<br>자세한 사용법은 생략합니다.<br>(<a href="https://heropy.blog/2018/01/20/parcel-1-start/">Parcel - 쉽고 빠르고 강력한 웹앱 번들러</a>)</p><pre><code class="bash">$ parcel index.html</code></pre><h1 id="참고-자료-References"><a href="#참고-자료-References" class="headerlink" title="참고 자료(References)"></a>참고 자료(References)</h1><p><a href="https://redstapler.co/3d-photo-from-image-javascript-tutorial/" target="_blank" rel="noopener">https://redstapler.co/3d-photo-from-image-javascript-tutorial/</a><br><a href="https://www.oculus.com/blog/introducing-new-features-for-3d-photos-on-facebook/?locale=en_US" target="_blank" rel="noopener">https://www.oculus.com/blog/introducing-new-features-for-3d-photos-on-facebook/?locale=en_US</a><br><a href="http://pixijs.download/release/docs/index.html" target="_blank" rel="noopener">http://pixijs.download/release/docs/index.html</a></p>]]></content>
    
    <summary type="html">
    
      PixiJS는 WebGL을 사용하는 고속 HTML5 2D 렌더링 라이브러리입니다. Canvas Fallback을 지원합니다. PixiJS와 Depth map을 사용해 이미지에 생명력을 불어 넣을 수 있습니다.
    
    </summary>
    
    
      <category term="pixijs" scheme="https://heropy.blog/tags/pixijs/"/>
    
      <category term="3d photo" scheme="https://heropy.blog/tags/3d-photo/"/>
    
      <category term="WebGL" scheme="https://heropy.blog/tags/WebGL/"/>
    
      <category term="canvas" scheme="https://heropy.blog/tags/canvas/"/>
    
  </entry>
  
  <entry>
    <title>Postman에서 API 테스트 자동화</title>
    <link href="https://heropy.blog/2020/08/31/postman-api-testing/"/>
    <id>https://heropy.blog/2020/08/31/postman-api-testing/</id>
    <published>2020-08-31T14:59:00.000Z</published>
    <updated>2020-08-31T14:23:44.125Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.postman.com/" target="_blank" rel="noopener">Postman</a>에서 API 요청과 응답에 대한 테스트를 자동화할 수 있습니다.<br><a href="https://reqres.in/" target="_blank" rel="noopener">Reqres.in</a>에서 제공하는 로그인 API를 사용합니다.</p><div class="toc"><ul><li><a href="52827ef4-8555-4c25-91ca-051ceef566dc">Request</a></li><li><a href="bac45bd4-1235-4d88-aec8-86f2c94c708f">Collection</a></li><li><a href="bb058ba3-c7d4-4e18-9ce2-38d652410215">참고 자료(References)</a></li></ul></div><h1><span id="52827ef4-8555-4c25-91ca-051ceef566dc">Request</span><a href="#52827ef4-8555-4c25-91ca-051ceef566dc" class="header-anchor"></a></h1><p>Postman에 로그인하고 새로운 Request 탭을 엽니다.<br>다음과 같이 POST Method로 요청(Send)합니다.</p><blockquote><p>파라미터 이메일은 <code>eve.holt@reqres.in</code>여야 합니다.</p></blockquote><ul><li>Method: <code>POST</code></li><li>Request URL: <code>https://reqres.in/api/login</code></li><li>Content-Type: <code>application/json</code></li><li>Parameter: <code>{ &quot;email&quot;: &quot;eve.holt@reqres.in&quot;, &quot;password&quot;: &quot;1234&quot; }</code></li></ul><p><img src="/images/screenshot/postman-api-testing/postman1.jpg" alt="Postman"></p><p>위와 같이 응답 코드 <code>200</code>과 함께 사용자 토큰을 반환합니다.</p><pre><code class="json">{  &quot;token&quot;: &quot;QpwL5tke4Pnpja7X4&quot;}</code></pre><p>이번엔 이 요청을 테스트해 봅시다.<br>‘Tests’ 탭을 선택하고 우측 ‘SNIPPETS’ 목록에서 다음과 같이 선택합니다.</p><ul><li><code>Status code: Code is 200</code></li><li><code>Response body: JSON value check</code></li></ul><p>자바스크립트로 <a href="https://www.chaijs.com/api/bdd/" target="_blank" rel="noopener">ChaiJS BDD</a> 구문과 <code>pm.expect</code>를 사용할 수 있습니다.<br>선택 후 생성된 코드를 다음과 같이 수정합니다.</p><p><img src="/images/screenshot/postman-api-testing/postman2.jpg" alt="Postman"></p><p>응답 코드가 <code>200</code>인지, 반환된 값에 <code>token</code> 속성이 포함되어 있는지 확인합니다.</p><pre><code class="js">pm.test(&quot;Status code is 200&quot;, function () {    pm.response.to.have.status(200);});// pm.test(&quot;Your test name&quot;, function () {pm.test(&quot;Response must have the token property&quot;, function () {    var jsonData = pm.response.json();    // pm.expect(jsonData.value).to.eql(100);    pm.expect(jsonData).to.have.keys(&#39;token&#39;);});</code></pre><p>요청(Send) 후 하단의 ‘Test Results’ 탭을 확인하고,<br>다음과 같이 2개의 테스트가 통과한 것을 확인할 수 있습니다.</p><p><img src="/images/screenshot/postman-api-testing/postman3.jpg" alt="Postman"></p><p>이러한 방식으로 수십, 수백의 상황을 테스트할 수 있으며 Collection Runner를 사용해 이런 상황들을 자동화할 수 있습니다.</p><h1><span id="bac45bd4-1235-4d88-aec8-86f2c94c708f">Collection</span><a href="#bac45bd4-1235-4d88-aec8-86f2c94c708f" class="header-anchor"></a></h1><p>새로운 Collection을 생성합시다!</p><p><img src="/images/screenshot/postman-api-testing/collection1.jpg" alt="Postman"></p><p>모달에서 Collection의 이름과 설명을 명시하고 생성합니다.</p><p><img src="/images/screenshot/postman-api-testing/collection2.jpg" alt="Postman"></p><p>위에서 만들었던 Request를 저장합시다!</p><p><img src="/images/screenshot/postman-api-testing/collection3.jpg" alt="Postman"></p><p>모달에서 하단에서 Collection을 선택하고 저장합니다.</p><p><img src="/images/screenshot/postman-api-testing/collection4.jpg" alt="Postman"></p><p>잠시후 왼쪽 메뉴에서 생성된 Collection(<code>Resres.in test</code>)과 Request 목록을 확인할 수 있습니다.</p><p><img src="/images/screenshot/postman-api-testing/collection5.jpg" alt="Postman"></p><p>Collection이 가진 모든 Request를 테스트합시다.<br>다음 이미지와 같이 순서대로 선택합니다.</p><p><img src="/images/screenshot/postman-api-testing/collection6.jpg" alt="Postman"></p><p>원하는 Request를 선택하고 테스트를 시작합니다.</p><p><img src="/images/screenshot/postman-api-testing/collection7.jpg" alt="Postman"></p><p>전체 테스트 결과를 확인할 수 있습니다!</p><p><img src="/images/screenshot/postman-api-testing/collection8.jpg" alt="Postman"></p><h1><span id="bb058ba3-c7d4-4e18-9ce2-38d652410215">참고 자료(References)</span><a href="#bb058ba3-c7d4-4e18-9ce2-38d652410215" class="header-anchor"></a></h1><p><a href="https://learning.postman.com/docs/writing-scripts/test-scripts/" target="_blank" rel="noopener">https://learning.postman.com/docs/writing-scripts/test-scripts/</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;a href=&quot;https://www.postman.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Postman&lt;/a&gt;에서 API 요청과 응답에 대한 테스트를 자동화할 수 있습니다.&lt;br&gt;&lt;a href=&quot;https://reqr
      
    
    </summary>
    
    
      <category term="postman" scheme="https://heropy.blog/tags/postman/"/>
    
      <category term="reqres.in" scheme="https://heropy.blog/tags/reqres-in/"/>
    
  </entry>
  
  <entry>
    <title>Jest와 Vue Test Utils(VTU)로 Vue 컴포넌트 단위(Unit) 테스트</title>
    <link href="https://heropy.blog/2020/05/20/vue-test-with-jest/"/>
    <id>https://heropy.blog/2020/05/20/vue-test-with-jest/</id>
    <published>2020-05-19T15:00:00.000Z</published>
    <updated>2020-05-27T01:28:46.729Z</updated>
    
    <content type="html"><![CDATA[<div class="toc"><ul><li><a href="6988760b-3efb-474b-bfea-f7d0c6917828">개요</a></li><li><a href="a3fc87f9-e227-46da-99b4-d8cbb20bbbb4">단위 테스트</a><ul><li><a href="e77dc318-1345-4171-9445-43ab1d327c4e">테스트 이해하기</a></li></ul></li><li><a href="e60ea5c3-4674-4d95-b03d-c835eff5c6d1">환경 설정</a><ul><li><a href="696ac970-a478-459f-91d2-3aa18fb74d56">Vue CLI로 빠른 환경 설정</a></li><li><a href="8ab26af4-e5d7-46b0-b3ca-fd10dd57dc06">별도 설치 및 설정</a><ul><li><a href="f5b1090d-c57b-496e-b1aa-230711d6cb08">필수 모듈 설치</a></li><li><a href="031d75fb-1df0-4937-893d-1854dff5cf34">Jest 구성 옵션</a></li><li><a href="1515b32f-a768-4458-a39c-988181770fdb">Babel 구성 옵션</a></li><li><a href="59f30cb5-c9a7-4f42-aaed-43fadc2d1c5d">테스트 스크립트 추가</a></li></ul></li><li><a href="a76cbe83-873a-468f-821c-1b6263f9d092">개선 사항</a><ul><li><a href="13f94727-8d47-4681-b352-2730628dd79e">Vuetify</a></li><li><a href="b4f640e1-cb9f-4e23-a9b8-1584d172b886">Watchman</a></li><li><a href="d6f21c27-f8d9-4559-a293-8972f594762b">no-undef 에러</a></li><li><a href="7e6877f7-71a2-499f-9590-e4bd55c61132">Unresolved~ 경고</a></li><li><a href="72b2ecb0-9f05-45b9-ba29-b46e1fc90919">regeneratorRuntime~ 에러</a></li><li><a href="5c5d916f-d6bd-40cd-97b1-62434bcc2015">jsdom 업그레이드</a></li></ul></li><li><a href="b73c0504-24ac-4146-8efb-d9de86c1791f">Jest 적용 범위(coverage)</a><ul><li><a href="d4e20fa3-b5ee-4fb5-a22b-1ad45d81ce6c">collectCoverage</a></li><li><a href="91362093-beae-46aa-a476-518e6fb5d4ed">coverageReporters</a></li><li><a href="bc6b23b7-fd68-47da-bba3-8f2cd564aa2b">coverageThreshold</a></li></ul></li><li><a href="251b5fe1-22e3-40fc-b4d7-c63f742cabc3">첫 번째 컴포넌트 테스트</a></li></ul></li><li><a href="d13491f0-7d73-4e7d-a9a0-a40683122239">Jest</a><ul><li><a href="c649243d-e763-4a4d-b4d9-8bca8ab5f308">전역 멤버(Globals)</a><ul><li><a href="4d41e387-ed4a-47e5-a79e-c69997a59de3">Hooks</a></li><li><a href="e006bfbb-706d-46ae-be79-616b92b4ad03">중첩(Nesting)</a></li><li><a href="de636945-7e1b-4649-98ac-4418c33cd3ef">only, skip</a></li></ul></li><li><a href="d811193d-14fe-469c-8e26-41e9d1b8e4eb">Matchers</a><ul><li><a href="601886ae-62df-4dc9-b616-ead4158614d2">toBe</a></li><li><a href="6dcf51d2-2941-4f4c-b525-49e88b0450e8">toEqual과 toStrictEqual</a></li><li><a href="f281320a-5edb-4585-9ca4-ed02a597da1f">toBeCloseTo</a></li><li><a href="28da73a9-cd6f-48ec-b90d-0d6c17ee4e69">toContain과 toContainEqual</a></li><li><a href="62a706f9-600b-4e28-b496-2e6c8193fbb3">toThrow</a></li><li><a href="f7446107-6a23-4ce0-939d-74baed4eacf3">toHaveBeenCalled</a></li><li><a href="c72c3804-4d4e-4c82-a8de-fc31ab45d4d5">toHaveReturned</a></li><li><a href="a46bed07-329d-45be-a3a7-f64b353a0091">toHaveReturnedWith</a></li><li><a href="2f469605-b71f-4c66-bae9-f032c5e8789d">toHaveProperty</a></li></ul></li><li><a href="9744d99e-309e-4c91-b4b8-50d6c66f2c70">비동기 테스트 패턴</a></li></ul></li><li><a href="0f2e0880-a5b4-4a81-bf16-c83c178e16cd">Vue Test Utils(VTU)</a><ul><li><a href="13e71408-5d55-49aa-b459-87e9512a671d">Mountings</a><ul><li><a href="f4c54da1-7a5b-43ff-822c-a704b07858fd">mount vs shallowMount</a></li><li><a href="75585722-755e-4cba-9451-902043ea51fd">Mounting options</a></li><li><a href="023c7748-b183-4c36-97bf-ee16604d7ca7">createLocalVue</a></li></ul></li><li><a href="725decea-a2ba-4f77-8671-55006a885467">Wrappers</a></li></ul></li><li><a href="707645f5-c4c9-42c9-b06c-9903ca825fbc">테스트 더블(Test double)</a></li><li><a href="3f7566ae-3844-437f-93dd-863e3aa5c7fb">모의 함수 만들기(Mocking)</a><ul><li><a href="b16182a1-7c4f-4d77-b930-5c61942084ee">jest.fn()</a></li><li><a href="3584efac-0062-4f41-9078-7f680b4551b5">jest.spyOn()</a></li><li><a href="5d9bbecf-0dd6-495e-8643-ec4c6973da88">Mock functions</a><ul><li><a href="0596326a-cd11-4f70-a17a-0138b3cc2d33">mockClear</a></li><li><a href="4359d2e0-bcf2-4f67-b81f-cbc24d9dfb30">mockReset</a></li><li><a href="dbee715c-56ad-4958-a771-dc556142ea9d">mockRestore</a></li></ul></li></ul></li><li><a href="c26d0d84-5c26-4995-b153-a6cacdd6dcd8">참고자료</a></li></ul></div><h1><span id="6988760b-3efb-474b-bfea-f7d0c6917828">개요</span><a href="#6988760b-3efb-474b-bfea-f7d0c6917828" class="header-anchor"></a></h1><p><a href="https://vue-test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils(VTU)</a>는 Vue.js 환경에서 단위 테스트를 하기 위한 공식(Official) 라이브러리입니다.<br><a href="https://jestjs.io/docs/en/getting-started" target="_blank" rel="noopener">Jest</a>는 페이스북에서 만든 테스트 프레임워크로 Vue Test Utils에서 권장하는 테스트 러너입니다.<br>두 가지 오픈 소스를 이용해 Vue 애플리케이션의 테스트를 진행합니다.</p><blockquote><p>이 글에서는 Jest 24버전 이상을 기준으로 설명합니다.<br>Jest 23버전 이하를 사용하는 경우 Babel 등의 설치 및 설정이 다릅니다.</p></blockquote><p><img src="/images/screenshot/vue-test-with-jest/npm-trends-com.jpg" alt="NPM Trends"></p><div class="image-caption">npmtrends.com</div><h1><span id="a3fc87f9-e227-46da-99b4-d8cbb20bbbb4">단위 테스트</span><a href="#a3fc87f9-e227-46da-99b4-d8cbb20bbbb4" class="header-anchor"></a></h1><p>단위(Unit) 테스트란 상태, 메소드, 컴포넌트 등의 정의된 프로그램 최소 단위들이 독립적으로 정상 동작하는지 확인하는 것을 말합니다.<br>이를 통해 프로그램 전체의 신뢰도를 향상하고 코드 리팩토링(Code refactoring)의 부담을 줄일 수 있습니다.</p><blockquote><p>이 글은 <a href="https://ko.wikipedia.org/wiki/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C" target="_blank" rel="noopener">TDD(Test Driven Development)</a>를 기준으로 하지 않습니다.</p></blockquote><h2><span id="e77dc318-1345-4171-9445-43ab1d327c4e">테스트 이해하기</span><a href="#e77dc318-1345-4171-9445-43ab1d327c4e" class="header-anchor"></a></h2><p>간단한 예제를 통해서 테스트에 대해 이해해 봅시다.<br>다음과 같이 하나의 인수에 <code>1</code>을 더한 후 반환하는 <code>addOne</code> 함수를 가진 CommonJS 스타일의 <code>calc.js</code> 모듈이 있습니다.</p><pre><code class="js">// calc.jsexports.addOne = function (a) {  return a + 1}</code></pre><p>이 <code>addOne</code>은 숫자 데이터를 인수로 ‘원하는 대로 정상 동작’하길 기대합니다.<br>(여기서 기대하는 정상 동작은 숫자 연산의 결과 반환을 의미하며 매우 주관적인 기준입니다)<br>하지만 다음과 같이 문자 데이터를 인수로 하게 되면 기대하지 않은 결과를 반환합니다.</p><pre><code class="js">// main.jsconst { addOne } = require(&#39;./calc.js&#39;)console.log(  addOne(1), // 2  addOne(&#39;1&#39;) // &#39;11&#39;)</code></pre><p>매번 콘솔 출력을 통해 기댓값을 하나씩 수동 확인하지 않고, 테스트를 통해서 언제든지 다시 검증할 수 있는 상태를 만들어 봅시다.<br>다음 테스트는 Jest를 기준으로 작성했습니다.<br>테스트 설정 및 동작에 관한 내용은 생략합니다.</p><pre><code class="js">// calc.test.js// 테스트 대상을 테스트 환경으로 가져옵니다.const { addOne } = require(&#39;./calc.js&#39;)// Test 1test(&#39;인수가 숫자인 경우&#39;, () =&gt; {  // expect()의 인수 결과가 .toBe()의 인수 값이 되길 기대합니다.  expect(addOne(1)).toBe(2)  expect(addOne(7)).toBe(8)})// Test 2test(&#39;인수가 문자인 경우&#39;, () =&gt; {  expect(addOne(&#39;1&#39;)).toBe(2)  expect(addOne(&#39;7&#39;)).toBe(8)})</code></pre><p>테스트를 동작시키면 다음과 같이 <code>인수가 문자일 경우</code>에서 테스트가 실패합니다.</p><p><img src="/images/screenshot/vue-test-with-jest/unit-test-basic-failed.jpg" alt="Test failed"></p><p>위 테스트를 성공시키기 위해서 <code>addOne</code> 함수를 수정(리팩토링)해 봅시다.<br>다음과 같이 <code>parseFloat</code>을 사용해 문자 데이터인 경우 숫자 데이터로 변환되도록 수정합니다.</p><pre><code class="js">exports.addOne = function (a) {  return parseFloat(a) + 1}</code></pre><p>다시 테스트를 실행하면 다음과 같이 테스트가 통과합니다.</p><p>이런 과정을 통해서 우리가 작성한 코드의 신뢰도를 향상할 수 있고,<br>새로운 로직을 추가하거나 수정할 때도 테스트 통과를 기준으로 문제 발생의 부담이 줄일 수 있습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/unit-test_basic-passed.jpg" alt="Test passed"></p><h1><span id="e60ea5c3-4674-4d95-b03d-c835eff5c6d1">환경 설정</span><a href="#e60ea5c3-4674-4d95-b03d-c835eff5c6d1" class="header-anchor"></a></h1><p>Jest CLI를 사용하기 위해서 전역으로 설치할 수 있습니다.</p><pre><code class="bash">$ npm i -g jest</code></pre><h2><span id="696ac970-a478-459f-91d2-3aa18fb74d56">Vue CLI로 빠른 환경 설정</span><a href="#696ac970-a478-459f-91d2-3aa18fb74d56" class="header-anchor"></a></h2><p><a href="https://cli.vuejs.org/guide/installation.html" target="_blank" rel="noopener">Vue CLI</a>를 사용하면 프로젝트 초기화 과정에서 빠르게 Jest와 Vue Test Utils를 설치 및 설정할 수 있어 편리합니다.</p><pre><code class="bash">$ npm i -g @vue/cli$ vue create YOUR_PROJECT_NAME$ cd YOUR_PROJECT_NAME</code></pre><blockquote><p>Vue CLI 3버전 이상에서는 비슷한 과정으로 설치할 수 있습니다.<br>[Space] 키로 선택/해제하고, [Enter] 키로 다음 질문으로 넘어갑니다.</p></blockquote><p><img src="/images/screenshot/vue-test-with-jest/vue-cli-install-options.jpg" alt="Vue CLI install options"></p><div class="image-caption">Unit Testing과 Jest 설정에 주의하세요!</div><p><img src="/images/screenshot/vue-test-with-jest/vue-cli-install-options-unit-testing.jpg" alt="Vue CLI install options unit testing"></p><div class="image-caption">Check the features needed for your project - Unit Testing 선택</div><p>Vue CLI로 설치하면, 이미 준비된 테스트 샘플(E.g. <code>tests/unit/example.spec.js</code>)이 있어서 바로 테스트할 수 있습니다.<br>(필요치 않으면 삭제하세요)</p><p><img src="/images/screenshot/vue-test-with-jest/vue-cli-scripts.jpg" alt="Vue CLI scripts in package.json"></p><div class="image-caption">package.json</div><pre><code class="bash">$ npm run test:unit</code></pre><p>이하 다른 예제들을 위해서 다음과 같이 스크립트를 하나 추가하겠습니다.<br>(일부 환경에서 다음 스크립트가 동작하지 않는 경우 Jest의 전역 설치를 권장합니다)</p><pre><code class="json">{  &quot;scripts&quot;: {    // ...    &quot;test&quot;: &quot;jest&quot;  }}</code></pre><p><code>run</code> 키워드 없이 좀 더 간단하게 테스트를 시작할 수 있습니다.</p><pre><code class="bash">$ npm t</code></pre><p>혹은 지정할 별도의 <a href="https://jestjs.io/docs/en/cli" target="_blank" rel="noopener">Jest CLI 옵션</a>이 없다면, 바로 <code>jest</code> 키워드로 실행할 수 있습니다.</p><pre><code class="bash">$ jest</code></pre><h2><span id="8ab26af4-e5d7-46b0-b3ca-fd10dd57dc06">별도 설치 및 설정</span><a href="#8ab26af4-e5d7-46b0-b3ca-fd10dd57dc06" class="header-anchor"></a></h2><p>이번엔 별도의 Vue 프로젝트에 Jest와 Vue Test Utils의 설치 과정을 소개합니다.<br>주변 설정이 좀 더 포함되지만 어렵지 않으니 하나씩 살펴봅시다.</p><h3><span id="f5b1090d-c57b-496e-b1aa-230711d6cb08">필수 모듈 설치</span><a href="#f5b1090d-c57b-496e-b1aa-230711d6cb08" class="header-anchor"></a></h3><p>테스트를 위한 다음의 여러 필수 모듈을 설치합니다.</p><pre><code class="bash">$ npm i -D jest @vue/test-utils vue-jest jest-serializer-vue babel-jest babel-core@bridge</code></pre><table><thead><tr><th>모듈</th><th>설명</th></tr></thead><tbody><tr><td>vue-jest</td><td>Vue 파일을 Jest가 실행할 수 있는 자바스크립트로 컴파일합니다.</td></tr><tr><td>jest-serializer-vue</td><td>저장된 Jest Snapshot을 VueJS에 맞게 개선하기 위해 사용합니다.</td></tr><tr><td>babel-jest</td><td>JS/JSX 파일을 Jest가 실행할 수 있는 자바스크립트로 컴파일합니다.</td></tr><tr><td>babel-core@bridge</td><td>Babel 6버전과의 호환을 위해 설치합니다. <a href="https://github.com/facebook/jest/issues/6913#issuecomment-417637086" target="_blank" rel="noopener">관련 이슈</a>가 있습니다.</td></tr></tbody></table><p>비교적 최신 <a href="https://nuxtjs.org/" target="_blank" rel="noopener">NuxtJS</a>나 Vue CLI를 사용한다면 이미 내부에 <code>@babel/core</code>와 <code>@babel/preset-env</code>가 포함되어 있습니다.<br>자신의 프로젝트를 확인하여 다음 모듈의 추가 설치 여부를 결정하세요.</p><table><thead><tr><th>모듈</th><th>설명</th></tr></thead><tbody><tr><td>@babel/core</td><td>Babel 7버전입니다.</td></tr><tr><td>@babel/preset-env</td><td>Babel의 지원 스펙을 지정합니다.</td></tr></tbody></table><pre><code class="bash">$ npm i -D @babel/core @babel/preset-env</code></pre><h3><span id="031d75fb-1df0-4937-893d-1854dff5cf34">Jest 구성 옵션</span><a href="#031d75fb-1df0-4937-893d-1854dff5cf34" class="header-anchor"></a></h3><p><code>jest.config.js</code> 파일에 Jest 구성 옵션을 설정할 수 있습니다.<br>다음 구성 옵션은 기본적인 예시입니다.<br>코드에 간단한 설명을 첨부하니 참고하세요.<br>더 많은 Jest 구성 옵션은 <a href="https://jestjs.io/docs/en/configuration" target="_blank" rel="noopener">여기</a>를 참고하시고, 기본값(Default)을 꼭 체크하세요!</p><pre><code class="js">// jest.config.jsmodule.exports = {  // 파일 확장자를 지정하지 않은 경우, Jest가 검색할 확장자 목록입니다.  // 일반적으로 많이 사용되는 모듈의 확장자를 지정합니다.  moduleFileExtensions: [    &#39;js&#39;,    &#39;jsx&#39;,    &#39;json&#39;,    &#39;vue&#39;  ],  // `@`나 `~` 같은 경로 별칭을 매핑합니다.  // E.g. `import HelloWorld from &#39;~/components/HelloWorld.vue&#39;;`  // `&lt;rootDir&gt;` 토큰을 사용해 루트 경로를 참조할 수 있습니다.  // TODO: 프로젝트에 맞는 경로로 수정하세요!  moduleNameMapper: {    &#39;^~/(.*)$&#39;: &#39;&lt;rootDir&gt;/src/$1&#39;,    &#39;^@/(.*)$&#39;: &#39;&lt;rootDir&gt;/src/$1&#39;  },  // 일치하는 경로에서는 모듈을 가져오지 않습니다.  // `&lt;rootDir&gt;` 토큰을 사용해 루트 경로를 참조할 수 있습니다.  // TODO: 프로젝트에 맞는 경로로 수정하세요!  modulePathIgnorePatterns: [    &#39;&lt;rootDir&gt;/node_modules&#39;,    &#39;&lt;rootDir&gt;/build&#39;,    &#39;&lt;rootDir&gt;/dist&#39;  ],  // 정규식과 일치하는 파일의 변환 모듈을 지정합니다.  transform: {    &#39;^.+\\.vue$&#39;: &#39;vue-jest&#39;,    &#39;^.+\\.jsx?$&#39;: &#39;babel-jest&#39;  },  // Jest Snapshot 테스트에 필요한 모듈을 지정합니다.  snapshotSerializers: [    &#39;jest-serializer-vue&#39;  ]}</code></pre><p>혹은 별도 파일이 아닌 <code>package.json</code>에 Jest 구성 옵션을 정리할 수 있습니다.</p><pre><code class="json">// package.json{  &quot;jest&quot;: {    &quot;moduleFileExtensions&quot;: [      // ...    ],    // ...  }}</code></pre><h3><span id="1515b32f-a768-4458-a39c-988181770fdb">Babel 구성 옵션</span><a href="#1515b32f-a768-4458-a39c-988181770fdb" class="header-anchor"></a></h3><p><code>.babelrc</code> 혹은 <code>babel.config.js</code> 파일에 다음과 같이 설정합니다.</p><blockquote><p>Babel의 이상적인 구성은 프로젝트에 따라 다를 수 있습니다.</p></blockquote><pre><code class="js">// babel.config.jsmodule.exports = {  presets: [&#39;@babel/preset-env&#39;]}</code></pre><p>테스트 환경에서 Jest는 <code>process.env.NODE_ENV</code>가 비어있는 경우 자동으로 <code>test</code>를 지정합니다.<br>따라서 <a href="https://babeljs.io/docs/en/options#env" target="_blank" rel="noopener"><code>env</code> 병합 옵션</a>을 통해 테스트 환경에서만 동작할 Babel 구성을 할 수 있습니다.</p><blockquote><p>다음 내용을 테스트하고 싶다면, <a href="https://jestjs.io/docs/en/cli#--cache" target="_blank" rel="noopener"><code>--no-cache</code> Jest CLI 옵션</a>을 사용하세요.</p></blockquote><pre><code class="js">module.exports = {  presets: [&#39;@babel/preset-env&#39;],  env: {    test: {      presets: [[        &#39;@babel/preset-env&#39;, {          debug: true        }      ]]    }  }}</code></pre><h3><span id="59f30cb5-c9a7-4f42-aaed-43fadc2d1c5d">테스트 스크립트 추가</span><a href="#59f30cb5-c9a7-4f42-aaed-43fadc2d1c5d" class="header-anchor"></a></h3><p>이제 마지막으로 <code>package.json</code>에 다음과 같이 테스트 스크립트를 추가합니다.<br><code>run</code> 키워드 없이, <code>npm test</code> 및 <code>npm t</code>를 통해 동작시킬 수 있습니다.</p><pre><code class="json">{  &quot;scripts&quot;: {    &quot;test&quot;: &quot;jest&quot;  }}</code></pre><p>혹은 <a href="https://jestjs.io/docs/en/cli#--watch" target="_blank" rel="noopener"><code>--watch</code> Jest CLI 옵션</a>을 사용할 수 있습니다.<br>이는 파일이 변경되었는지 확인하고 변경된 파일과 관련된 테스트만 다시 실행할 수 있어 편리합니다.</p><pre><code class="json">{  &quot;scripts&quot;: {    &quot;test&quot;: &quot;jest --watch&quot;  }}</code></pre><pre><code class="bash">$ npm test# or$ npm t# or$ jest --watch</code></pre><h2><span id="a76cbe83-873a-468f-821c-1b6263f9d092">개선 사항</span><a href="#a76cbe83-873a-468f-821c-1b6263f9d092" class="header-anchor"></a></h2><h3><span id="13f94727-8d47-4681-b352-2730628dd79e">Vuetify</span><a href="#13f94727-8d47-4681-b352-2730628dd79e" class="header-anchor"></a></h3><p><a href="https://vuetifyjs.com/ko/" target="_blank" rel="noopener">Vuetify</a> UI 라이브러리를 사용하는 경우, 테스트 통과 여부와 상관없이 다음과 같은 에러가 발생할 수 있습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/use-vuetify-error.jpg" alt="When using Vuetify in tests"></p><p><a href="https://github.com/vuetifyjs/vuetify/issues/4964" target="_blank" rel="noopener">관련 이슈</a>는 아직 오픈되어 있으니 지속적인 확인을 권장하며,<br><a href="https://jestjs.io/docs/en/configuration#setupfilesafterenv-array" target="_blank" rel="noopener"><code>setupFilesAfterEnv</code> Jest Config 옵션</a>에 <code>jest.setup.js</code> 파일 경로를 지정해 간단하게 해결할 수 있습니다.</p><pre><code class="js">// jest.config.jsmodule.exports = {  // ...  // 각 테스트 파일이 실행되기 전,  // 테스트 프레임워크를 구성 또는 설정하기 위한 실행 코드의  // 모듈 경로 목록을 지정합니다.  setupFilesAfterEnv: [    &#39;./jest.setup.js&#39;  ]}</code></pre><pre><code class="js">// jest.setup.jsimport Vue from &#39;vue&#39;import Vuetify from &#39;vuetify&#39;Vue.use(Vuetify)</code></pre><p>추가로, Vuetify의 <code>&lt;v-dialog&gt;</code>와 같이 기본적으로 <code>&lt;v-app&gt;</code> 루트 컴포넌트에 연결되는 컴포넌트를 사용하는 경우,<br>테스트에서는 루트 컴포넌트를 찾을 수 없기 때문에 다음과 같은 경고 메시지를 볼 수 있습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/use-vuetify-warning.jpg" alt="When using Vuetify in tests"></p><p>따라서 위에서 살펴본 <code>jest.setup.js</code> 파일에 다음과 같이 루트 컴포넌트의 렌더링 결과를 추가해 줍니다.</p><pre><code class="js">// jest.setup.jsimport Vue from &#39;vue&#39;import Vuetify from &#39;vuetify&#39;Vue.use(Vuetify)// &lt;v-app&gt; 루트 컴포넌트 그리고 테스트 컴포넌트로 대체될 요소(&lt;div&gt;) 생성const app = &#39;&lt;div id=&quot;app&quot; data-app=&quot;true&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&#39;document.body.innerHTML += app</code></pre><p>그리고 테스트에서 컴포넌트를 연결(Mounting)할 때 <a href="https://vue-test-utils.vuejs.org/api/options.html#attachto" target="_blank" rel="noopener">attachTo 옵션</a>을 통해 테스트 컴포넌트로 대체될 요소를 선택자(Selector)로 지정합니다.<br>루트 컴포넌트 구조와 해당 선택자는 프로젝트에 맞게 지정하되,<br>선택자의 자식 요소로 삽입되는 것이 아닌 ‘대체됨’에 주의합시다.<br>다음은 <code>attachTo</code> 옵션을 사용하는 예시 코드입니다.</p><pre><code class="js">import { mount, createLocalVue } from &#39;@vue/test-utils&#39;import Vuetify from &#39;vuetify&#39;import MyComponent from &#39;../MyComponent&#39;const localVue = createLocalVue()localVue.use(Vuetify)describe(&#39;MyComponent&#39;, () =&gt; {  let wrapper  beforeEach(() =&gt; {    wrapper = mount(MyComponent, {      localVue,      attachTo: &#39;#app &gt; div&#39;,      vuetify: new Vuetify()    })  })})</code></pre><h3><span id="b4f640e1-cb9f-4e23-a9b8-1584d172b886">Watchman</span><a href="#b4f640e1-cb9f-4e23-a9b8-1584d172b886" class="header-anchor"></a></h3><p><a href="https://facebook.github.io/watchman/" target="_blank" rel="noopener">Watchman</a>은 페이스북에서 만든 파일 변경을 감시하는 서비스입니다.<br>Jest의 <code>--watch</code> 혹은 <code>--watchAll</code> CLI 옵션과 관련해 문제가 발생하는 경우 설치합니다.</p><p>설치에 대한 내용은 <a href="https://facebook.github.io/watchman/docs/install" target="_blank" rel="noopener">Watchman Installation</a>을 참고하세요.<br><a href="https://brew.sh/index_ko" target="_blank" rel="noopener">Homebrew</a>를 사용한다면 다음과 같이 간단하게 설치할 수 있습니다.</p><pre><code class="bash">$ brew update$ brew install watchman</code></pre><h3><span id="d6f21c27-f8d9-4559-a293-8972f594762b">no-undef 에러</span><a href="#d6f21c27-f8d9-4559-a293-8972f594762b" class="header-anchor"></a></h3><p>테스트 코드에서 Jest의 <code>describe</code>나 <code>test</code> 같은 전역(Global) 메소드나 오브젝트를 사용하면 ESLint에서 에러(<a href="https://eslint.org/docs/rules/no-undef" target="_blank" rel="noopener">no-undef</a>)가 발생할 수 있습니다.<br>ESLint 구성 옵션에서 <code>env.jest = true</code>로 설정하면 해결할 수 있습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/eslint-error-no-undef.jpg" alt="ESLint error no-undef"></p><div class="image-caption">ESLint ‘no-undef’ 에러</div><p><img src="/images/screenshot/vue-test-with-jest/eslint-error-no-undef-resolved.jpg" alt="ESLint error no-undef"></p><div class="image-caption">.eslintrc.js 파일</div><h3><span id="7e6877f7-71a2-499f-9590-e4bd55c61132">Unresolved~ 경고</span><a href="#7e6877f7-71a2-499f-9590-e4bd55c61132" class="header-anchor"></a></h3><p>만약 Jest API의 사전 정의를 활성화해야 하는 경우, IDE 옵션을 설정하지 않아도 Jest의 타입 선언(<a href="https://github.com/DefinitelyTyped/DefinitelyTyped" target="_blank" rel="noopener">DefinitelyTyped</a>)을 설치하면 간단하게 해결할 수 있습니다.</p><pre><code class="bash">$ npm i -D @types/jest</code></pre><p><img src="/images/screenshot/vue-test-with-jest/unresolved-function-or-method.jpg" alt="Unresolved function or method"></p><div class="image-caption">@types/jest 설치 전</div><p><img src="/images/screenshot/vue-test-with-jest/unresolved-function-or-method-resolved.jpg" alt="Unresolved function or method"></p><div class="image-caption">@types/jest 설치 후</div><h3><span id="72b2ecb0-9f05-45b9-ba29-b46e1fc90919">regeneratorRuntime~ 에러</span><a href="#72b2ecb0-9f05-45b9-ba29-b46e1fc90919" class="header-anchor"></a></h3><p>테스트에서 비동기 코드를 작성할 때 다음과 같은 에러가 발생할 수 있습니다.<br>이 에러는 다양한 이유가 있지만, 많은 경우 간단하게 <a href="https://babeljs.io/docs/en/babel-polyfill" target="_blank" rel="noopener">@babel/polyfill</a>을 포함하는 것으로 해결할 수 있습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/regeneratorRuntime-error.jpg" alt="regeneratorRuntime error"></p><pre><code class="bash">$ npm i -D @babel/polyfill</code></pre><p>테스트에 직접 포함하거나 <code>jest.setup.js</code> 파일에 포함하면 됩니다.<br><code>jest.setup.js</code> 설정은 ‘’개선 사항 &gt; Vuetify’ 파트를 참고하세요.</p><pre><code class="js">import &#39;@babel/polyfill&#39;</code></pre><h3><span id="5c5d916f-d6bd-40cd-97b1-62434bcc2015">jsdom 업그레이드</span><a href="#5c5d916f-d6bd-40cd-97b1-62434bcc2015" class="header-anchor"></a></h3><p>Jest <code>25.1.0</code>버전부터 내부에서 사용하는 <a href="https://github.com/jsdom/jsdom" target="_blank" rel="noopener">jsdom</a>이 v11에서 v15로 업그레이드되었습니다.<br>하지만 이전 버전의 Jest를 사용하거나 jsdom의 최신 버전(권장 사항, 현재 이 글을 쓰는 시점에서 v16)을 사용하려면 다음 모듈과 함께 Jest 구성 옵션을 추가하세요</p><table><thead><tr><th>모듈</th><th>설명</th></tr></thead><tbody><tr><td>jest-environment-jsdom-sixteen</td><td>jsdom 16버전을 지정합니다.</td></tr></tbody></table><blockquote><p>최신 버전의 jsdom에는 Node.js v10 이상이 필요합니다.</p></blockquote><pre><code class="bash"># jest-environment-jsdom-MAJOR_VERSION$ npm i -D jest-environment-jsdom-sixteen</code></pre><pre><code class="js">// jest.config.jsmodule.exports = {  // ...  // 테스트 환경을 지정합니다.  // 기본값은 `&#39;jsdom&#39;`이며 브라우저와 유사한 환경을 구성합니다.  // `&#39;node&#39;`를 작성하면 NodeJS와 유사한 환경을 제공할 수 있습니다.  // jsdom의 최신 버전을 별도 사용하는 경우에는, 다음과 같이 해당 모듈을 설치 후 옵션을 지정합니다.  testEnvironment: &#39;jest-environment-jsdom-sixteen&#39;}</code></pre><h2><span id="b73c0504-24ac-4146-8efb-d9de86c1791f">Jest 적용 범위(coverage)</span><a href="#b73c0504-24ac-4146-8efb-d9de86c1791f" class="header-anchor"></a></h2><p>Jest를 통해 테스트의 적용 범위(coverage) 보고서를 확인할 수 있습니다.<br>이 보고서는 작성한 테스트 코드를 통해 테스트 대상(Vue 컴포넌트 등)의 코드가 실행된 범위를 확인합니다.</p><h3><span id="d4e20fa3-b5ee-4fb5-a22b-1ad45d81ce6c">collectCoverage</span><a href="#d4e20fa3-b5ee-4fb5-a22b-1ad45d81ce6c" class="header-anchor"></a></h3><p>보고서를 확인하려면 <a href="https://jestjs.io/docs/en/configuration#collectcoverage-boolean" target="_blank" rel="noopener">Jest 구성 옵션 <code>collectCoverage</code></a>를 사용합니다.</p><pre><code class="js">module.exports = {  // ...  collectCoverage: true}</code></pre><p>이 보고서 옵션을 사용하면 테스트 속도가 크게 떨어지기 때문에, 필요에 따라서 확인하는 것도 좋습니다.<br>매번 구성 옵션을 수정하는 것이 불편하다면 보고서를 확인하는 경우에만 <a href="https://jestjs.io/docs/en/cli#--coverageboolean" target="_blank" rel="noopener">Jest CLI 옵션 <code>--coverage</code></a>을 사용할 수 있습니다.</p><pre><code class="bash">$ jest --coverage</code></pre><p>이제 보고서 확인을 위한 테스트 코드를 작성해 보겠습니다.<br>다음과 같이 간단하게 컴포넌트를 하나 추가합니다.</p><pre><code class="vue">&lt;!-- HelloJest.vue --&gt;&lt;template&gt;  &lt;div&gt;    {{ reversedMsg }}  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;export default {  data () {    return {      msg: &#39;Test coverage&#39;    }  },  computed: {    reversedMsg () {      return this.reverseString(this.msg)    }  },  methods: {    reverseString (str) {      return str.split(&#39;&#39;).reverse().join(&#39;&#39;)    }  }}&lt;/script&gt;</code></pre><p>이제 테스트 코드를 추가합니다.<br>뭔가 이상하지만, 테스트를 진행합니다.</p><pre><code class="js">// HelloJest.test.jsimport { shallowMount } from &#39;@vue/test-utils&#39;import HelloJest from &#39;../HelloJest&#39;describe(&#39;HelloJest&#39;, () =&gt; {  test(&#39;1&#39;, () =&gt; {    shallowMount(HelloJest)  })})</code></pre><pre><code class="bash">$ npm t# or$ jest --coverage</code></pre><p><img src="/images/screenshot/vue-test-with-jest/jest-coverage-without-test-code.jpg" alt="Jest coverage"></p><div class="image-caption">모든 적용 범위 100%</div><p>테스트 코드를 잘 살펴보면 실제 테스트한 코드가 없지만,<br>테스트 결과를 보면 <code>Stmts</code>, <code>Branch</code> 등 모든 항목이 적용 범위 100%로 표시됩니다.</p><p>각 항목은 다음 표와 같으며,<br>백분율(%)은 전체 코드 중 실행(호출)된 코드의 비율입니다.</p><table><thead><tr><th>항목</th><th>설명</th></tr></thead><tbody><tr><td>Statements(Stmts)</td><td>각 구문의 실행을 확인</td></tr><tr><td>Branches</td><td>If, Switch 같은 조건문의 각 분기 실행을 확인</td></tr><tr><td>Functions(Funcs)</td><td>각 함수 호출을 확인</td></tr><tr><td>Lines</td><td>각 라인의 실행을 확인(Statements와 비슷)</td></tr><tr><td>Uncovered Line</td><td>테스트를 통해 실행되지 않은 코드 라인을 표시</td></tr></tbody></table><p>모든 항목이 100%인 이유는, 컴포넌트 마운트(shallowMount)를 통해서 <code>reversedMsg</code>와 연결된 모든 코드가 실행되었기 때문입니다.<br>컴포넌트에서 다음과 같이 일부 코드를 주석 처리 후 다시 테스트를 진행합니다.<br>이제 테스트 대상의 어느 부분이 작성한 테스트를 통해 실행되지 않았는지 확인할 수 있습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/jest-coverage-vue-component-comment.jpg" alt="Jest coverage"></p><p><img src="/images/screenshot/vue-test-with-jest/jest-coverage-without-test-code2.jpg" alt="Jest coverage"></p><h3><span id="91362093-beae-46aa-a476-518e6fb5d4ed">coverageReporters</span><a href="#91362093-beae-46aa-a476-518e6fb5d4ed" class="header-anchor"></a></h3><p>만약 보고서를 좀 더 자세하게 확인하고 싶다면, <a href="https://jestjs.io/docs/en/configuration#coveragereporters-arraystring--stringany" target="_blank" rel="noopener">Jest 구성 옵션 <code>coverageReporters</code></a>을 통해 보고서 양식을 HTML 문서로 변경할 수 있습니다.<br>다음과 같이 구성 옵션을 수정하고 테스트를 실행합니다.</p><pre><code class="js">module.exports = {  // ...  collectCoverage: true,  coverageReporters: [&#39;html&#39;]}</code></pre><pre><code class="bash">$ npm t</code></pre><p>더 이상 터미널에서는 보고서가 표시되지 않습니다.<br>지정한 옵션을 통해 루트 경로에 <code>coverage</code>라는 새로운 디렉터리가 생성되었으며,<br>내부의 <code>index.html</code> 파일을 실행해 보고서를 확인할 수 있습니다.</p><blockquote><p>변경 사항이 발생하면 ‘새로고침’을 누르세요.</p></blockquote><p><img src="/images/screenshot/vue-test-with-jest/jest-coverage-reporters-html-coverage-directory.jpg" alt="Jest Coverage directory for HTML coverage reporter"></p><p><img src="/images/screenshot/vue-test-with-jest/jest-coverage-reporters-html.gif" alt="Jest Coverage directory for HTML coverage reporter"></p><div class="image-caption">HTML 범위 보고서.gif</div><h3><span id="bc6b23b7-fd68-47da-bba3-8f2cd564aa2b">coverageThreshold</span><a href="#bc6b23b7-fd68-47da-bba3-8f2cd564aa2b" class="header-anchor"></a></h3><p><a href="https://jestjs.io/docs/en/configuration#coveragethreshold-object" target="_blank" rel="noopener">Jest 구성 옵션 <code>coverageThreshold</code></a>를 사용하면 보고서의 최소 임곗값(threshold)을 지정할 수 있습니다.<br>이 테스트가 지정된 임곗값을 충족하지 못하면 테스트가 실패하며, 이를 통해 더 높은 테스트 신뢰도를 유지할 수 있습니다.</p><p>양수로 지정하면 백분율을,<br>음수로 지정하면 허용된 실행(호출)되지 않은 코드 수 의미합니다.</p><p>이해를 돕기 위해 임곗값을 충족하지 못하는 간단한 예를 들어보겠습니다.<br>대상의 총 3개 코드 중 테스트에서 1개 코드만 실행되었다면 2개 코드가 실행되지 않은 것입니다.<br>만약 임곗값을 <code>50</code>(양수)으로 지정하면 실행된 코드의 백분율은 33.33%이기 때문에 테스트가 실패하며,<br>만약 임곗값을 <code>-1</code>(음수)로 지정하면 실행되지 않은 코드를 1개까지 허용하겠다는 의미이기 때문에 테스트는 실패합니다.</p><pre><code class="js">module.exports = {  // ...  collectCoverage: true,  coverageReporters: [&#39;html&#39;],  coverageThreshold: {    global: {      statements: 50,      branches: 50,      functions: 50,      lines: -1    }  }}</code></pre><p><img src="/images/screenshot/vue-test-with-jest/jest-coverage-coverage-threshold.jpg" alt="Jest coverage threshold"></p><h2><span id="251b5fe1-22e3-40fc-b4d7-c63f742cabc3">첫 번째 컴포넌트 테스트</span><a href="#251b5fe1-22e3-40fc-b4d7-c63f742cabc3" class="header-anchor"></a></h2><p>환경 설정에 맞게 테스트가 정상 동작하는지 확인하기 위해 매우 간단한 테스트용 코드를 작성해 보겠습니다.</p><blockquote><p>이 글에서의 기본 프로젝트는 Vue CLI로 설치합니다.<br>최초 설치는 ‘Vue CLI로 빠른 환경 설정’ 파트를 참고하세요.</p></blockquote><p><code>src/components/HelloJest.vue</code> 파일을 생성하고 다음과 같이 작성합니다.</p><pre><code class="vue">&lt;!-- src/components/HelloJest.vue --&gt;&lt;template&gt;  &lt;div&gt;    {{ msg }}  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;export default {  data () {    return {      msg: &#39;Hello Jest~&#39;    }  }}&lt;/script&gt;</code></pre><p>Jest는 <a href="https://jestjs.io/docs/en/configuration#testregex-string--arraystring" target="_blank" rel="noopener">구성 옵션(<code>testRegex</code>)</a>을 통해 <code>.test</code> 혹은 <code>.spec</code>과 일치하는 프로젝트 내 모든 파일을 탐색합니다.<br>이 컴포넌트의 테스트를 위해 <code>src/components/</code> 경로에 <code>__tests__</code> 디렉터리와 내부 <code>HelloJest.test.js</code> 파일을 추가합니다.<br>최종 경로는 <code>src/components/__tests__/HelloJest.test.js</code> 입니다.</p><blockquote><p>Jest는 <code>__tests__</code> 디렉터리를 테스트할 코드와 같은 경로에 생성할 것을 권장합니다.</p></blockquote><p><img src="/images/screenshot/vue-test-with-jest/first-test-directories.jpg" alt="First test directories"></p><p>다음과 같이 테스트를 작성합니다.</p><pre><code class="js">// src/components/__tests__/HelloJest.test.jsimport { shallowMount } from &#39;@vue/test-utils&#39;import HelloJest from &#39;../HelloJest&#39;describe(&#39;HelloJest&#39;, () =&gt; {  let wrapper  beforeEach(() =&gt; {    wrapper = shallowMount(HelloJest)  })  test(&#39;1&#39;, () =&gt; {    expect(wrapper.vm.msg).toBe(&#39;Hello Jest!&#39;)  })})</code></pre><p>테스트를 실행합니다.</p><pre><code class="bash">$ npm t</code></pre><p>다음과 같은 에러가 발생해야 합니다.<br>이제 무엇을 수정해야 테스트가 통과할지 명확해졌습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/first-test-failed.jpg" alt="First test fails"></p><p>잘못된 받은 값(received)과 기댓값(expected)에 의해서도 테스트가 통과할 수 있어서,<br>의도적인 실패를 통해 테스트 신뢰도를 높일 수 있습니다.<br>특히 테스트가 복잡해지는 경우 더욱 조심해야 합니다.</p><h1><span id="d13491f0-7d73-4e7d-a9a0-a40683122239">Jest</span><a href="#d13491f0-7d73-4e7d-a9a0-a40683122239" class="header-anchor"></a></h1><h2><span id="c649243d-e763-4a4d-b4d9-8bca8ab5f308">전역 멤버(Globals)</span><a href="#c649243d-e763-4a4d-b4d9-8bca8ab5f308" class="header-anchor"></a></h2><p><code>describe</code>는 테스트의 범위를 설정하고,<br><code>test</code>는 단위 테스트를 설정합니다.</p><pre><code class="js">describe(&#39;테스트 범위&#39;, () =&gt; {  test(&#39;단위 테스트 1&#39;, () =&gt; {})  test(&#39;단위 테스트 2&#39;, () =&gt; {})})</code></pre><h3><span id="4d41e387-ed4a-47e5-a79e-c69997a59de3">Hooks</span><a href="#4d41e387-ed4a-47e5-a79e-c69997a59de3" class="header-anchor"></a></h3><p>Jest는 <code>beforeAll</code>, <code>afterAll</code>, <code>beforeEach</code>, <code>afterEach</code>의 4가지 전역 함수를 제공하며, 이를 통해 테스트 코드 전후를 제어할 수 있습니다.</p><p><code>beforeAll</code>과 <code>afterAll</code>은 선언된 <code>describe</code> 범위 안에서 전후 동작하며,<br><code>beforeEach</code>와 <code>afterEach</code>는 선언된 <code>describe</code> 범위 안에서 각 <code>test</code> 단위 전후로 동작합니다.</p><pre><code class="js">describe(&#39;Jest Hooks&#39;, () =&gt; {  beforeAll(() =&gt; {    console.log(&#39;beforeAll&#39;)  })  afterAll(() =&gt; {    console.log(&#39;afterAll&#39;)  })  beforeEach(() =&gt; {    console.log(&#39;beforeEach&#39;)  })  afterEach(() =&gt; {    console.log(&#39;afterEach&#39;)  })  test(&#39;1&#39;, () =&gt; {    expect(1 + 1).toBe(2)    console.log(&#39;test 1 is done!&#39;)  })  test(&#39;2&#39;, () =&gt; {    expect(2 + 1).toBe(3)    console.log(&#39;test 2 is done!&#39;)  })})</code></pre><p><img src="/images/screenshot/vue-test-with-jest/jest-hooks.jpg" alt="Jest Hooks"></p><h3><span id="e006bfbb-706d-46ae-be79-616b92b4ad03">중첩(Nesting)</span><a href="#e006bfbb-706d-46ae-be79-616b92b4ad03" class="header-anchor"></a></h3><p><code>describe</code> 안에 또 다른 <code>describe</code>를 중첩하여 사용할 수 있습니다.</p><pre><code class="js">describe(&#39;Vue Component&#39;, () =&gt; {  describe(&#39;Computed&#39;, () =&gt; {})  describe(&#39;Created&#39;, () =&gt; {})  describe(&#39;Mounted&#39;, () =&gt; {})  describe(&#39;Methods&#39;, () =&gt; {    describe(&#39;Sync&#39;, () =&gt; {})    describe(&#39;Async&#39;, () =&gt; {})  })})</code></pre><p>다음 예제와 같이 여러 범위를 만들어 다양하게 테스트할 수 있습니다.</p><pre><code class="js">import { shallowMount, mount } from &#39;@vue/test-utils&#39;import HelloJest from &#39;../HelloJest&#39;describe(&#39;HelloJest.vue&#39;, () =&gt; {  let wrapper  describe(&#39;ShallowMount&#39;, () =&gt; {    beforeEach(() =&gt; {      // 얕은 마운트      // 하위 컴포넌트를 포함(렌더링)하지 않습니다.      wrapper = shallowMount(HelloJest)    })    test(&#39;1&#39;, () =&gt; {      expect(wrapper.text()).toBe(&#39;Hello&#39;)    })  })  describe(&#39;Mount&#39;, () =&gt; {    beforeEach(() =&gt; {      // 마운트      // 하위 컴포넌트를 포함합니다.      wrapper = mount(HelloJest)    })    test(&#39;1&#39;, () =&gt; {      expect(wrapper.text()).toBe(&#39;Hello World!&#39;)    })  })})</code></pre><h3><span id="de636945-7e1b-4649-98ac-4418c33cd3ef">only, skip</span><a href="#de636945-7e1b-4649-98ac-4418c33cd3ef" class="header-anchor"></a></h3><p><code>describe</code>와 <code>test</code>에 사용할 수 있는 <code>only</code>와 <code>skip</code> 멤버가 있습니다.<br>일부 테스트만 실행하고 싶을 때는 <code>only</code>를 사용하고,<br>일부 테스트를 중단하고 싶을 때 <code>skip</code>을 사용합니다.<br>각 테스트 및 집합을 삭제하거나 주석 처리하지 않아도 되기 때문에 편리합니다.</p><pre><code class="js">describe(&#39;Vue Component&#39;, () =&gt; {  describe.only(&#39;Computed&#39;, () =&gt; {    test(&#39;1&#39;, () =&gt; {})    test(&#39;2&#39;, () =&gt; {})  })  describe(&#39;Created&#39;, () =&gt; {    test(&#39;1&#39;, () =&gt; {})    test.only(&#39;2&#39;, () =&gt; {})  })  describe(&#39;Methods&#39;, () =&gt; {    test(&#39;1&#39;, () =&gt; {})    test(&#39;2&#39;, () =&gt; {})  })})</code></pre><p><img src="/images/screenshot/vue-test-with-jest/jest-global-only-member.jpg" alt="Jest Global Only"></p><div class="image-caption">only 멤버로 일부 테스트만 실행합니다.</div><pre><code class="js">describe(&#39;Vue Component&#39;, () =&gt; {  describe.skip(&#39;Computed&#39;, () =&gt; {    test(&#39;1&#39;, () =&gt; {})    test(&#39;2&#39;, () =&gt; {})  })  describe(&#39;Created&#39;, () =&gt; {    test(&#39;1&#39;, () =&gt; {})    test.skip(&#39;2&#39;, () =&gt; {})  })  describe(&#39;Methods&#39;, () =&gt; {    test(&#39;1&#39;, () =&gt; {})    test(&#39;2&#39;, () =&gt; {})  })})</code></pre><p><img src="/images/screenshot/vue-test-with-jest/jest-global-skip-member.jpg" alt="Jest Global Skip"></p><div class="image-caption">skip 멤버로 일부 테스트를 중단합니다.</div><h2><span id="d811193d-14fe-469c-8e26-41e9d1b8e4eb">Matchers</span><a href="#d811193d-14fe-469c-8e26-41e9d1b8e4eb" class="header-anchor"></a></h2><p>Matcher는 <code>expect(받은_값)</code>를 통해 받은 값을 확인하는 용도로 사용됩니다.</p><p>각 Matcher를 빠르게 정리합니다.<br>자세한 내용은 해당 Matcher의 공식 문서를 참고하시고,<br>일부 Matcher의 내용은 하단에 정리했습니다.</p><p>기본 사용법은 다음과 같습니다.<br><code>?</code>는 선택적(Optional) 인수를 의미합니다.</p><pre><code class="js hljs javascript" data-lang="JAVASCRIPT">expect(받은_값).MATCHER(기댓값?);</code></pre><p>다음과 같이 중간에 <code>.not</code> 속성을 사용하면 받은 값의 반대를 확인할 수 있습니다.</p><pre><code class="js">expect(받은_값).not.MATCHER();</code></pre><p><strong>공통</strong></p><table><thead><tr><th></th><th>Matcher</th><th>설명</th><th>비고</th></tr></thead><tbody><tr><td><a href="https://jestjs.io/docs/en/expect#tobevalue" target="_blank" rel="noopener">📎</a></td><td><code>.toBe(값)</code></td><td>받은 값과 기본 동등 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#toequalvalue" target="_blank" rel="noopener">📎</a></td><td><code>.toEqual(값)</code></td><td>받은 값과 깊은(Deep) 동등 비교</td><td><code>{ a: undefined } === {}</code></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tostrictequalvalue" target="_blank" rel="noopener">📎</a></td><td><code>.toStrictEqual(값)</code></td><td>받은 값과 엄격한 깊은(Deep) 동등 비교</td><td><code>{ a: undefined } !== {}</code></td></tr></tbody></table><p><strong>자료형</strong></p><table><thead><tr><th></th><th>Matcher</th><th>설명</th><th>비고</th></tr></thead><tbody><tr><td><a href="https://jestjs.io/docs/en/expect#tobetruthy" target="_blank" rel="noopener">📎</a></td><td><code>.toBeTruthy()</code></td><td>받은 값이 <a href="https://developer.mozilla.org/ko/docs/Glossary/Truthy" target="_blank" rel="noopener">Truthy</a> 값인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobefalsy" target="_blank" rel="noopener">📎</a></td><td><code>.toBeFalsy()</code></td><td>받은 값이 <a href="https://developer.mozilla.org/ko/docs/Glossary/Falsy" target="_blank" rel="noopener">Falsy</a> 값인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobedefined" target="_blank" rel="noopener">📎</a></td><td><code>.toBeDefined()</code></td><td>받은 값이 정의된 값인지 확인</td><td><code>undefined</code>가 아닌 값</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobeundefined" target="_blank" rel="noopener">📎</a></td><td><code>.toBeUndefined()</code></td><td>받은 값이 정의되지 않은 값인지 확인</td><td><code>undefined</code>인 값</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobenull" target="_blank" rel="noopener">📎</a></td><td><code>.toBeNull()</code></td><td>받은 값이 <code>null</code>인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobenan" target="_blank" rel="noopener">📎</a></td><td><code>.toBeNaN()</code></td><td>받은 값이 <code>NaN</code>인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobeinstanceofclass" target="_blank" rel="noopener">📎</a></td><td><code>.toBeInstanceOf(클래스)</code></td><td>받은 값이 클래스의 인스턴스인지 확인</td><td><code>instanceof</code>를 사용</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobegreaterthannumber--bigint" target="_blank" rel="noopener">📎</a></td><td><code>.toBeGreaterThan(숫자)</code></td><td><code>받은 값 &gt; number</code>인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobegreaterthanorequalnumber--bigint" target="_blank" rel="noopener">📎</a></td><td><code>.toBeGreaterThanOrEqual(숫자)</code></td><td><code>받은 값 &gt;= number</code>인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobelessthannumber--bigint" target="_blank" rel="noopener">📎</a></td><td><code>.toBeLessThan(숫자)</code></td><td><code>받은 값 &lt; number</code>인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobelessthanorequalnumber--bigint" target="_blank" rel="noopener">📎</a></td><td><code>.toBeLessThanOrEqual(숫자)</code></td><td><code>받은 값 &lt;= number</code>인지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tobeclosetonumber-numdigits" target="_blank" rel="noopener">📎</a></td><td><code>.toBeCloseTo(숫자, 자릿수?)</code></td><td>받은 값과 부동 소수점 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tocontainitem" target="_blank" rel="noopener">📎</a></td><td><code>.toContain(요소)</code></td><td>받은 값에 요소가 포함되어 있는지 확인</td><td>원시 데이터(Primitives)</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tocontainequalitem" target="_blank" rel="noopener">📎</a></td><td><code>.toContainEqual(요소)</code></td><td>받은 값에 요소가 포함되어 있는지 깊은(Deep) 확인</td><td><code>object</code>, <code>Array&lt;object&gt;</code></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tomatchregexporstring" target="_blank" rel="noopener">📎</a></td><td><code>.toMatch(정규식 &#124; 문자열)</code></td><td>받은 값이 정규식과 일치 또는 문자열을 포함하는지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tomatchobjectobject" target="_blank" rel="noopener">📎</a></td><td><code>.toMatchObject(객체)</code></td><td>객체가 받은 값의 하위 집합인지 확인</td><td><code>object</code>, <code>Array&lt;object&gt;</code></td></tr></tbody></table><p><strong>스냅샷과 예외</strong></p><table><thead><tr><th></th><th>Matcher</th><th>설명</th><th>비고</th></tr></thead><tbody><tr><td><a href="https://jestjs.io/docs/en/expect#tomatchsnapshotpropertymatchers-hint" target="_blank" rel="noopener">📎</a></td><td><code>.toMatchSnapshot()</code></td><td>받은 값의 스냅샷을 생성 or 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tomatchinlinesnapshotpropertymatchers-inlinesnapshot" target="_blank" rel="noopener">📎</a></td><td><code>.toMatchInlineSnapshot(인라인스냅샷)</code></td><td>받은 값의 인라인 스냅샷을 생성 or 비교</td><td><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals" target="_blank" rel="noopener">템플릿 리터럴</a> 스냅샷</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tothrowerrormatchingsnapshothint" target="_blank" rel="noopener">📎</a></td><td><code>.toThrowErrorMatchingSnapshot()</code></td><td>받은 값(함수)이 호출될 때 에러 스냅샷을 생성 or 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tothrowerrormatchinginlinesnapshotinlinesnapshot" target="_blank" rel="noopener">📎</a></td><td><code>.toThrowErrorMatchingInlineSnapshot()</code></td><td>받은 값(함수)이 호출될 때 에러 인라인 스냅샷을 생성 or 비교</td><td><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals" target="_blank" rel="noopener">템플릿 리터럴</a> 스냅샷</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tothrowerror" target="_blank" rel="noopener">📎</a></td><td><code>.toThrow(에러?)</code></td><td>받은 값(함수)이 호출될 때 에러가 발생하는지 확인</td><td>받은 값은 함수로 랩핑<br><code>expect(() =&gt; errFn(1, 2))</code></td></tr></tbody></table><p><strong>toHave</strong></p><table><thead><tr><th></th><th>Matcher</th><th>설명</th><th>비고</th></tr></thead><tbody><tr><td><a href="https://jestjs.io/docs/en/expect#tohavebeencalled" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveBeenCalled()</code></td><td>받은 값(함수)이 호출되었는지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavebeencalledtimesnumber" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveBeenCalledTimes(횟수)</code></td><td>받은 값(함수)의 호출 횟수를 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavebeencalledwitharg1-arg2-" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveBeenCalledWith(인수1, 인수2, ...)</code></td><td>받은 값(함수)이 해당 인수와 함께 호출되었는지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavebeenlastcalledwitharg1-arg2-" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveBeenLastCalledWith(인수1, 인수2, ...)</code></td><td>받은 값(함수)의 마지막 호출이 해당 인수와 함께 호출되었는지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavebeennthcalledwithnthcall-arg1-arg2-" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveBeenNthCalledWith(n번째, 인수1, 인수2, ...)</code></td><td>받은 값(함수)의 n번째 호출이 해당 인수와 함께 호출되었는지 확인</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavereturned" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveReturned()</code></td><td>받은 값(함수)이 호출 후 에러 없이 값을 반환하는지 확인</td><td><code>undefined</code> 반환도 포함</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavereturnedtimesnumber" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveReturnedTimes(횟수)</code></td><td>받은 값(함수)이 호출 후 에러 없이 값을 반환하는 횟수를 확인</td><td><code>undefined</code> 반환도 포함</td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavereturnedwithvalue" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveReturnedWith(반환값)</code></td><td>받은 값(함수)의 모든 호출이 반환한 값 중 같은 값이 있는지 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavelastreturnedwithvalue" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveLastReturnedWith(반환값)</code></td><td>받은 값(함수)의 마지막 호출이 반환한 값과 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohaventhreturnedwithnthcall-value" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveNthReturnedWith(n번째, 반환)</code></td><td>받은 값(함수)의 n번째 호출이 반환한 값과 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavelengthnumber" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveLength(숫자)</code></td><td>받은 값에 <code>.length</code> 속성값과 비교</td><td></td></tr><tr><td><a href="https://jestjs.io/docs/en/expect#tohavepropertykeypath-value" target="_blank" rel="noopener">📎</a></td><td><code>.toHaveProperty(속성경로, 값?)</code></td><td>받은 값에 속성경로 존재 또는 속성경로에 해당 값이 존재하는지 확인</td><td></td></tr></tbody></table><h3><span id="601886ae-62df-4dc9-b616-ead4158614d2">toBe</span><a href="#601886ae-62df-4dc9-b616-ead4158614d2" class="header-anchor"></a></h3><p>가장 기본적인 Matcher로, 받은 값과 기댓값이 같은지 비교합니다.<br><a href="https://developer.mozilla.org/ko/docs/Glossary/Primitive" target="_blank" rel="noopener">원시 데이터(Primitives)</a>를 비교하는 용도로 사용합니다.</p><pre><code class="js">const user = {  name: &#39;Neo&#39;,  age: 85}test(&#39;Name&#39;, () =&gt; {  expect(user.name).toBe(&#39;Neo&#39;)})test(&#39;Age&#39;, () =&gt; {  expect(user.age).toBe(85)})</code></pre><h3><span id="6dcf51d2-2941-4f4c-b525-49e88b0450e8">toEqual과 toStrictEqual</span><a href="#6dcf51d2-2941-4f4c-b525-49e88b0450e8" class="header-anchor"></a></h3><p><code>toEqual</code>과 <code>toStrictEqual</code>은 객체나 배열 데이터의 모든 속성(요소)을 재귀적으로 비교하며, 이를 깊은(Deep) 동등 비교라고 합니다.<br>여기서 ‘깊은(Deep)’은 ‘데이터 안으로 들어가서 모두 꼼꼼하게’ 정도로 의역할 수 있습니다.</p><p>우선, <code>toBe</code>를 사용하는 다음 예제는 실패합니다.</p><pre><code class="js">const user = {  name: &#39;Neo&#39;,  age: 85}test(&#39;User&#39;, () =&gt; {  expect(user).toBe({    name: &#39;Neo&#39;,    age: 85  })})</code></pre><p><img src="/images/screenshot/vue-test-with-jest/jest-to-be-failed.jpg" alt="Jest failed toBe Matcher"></p><div class="image-caption">실패한 테스트를 통해 ‘toBe’가 아닌 ‘toStrictEqual’ Matcher를 사용해야 한다는 것을 알 수 있습니다.</div><p>다음과 같이 <code>toBe</code>를 <code>toStrictEqual</code>로 변경하면, 테스트가 통과합니다.</p><pre><code class="js">// ...test(&#39;User&#39;, () =&gt; {  // Passes  expect(user).toStrictEqual({    name: &#39;Neo&#39;,    age: 85  })})</code></pre><p><code>toEqual</code>과 <code>toStrictEqual</code>은 비슷하지만, 다음과 같은 일부 엄격함의 차이가 있습니다.<br>실패하는 테스트만 <code>// Fails</code>로 표시했습니다.</p><pre><code class="js">test(&#39;Undefined&#39;, () =&gt; {  expect({ a: undefined }).toEqual({})  expect({ a: undefined }).toStrictEqual({}) // Fails  expect([undefined, 1]).toEqual([, 1])  expect([undefined, 1]).toStrictEqual([, 1]) // Fails})test(&#39;Class instance&#39;, () =&gt; {  class User {    constructor (name) {      this.name = name    }  }  expect(new User(&#39;Neo&#39;)).toEqual({ name: &#39;Neo&#39; })  expect(new User(&#39;Neo&#39;)).toStrictEqual({ name: &#39;Neo&#39; }) // Fails})</code></pre><h3><span id="f281320a-5edb-4585-9ca4-ed02a597da1f">toBeCloseTo</span><a href="#f281320a-5edb-4585-9ca4-ed02a597da1f" class="header-anchor"></a></h3><p>자바스크립트에서 10진수를 2진수로 변환하게 되면 <code>0.1</code>이나 <code>0.2</code> 소수점 숫자 같은 경우 무한 소수가 발생하게 됩니다.<br>따라서 <code>toBe</code>를 사용하는 다음 테스트는 실패하게 됩니다.</p><pre><code class="js">test(&#39;Floating point numbers&#39;, () =&gt; {  expect(0.1 + 0.2).toBe(0.3)})</code></pre><p><img src="/images/screenshot/vue-test-with-jest/floating-point-number-operation.jpg" alt="Floating point numbers operation"></p><p><code>toBeCloseTo</code>로 변경하면, 테스트가 통과합니다.</p><pre><code class="js">test(&#39;Floating point numbers&#39;, () =&gt; {  expect(0.1 + 0.2).toBeCloseTo(0.3) // Passes})</code></pre><h3><span id="28da73a9-cd6f-48ec-b90d-0d6c17ee4e69">toContain과 toContainEqual</span><a href="#28da73a9-cd6f-48ec-b90d-0d6c17ee4e69" class="header-anchor"></a></h3><p>두 Matcher 모두 받은 값(배열)에 특정 요소가 포함되어 있는지 확인합니다.<br><code>toContain</code>은 원시 데이터의 포함 여부를, <code>toContainEqual</code>은 객체 데이터의 포함 여부를 깊게(Deep) 확인하는 차이가 있습니다.</p><p>다음 예제를 보면 차이점을 쉽게 이해할 수 있습니다.</p><pre><code class="js">test(&#39;Primitives&#39;, () =&gt; {  // 두 Matcher 모두 배열 내 원시 데이터 포함을 확인할 수 있습니다.  const numbers = [1, 2, 3]  expect(numbers).toContain(1)  expect(numbers).toContainEqual(1)  // toContainEqual은 문자열 포함은 확인할 수 없습니다.  const string = &#39;Hello World&#39;  expect(string).toContain(&#39;Hello&#39;)  expect(string).toContainEqual(&#39;Hello&#39;) // Fails})test(&#39;Objects&#39;, () =&gt; {  // toContain은 객체 데이터 포함을 확인할 수 없습니다.  const users = [    { name: &#39;Neo&#39; },    { name: &#39;Evan&#39; },    { name: &#39;Lewis&#39; }  ]  expect(users).toContain({ name: &#39;Neo&#39; }) // Fails  expect(users).toContainEqual({ name: &#39;Neo&#39; })})test(&#39;Recommended&#39;, () =&gt; {  // 배열 내 포함 확인은 toContainEqual을 사용하고,  const numbers = [1, 2, 3]  expect(numbers).toContainEqual(1)  // 문자열 포함은 toMatch를 사용하는 것을 추천합니다.  const string = &#39;Hello World&#39;  expect(string).toMatch(&#39;Hello&#39;)})</code></pre><h3><span id="62a706f9-600b-4e28-b496-2e6c8193fbb3">toThrow</span><a href="#62a706f9-600b-4e28-b496-2e6c8193fbb3" class="header-anchor"></a></h3><p>함수가 호출될 때 에러가 발생하는지 확인합니다.<br>주의할 점은 에러를 던지는 함수를 별도의 함수로 한 번 랩핑해 실행하거나, 호출 없이 함수 자체를 받은 값 인수로 사용해야 테스트 자체의 에러로 인식되지 않습니다.</p><p>다음 예제를 통해 <code>toThrow</code>에 대해 좀 더 자세하게 이해할 수 있습니다.</p><pre><code class="js">function err (isPass) {  if (!isPass) {    throw new Error(&#39;I am your error.&#39;)  }}// 에러가 발생할 함수를 직접 호출하지 마세요!test(&#39;Wrapping to a function&#39;, () =&gt; {  // 익명 함수로 랩핑  expect(() =&gt; { err() }).toThrow()  // 호출 없이 함수 인수  expect(err).toThrow()  // Fails  expect(err()).toThrow()})// 선택적으로 다음과 같은 인수를 사용할 수 있습니다.test(&#39;Arguments&#39;, () =&gt; {  // 오류 메시지의 하위 문자열  expect(() =&gt; { err() }).toThrow(&#39;I am your error.&#39;)  expect(() =&gt; { err() }).toThrow(&#39;m your err&#39;)  // 오류 메시지 패턴과 일치하는 정규식  expect(() =&gt; { err() }).toThrow(/^I.*errors?\.$/)  // 오류 메시지와 일치하는 에러 인스턴스  expect(() =&gt; { err() }).toThrow(new Error(&#39;I am your error.&#39;))  // 에러 클래스  expect(() =&gt; { err() }).toThrow(Error)})// 해당 함수가 에러를 던지지 않으면 테스트는 실패합니다.test(&#39;None error&#39;, () =&gt; {  // Fails - Received function did not throw  expect(() =&gt; { err(true) }).toThrow()})</code></pre><h3><span id="f7446107-6a23-4ce0-939d-74baed4eacf3">toHaveBeenCalled</span><a href="#f7446107-6a23-4ce0-939d-74baed4eacf3" class="header-anchor"></a></h3><p>테스트에서 함수가 호출된 적이 있는지 확인합니다.<br>중요한 것은 해당 함수가 감시(spy)되고 있거나 모의(mock) 함수여야 합니다.</p><p>또한 <code>toHaveBeenCalled</code>는 함수의 호출만 체크하기 때문에 함수에서 에러를 던져도 테스트는 통과합니다.(Try/Catch문을 사용했을 때)<br>함수에서 에러를 던질 때 테스트가 실패하도록 하려면 <code>toHaveReturned</code>가 좋은 선택입니다.</p><pre><code class="js">const user = {  name: &#39;Neo&#39;,  getName () {    return this.name  }}test(&#39;Called&#39;, () =&gt; {  jest.spyOn(user, &#39;getName&#39;) // 이제 해당 함수를 감시합니다.  user.getName() // 호출됨  expect(user.getName).toHaveBeenCalled() // Passes})</code></pre><h3><span id="c72c3804-4d4e-4c82-a8de-fc31ab45d4d5">toHaveReturned</span><a href="#c72c3804-4d4e-4c82-a8de-fc31ab45d4d5" class="header-anchor"></a></h3><p><code>toHaveReturned</code>는 <code>toHaveBeenCalled</code>와 비슷하지만, 함수의 호출뿐만 아니라 반환 값이 있어야 합니다.<br>함수는 <code>return</code> 키워드를 사용하지 않아도 기본적으로 <code>undefined</code>를 반환하는데, <code>toHaveReturned</code>는 이 <code>undefined</code>도 함수의 반환 값으로 확인합니다.<br>따라서 함수가 에러 없이 정상적으로 호출되는지 확인하는 용도로 사용할 수 있습니다.<br>그리고 <code>toHaveBeenCalled</code>와 마찬가지로 해당 함수는 감시(spy)되고 있거나 모의(mock) 함수여야 테스트할 수 있습니다.</p><p>만약, 반환 값이 무엇인지 확인하고 싶다면 <code>toHaveReturnedWith</code>를 사용하세요.</p><pre><code class="js">const user = {  name: &#39;&#39;, // None name..  getName () {    if (!this.name) {      throw new Error(&#39;Not Neo!&#39;)    }    return this.name  }}function displayName (user) {  try {    return user.getName()  } catch (err) {    // console.error(err)  }}test(&#39;Called&#39;, () =&gt; {  jest.spyOn(user, &#39;getName&#39;) // 이제 해당 함수를 감시합니다.  displayName(user)  expect(user.getName).toHaveBeenCalled() // Passes  expect(user.getName).toHaveReturned() // Fails})</code></pre><h3><span id="a46bed07-329d-45be-a3a7-f64b353a0091">toHaveReturnedWith</span><a href="#a46bed07-329d-45be-a3a7-f64b353a0091" class="header-anchor"></a></h3><p>함수가 호출되고 어떤 값을 반환했는지 확인합니다.<br>함수가 여러 번 호출되고 각각 반환 값이 다른 경우, 기댓값이 모든 호출 중 하나의 반환과 일치하면 테스트는 통과합니다.</p><pre><code class="js">const users = {  names: [&#39;Neo&#39;, &#39;Evan&#39;, &#39;Lewis&#39;, &#39;Emily&#39;],  getName (order) {    return this.names[order]  }}test(&#39;Return value&#39;, () =&gt; {  jest.spyOn(users, &#39;getName&#39;)  users.getName(0) // Neo  users.getName(1) // Evan  users.getName(2) // Lewis  expect(users.getName).toHaveReturnedWith(&#39;Evan&#39;) // Passes  expect(users.getName).toHaveReturnedWith(&#39;Emily&#39;) // Fails})</code></pre><p><img src="/images/screenshot/vue-test-with-jest/jest-to-have-returned-with.jpg" alt="Jest toHaveReturnedWith"></p><h3><span id="2f469605-b71f-4c66-bae9-f032c5e8789d">toHaveProperty</span><a href="#2f469605-b71f-4c66-bae9-f032c5e8789d" class="header-anchor"></a></h3><p>객체에서 찾고자 하는 속성의 존재 여부와 그 값을 확인할 수 있습니다.<br>속성의 경로(keyPath)를 명시할 때 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Property_Accessors" target="_blank" rel="noopener">점 표기법</a>이나 속성 경로를 나열하는 배열을 사용할 수 있습니다.</p><pre><code class="js">const user = {  name: &#39;Neo&#39;,  age: 85,  address: {    address1: &#39;서울특별시 종로구 종로 6&#39;,    sido: &#39;서울특별시&#39;,    zonecode: &#39;03187&#39;  },  belongings: [    &#39;phone&#39;,    &#39;laptop&#39;,    { mouse: &#39;MX Vertical&#39; },    [100, 1000]  ],  girlFriend: undefined,  getName () {    return this.name  }}// 이하 실패하는 테스트는 없습니다!test(&#39;Key and Value&#39;, () =&gt; {  // user 객체에 age 속성이 존재하는지 확인합니다.  expect(user).toHaveProperty(&#39;age&#39;)  // age 속성의 값도 확인할 수 있습니다.  expect(user).toHaveProperty(&#39;age&#39;, 85)  // 점 표기법을 사용하면 더 깊이 들어갈 수 있습니다.  expect(user).toHaveProperty(&#39;address.zonecode&#39;)  // 역시 속성의 값도 체크할 수 있고요.  expect(user).toHaveProperty(&#39;address.zonecode&#39;, &#39;03187&#39;)  // 점 표기법 대신에 배열에 속성 경로를 나열하는 표기 방법도 사용할 수 있습니다.  expect(user).toHaveProperty([&#39;address&#39;, &#39;zonecode&#39;], &#39;03187&#39;)  // 배열로 나열하는 표기 방법은 값이 배열 데이터인 경우 요소를 인덱싱할 수 있습니다.  expect(user).toHaveProperty([&#39;belongings&#39;, 0], &#39;phone&#39;)  // 더 깊게 들어갈 수도 있네요!  expect(user).toHaveProperty([&#39;belongings&#39;, 2, &#39;mouse&#39;], &#39;MX Vertical&#39;)  expect(user).toHaveProperty([&#39;belongings&#39;, 3, 1], 1000)  // 명시적 undefined를 가지는 속성은 존재한다고 판단하니 주의하세요.  expect(user).toHaveProperty(&#39;girlFriend&#39;)  // 당연히 메소드도 확인할 수 있습니다.  expect(user).toHaveProperty(&#39;getName&#39;)})</code></pre><h2><span id="9744d99e-309e-4c91-b4b8-50d6c66f2c70">비동기 테스트 패턴</span><a href="#9744d99e-309e-4c91-b4b8-50d6c66f2c70" class="header-anchor"></a></h2><p>많은 경우 프로젝트에서 비동기 코드를 작성하게 되는데,<br>Jest에서 이 비동기 코드를 쉽게 테스트할 수 있습니다.</p><p>0.5초 후 실행되는 단순한 비동기 함수를 예시로, 다음의 테스트 패턴을 정리했습니다.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Using_promises" target="_blank" rel="noopener">Promise</a>에 대해서 알고 있다면 어렵지 않을 겁니다.</p><pre><code class="js">function asyncFn (hasToFail) {  return new Promise((resolve, reject) =&gt; {    setTimeout(() =&gt; {      if (hasToFail) {        reject(new Error(&#39;Fails!&#39;))      }      resolve(&#39;Passes!&#39;)    }, 500)  })}// 다음 테스트는 모두 통과합니다.describe(&#39;Async&#39;, () =&gt; {  // test()에서 done을 인수로 사용하는 경우,  // done이 호출될 때까지 테스트는 기다립니다.  // done이 호출되지 않으면 테스트는 기본 5초 후 실패합니다.  test(&#39;done&#39;, done =&gt; {    asyncFn().then(r =&gt; {      expect(r).toBe(&#39;Passes!&#39;)      done()    })  })  // test()에서 Promise를 반환하면,  // 그 Promise가 이행(resolved)될 때까지 기다립니다.  // 거부(reject)되면 테스트는 실패합니다.  test(&#39;return promise&#39;, () =&gt; {    return asyncFn().then(r =&gt; {      expect(r).toBe(&#39;Passes!&#39;)    })  })  // 받은 값(Expect)과 기댓값(Matcher) 사이에 2개의 Bridge 속성(.not과 같이)을 사용할 수 있습니다.  // .resolves는 받은 Promise가 이행될 때까지 기다리고, .rejects는 거부될 때까지 기다립니다.  // Assertion(Expect 구문)을 반환(return)해야 테스트는 기다립니다.  test(&#39;resolves&#39;, () =&gt; {    return expect(asyncFn()).resolves.toBe(&#39;Passes!&#39;)  })  test(&#39;rejects&#39;, () =&gt; {    return expect(asyncFn(&#39;hasToFail&#39;)).rejects.toThrow(&#39;Fails!&#39;)  })  // test()의 콜백을 비동기 함수(async function)로 선언하면,  // 테스트는 작성된 await에 맞게 기다립니다.  test(&#39;async/await&#39;, async () =&gt; {    const r = await asyncFn()    expect(r).toBe(&#39;Passes!&#39;)  })})</code></pre><p><code>test</code> 함수는 테스트 완료까지 기본적으로 최대 5초까지 기다립니다.<br>5초가 지나면 테스트가 실패하기 때문에 상황에 따라서 기다리는 시간을 늘려줄 필요가 있습니다.<br><code>test</code> 함수의 3번째 인수로 밀리초(ms) 단위의 시간을 입력합니다.<br>기본값은 <code>5000</code>입니다.</p><pre><code class="js">// 위 예제에서 사용한 함수에요!// 동작 시간을 0.5초에서 6초로 늘렸습니다.function asyncFn (hasToFail) {  return new Promise((resolve, reject) =&gt; {    setTimeout(() =&gt; {      if (hasToFail) {        reject(new Error(&#39;Fails!&#39;))      }      resolve(&#39;Passes!&#39;)    }, 6000) // 6초  })}describe(&#39;Async&#39;, () =&gt; {  // 이 테스트는 최대 5초까지 기다리기 때문에 실패합니다.  test(&#39;timeout 5000&#39;, async () =&gt; {    const h = await asyncFn()    expect(h).toBe(&#39;Passes!&#39;)  })  // 이 테스트는 최대 7초까지 기다리기 때문에 통과합니다.  // test(이름, 콜백, 시간)  test(&#39;timeout 7000&#39;, async () =&gt; {    const h = await asyncFn()    expect(h).toBe(&#39;Passes!&#39;)  }, 7000)})</code></pre><h1><span id="0f2e0880-a5b4-4a81-bf16-c83c178e16cd">Vue Test Utils(VTU)</span><a href="#0f2e0880-a5b4-4a81-bf16-c83c178e16cd" class="header-anchor"></a></h1><h2><span id="13e71408-5d55-49aa-b459-87e9512a671d">Mountings</span><a href="#13e71408-5d55-49aa-b459-87e9512a671d" class="header-anchor"></a></h2><p>테스트를 위해 Vue 컴포넌트를 연결 및 렌더링(Mounting)하는 다음 함수들이 있습니다.</p><table><thead><tr><th>함수</th><th>반환</th><th>특징</th></tr></thead><tbody><tr><td>mount()</td><td>Wrapper</td><td>마운트(하위 컴포넌트 렌더링)</td></tr><tr><td>shallowMount()</td><td>Wrapper</td><td>얕은 마운트(하위 컴포넌트 스텁)</td></tr><tr><td>render()</td><td>Promise<cheeriowrapper></cheeriowrapper></td><td>문자열로 렌더링하고 <a href="https://cheerio.js.org/" target="_blank" rel="noopener">Cheerio</a> 객체(Promise)를 반환</td></tr><tr><td>renderToString()</td><td>Promise<string></string></td><td>HTML로 렌더링</td></tr></tbody></table><p><code>render()</code>와 <code>renderToString()</code>을 사용하려면 <code>@vue/server-test-utils</code> 설치가 필요합니다.</p><pre><code class="bash">$ npm i -D @vue/server-test-utils</code></pre><p>다음과 같이 가져옵니다.</p><pre><code class="js">import { mount, shallowMount } from &#39;@vue/test-utils&#39;import { render, renderToString } from &#39;@vue/server-test-utils&#39;</code></pre><h3><span id="f4c54da1-7a5b-43ff-822c-a704b07858fd">mount vs shallowMount</span><a href="#f4c54da1-7a5b-43ff-822c-a704b07858fd" class="header-anchor"></a></h3><p>테스트에서 컴포넌트를 연결 및 렌더링하는 <code>mount</code>와 <code>shallowMount</code> 함수가 있습니다.<br>이 함수들은 Wrapper 객체를 반환하는데, 이 Wrapper 객체를 통해 Vue 인스턴스(vm)에 접근하거나 제공되는 다양한 메소드를 활용할 수 있습니다.</p><p>아래에 <code>msg</code>를 출력하는 아주 단순한 컴포넌트가 있습니다.</p><blockquote><p>프로젝트 기본 구조는 ‘환경 설정 &gt; 첫 번째 테스트’ 파트를 참고하세요.<br><code>mount</code>로 설명하지만 <code>shallowMount</code>를 적용해도 같은 결과를 볼 수 있습니다.<br>차이점은 뒤에서 설명합니다.</p></blockquote><pre><code class="vue">&lt;!-- src/components/HelloJest.vue --&gt;&lt;template&gt;  &lt;h1&gt;Hello {{ msg }}&lt;/div&gt;  &lt;/template&gt;&lt;script&gt;export default {  data () {    return {      msg: &#39;Jest&#39;    }  }}&lt;/script&gt;</code></pre><p>다음은 위 컴포넌트를 테스트하는 코드입니다.</p><pre><code class="js">// src/components/__tests__/Hello.test.jsimport { mount } from &#39;@vue/test-utils&#39;import HelloJest from &#39;../HelloJest&#39; // HelloJest.vue// 이하 테스트는 모두 통과합니다.describe(&#39;HelloJest&#39;, () =&gt; {  let wrapper  beforeEach(() =&gt; {    // 컴포넌트를 마운트하고 Wrapper 객체를 반환합니다.    // mount(컴포넌트, 옵션)    wrapper = mount(HelloJest)  })  test(&#39;Vue instance&#39;, () =&gt; {    // `wrapper.vm`로 Vue 인스턴스에 접근합니다.    expect(wrapper.vm.msg).toBe(&#39;Jest&#39;)  })  test(&#39;Wrapper methods&#39;, () =&gt; {    // Wrapper 객체는 다양한 메소드를 제공합니다.    // .text()는 렌더링된 컴포넌트의 Text 내용을 반환합니다.(`HTMLElement.innerText`와 비슷합니다)    expect(wrapper.text()).toBe(&#39;Hello Jest&#39;)  })})</code></pre><p>매우 비슷하지만, <code>mount</code>와 <code>shallowMount</code>는 큰 차이점이 있습니다.<br><code>mount()</code>는 기본 마운트로 하위 컴포넌트를 렌더링하지만,<br><code>shallowMount()</code>는 얕은 마운트로 하위 컴포넌트를 Stub(스텁)합니다.</p><blockquote><p>Stub(스텁)이란 실제로 동작하는 것처럼 보이는 객체를 의미합니다.</p></blockquote><p>역시 예제부터 살펴봅시다.<br>두 개의 부모/자식 컴포넌트(<code>ChildComp.vue</code>, <code>ParentComp.vue</code>)가 있습니다.</p><pre><code class="vue">&lt;!-- src/components/ChildComp.vue --&gt;&lt;template&gt;  &lt;span&gt;Child!&lt;/span&gt;&lt;/template&gt;</code></pre><pre><code class="vue">&lt;!-- src/components/ParentComp.vue --&gt;&lt;template&gt;  &lt;div&gt;    Hello &lt;child-comp /&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;import ChildComp from &#39;./ChildComp&#39;export default {  components: {    ChildComp  }}&lt;/script&gt;</code></pre><p>테스트 코드를 작성합니다.<br>콘솔만 확인할 것이기 때문에 Assertion(Expect 구문)은 작성하지 않습니다.</p><pre><code class="js">// src/components/__tests__/ParentComp.test.jsimport { shallowMount, mount } from &#39;@vue/test-utils&#39;import ParentComp from &#39;../ParentComp&#39;describe(&#39;ParentComp&#39;, () =&gt; {  test(&#39;mount&#39;, () =&gt; {    const wrapper = mount(ParentComp)    console.log(wrapper.html())  })  test(&#39;shallowMount&#39;, () =&gt; {    const wrapper = shallowMount(ParentComp)    console.log(wrapper.html())  })})</code></pre><p>다음과 같은 결과를 볼 수 있습니다.</p><p><img src="/images/screenshot/vue-test-with-jest/vtu-mount-vs-shallowMount.jpg" alt="Vue test utils shallowMount vs mount"></p><p><code>mount</code>는 하위 컴포넌트도 같이 렌더링했기 때문에 <code>&lt;span&gt;Child!&lt;/span&gt;</code>가 출력되었습니다.</p><p><code>shallowMount</code>는 하위 컴포넌트를 Stub(스텁) 컴포넌트(<code>&lt;child-comp-stub&gt;&lt;/child-comp-stub&gt;</code>)로 대체하여 실제 작동하는 것처럼 보이게 합니다.<br>이는 하위 컴포넌트를 테스트에 포함하지 않아 독립적인 테스트가 가능합니다.<br>특히 테스트 컴포넌트가 많은 하위 컴포넌트를 가지고 있는 경우 유용합니다.</p><h3><span id="75585722-755e-4cba-9451-902043ea51fd">Mounting options</span><a href="#75585722-755e-4cba-9451-902043ea51fd" class="header-anchor"></a></h3><p>테스트에서 컴포넌트를 마운트하면서 다음과 같은 추가 옵션을 지정할 수 있습니다.</p><pre><code class="js">shallowMount(컴포넌트, 옵션)</code></pre><table><thead><tr><th></th><th>옵션</th><th>설명</th><th>타입</th><th>기본값</th></tr></thead><tbody><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#context" target="_blank" rel="noopener">📎</a></td><td><code>context</code></td><td>오직 <a href="https://vuejs.org/v2/guide/render-function.html#Functional-Components" target="_blank" rel="noopener">Functional components</a>에 전달할 <code>context</code>를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#slots" target="_blank" rel="noopener">📎</a></td><td><code>slots</code></td><td>컴포넌트의 <a href="https://vuejs.org/v2/guide/components-slots.html" target="_blank" rel="noopener">Slots</a>에 삽입할 객체를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#scopedslots" target="_blank" rel="noopener">📎</a></td><td><code>scopedSlots</code></td><td>컴포넌트의 <a href="https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots" target="_blank" rel="noopener">Scoped Slots</a>에 삽입할 객체를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#stubs" target="_blank" rel="noopener">📎</a></td><td><code>stubs</code></td><td>스텁할 하위 컴포넌트들을 지정</td><td>Object / Array</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#mocks" target="_blank" rel="noopener">📎</a></td><td><code>mocks</code></td><td>모의 객체나 모의 함수를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#localvue" target="_blank" rel="noopener">📎</a></td><td><code>localVue</code></td><td><code>createLocalVue</code>로 생성된 Vue의 로컬 복사본을 지정</td><td><code>createLocalVue()</code></td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#attachto" target="_blank" rel="noopener">📎</a></td><td><code>attachTo</code></td><td><code>window</code>나 DOM-Events 등을 사용할 때, 마운트할 컴포넌트로 대체될 요소의 선택자를 지정</td><td>HTMLElement / String</td><td><code>null</code></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#attachtodocument" target="_blank" rel="noopener">📎</a></td><td><code>attachToDocument</code></td><td>(Deprecated) <code>window</code>나 DOM-Events 등을 사용할 때 지정<br>대신 <code>attachTo</code> 옵션 사용을 권장</td><td>Boolean</td><td><code>false</code></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#attrs" target="_blank" rel="noopener">📎</a></td><td><code>attrs</code></td><td>부모 컴포넌트에게 받을 <a href="https://kr.vuejs.org/v2/api/index.html#vm-attrs" target="_blank" rel="noopener"><code>$attrs</code> 객체</a>를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#propsdata" target="_blank" rel="noopener">📎</a></td><td><code>propsData</code></td><td>컴포넌트가 마운트될 때 전달할 Props를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#listeners" target="_blank" rel="noopener">📎</a></td><td><code>listeners</code></td><td>부모 컴포넌트에게 받을 <a href="https://kr.vuejs.org/v2/api/index.html#vm-listeners" target="_blank" rel="noopener"><code>$listeners</code> 객체</a>를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#parentcomponent" target="_blank" rel="noopener">📎</a></td><td><code>parentComponent</code></td><td>부모 컴포넌트를 지정</td><td>Object</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/options.html#provide" target="_blank" rel="noopener">📎</a></td><td><code>provide</code></td><td><a href="https://vuejs.org/v2/api/#provide-inject" target="_blank" rel="noopener"><code>inject</code>에서 사용될 <code>provide</code> 객체</a>를 지정</td><td>Object</td><td></td></tr></tbody></table><h3><span id="023c7748-b183-4c36-97bf-ee16604d7ca7">createLocalVue</span><a href="#023c7748-b183-4c36-97bf-ee16604d7ca7" class="header-anchor"></a></h3><p>테스트에서 사용할 로컬 Vue 클래스를 반환합니다.<br>전역 Vue 클래스를 오염시키지 않고 믹스인, 플러그인 등을 추가할 때 사용할 수 있는 일종의 가짜 Vue 객체입니다.</p><p>예를 들어 컴포넌트에서 VueRouter의 <code>&lt;router-link&gt;</code>나 Vuex(Store)의 <code>state</code>, <code>actions</code> 등을 사용하고 있다면, 다음 예제와 같이 연결할 수 있습니다.<br>물론 실제 똑같은 환경을 만들지 않고 테스트를 위해 <code>&lt;router-link&gt;</code>를 스텁하거나(Stubbing) Store을 모의 객체로 대체(Mocking)할 수도 있습니다.<br>핵심은 테스트에서 Vue 컴포넌트가 동작할 수 있는 환경을 만드는 것입니다.</p><pre><code class="js">import { mount, createLocalVue } from &#39;@vue/test-utils&#39;import VueRouter from &#39;vue-router&#39;import Vuex from &#39;vuex&#39;import App from &#39;@/App.vue&#39;import router from &#39;@/router.js&#39;import store from &#39;@/store.js&#39;const localVue = createLocalVue()localVue.use(VueRouter)localVue.use(Vuex)const wrapper = mount(App, {  localVue,  router,  store})</code></pre><p>위 테스트 예제는 다음의 익숙한 설정과 비슷합니다.</p><pre><code class="js">import Vue from &#39;vue&#39;import VueRouter from &#39;vue-router&#39;import Vuex from &#39;vuex&#39;import App from &#39;./App.vue&#39;import router from &#39;./router.js&#39;import store from &#39;./store.js&#39;Vue.use(VueRouter)Vue.use(Vuex)new Vue({  router,  store,  render: h =&gt; h(App)}).$mount(&#39;#app&#39;)</code></pre><h2><span id="725decea-a2ba-4f77-8671-55006a885467">Wrappers</span><a href="#725decea-a2ba-4f77-8671-55006a885467" class="header-anchor"></a></h2><p>테스트에서 <code>mount</code>나 <code>shallowMount</code>로 컴포넌트를 마운트하면 Wrapper 객체가 반환됩니다.</p><p>Wrapper 객체에서 사용할 수 있는 속성과 메소드를 정리합니다.<br>자세한 내용은 해당 멤버의 공식 문서를 참고하세요.</p><p>기본 사용법은 다음과 같습니다.<br><code>?</code>는 선택적(Optional) 인수를 의미합니다.</p><pre><code class="js">const wrapper = shallowMount(Component)const vm = wrapper.vmconst html = wrapper.html()expect(vm.msg).toBe(&#39;Hello Jest~&#39;)expect(html).toBe(&#39;&lt;h1 class=&quot;title&quot;&gt;Hello Jest~&lt;/h1&gt;&#39;)</code></pre><p>그리고 아래 정리된 메소드 중,<br><code>wrapper.find()</code>는 찾은 결과의 또 다른 Wrapper를 반환하고,<br><code>wrapper.findAll()</code>은 찾은 결과들의 WrapperArray(Wrapper의 배열)를 반환합니다.</p><p>Wrapper와 WrapperArray는 사용 가능한 멤버가 일부 다르므로 ‘구분’에 정리했습니다.<br>구분이 비어있는 것은 Wrapper 전용입니다!</p><table><thead><tr><th></th><th>멤버</th><th>설명</th><th>구분</th></tr></thead><tbody><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#properties" target="_blank" rel="noopener">📎</a></td><td><code>.vm</code></td><td>Vue 인스턴스를 반환(읽기 전용)</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#properties" target="_blank" rel="noopener">📎</a></td><td><code>.element</code></td><td>루트 요소를 반환(읽기 전용)</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper-array/#properties" target="_blank" rel="noopener">📎</a></td><td><code>.wrappers</code></td><td>찾은 결과의 모든 Wrapper의 배열을 반환(읽기 전용)</td><td>WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper-array/#properties" target="_blank" rel="noopener">📎</a></td><td><code>.length</code></td><td>배열 길이를 반환(읽기 전용)</td><td>WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper-array/#at" target="_blank" rel="noopener">📎</a></td><td><code>.at(숫자)</code></td><td>Zero based 배열 인덱싱(음수는 뒤에서부터), Wrapper를 반환</td><td>WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#attributes" target="_blank" rel="noopener">📎</a></td><td><code>.attributes(속성?)</code></td><td>루트 요소의 HTML 속성 객체 혹은 속성값을 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#classes" target="_blank" rel="noopener">📎</a></td><td><code>.classes(클래스?)</code></td><td>루트 요소의 HTML 클래스 속성값의 배열 혹은 속성값의 유무 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#contains" target="_blank" rel="noopener">📎</a></td><td><code>.contains(선택자 &#124; 컴포넌트)</code></td><td>특정 CSS선택자 혹은 컴포넌트가 포함되어 있는지 확인</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#destroy" target="_blank" rel="noopener">📎</a></td><td><code>.destroy()</code></td><td>Vue 인스턴스를 제거(<code>beforeDestroy</code>와 <code>destroyed</code> 라이프사이클 Hook이 동작)</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#emitted" target="_blank" rel="noopener">📎</a></td><td><code>.emitted()</code></td><td><code>$emit(이름, 값)</code>된 결과 객체를 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#emittedbyorder" target="_blank" rel="noopener">📎</a></td><td><code>.emittedByOrder()</code></td><td><code>$emit(이름, 값)</code>된 결과 객체를 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#exists" target="_blank" rel="noopener">📎</a></td><td><code>.exists()</code></td><td>Wrapper(<code>.find()</code>)나 WrapperArray(<code>.findAll()</code>)의 존재 여부를 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#find" target="_blank" rel="noopener">📎</a></td><td><code>.find(선택자 &#124; 컴포넌트)</code></td><td>특정 CSS선택자 혹은 컴포넌트의 Wrapper를 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper-array/#filter" target="_blank" rel="noopener">📎</a></td><td><code>.filter(숫자)</code></td><td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter" target="_blank" rel="noopener">Array.prototype.filter</a>와 유사하게 배열 필터</td><td>WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#findall" target="_blank" rel="noopener">📎</a></td><td><code>.findAll(선택자 &#124; 컴포넌트)</code></td><td>특정 CSS선택자 혹은 컴포넌트의 WrapperArray를 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#html" target="_blank" rel="noopener">📎</a></td><td><code>.html()</code></td><td>HTML을 문자열로 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#get" target="_blank" rel="noopener">📎</a></td><td><code>.get()</code></td><td><code>.find()</code>와 동일하게 작동, 존재하지 않으면 에러</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#is" target="_blank" rel="noopener">📎</a></td><td><code>.is(선택자 &#124; 컴포넌트)</code></td><td>특정 CSS선택자 혹은 컴포넌트와 일치하는지 확인</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#isempty" target="_blank" rel="noopener">📎</a></td><td><code>.isEmpty()</code></td><td>자식 요소가 존재하지 않는지 확인</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#isvisible" target="_blank" rel="noopener">📎</a></td><td><code>.isVisible()</code></td><td>(Deprecaed) 보이는 요소인지 확인(<code>display: none</code> 혹은 <code>visibility: hidden</code>이면 <code>false</code>)</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#isvueinstance" target="_blank" rel="noopener">📎</a></td><td><code>.isVueInstance()</code></td><td>(Deprecaed) Vue 인스턴스인지 확인</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#name" target="_blank" rel="noopener">📎</a></td><td><code>.name()</code></td><td>컴포넌트의 이름 혹은 루트 요소의 태그 명을 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#props" target="_blank" rel="noopener">📎</a></td><td><code>.props(Prop?)</code></td><td>Vue 인스턴스의 Props 객체를 반환</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#setchecked" target="_blank" rel="noopener">📎</a></td><td><code>.setChecked(체크_값?)</code></td><td>체크 값(Checked)을 input 요소(checkbox, radio)에 지정(반응성, 체크 값의 기본값은 <code>true</code>)</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#setdata" target="_blank" rel="noopener">📎</a></td><td><code>.setData(Data)</code></td><td>Vue 인스턴스의 Data를 업데이트(반응성)</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#setmethods" target="_blank" rel="noopener">📎</a></td><td><code>.setMethods(Methods)</code></td><td>(Deprecaed) Vue 인스턴스의 Methods를 업데이트(반응성)</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#setprops" target="_blank" rel="noopener">📎</a></td><td><code>.setProps(Props)</code></td><td>Vue 인스턴스의 Props를 업데이트(반응성)</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#setselected" target="_blank" rel="noopener">📎</a></td><td><code>.setSelected()</code></td><td>select/option 요소 선택(반응성)</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#setvalue" target="_blank" rel="noopener">📎</a></td><td><code>.setValue(값)</code></td><td>input 요소(text)의 값을 지정(반응성)</td><td>Wrapper / WrapperArray</td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#text" target="_blank" rel="noopener">📎</a></td><td><code>.text()</code></td><td>Text를 반환(<code>.innerText</code>와 비슷)</td><td></td></tr><tr><td><a href="https://vue-test-utils.vuejs.org/api/wrapper/#trigger" target="_blank" rel="noopener">📎</a></td><td><code>.trigger(이벤트, 옵션?)</code></td><td>비동기적으로 이벤트를 트리거</td><td>Wrapper / WrapperArray</td></tr></tbody></table><h1><span id="707645f5-c4c9-42c9-b06c-9903ca825fbc">테스트 더블(Test double)</span><a href="#707645f5-c4c9-42c9-b06c-9903ca825fbc" class="header-anchor"></a></h1><p>테스트에 대해서 학습하다 보면 Mock, Spy, Stub 같은 난해한 용어들이 나오기 시작하는데,<br>이런 용어들을 일반적으로 테스트 더블(Test double)이라고 합니다.<br>영화 제작의 스턴트 대역(Stunt double)이 실제 배우를 대신하는 것처럼 테스트를 위해 실제 객체를 대신하는 것이죠.</p><p>각각 의미가 조금씩 다르고 설명만으로 정확한 의도를 파악하기 어렵지만,<br>모두 크게는 ‘<strong>테스트를 위한 가짜 객체</strong>‘ 정도로 정리할 수 있습니다.<br>일부 문서에서는 혼용되기도 합니다.</p><table><thead><tr><th>용어</th><th>설명</th></tr></thead><tbody><tr><td>더미(Dummy)</td><td>실제로는 동작하지 않고 데이터를 채우기 위한 객체</td></tr><tr><td>스텁(Stub)</td><td>실제로는 동작하지 않지만, 동작하는 것처럼 보이기 위해 준비된 값만을 반환하는 객체</td></tr><tr><td>모조품(Fake)</td><td>실제로 동작하지만 프로덕트에는 적합하지 않은 객체</td></tr><tr><td>모의(Mock)</td><td>실제로 동작하지만 준비된 값만을 반환하는 객체</td></tr><tr><td>스파이(Spy)</td><td>호출 등 일부 정보를 기록해 다양한 상황을 감시하는 객체</td></tr></tbody></table><h1><span id="3f7566ae-3844-437f-93dd-863e3aa5c7fb">모의 함수 만들기(Mocking)</span><a href="#3f7566ae-3844-437f-93dd-863e3aa5c7fb" class="header-anchor"></a></h1><p>테스트 환경에서는 실제 구현한 함수를 그대로 사용할 수 없는 경우가 많습니다.<br>네트워크 비용이 발생하는 외부 API에 의존하는 함수라던가 함수 실행 후 불필요하게 많은 상태(데이터)가 변경되거나 등등 테스트 자체에 집중할 수 없게 방해되는 외부 요인들은 얼마든지 있습니다.</p><p>이때 필요한 것이 바로 모의(Mock) 함수입니다.<br>말 그대로 ‘가짜’이기 때문에 네트워크 비용 없이 바로 특정 결과를 반환하게 하거나 의도적으로 에러를 던지거나 함수가 몇 번 호출되었는지 감시하거나 등등 원하는 대로 마음껏 주무를 수 있습니다.<br>따라서 방해 요인을 최소화하고 온전히 테스트에만 집중할 수 있습니다.</p><p>개인적으로 테스트에서 처음 ‘Mock(모의)’란 단어를 접했을 때 이해하기 참 어려웠는데 사실은 ‘모의고사’, ‘모의실험’, ‘모의훈련’, ‘목업(Mock-up)’ 등 평소에 많이 접할 수 있는 단어입니다.</p><blockquote><p>[모의]: 실제의 것을 흉내 내어 그대로 해 봄.</p></blockquote><p>간단하게 실제 Todo API를 사용하여 컴포넌트를 구성해 봅시다.<br>다음은 Axios로 Todo 정보를 가져와서 화면에 제목(<code>title</code>)을 출력하는 예제입니다.</p><pre><code class="vue">&lt;!-- HelloJest.vue --&gt;&lt;template&gt;  &lt;div&gt;    {{ todo.title }}  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;import axios from &#39;axios&#39;export default {  data () {    return {      todo: {}    }  },  created () {    this.fetchTodo()  },  methods: {    async fetchTodo () {      // https://jsonplaceholder.typicode.com/      const { data } = await axios.get(&#39;https://jsonplaceholder.typicode.com/todos/1&#39;)      this.todo = data    }  }}&lt;/script&gt;</code></pre><p>다음은 실제 응답 결과의 <code>data</code> 값입니다.<br><code>title</code> 속성만 확인합니다!</p><pre><code class="json">{  &quot;completed&quot;: false,  &quot;id&quot;: 1,  &quot;title&quot;: &quot;delectus aut autem&quot;,  &quot;userId&quot;: 1}</code></pre><p>이제 다음과 같이 테스트를 작성합니다.<br>주의할 점은, 실제 요청/응답을 통해야 하므로 화면에 내용이 반영될 수 있는 대략적인 시간을 고려해야 합니다.<br>따라서 <code>setTimeout</code>을 사용해 1초 후 테스트하도록 작성했습니다.<br>네트워크가 속도가 느리면 2초, 3초 등으로 시간을 늘려야 할 수 있겠네요.</p><pre><code class="js">// HelloJest.test.jsimport { shallowMount } from &#39;@vue/test-utils&#39;import HelloJest from &#39;../HelloJest&#39;describe(&#39;HellJest&#39;, () =&gt; {  let wrapper  beforeEach(() =&gt; {    wrapper = shallowMount(HelloJest)  })  test(&#39;가져온 텍스트가 정상적으로 렌더링&#39;, done =&gt; {    setTimeout(() =&gt; {      expect(wrapper.text()).toBe(&#39;delectus aut autem&#39;)      done()    }, 1000) // 1초  })})</code></pre><p>일반적인 네트워크 속도라면 위 테스트는 통과합니다.<br>하지만 실제 요청/응답을 사용하기 때문에 만약 네트워크가 오프라인이면 테스트는 실패하며, 이는 외부 요인에 의한 실패가 됩니다.<br>또한, 네트워크가 온라인이지만 속도가 아주 느리거나 API 서버에 문제가 발생하거나 등등 다양한 외부 요인에 의해 테스트는 실패할 수 있습니다.</p><p>그렇다면 이런 외부 요인(네트워크 비용)을 제거하기 위해 <code>axios.get()</code>을 모의 함수로 만들어(Mocking) 필요한 값만 반환하게 만들어 봅시다.<br>실제 요청에서는 <code>title</code> 속성이 포함된 <code>todo</code> 객체를 응답받기 때문에 똑같이 작성합니다.<br>다음과 같이 수정합니다.</p><pre><code class="js">// HelloJest.test.jsimport { shallowMount } from &#39;@vue/test-utils&#39;import axios from &#39;axios&#39;import HelloJest from &#39;../HelloJest&#39;describe(&#39;HellJest&#39;, () =&gt; {  let wrapper  beforeEach(() =&gt; {    // 반환할 가짜 응답 결과    const response = {      data: {        title: &#39;delectus aut autem&#39;      }    }    // 컴포넌트가 마운트되기 전에 Mocking합니다.    // axios.get()은 Promise를 반환하기 때문에 mockResolvedValue를 사용합니다.    axios.get = jest.fn().mockResolvedValue(response)    // 위 코드는 다음과 같습니다.    // axios.get = jest.fn(() =&gt; new Promise(resolve =&gt; resolve(response)))    // 컴포넌트 마운트!    wrapper = shallowMount(HelloJest)  })  test(&#39;가져온 텍스트가 정상적으로 렌더링&#39;, () =&gt; {    // 네트워크에 의존할 필요가 없어서(모의 요청/응답이기 때문에) 더는 setTimeout이 필요하지 않습니다.    expect(wrapper.text()).toBe(&#39;delectus aut autem&#39;)  })})</code></pre><p>이어서 설명합니다.</p><h2><span id="b16182a1-7c4f-4d77-b930-5c61942084ee">jest.fn()</span><a href="#b16182a1-7c4f-4d77-b930-5c61942084ee" class="header-anchor"></a></h2><p>위에서 살펴본 것과 같이 <code>jest.fn()</code>은 모의 함수를 반환하는데, 이 반환될 모의 함수의 구현을 함께 작성할 수 있습니다.<br>또한 모의 함수는 기본적으로 호출이 감시되며, 추가로 사용할 수 있는 여러 메소드도 가지고 있습니다.</p><p>우선, 위에서 작성했던 예제를 기준으로 함수 구현 작성에 대해서 알아봅시다.<br>실제로 <code>axios.get</code>은 비동기로 데이터를 가져오며, Promise 객체를 반환합니다.<br>우리는 <code>axios.get</code>을 네트워크 비용을 사용하지 않도록 모의 함수로 만들지만, 컴포넌트의 로직이 동작해야 하니 똑같이 Promise가 반환되는 구조를 만들어줘야 합니다.<br>따라서 다음과 같이 <code>jest.fn()</code>의 첫 번째 인수로 Promise가 반환될 수 있는 콜백 함수를 만들어 줍니다.<br><code>jest.fn()</code>은 모의 함수를 반환하기 때문에 <code>.mockImplementation()</code>을 사용할 수 있습니다.</p><blockquote><p>모의 함수에 사용할 수 있는 여러 메소드는 아래에서 정리합니다.</p></blockquote><pre><code class="js">axios.get = jest.fn(() =&gt; new Promise(resolve =&gt; resolve(response)))// Or// axios.get = jest.fn().mockImplementation(() =&gt; new Promise(resolve =&gt; resolve(response)))</code></pre><p>특별한 구현이 없고 원하는 값(Promise)만 반환하면 되기 때문에,<br>위 방법을 <code>.mockResolvedValue()</code> 메소드로 대체할 수 있습니다.</p><pre><code class="js">// axios.get = jest.fn(() =&gt; new Promise(resolve =&gt; resolve(response)))// Or// axios.get = jest.fn().mockImplementation(() =&gt; new Promise(resolve =&gt; resolve(response)))axios.get = jest.fn().mockResolvedValue(response)</code></pre><p>또한 모의 함수는 생성된 후 바로 호출이 감시되는데,<br><code>axios.get</code>은 컴포넌트의 <code>fetchTodo</code>에서, <code>fetchTodo</code>는 <code>created</code> Hook에서 호출되기 때문에,<br>결국 다음과 같이 호출 여부를 확인할 수 있습니다.<br>(<code>axios.get</code>은 결과적으로 <code>created</code> Hook에서 호출되기 때문에 꼭 Mounting 전에 모의 함수로 만들어야 합니다)</p><pre><code class="js">// HelloJest.test.jsdescribe(&#39;HellJest&#39;, () =&gt; {  let wrapper  beforeEach(() =&gt; {    // ...    // axios.get = jest.fn(() =&gt; new Promise(resolve =&gt; resolve(response)))    axios.get = jest.fn().mockResolvedValue(response)        wrapper = shallowMount(HelloJest)  })  test(&#39;가져오기 호출 확인&#39;, () =&gt; {    // 모의 함수의 호출 여부를 확인합니다.    expect(axios.get).toHaveBeenCalled() // Passes  })})</code></pre><h2><span id="3584efac-0062-4f41-9078-7f680b4551b5">jest.spyOn()</span><a href="#3584efac-0062-4f41-9078-7f680b4551b5" class="header-anchor"></a></h2><p><code>jest.spyOn()</code>은 <strong>실제 함수를 모의 함수로 덮어쓰지 않고도 호출 감시</strong>를 목적으로 아주 유용하게 사용할 수 있습니다.</p><p>기존 예제에서 컴포넌트는 <code>fetchTodo</code>를 <code>created</code> Hook에서 호출해 원하는 데이터를 가져옵니다.<br>Mounting 이후 <code>fetchTodo</code>가 호출되었는지 확인하기 위해 다음과 같이 <code>jest.spyOn()</code>을 사용합니다.<br>(<code>fetchTodo</code>가 <code>created</code> Hook에서 호출되기 때문에 꼭 Mounting 전에 감시를 시작해야 합니다)</p><blockquote><p>감시 대상(메소드)은 곧 모의 함수입니다.</p></blockquote><pre><code class="js">const spy = jest.spyOn(객체, &#39;메소드 이름&#39;)</code></pre><pre><code class="js">// HelloJest.test.jsdescribe(&#39;HellJest&#39;, () =&gt; {  let wrapper  const spy = {}  beforeEach(() =&gt; {    // ...    // 컴포넌트가 마운트되기 전에 `fetchTodo` 메소드에 스파이를 심어 감시합니다.    spy.fetchTodo = jest.spyOn(HelloJest.methods, &#39;fetchTodo&#39;)    wrapper = shallowMount(HelloJest)  })  test(&#39;가져오기 호출 확인&#39;, () =&gt; {    // 스파이를 통해 호출 여부를 확인할 수 있습니다.    expect(spy).toHaveBeenCalled() // Passes  })})</code></pre><p>만약 기존 예제의 <code>fetchTodo</code>를 테스트를 위해 다르게 구현해야 한다면 다음 예제와 같이 작성할 수 있습니다.<br><code>jest.fn()</code>과 같이 <code>jest.spyOn()</code>도 <strong>모의 함수를 반환</strong>하기 때문에 <code>.mockImplementation()</code>을 사용할 수 있습니다.</p><pre><code class="js">// HelloJest.test.jsdescribe(&#39;HellJest&#39;, () =&gt; {  let wrapper  const spy = {}  beforeEach(() =&gt; {    // ...    // 스파이를 심으면서 모의 구현 부를 만듭니다.    spy.fetchTodo = jest.spyOn(HelloJest.methods, &#39;fetchTodo&#39;).mockImplementation(num =&gt; num + 1)    wrapper = shallowMount(HelloJest)  })  test(&#39;가져오기 호출 확인&#39;, () =&gt; {    expect(spy.fetchTodo).toHaveBeenCalled() // Passes    expect(wrapper.vm.fetchTodo(1)).toBe(2) // Passes  })})</code></pre><p>한 가지 더 설명하자면, <code>spy.fetchTodo</code>는 이전 테스트(<code>&#39;가져오기 호출 확인&#39;</code>)를 통해 호출 정보가 추가되었기 때문에 다음 테스트에 영향을 주게 됩니다.<br>따라서 다음과 같이 <code>afterEach</code> Hook에서 <code>jest.clearAllMocks()</code> 등을 사용해 모의 함수 호출 정보를 초기화하는 것이 좋습니다.</p><pre><code class="js">// HelloJest.test.jsdescribe(&#39;HellJest&#39;, () =&gt; {  let wrapper  const spy = {}  beforeEach(() =&gt; {    // ...    spy.fetchTodo = jest.spyOn(HelloJest.methods, &#39;fetchTodo&#39;).mockImplementation(num =&gt; num + 1)    wrapper = shallowMount(HelloJest)  })  afterEach(() =&gt; {    // 모든 모의 함수의 호출 정보를 초기화합니다.    jest.clearAllMocks()    // 원하는 모의 함수만 초기화하고 싶은 경우,    // spy.fetchTodo.mockClear()  })  test(&#39;가져오기 호출 확인&#39;, () =&gt; {    expect(spy.fetchTodo).toHaveBeenCalled() // Passes    expect(wrapper.vm.fetchTodo(1)).toBe(2) // Passes  })  test(&#39;가져오기 호출 확인 다음 테스트&#39;, () =&gt; {    // 모의 함수의 호출 정보를 초기화하지 않으면 호출 횟수는 1이 아닌 3이 됩니다.    expect(spy.fetchTodo),toHaveBeenCalledTimes(1) // Passes  })})</code></pre><h2><span id="5d9bbecf-0dd6-495e-8643-ec4c6973da88">Mock functions</span><a href="#5d9bbecf-0dd6-495e-8643-ec4c6973da88" class="header-anchor"></a></h2><p><code>jest.fn()</code>와 <code>jest.spyOn()</code>를 통해 반환된 모의 함수는 다음과 같은 여러 메소드를 사용할 수 있습니다.<br>위에서 살펴본 익숙한 메소드들도 보입니다.</p><table><thead><tr><th></th><th>멤버</th><th>설명</th></tr></thead><tbody><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockcalls" target="_blank" rel="noopener">📎</a></td><td><code>.mock.calls</code></td><td>모의 함수의 호출 정보 배열<br>(인수를 포함)</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockresults" target="_blank" rel="noopener">📎</a></td><td><code>.mock.results</code></td><td>모의 함수가 호출된 결과 정보 배열<br>(결과 타입과 반환 값을 포함)</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockinstances" target="_blank" rel="noopener">📎</a></td><td><code>.mock.instances</code></td><td>모의 함수로 만들어진 인스턴스들의 배열<br>(모의 함수가 생성자인 경우)</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfngetmockname" target="_blank" rel="noopener">📎</a></td><td><code>.getMockName()</code></td><td>모의 함수의 이름을 반환<br>(기본값 <code>&quot;jest.fn()&quot;</code>)</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmocknamevalue" target="_blank" rel="noopener">📎</a></td><td><code>.mockName(이름)</code></td><td>모의 함수의 이름을 지정</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockclear" target="_blank" rel="noopener">📎</a></td><td><code>.mockClear()</code></td><td>모의 함수의 호출 정보(<code>.mock.xxx</code>) 배열을 초기화</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockreset" target="_blank" rel="noopener">📎</a></td><td><code>.mockReset()</code></td><td>모의 함수의 모든 상태를 초기화</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockrestore" target="_blank" rel="noopener">📎</a></td><td><code>.mockRestore()</code></td><td>모의 함수의 모든 상태를 초기화하고 실제 함수로 복원<br>(<code>jest.spyOn()</code>을 통한 구현만 복원 가능)</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockreturnthis" target="_blank" rel="noopener">📎</a></td><td><code>.mockReturnThis()</code></td><td>모의 함수가 <code>this</code>를 반환</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockimplementationfn" target="_blank" rel="noopener">📎</a></td><td><code>.mockImplementation(구현)</code></td><td>모의 함수의 구현 부를 작성</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockreturnvaluevalue" target="_blank" rel="noopener">📎</a></td><td><code>.mockReturnValue(값)</code></td><td>모의 함수가 지정한 <code>값</code>을 반환</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockresolvedvaluevalue" target="_blank" rel="noopener">📎</a></td><td><code>.mockResolvedValue(값)</code></td><td>비동기 모의 함수가 이행(Resolved)되면 지정한 <code>값</code>을 반환</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockrejectedvaluevalue" target="_blank" rel="noopener">📎</a></td><td><code>.mockRejectedValue(값)</code></td><td>비동기 모의 함수가 거부(Rejected)되면 지정한 <code>값</code>을 반환</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockimplementationoncefn" target="_blank" rel="noopener">📎</a></td><td><code>.mockImplementationOnce(구현)</code></td><td>(한 번만) 모의 함수의 구현 부를 작성</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockreturnvalueoncevalue" target="_blank" rel="noopener">📎</a></td><td><code>.mockReturnValueOnce(값)</code></td><td>(한 번만) 모의 함수가 지정한 <code>값</code>을 반환</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockresolvedvalueoncevalue" target="_blank" rel="noopener">📎</a></td><td><code>.mockResolvedValueOnce(값)</code></td><td>(한 번만) 비동기 모의 함수가 이행(Resolved)되면 지정한 <code>값</code>을 반환</td></tr><tr><td><a href="https://jestjs.io/docs/en/mock-function-api#mockfnmockrejectedvalueoncevalue" target="_blank" rel="noopener">📎</a></td><td><code>.mockRejectedValueOnce(값)</code></td><td>(한 번만) 비동기 모의 함수가 거부(Rejected)되면 지정한 <code>값</code>을 반환</td></tr></tbody></table><h3><span id="0596326a-cd11-4f70-a17a-0138b3cc2d33">mockClear</span><a href="#0596326a-cd11-4f70-a17a-0138b3cc2d33" class="header-anchor"></a></h3><p><code>.mockClear()</code>는 모의 함수 호출에 대한 모든 정보를 초기화합니다.<br>호출 정보는 <code>.mock.calls</code>와 <code>.mock.results</code>에 배열로 저장됩니다.</p><pre><code class="js">const module = {  api: value =&gt; value + &#39;DEF&#39;}test(&#39;mockClear&#39;, () =&gt; {  jest.spyOn(module, &#39;api&#39;)  // Or  // module.api = jest.fn().mockImplementation(module.api)  console.log(module.api.mock.calls) // []  console.log(module.api.mock.results) // []  module.api(&#39;ABC&#39;) // 호출됨  expect(module.api).toHaveBeenCalledTimes(1) // Passes  console.log(module.api.mock.calls) // [[&#39;ABC&#39;]]  console.log(module.api.mock.results) // [{ type: &#39;return&#39;, value: &#39;ABCDEF&#39; }]  module.api.mockClear() // 모의 함수의 호출 정보 초기화  expect(module.api).toHaveBeenCalledTimes(0) // Passes  console.log(module.api.mock.calls) // []  console.log(module.api.mock.results) // []})</code></pre><h3><span id="4359d2e0-bcf2-4f67-b81f-cbc24d9dfb30">mockReset</span><a href="#4359d2e0-bcf2-4f67-b81f-cbc24d9dfb30" class="header-anchor"></a></h3><p><code>.mockReset()</code>은 모의 함수의 모든 상태를 초기화합니다.<br>여기서 상태는 모든 호출 정보 및 모의 함수 구현 부를 포함합니다.<br>따라서 초기화된 모의 함수는 <code>undefined</code>가 반환됩니다.</p><blockquote><p>.mockReset() = .mockClear() + Reset</p></blockquote><pre><code class="js">const module = {  api: value =&gt; value + &#39;DEF&#39;}test(&#39;mockReset&#39;, () =&gt; {  jest.spyOn(module, &#39;api&#39;).mockImplementation(() =&gt; &#39;Fake value..&#39;)  // Or  // module.api = jest.fn().mockImplementation(() =&gt; &#39;Fake value..&#39;)  expect(module.api(&#39;ABC&#39;)).toBe(&#39;Fake value..&#39;) // 호출됨 &amp; Passes  expect(module.api).toHaveBeenCalledTimes(1) // Passes  console.log(module.api.mock.calls) // [[&#39;ABC&#39;]]  console.log(module.api.mock.results) // [{ type: &#39;return&#39;, value: &#39;Fake value..&#39; }]  module.api.mockReset() // 모의 함수 초기화  expect(module.api(123)).toBe(undefined) // 호출됨 &amp; Passes  expect(module.api).toHaveBeenCalledTimes(1) // Passes  console.log(module.api.mock.calls) // [[123]]  console.log(module.api.mock.results) // [{ type: &#39;return&#39;, value: undefined }]})</code></pre><h3><span id="dbee715c-56ad-4958-a771-dc556142ea9d">mockRestore</span><a href="#dbee715c-56ad-4958-a771-dc556142ea9d" class="header-anchor"></a></h3><p><code>.mockRestore()</code>는 모의 함수의 모든 상태를 초기화하고, 모의 함수 구현을 원래의 함수 구현으로 복원(restore)합니다.<br>단, <code>jest.spyOn()</code>을 사용한 경우만 복원할 수 있습니다.</p><blockquote><p>.mockRestore() = .mockReset() + Restore</p></blockquote><pre><code class="js">const module = {  api: value =&gt; value + &#39;DEF&#39;}test(&#39;mockRestore&#39;, () =&gt; {  jest.spyOn(module, &#39;api&#39;).mockImplementation(() =&gt; &#39;Fake value..&#39;) // Only `jest.spyOn()`  expect(module.api(&#39;ABC&#39;)).toBe(&#39;Fake value..&#39;) // 호출됨 &amp; Passes  expect(module.api).toHaveBeenCalledTimes(1) // Passes  module.api.mockRestore() // 모의 함수 초기화 및 구현 부를 복원합니다.  expect(module.api(&#39;ABC&#39;)).toBe(&#39;ABCDEF&#39;) // 호출됨 &amp; Passes  expect(module.api).toHaveBeenCalledTimes(1) // Passes})</code></pre><h1><span id="c26d0d84-5c26-4995-b153-a6cacdd6dcd8">참고자료</span><a href="#c26d0d84-5c26-4995-b153-a6cacdd6dcd8" class="header-anchor"></a></h1><p><a href="https://jestjs.io/docs/en/getting-started" target="_blank" rel="noopener">https://jestjs.io/docs/en/getting-started</a><br><a href="https://vue-test-utils.vuejs.org/guides/" target="_blank" rel="noopener">https://vue-test-utils.vuejs.org/guides/</a><br><a href="https://medium.com/@syalot005006/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%8B%A4%EC%88%98-%EA%B3%84%EC%82%B0-%EC%98%A4%EB%A5%98-a72ec3326b50" target="_blank" rel="noopener">https://medium.com/@syalot005006/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%8B%A4%EC%88%98-%EA%B3%84%EC%82%B0-%EC%98%A4%EB%A5%98-a72ec3326b50</a><br><a href="https://martinfowler.com/bliki/TestDouble.html" target="_blank" rel="noopener">https://martinfowler.com/bliki/TestDouble.html</a></p>]]></content>
    
    <summary type="html">
    
      Vue Test Utils(VTU)는 Vue.js 환경에서 단위 테스트를 하기 위한 공식(Official) 라이브러리입니다. Jest는 페이스북에서 만든 테스트 프레임워크로 Vue Test Utils에서 권장하는 테스트 러너입니다. 두 가지 오픈 소스를 이용해 Vue 애플리케이션의 테스트를 진행합니다.
    
    </summary>
    
    
      <category term="jest" scheme="https://heropy.blog/tags/jest/"/>
    
      <category term="vue-test-utils" scheme="https://heropy.blog/tags/vue-test-utils/"/>
    
      <category term="unit test" scheme="https://heropy.blog/tags/unit-test/"/>
    
  </entry>
  
  <entry>
    <title>이미 사용 중인 MongoDB 인스턴스 종료하기</title>
    <link href="https://heropy.blog/2020/02/08/mongodb-another-instance-is-already-running/"/>
    <id>https://heropy.blog/2020/02/08/mongodb-another-instance-is-already-running/</id>
    <published>2020-02-07T15:00:00.000Z</published>
    <updated>2020-03-31T13:43:50.876Z</updated>
    
    <content type="html"><![CDATA[<p>간혹 시동 중이던 MongoDB가 비정상적으로 종료된 후 다시 MongoDB를 시동하기 위해 명령어를 삽입하면 다음과 같은 에러를 반환할 수 있습니다.</p><blockquote><p>Another mongod instance is already running on the ‘xxx’ directory,</p></blockquote><p><img src="/images/screenshot/mongodb-another-instance-is-already-running/mongodb-exception-error.png" alt="MongoDB"></p><p>이 경우 MongoDB 인스턴스로 사용되는 포트를 종료하면 쉽게 해결할 수 있습니다.<br>다음과 같이 (슈퍼 유저 권한으로) <code>kill</code> 명령에 프로세스 종료(TERM 시그널)할 PID(Process ID)를 입력합니다.</p><pre><code class="bash">$ sudo kill -TERM &lt;PID&gt;</code></pre><p>PID를 모르는 경우,<br>MongoDB 기본 포트 번호(27017)를 사용해 다음과 같이 <code>lsof</code> 명령으로 검색할 수 있습니다.<br><code>-i</code> 옵션으로 프로토콜(<code>TCP</code>)과 포트(<code>27017</code>)를 명시합니다.</p><pre><code class="bash">$ sudo lsof -i TCP:27017</code></pre><p>다음과 같이 PID를 찾을 수 있습니다.</p><p><img src="/images/screenshot/mongodb-another-instance-is-already-running/mongodb-search-by-port-number.png" alt="MongoDB"></p><p>하지만 포트 번호로 검색할 수 없는 경우,(포트 번호를 모르거나, 검색이 안되는 경우)<br>다음과 같이 <code>-s</code> 옵션을 추가해 TCP 상태가 <code>LISTEN</code>인 목록을 검색 후 COMMAND가 <code>mongod</code>인 항목을 찾습니다.</p><pre><code class="bash">$ sudo lsof -i TCP -s TCP:LISTEN</code></pre><p>역시 다음과 같이 MongoDB의 PID를 찾을 수 있습니다.</p><p><img src="/images/screenshot/mongodb-another-instance-is-already-running/mongodb-search-by-list.png" alt="MongoDB"></p><p>PID를 찾았다면 다음과 같이 포트를 종료합니다.</p><pre><code class="bash">$ sudo kill -TERM 12020</code></pre><h1 id="참고자료"><a href="#참고자료" class="headerlink" title="참고자료"></a>참고자료</h1><p><a href="https://medium.com/@balasubramanim/how-to-resolve-socketexception-address-already-in-use-mongodb-75fa8ea4a2a6" target="_blank" rel="noopener">https://medium.com/@balasubramanim/how-to-resolve-socketexception-address-already-in-use-mongodb-75fa8ea4a2a6</a><br><a href="http://man7.org/linux/man-pages/man8/lsof.8.html" target="_blank" rel="noopener">http://man7.org/linux/man-pages/man8/lsof.8.html</a><br><a href="https://www.lesstif.com/pages/viewpage.action?pageId=12943674" target="_blank" rel="noopener">https://www.lesstif.com/pages/viewpage.action?pageId=12943674</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;간혹 시동 중이던 MongoDB가 비정상적으로 종료된 후 다시 MongoDB를 시동하기 위해 명령어를 삽입하면 다음과 같은 에러를 반환할 수 있습니다.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Another mongod instance is already
      
    
    </summary>
    
    
      <category term="mongodb" scheme="https://heropy.blog/tags/mongodb/"/>
    
  </entry>
  
  <entry>
    <title>한눈에 보는 타입스크립트(updated)</title>
    <link href="https://heropy.blog/2020/01/27/typescript/"/>
    <id>https://heropy.blog/2020/01/27/typescript/</id>
    <published>2020-01-26T15:00:00.000Z</published>
    <updated>2021-05-04T11:17:01.188Z</updated>
    
    <content type="html"><![CDATA[<h1 id="변경사항"><a href="#변경사항" class="headerlink" title="변경사항"></a>변경사항</h1><h2 id="2020년-2월"><a href="#2020년-2월" class="headerlink" title="2020년 2월"></a>2020년 2월</h2><ul><li>다음 파트들을 추가했습니다.<ul><li>keyof &lt;인터페이스(Interface)/인덱싱 가능 타입(Indexable Types)&gt;</li><li>타입 별칭(Type Aliases)</li></ul></li><li>일부 내용과 오타 등을 수정했습니다.</li></ul><h2 id="2020년-3월"><a href="#2020년-3월" class="headerlink" title="2020년 3월"></a>2020년 3월</h2><ul><li>다음의 파트들을 추가했습니다.<ul><li>알 수 없는 타입(Unknown) &lt;타입 기본(Types)/타입 선언&gt;</li><li>인터섹션(Intersection) &lt;타입 기본(Types)/타입 선언&gt;</li><li>함수 타입 &lt;인터페이스(Interface)&gt;</li><li>클래스 타입 &lt;인터페이스(Interface)&gt;</li><li>인터페이스 확장 &lt;인터페이스(Interface)&gt;</li><li>함수</li><li>this &lt;함수&gt;</li><li>명시적 this &lt;함수&gt;</li><li>오버로드(Overloads) &lt;함수&gt;</li></ul></li><li>목차 흐름을 위해 ‘인덱스 시그니처(Index signature)’ 파트 제목을 삭제했습니다.(내용은 삭제하지 않았습니다)</li><li>일부 내용과 오타 등을 수정했습니다.</li></ul><h2 id="2020년-4월"><a href="#2020년-4월" class="headerlink" title="2020년 4월"></a>2020년 4월</h2><ul><li>다음의 파트들을 추가했습니다.<ul><li>TS Node &lt;개발환경&gt;</li><li>모듈</li><li>내보내기(export)와 가져오기(import) &lt;모듈&gt;</li><li>모듈의 타입 선언(Ambient module declaration) &lt;모듈&gt;</li><li>Definitely Typed(@types) &lt;모듈&gt;</li><li>typeRoots와 types 옵션 &lt;모듈&gt;</li></ul></li><li>컴파일 옵션에 대한 여러 링크를 추가했습니다.</li><li>일부 내용과 오타 등을 수정했습니다.</li></ul><h2 id="2020년-6월"><a href="#2020년-6월" class="headerlink" title="2020년 6월"></a>2020년 6월</h2><ul><li>다음의 파트들을 추가했습니다.<ul><li>제약 조건(Constraints) &lt;제네릭(Generic)&gt;</li><li>조건부 타입(Conditional Types) &lt;제네릭(Generic)&gt;</li><li>infer &lt;제네릭(Generic)&gt;</li><li>Partial &lt;TS 유틸리티 타입&gt;</li><li>Required &lt;TS 유틸리티 타입&gt;</li><li>Readonly &lt;TS 유틸리티 타입&gt;</li><li>Record &lt;TS 유틸리티 타입&gt;</li><li>Pick &lt;TS 유틸리티 타입&gt;</li><li>Omit &lt;TS 유틸리티 타입&gt;</li><li>Exclude &lt;TS 유틸리티 타입&gt;</li><li>Extract &lt;TS 유틸리티 타입&gt;</li><li>NonNullable &lt;TS 유틸리티 타입&gt;</li><li>Parameters &lt;TS 유틸리티 타입&gt;</li><li>ConstructorParameters &lt;TS 유틸리티 타입&gt;</li><li>ReturnType &lt;TS 유틸리티 타입&gt;</li><li>InstanceType &lt;TS 유틸리티 타입&gt;</li><li>ThisParameterType &lt;TS 유틸리티 타입&gt;</li><li>OmitThisParameter &lt;TS 유틸리티 타입&gt;</li><li>ThisType &lt;TS 유틸리티 타입&gt;</li></ul></li><li>‘타입 가드’ 파트에 <code>in</code> 연산자에 대한 내용을 추가했습니다.</li><li>일부 내용과 오타 등을 수정했습니다.</li></ul><div class="toc"><ul><li><a href="392efe52-50c7-4406-9cd8-1a7082820bf5">타입스크립트 개요</a><ul><li><a href="17b1bb31-164b-47ac-843b-b52b4d8aee21">왜 타입스크립트인가?</a></li><li><a href="4dcd77fd-3175-4cc1-b8f6-170f25c893d9">타입스크립트 사용법</a></li><li><a href="458a8f82-f339-43af-afe9-21d12e5740b5">타입스크립트의 기능</a></li></ul></li><li><a href="25153559-6d2b-42cb-905b-0cc4e991e004">개발환경</a><ul><li><a href="08f4b793-8927-4a97-8794-a337dbd815bb">VSCode와 WebStorm</a></li><li><a href="0e7d4bfe-589e-42a6-a55f-c07c91485e00">컴파일러 설치</a><ul><li><a href="70609306-b12d-44c6-b75a-cbcf06318844">컴파일러 옵션</a></li></ul></li><li><a href="32740412-430d-48ee-bec5-28d75eefe4b3">TypeScript Playground</a></li><li><a href="fa1dbeb8-58dd-419d-9a3c-e5611e8a72ff">Repl.it</a></li><li><a href="26fe8551-f507-47c1-9455-9383cd016e7c">Parcel</a></li><li><a href="be20720f-3539-4e9c-bfa6-2e221020a5cb">TS Node</a></li></ul></li><li><a href="3c684862-a69c-410f-a583-91ce74f0a792">타입 기본(Types)</a><ul><li><a href="ae9f418a-1f4b-44ff-9b01-ed4e72665cd0">타입 지정</a></li><li><a href="4cc1e1f3-993e-48f9-bd84-e251206f88d5">타입 에러</a></li><li><a href="520fab5f-18f7-46b9-bb6b-a44bd2b2bf54">타입 선언</a><ul><li><a href="35c17703-f159-482d-9fd8-20170cc4d257">불린: Boolean</a></li><li><a href="fa184662-37fe-4e42-a88c-932ba52e0c33">숫자: Number</a></li><li><a href="e345df80-c319-431f-a8bc-eaf8dd96a2f4">문자열: String</a></li><li><a href="b037708c-2452-44dd-ae1f-8aa1f640a145">배열: Array</a></li><li><a href="b9e42406-20ab-4967-b0d9-edbde3edb737">튜플: Tuple</a></li><li><a href="3c32325c-1c0d-4a03-a2d2-636d765cdb96">열거형: Enum</a></li><li><a href="07f0d81f-76f9-43f7-9aa2-352e8d6f5e7d">모든 타입: Any</a></li><li><a href="a5dad8f9-a787-4138-b635-d007f08a3f40">알 수 없는 타입: Unknown</a></li><li><a href="ff2d089c-bdc0-4b7f-8465-17782e61e392">객체: Object</a></li><li><a href="0b1b5a8c-5ae4-4b5f-ab95-a3de3648a916">Null과 Undefined</a></li><li><a href="68935ba2-87e2-4a47-8ec4-d5fb9575e6a7">Void</a></li><li><a href="142cba8c-4539-4317-8114-ed552101bc6d">Never</a></li><li><a href="615d1025-1a96-435b-bb8a-05882e6019f9">유니언(Union)</a></li><li><a href="56514367-689d-4ace-b0f8-3e34a4a90dce">인터섹션(Intersection)</a></li><li><a href="596ddd2e-a100-403a-8cab-4872dcca0d66">함수(Function)</a></li></ul></li><li><a href="6d8b6f3e-1fe6-4bc0-b713-4f4a9b8f94de">타입 추론(Inference)</a></li><li><a href="99ea03b0-5e79-44b2-8b7e-9f5c60d39d96">타입 단언(Assertions)</a><ul><li><a href="609cc130-b1a8-4886-b83e-0b510d3230de">Non-null 단언 연산자</a></li></ul></li><li><a href="435ddba7-cdc0-4b4f-97f2-9e59ad54ac16">타입 가드(Guards)</a></li></ul></li><li><a href="a40c1a59-3c02-44c0-929d-f10f8755c2e4">인터페이스(interface)</a><ul><li><a href="4c526ed8-1a5a-4283-b452-141a6cdbb6e4">읽기 전용 속성(Readonly properties)</a></li><li><a href="8d103835-6101-42d3-a4b2-7b3ad57c081b">함수 타입</a></li><li><a href="e07aeb19-b5bc-4ea2-a180-dcdc7edd2641">클래스 타입</a></li><li><a href="6159c981-ebd9-4c67-94ed-75b2a65ddcac">인덱싱 가능 타입(Indexable types)</a><ul><li><a href="037dda6b-6fe6-4ba3-9241-b17376d7c9c7">keyof</a></li></ul></li><li><a href="1bb79d5a-43f1-4f35-8569-b8229e019f0c">인터페이스 확장</a></li></ul></li><li><a href="1e186f94-561d-44f0-a5a7-485f24d87550">타입 별칭(Type Aliases)</a></li><li><a href="fa4c8277-05d5-46de-96bc-08829f8a2730">제네릭(Generic)</a><ul><li><a href="4e85e0e2-c766-4b71-9e0a-bc54404149e6">제약 조건(Constraints)</a></li><li><a href="b76a6645-d2be-4a59-beb1-e9b08c63a220">조건부 타입(Conditional Types)</a><ul><li><a href="6e48ee69-e26f-4ab5-aed2-8a940a904f01">infer</a></li></ul></li></ul></li><li><a href="6ad05084-17a4-472c-b143-9e30a3d10913">함수</a><ul><li><a href="99be3e90-c8b5-49c7-9647-e45ad955029f">this</a><ul><li><a href="c3d829e4-7c79-4a24-9ae3-73dd19b7fbe5">명시적 this</a></li></ul></li><li><a href="134bab2c-5ca7-4df6-b727-11d9be6ce1bd">오버로드(Overloads)</a></li></ul></li><li><a href="bbbfc116-8ff2-42ca-87ad-e96452856f80">클래스</a><ul><li><a href="a2e52b48-574b-4c7f-90c3-8b5b2310fcd2">클래스 수식어(Modifiers)</a></li><li><a href="52b81c3d-1be2-4ff9-b1d7-6a417060e9de">추상(Abstract) 클래스</a></li></ul></li><li><a href="2bc73bb2-5866-430a-9317-1f02743f5208">Optional</a><ul><li><a href="d873cc52-2596-4210-8e71-d9ef89b5559f">매개 변수(Parameters)</a></li><li><a href="cedbabd1-f4ba-4cff-89a2-ee6034c461ea">속성과 매소드(Properties and Methods)</a></li><li><a href="979e3adb-e58b-42bc-85c7-630bccce8bff">체이닝(Chaining)</a></li><li><a href="836a4610-2545-4cf4-971a-e729850ba7cf">Nullish 병합 연산자</a></li></ul></li><li><a href="436b5c21-5d10-4d75-bc83-2ce182ccec29">모듈</a><ul><li><a href="935a20d1-740c-4b56-bf67-7e21274deab3">내보내기(export)와 가져오기(import)</a></li><li><a href="cdf941c1-19d9-4398-8d06-04d047f1d3c2">모듈의 타입 선언(Ambient module declaration)</a><ul><li><a href="0c7ffebc-a75e-47d0-be14-5b917c5e717a">Definitely Typed(@types)</a></li><li><a href="9901349e-5ff2-4194-903d-c9b95b5e5bb7">typeRoots와 types 옵션</a></li></ul></li></ul></li><li><a href="0b1cf61f-78d7-41ad-bad0-04f503369cfe">TS 유틸리티 타입</a><ul><li><a href="a066a2de-5158-4ecf-b81a-5e7b1959e044">Partial</a></li><li><a href="4108ecb6-8291-4f4d-b95e-98144e0dc520">Required</a></li><li><a href="b300c80a-b730-47f0-9947-be63f2ccf472">Readonly</a></li><li><a href="40c37b29-aa4d-4074-8a41-1a39dbf80d88">Record</a></li><li><a href="10d82899-caf8-4a70-a91e-01d91005f433">Pick</a></li><li><a href="a65891ae-83f3-4378-9fe3-f635f084add4">Omit</a></li><li><a href="3e647077-1830-431e-a024-8f77a5022fd9">Exclude</a></li><li><a href="881c8f60-13cb-41dc-93fb-5e766941c206">Extract</a></li><li><a href="58802eb2-b33b-4eef-8527-412e21ed9155">NonNullable</a></li><li><a href="f4f1bf6d-6b8c-42cf-81ca-c3a94ac67413">Parameters</a></li><li><a href="f6dadbcc-5a9f-4de4-a7b1-2103dc6364e0">ConstructorParameters</a></li><li><a href="ca2c341c-15d6-46ca-bbae-afbeca069200">ReturnType</a></li><li><a href="a86cfff2-09ed-40d1-931f-bb3d637174d5">InstanceType</a></li><li><a href="32b8bb23-cfd8-4fcc-9b42-fa34473672a8">ThisParameterType</a></li><li><a href="d953821c-5ccb-4c95-bf49-a227c28a67de">OmitThisParameter</a></li><li><a href="d44a1010-ee24-4ed7-a879-51dabcaff1dd">ThisType</a></li></ul></li><li><a href="dc7f7ff0-7a42-47ff-9cba-f3676988fd58">참고 자료(References)</a></li></ul></div><h1><span id="392efe52-50c7-4406-9cd8-1a7082820bf5">타입스크립트 개요</span><a href="#392efe52-50c7-4406-9cd8-1a7082820bf5" class="header-anchor"></a></h1><p><a href="https://www.typescriptlang.org/index.html" target="_blank" rel="noopener">타입스크립트(TypeScript)</a>는 Microsoft에서 개발하고 유지/관리하는 <a href="https://ko.wikipedia.org/wiki/%EC%95%84%ED%8C%8C%EC%B9%98_%EB%9D%BC%EC%9D%B4%EC%84%A0%EC%8A%A4" target="_blank" rel="noopener">Apache 라이센스</a>가 부여된 오픈 소스입니다.<br>일반 자바스크립트로 컴파일되는 자바스크립트 Superset(상위 호환)으로 2012년 10월에 처음 릴리스 되었습니다.</p><h2><span id="17b1bb31-164b-47ac-843b-b52b4d8aee21">왜 타입스크립트인가?</span><a href="#17b1bb31-164b-47ac-843b-b52b4d8aee21" class="header-anchor"></a></h2><p>C#과 Java 같은 체계적이고 정제된 언어들에서 사용하는 강한 타입 시스템은 높은 가독성과 코드 품질 등을 제공할 수 있고 런타임이 아닌 컴파일 환경에서 에러가 발생해 치명적인 오류들을 더욱더 쉽게 잡아낼 수 있습니다.</p><p>반면 자바스크립트는 타입 시스템이 없는 동적 프로그래밍 언어로, 자바스크립트 변수는 문자열, 숫자, 불린 등 여러 타입의 값을 가질 수 있습니다.<br>이를 약한 타입 언어라고 표현할 수 있으며 비교적 유연하게 개발할 수 있는 환경을 제공하는 한편 런타임 환경에서 쉽게 에러가 발생할 수 있는 단점을 가집니다.</p><p>그리고 타입스크립트는 이러한 자바스크립트에 강한 타입 시스템을 적용해 대부분의 에러를 컴파일 환경에서 코드를 입력하는 동안 체크할 수 있습니다.</p><h2><span id="4dcd77fd-3175-4cc1-b8f6-170f25c893d9">타입스크립트 사용법</span><a href="#4dcd77fd-3175-4cc1-b8f6-170f25c893d9" class="header-anchor"></a></h2><p>자바스크립트가 <code>.js</code> 확장자를 가진 파일로 작성되는 것과 같이 타입스크립트는 <code>.ts</code> 확장자를 가진 파일로 작성할 수 있고, 작성 후 타입스크립트 컴파일러를 통해 자바스크립트 파일로 컴파일하여 사용하게 됩니다.</p><pre><code class="bash">$ tsc sample.ts# compiled to `sample.js`</code></pre><h2><span id="458a8f82-f339-43af-afe9-21d12e5740b5">타입스크립트의 기능</span><a href="#458a8f82-f339-43af-afe9-21d12e5740b5" class="header-anchor"></a></h2><ul><li><strong>크로스 플랫폼 지원</strong>: 자바스크립트가 실행되는 모든 플랫폼에서 사용할 수 있습니다.</li><li><strong>객체 지향 언어</strong>: 클래스, 인터페이스, 모듈 등의 강력한 기능을 제공하며, 순수한 객체 지향 코드를 작성할 수 있습니다.</li><li><strong>정적 타입</strong>: 정적 타입을 사용하기 때문에 코드를 입력하는 동안에 오류를 체크할 수 있습니다.(단 에디터 혹은 플러그인의 도움의 필요)</li><li><strong>DOM 제어</strong>: 자바스크립트와 같이 DOM을 제어해 요소를 추가하거나 삭제할 수 있습니다.</li><li><strong>최신 ECMAScript 기능 지원</strong>: ES6 이상의 최신 자바스크립트 문법을 손쉽게 지원할 수 있습니다.</li></ul><h1><span id="25153559-6d2b-42cb-905b-0cc4e991e004">개발환경</span><a href="#25153559-6d2b-42cb-905b-0cc4e991e004" class="header-anchor"></a></h1><h2><span id="08f4b793-8927-4a97-8794-a337dbd815bb">VSCode와 WebStorm</span><a href="#08f4b793-8927-4a97-8794-a337dbd815bb" class="header-anchor"></a></h2><p><a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode(Visual Studio Code)</a>와 <a href="https://www.jetbrains.com/ko-kr/webstorm/" target="_blank" rel="noopener">WebStorm</a>은 타입스크립트 지원 기능이 내장되어 있기 때문에 별도의 설정 없이도 타입스크립트 파일을(<code>.ts</code>, <code>tsconfig.json</code> 등) 인식할 수 있고 코드 검사, 빠른 수정, 실행 및 디버깅 등의 다양한 기능을 바로 사용할 수 있습니다.<br>단, 컴파일러는 포함되어 있지 않기 때문에 별도로 설치해야 합니다.(E.g. <code>npm install typescript</code>)</p><p><img src="/images/screenshot/typescript/typescript-vscode-error.jpg" alt="VSCode Error"></p><div class="image-caption">Visual Studio Code</div><p><img src="/images/screenshot/typescript/typescript-webstorm-error.jpg" alt="WebStorm Error"></p><div class="image-caption">WebStorm</div><h2><span id="0e7d4bfe-589e-42a6-a55f-c07c91485e00">컴파일러 설치</span><a href="#0e7d4bfe-589e-42a6-a55f-c07c91485e00" class="header-anchor"></a></h2><p><code>tsc</code> 명령을 사용하기 위해 다음과 같이 타입스크립트를 전역 설치할 수 있습니다.<br>타입스크립트 파일을 경로로 지정하면 해당 파일을 컴파일합니다.</p><pre><code class="bash">$ npm install -g typescript$ tsc --version$ tsc ./src/index.ts</code></pre><p>혹은, 단일 프로젝트에서만 사용하길 희망하는 경우 일반 지역 설치 후 <code>npx tsc</code> 명령으로 실행할 수도 있습니다.</p><pre><code class="bash">$ npm install -D typescript$ npx tsc --version$ npx tsc ./src/index.ts</code></pre><h3><span id="70609306-b12d-44c6-b75a-cbcf06318844">컴파일러 옵션</span><a href="#70609306-b12d-44c6-b75a-cbcf06318844" class="header-anchor"></a></h3><p>타입스크립트 <a href="https://www.typescriptlang.org/docs/handbook/compiler-options.html" target="_blank" rel="noopener">컴파일을 위한 다양한 옵션</a>을 지정할 수 있습니다.</p><ul><li>공식 문서: <a href="https://www.typescriptlang.org/docs/handbook/compiler-options.html" target="_blank" rel="noopener">https://www.typescriptlang.org/docs/handbook/compiler-options.html</a></li><li>공식 문서 한글 번역: <a href="https://typescript-kr.github.io/pages/Compiler%20Options.html" target="_blank" rel="noopener">https://typescript-kr.github.io/pages/Compiler%20Options.html</a></li><li>vomvoru’s blog: <a href="https://vomvoru.github.io/blog/tsconfig-compiler-options-kr/" target="_blank" rel="noopener">https://vomvoru.github.io/blog/tsconfig-compiler-options-kr/</a></li></ul><pre><code class="bash">$ tsc ./src/index.ts --watch --strict true --target ES6 --lib ES2015,DOM --module CommonJS</code></pre><p>혹은 아래와 같이 <code>tsconfig.json</code> 파일로 옵션을 관리할 수 있습니다.<br><code>&quot;include&quot;</code>와 <code>&quot;exclude&quot;</code> 옵션을 같이 추가해, 컴파일에 <strong>포함할 경로</strong>와 <strong>제외할 경로</strong>를 설정할 수 있습니다.</p><blockquote><p>VScode와 WebStorm을 사용하는 경우, <code>tsconfig.json</code> 파일을 프로젝트 루트 경로에 생성하면 에디터에 의해 구성 옵션이 분석됩니다.</p></blockquote><pre><code class="json">{  &quot;compilerOptions&quot;: {    &quot;strict&quot;: true,    &quot;target&quot;: &quot;ES6&quot;,    &quot;lib&quot;: [&quot;ES2015&quot;, &quot;DOM&quot;],    &quot;module&quot;: &quot;CommonJS&quot;  },  &quot;include&quot;: [    &quot;src/**/*.ts&quot;  ],  &quot;exclude&quot;: [    &quot;node_modules&quot;  ]}</code></pre><pre><code class="bash">$ tsc --watch</code></pre><h2><span id="32740412-430d-48ee-bec5-28d75eefe4b3">TypeScript Playground</span><a href="#32740412-430d-48ee-bec5-28d75eefe4b3" class="header-anchor"></a></h2><p><a href="https://www.typescriptlang.org/play/index.html" target="_blank" rel="noopener">https://www.typescriptlang.org/play/index.html</a></p><p>타입스크립트 공식 페이지에서 제공하는 REPL로, 작성한 내용이 컴파일러 옵션에 따라 어떻게 자바스크립트로 변환되는지 바로 확인할 수 있습니다.</p><p><img src="/images/screenshot/typescript/typescript-playground.jpg" alt="typescript playground"></p><h2><span id="fa1dbeb8-58dd-419d-9a3c-e5611e8a72ff">Repl.it</span><a href="#fa1dbeb8-58dd-419d-9a3c-e5611e8a72ff" class="header-anchor"></a></h2><p><a href="https://repl.it/languages/typescript" target="_blank" rel="noopener">https://repl.it/languages/typescript</a></p><p>파일과 디렉터리로 관리되는 타입스크립트 프로젝트를 손쉽게 구성할 수 있습니다.<br>간단한 프로젝트로 타입스크립트를 테스트하기 좋습니다.</p><p><img src="/images/screenshot/typescript/typescript-repl.jpg" alt="repl.io - typescript"></p><h2><span id="26fe8551-f507-47c1-9455-9383cd016e7c">Parcel</span><a href="#26fe8551-f507-47c1-9455-9383cd016e7c" class="header-anchor"></a></h2><p>타입스크립트를 로컬 환경에서 빠르게 테스트하고 싶다면 <a href="https://ko.parceljs.org/getting_started.html" target="_blank" rel="noopener">Parcel 번들러</a>가 좋은 선택입니다.<br>다음과 같이 간단하게 프로젝트를 구성합니다.</p><pre><code class="bash">$ mkdir typescript-test$ cd typescript-test$ npm init -y$ npm install -D typescript parcel-bundler</code></pre><p><img src="/images/screenshot/typescript/typescript-test-with-parcel-bundler.jpg" alt="프로젝트 구조"></p><p><code>tsconfig.json</code> 파일을 생성하고 원하는 옵션을 추가합니다.<br>다음은 예시입니다.</p><pre><code class="json">{  &quot;compilerOptions&quot;: {    &quot;strict&quot;: true  },  &quot;exclude&quot;: [    &quot;node_modules&quot;  ]}</code></pre><p><code>main.ts</code> 파일을 생성하고 원하는 타입스크립트 코드를 입력합니다.</p><pre><code class="typescript">function add(a: number, b: number) {  return a + b;}const sum: number = add(1, 2);console.log(sum);</code></pre><p><code>index.html</code> 파일을 생성하고 다음과 같이 <code>.js</code>가 아닌 <code>.ts</code> 파일을 연결합니다.<br>Parcel 번들러가 빌드시 자동으로 타입스크립트를 컴파일합니다.</p><pre><code class="html">&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;  &lt;meta charset=&quot;UTF-8&quot;&gt;  &lt;title&gt;TypeScript Test&lt;/title&gt;&lt;/head&gt;&lt;body&gt;  &lt;script src=&quot;main.ts&quot;&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><p>마지막으로 다음과 같이 진입 파일로 <code>index.html</code>를 지정하고 Parcel 번들러로 빌드합니다.</p><pre><code class="bash">$ npx parcel index.html# Server running at http://localhost:1234</code></pre><h2><span id="be20720f-3539-4e9c-bfa6-2e221020a5cb">TS Node</span><a href="#be20720f-3539-4e9c-bfa6-2e221020a5cb" class="header-anchor"></a></h2><p>NodeJS 환경에서 테스트하고 싶다면 <a href="https://github.com/TypeStrong/ts-node" target="_blank" rel="noopener">TS Node</a>를 사용하세요.<br>다음과 같이 간단하게 프로젝트를 구성합니다.</p><pre><code class="bash">$ mkdir typescript-test$ cd typescript-test$ npm init -y$ npm install -D typescript @types/node ts-node</code></pre><blockquote><p><a href="https://github.com/DefinitelyTyped/DefinitelyTyped" target="_blank" rel="noopener"><code>@types/node</code></a>는 Node.js API를 위한 타입 선언 모듈입니다.<br><code>@types</code>에 대한 자세한 내용은 ‘모듈’ 파트를 참고하세요.</p></blockquote><p><img src="/images/screenshot/typescript/typescript-test-with-ts-node.jpg" alt="프로젝트 구조"></p><p><code>tsconfig.json</code> 파일을 생성하고 원하는 옵션을 추가합니다.<br>다음은 예시입니다.</p><pre><code class="json">{  &quot;compilerOptions&quot;: {    &quot;strict&quot;: true,    &quot;module&quot;: &quot;CommonJS&quot;  },  &quot;exclude&quot;: [    &quot;node_modules&quot;  ]}</code></pre><p><code>main.ts</code> 파일을 생성하고 원하는 타입스크립트 코드를 입력합니다.</p><pre><code class="typescript">console.log(&#39;TypeScript on NodeJS!&#39;);</code></pre><p>TS Node를 사용해 <code>main.ts</code>를 실행합니다.</p><pre><code class="bash">$ npx ts-node main.ts# TypeScript on NodeJS!</code></pre><h1><span id="3c684862-a69c-410f-a583-91ce74f0a792">타입 기본(Types)</span><a href="#3c684862-a69c-410f-a583-91ce74f0a792" class="header-anchor"></a></h1><h2><span id="ae9f418a-1f4b-44ff-9b01-ed4e72665cd0">타입 지정</span><a href="#ae9f418a-1f4b-44ff-9b01-ed4e72665cd0" class="header-anchor"></a></h2><p>타입스크립트는 일반 변수, 매개 변수(Parameter), 객체 속성(Property) 등에 <code>: TYPE</code>과 같은 형태로 타입을 지정할 수 있습니다.</p><pre><code class="typescript">function someFunc(a: TYPE_A, b: TYPE_B): TYPE_RETURN {  return a + b;}let some: TYPE_SOME = someFunc(1, 2);</code></pre><p>다음 예시를 보면,<br><code>add</code> 함수의 매개 변수 <code>a</code>와 <code>b</code>는 <code>number</code> 타입이어야 한다고 지정했고,<br>그렇게 실행된 함수의 반환 값은 숫자로 추론(Inference)되기 때문에 변수 <code>sum</code>도 <code>number</code> 타입이어야 한다고 지정했습니다.</p><pre><code class="typescript">function add(a: number, b: number) {  return a + b;}const sum: number = add(1, 2);console.log(sum); // 3</code></pre><p>자바스크립트로 컴파일한 결과는 다음과 같습니다.</p><pre><code class="js">&quot;use strict&quot;;function add(a, b) {  return a + b;}const sum = add(1, 2);console.log(sum);</code></pre><h2><span id="4cc1e1f3-993e-48f9-bd84-e251206f88d5">타입 에러</span><a href="#4cc1e1f3-993e-48f9-bd84-e251206f88d5" class="header-anchor"></a></h2><p>만약 다음과 같이 변수 <code>sum</code>을 <code>number</code>가 아닌 <code>string</code> 타입이어야 한다고 지정했다면, 컴파일조차 하지 않고 코드를 작성하는 시점에서 에러가 발생합니다.</p><pre><code class="typescript">function add(a: number, b: number) {  return a + b;}const sum: string = add(1, 2);console.log(sum);</code></pre><p><img src="/images/screenshot/typescript/typescript-error-ts2322.jpg" alt="Typescript error - TS2322"></p><div class="image-caption">TS2322 Error</div><p>위 이미지에서 <a href="https://www.google.com/search?newwindow=1&amp;sxsrf=ACYBGNQeWU6z1K7DXQ9oM0VR4gV-KPSd2Q%3A1580379601357&amp;ei=0a0yXpuyFZyCr7wPi96owAU&amp;q=TS2322&amp;oq=TS2322&amp;gs_l=psy-ab.3..35i39j0j0i203l4j0j0i203l3.15216190.15216190..15216895...0.0..0.114.321.1j2......0....2j1..gws-wiz.......0i67j0i7i30j0i7i10i30j0i5i30j0i5i10i30.1W9DJiS_O3c&amp;ved=0ahUKEwjb3v7ljKvnAhUcwYsBHQsvClgQ4dUDCAs&amp;uact=5" target="_blank" rel="noopener">TS2322</a>라는 에러 코드를 볼 수 있으며, 이를 검색하면 쉽게 에러 코드에 대한 정보를 얻을 수 있습니다.</p><p><img src="/images/screenshot/typescript/search-typescript-error-code.jpg" alt="Search Typescript error code"></p><h2><span id="520fab5f-18f7-46b9-bb6b-a44bd2b2bf54">타입 선언</span><a href="#520fab5f-18f7-46b9-bb6b-a44bd2b2bf54" class="header-anchor"></a></h2><h3><span id="35c17703-f159-482d-9fd8-20170cc4d257">불린: Boolean</span><a href="#35c17703-f159-482d-9fd8-20170cc4d257" class="header-anchor"></a></h3><p>단순한 참(<code>true</code>)/거짓(<code>false</code>) 값을 나타냅니다.</p><pre><code class="typescript">let isBoolean: boolean;let isDone: boolean = false;</code></pre><h3><span id="fa184662-37fe-4e42-a88c-932ba52e0c33">숫자: Number</span><a href="#fa184662-37fe-4e42-a88c-932ba52e0c33" class="header-anchor"></a></h3><p>모든 부동 소수점 값을 사용할 수 있습니다.<br>ES6에 도입된 2진수 및 8진수 리터럴도 지원합니다.</p><pre><code class="typescript">let num: number;let integer: number = 6;let float: number = 3.14;let hex: number = 0xf00d; // 61453let binary: number = 0b1010; // 10let octal: number = 0o744; // 484let infinity: number = Infinity;let nan: number = NaN;</code></pre><h3><span id="e345df80-c319-431f-a8bc-eaf8dd96a2f4">문자열: String</span><a href="#e345df80-c319-431f-a8bc-eaf8dd96a2f4" class="header-anchor"></a></h3><p>문자열을 나타냅니다.<br>작은따옴표(<code>&#39;</code>), 큰따옴표(<code>&quot;</code>) 뿐만 아니라 ES6의 템플릿 문자열도 지원합니다.</p><pre><code class="typescript">let str: string;let red: string = &#39;Red&#39;;let green: string = &quot;Green&quot;;let myColor: string = `My color is ${red}.`;let yourColor: string = &#39;Your color is&#39; + green;</code></pre><h3><span id="b037708c-2452-44dd-ae1f-8aa1f640a145">배열: Array</span><a href="#b037708c-2452-44dd-ae1f-8aa1f640a145" class="header-anchor"></a></h3><p>순차적으로 값을 가지는 일반 배열을 나타냅니다.<br>배열은 다음과 같이 두 가지 방법으로 타입을 선언할 수 있습니다.</p><pre><code class="typescript">// 문자열만 가지는 배열let fruits: string[] = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Mango&#39;];// Orlet fruits: Array&lt;string&gt; = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Mango&#39;];// 숫자만 가지는 배열let oneToSeven: number[] = [1, 2, 3, 4, 5, 6, 7];// Orlet oneToSeven: Array&lt;number&gt; = [1, 2, 3, 4, 5, 6, 7];</code></pre><p>유니언 타입(다중 타입)의 ‘문자열과 숫자를 동시에 가지는 배열’도 선언할 수 있습니다.</p><pre><code class="typescript">let array: (string | number)[] = [&#39;Apple&#39;, 1, 2, &#39;Banana&#39;, &#39;Mango&#39;, 3];// Orlet array: Array&lt;string | number&gt; = [&#39;Apple&#39;, 1, 2, &#39;Banana&#39;, &#39;Mango&#39;, 3];</code></pre><p>배열이 가지는 항목의 값을 단언할 수 없다면 <code>any</code>를 사용할 수 있습니다.</p><pre><code class="typescript">let someArr: any[] = [0, 1, {}, [], &#39;str&#39;, false];</code></pre><p>인터페이스(Interface)나 커스텀 타입(Type)을 사용할 수도 있습니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number,  isValid: boolean}let userArr: IUser[] = [  {    name: &#39;Neo&#39;,    age: 85,    isValid: true  },  {    name: &#39;Lewis&#39;,    age: 52,    isValid: false  },  {    name: &#39;Evan&#39;,    age: 36,    isValid: true  }];</code></pre><p>유용하진 않지만, 다음과 같이 특정한 값으로 타입을 대신해 작성할 수도 있습니다.</p><pre><code class="typescript">let array = 10[];array = [10];array.push(10);array.push(11); // Error - TS2345</code></pre><p>읽기 전용 배열을 생성할 수도 있습니다.<br><code>readonly</code> 키워드나 <code>ReadonlyArray</code> 타입을 사용하면 됩니다.</p><pre><code class="typescript">let arrA: readonly number[] = [1, 2, 3, 4];let arrB: ReadonlyArray&lt;number&gt; = [0, 9, 8, 7];arrA[0] = 123; // Error - TS2542: Index signature in type &#39;readonly number[]&#39; only permits reading.arrA.push(123); // Error - TS2339: Property &#39;push&#39; does not exist on type &#39;readonly number[]&#39;.arrB[0] = 123; // Error - TS2542: Index signature in type &#39;readonly number[]&#39; only permits reading.arrB.push(123); // Error - TS2339: Property &#39;push&#39; does not exist on type &#39;readonly number[]&#39;.</code></pre><h3><span id="b9e42406-20ab-4967-b0d9-edbde3edb737">튜플: Tuple</span><a href="#b9e42406-20ab-4967-b0d9-edbde3edb737" class="header-anchor"></a></h3><p>Tuple 타입은 배열과 매우 유사합니다.<br>차이점이라면 <strong>정해진 타입의 고정된 길이(length) 배열</strong>을 표현합니다.</p><pre><code class="typescript">let tuple: [string, number];tuple = [&#39;a&#39;, 1];tuple = [&#39;a&#39;, 1, 2]; // Error - TS2322tuple = [1, &#39;a&#39;]; // Error - TS2322</code></pre><p>다음과 같이 데이터를 개별 변수로 지정하지 않고, 단일 Tuple 타입으로 지정해 사용할 수 있습니다.</p><pre><code class="typescript">// Variableslet userId: number = 1234;let userName: string = &#39;HEROPY&#39;;let isValid: boolean = true;// Tuplelet user: [number, string, boolean] = [1234, &#39;HEROPY&#39;, true];console.log(user[0]); // 1234console.log(user[1]); // &#39;HEROPY&#39;console.log(user[2]); // true</code></pre><p>나아가 위 방식을 활용해 다음과 같은 Tuple 타입의 배열(2차원 배열)을 사용할 수 있습니다.</p><pre><code class="typescript">let users: [number, string, boolean][];// Or// let users: Array&lt;[number, string, boolean]&gt;;users = [[1, &#39;Neo&#39;, true], [2, &#39;Evan&#39;, false], [3, &#39;Lewis&#39;, true]];</code></pre><p>역시 값으로 타입을 대신할 수도 있습니다.</p><pre><code class="typescript">let tuple: [1, number];tuple = [1, 2];tuple = [1, 3];tuple = [2, 3]; // Error - TS2322: Type &#39;2&#39; is not assignable to type &#39;1&#39;.</code></pre><p>Tuple은 <strong>정해진 타입의 고정된 길이 배열</strong>을 표현하지만, 이는 할당(Assign)에 국한됩니다.<br><code>.push()</code>나 <code>.splice()</code> 등을 통해 값을 넣는 행위는 막을 수 없습니다.</p><pre><code class="typescript">let tuple: [string, number];tuple = [&#39;a&#39;, 1];tuple = [&#39;b&#39;, 2];tuple.push(3);console.log(tuple); // [&#39;b&#39;, 2, 3];tuple.push(true); // Error - TS2345: Argument of type &#39;true&#39; is not assignable to parameter of type &#39;string | number&#39;.</code></pre><p>배열에서 사용한 것과 같이 <code>readonly</code> 키워드를 사용해 읽기 전용 튜플을 생성할 수도 있습니다.</p><pre><code class="typescript">let a: readonly [string, number] = [&#39;Hello&#39;, 123];a[0] = &#39;World&#39;; // Error - TS2540: Cannot assign to &#39;0&#39; because it is a read-only property.</code></pre><h3><span id="3c32325c-1c0d-4a03-a2d2-636d765cdb96">열거형: Enum</span><a href="#3c32325c-1c0d-4a03-a2d2-636d765cdb96" class="header-anchor"></a></h3><p>Enum은 숫자 혹은 문자열 값 집합에 이름(Member)을 부여할 수 있는 타입으로, 값의 종류가 일정한 범위로 정해져 있는 경우 유용합니다.</p><p>기본적으로 <code>0</code>부터 시작하며 값은 <code>1</code>씩 증가합니다.</p><pre><code class="typescript">enum Week {  Sun,  Mon,  Tue,  Wed,  Thu,  Fri,  Sat}</code></pre><p><img src="/images/screenshot/typescript/typescript-enum-example1.jpg" alt="enum example"></p><div class="image-caption">WebStorm을 통해 보여지는 enum</div><p>수동으로 값을 변경할 수 있으며, 값을 변경한 부분부터 다시 <code>1</code>씩 증가합니다.</p><p><img src="/images/screenshot/typescript/typescript-enum-example2.jpg" alt="enum example"></p><p>Enum 타입의 재미있는 부분은 역방향 매핑(Reverse Mapping)을 지원한다는 것입니다.<br>이것은 열거된 멤버(<code>Sun</code>, <code>Mon</code> 같은)로 값에, 값으로 멤버에 접근할 수 있다는 것을 의미합니다.</p><p><code>Week</code>를 콘솔로 출력합니다.</p><pre><code class="typescript">enum Week {  // ...}console.log(Week);console.log(Week.Sun); // 0console.log(Week[&#39;Sun&#39;]); // 0console.log(Week[0]); // &#39;Sun&#39;</code></pre><p><img src="/images/screenshot/typescript/typescript-enum-console-log.jpg" alt="enum reverse mapping"></p><div class="image-caption">역방향 매핑(Reverse Mapping)</div><p>추가로, Enum은 숫자 값 열거뿐만아니라 다음과 같이 문자열 값으로 초기화할 수 있습니다.<br>이 방법은 역방향 매핑(Reverse Mapping)을 지원하지 않으며 개별적으로 초기화해야 하는 단점이 있습니다.</p><pre><code class="typescript">enum Color {  Red = &#39;red&#39;,  Green = &#39;green&#39;,  Blue = &#39;blue&#39;}console.log(Color.Red); // redconsole.log(Color[&#39;Green&#39;]); // green</code></pre><h3><span id="07f0d81f-76f9-43f7-9aa2-352e8d6f5e7d">모든 타입: Any</span><a href="#07f0d81f-76f9-43f7-9aa2-352e8d6f5e7d" class="header-anchor"></a></h3><p>Any는 모든 타입을 의미합니다.<br>따라서 일반적인 자바스크립트 변수와 동일하게 어떤 타입의 값도 할당할 수 있습니다.<br>외부 자원을 활용해 개발할 때 불가피하게 타입을 단언할 수 없는 경우, 유용할 수 있습니다.</p><pre><code class="typescript">let any: any = 123;any = &#39;Hello world&#39;;any = {};any = null;</code></pre><p>다양한 값을 포함하는 배열을 나타낼 때 사용할 수도 있습니다.</p><pre><code class="typescript">const list: any[] = [1, true, &#39;Anything!&#39;];</code></pre><p>강한 타입 시스템의 장점을 유지하기 위해 Any 사용을 엄격하게 금지하려면, 컴파일 옵션 <code>&quot;noImplicitAny&quot;: true</code>를 통해 Any 사용 시 에러를 발생시킬 수 있습니다.</p><h3><span id="a5dad8f9-a787-4138-b635-d007f08a3f40">알 수 없는 타입: Unknown</span><a href="#a5dad8f9-a787-4138-b635-d007f08a3f40" class="header-anchor"></a></h3><p>Any와 같이 최상위 타입인 Unknown은 알 수 없는 타입을 의미합니다.<br>Any와 같이 Unknown에는 어떤 타입의 값도 할당할 수 있지만, Unknown을 다른 타입에는 할당할 수 없습니다.</p><blockquote><p>일반적인 경우 Unknown은 타입 단언(Assertions)이나 타입 가드(Guards)를 필요로 합니다.<br>타입 단언이나 가드에 대한 내용은 다른 파트에서 정리합니다.</p></blockquote><pre><code class="typescript">let a: any = 123;let u: unknown = 123;let v1: boolean = a; // 모든 타입(any)은 어디든 할당할 수 있습니다.let v2: number = u; // 알 수 없는 타입(unknown)은 모든 타입(any)을 제외한 다른 타입에 할당할 수 없습니다.let v3: any = u; // OK!let v4: number = u as number; // 타입을 단언하면 할당할 수 있습니다.</code></pre><p>다양한 타입을 반환할 수 있는 API에서 유용할 수 있습니다.</p><blockquote><p>Unknown 보단 좀 더 명확한 타입을 사용하는 것이 좋습니다.</p></blockquote><pre><code class="typescript">type Result = {  success: true,  value: unknown} | {  success: false,  error: Error}export default function getItems(user: IUser): Result {  // Some logic...  if (id.isValid) {    return {      success: true,      value: [&#39;Apple&#39;, &#39;Banana&#39;]    };  } else {    return {      success: false,      error: new Error(&#39;Invalid user.&#39;)    }  }}</code></pre><h3><span id="ff2d089c-bdc0-4b7f-8465-17782e61e392">객체: Object</span><a href="#ff2d089c-bdc0-4b7f-8465-17782e61e392" class="header-anchor"></a></h3><p>기본적으로 <code>typeof</code> 연산자가 <code>&quot;object&quot;</code>로 반환하는 모든 타입을 나타냅니다.</p><blockquote><p>컴파일러 옵션에서 엄격한 타입 검사(<code>strict</code>)를 <code>true</code>로 설정하면, <code>null</code>은 포함하지 않습니다.</p></blockquote><pre><code class="typescript">let obj: object = {};let arr: object = [];let func: object = function () {};let nullValue: object = null;let date: object = new Date();// ...</code></pre><p>여러 타입의 상위 타입이기 때문에 그다지 유용하지 않습니다.<br>보다 정확하게 타입 지정을 하기 위해 다음과 같이 객체 속성(Properties)들에 대한 타입을 개별적으로 지정할 수 있습니다.</p><pre><code class="typescript">let userA: { name: string, age: number } = {  name: &#39;HEROPY&#39;,  age: 123};let userB: { name: string, age: number } = {  name: &#39;HEROPY&#39;,  age: false, // Error  email: &#39;thesecon@gmail.com&#39; // Error};</code></pre><p>반복적인 사용을 원하는 경우, <code>interface</code>나 <code>type</code>을 사용하는 것을 추천합니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number}let userA: IUser = {  name: &#39;HEROPY&#39;,  age: 123};let userB: IUser = {  name: &#39;HEROPY&#39;,  age: false, // Error  email: &#39;thesecon@gmail.com&#39; // Error};</code></pre><h3><span id="0b1b5a8c-5ae4-4b5f-ab95-a3de3648a916">Null과 Undefined</span><a href="#0b1b5a8c-5ae4-4b5f-ab95-a3de3648a916" class="header-anchor"></a></h3><p>기본적으로 Null과 Undefined는 모든 타입의 하위 타입으로, 다음과 같이 각 타입에 할당할 수 있습니다.<br>심지어 서로의 타입에도 할당 가능합니다.</p><pre><code class="typescript">let num: number = undefined;let str: string = null;let obj: { a: 1, b: false } = undefined;let arr: any[] = null;let und: undefined = null;let nul: null = undefined;let voi: void = null;// ...</code></pre><p>이는 컴파일 옵션 <code>&quot;strictNullChecks&quot;: true</code>을 통해 엄격하게 Null과 Undefined 서로의 타입까지 더 이상 할당할 수 없게 합니다.<br>단, Void에는 Undefined를 할당할 수 있습니다.</p><pre><code class="typescript">let voi: void = undefined; // ok</code></pre><h3><span id="68935ba2-87e2-4a47-8ec4-d5fb9575e6a7">Void</span><a href="#68935ba2-87e2-4a47-8ec4-d5fb9575e6a7" class="header-anchor"></a></h3><p>Void는 일반적으로 값을 반환하지 않는 함수에서 사용합니다.<br><code>: void</code> 위치는 함수가 반환 타입을 명시하는 곳입니다.</p><pre><code class="typescript">function hello(msg: string): void {  console.log(`Hello ${msg}`);}</code></pre><p>값을 반환하지 않는 함수는 실제로는 <code>undefined</code>를 반환합니다.</p><pre><code class="typescript">function hello(msg: string): void {  console.log(`Hello ${msg}`);}const hi: void = hello(&#39;world&#39;); // Hello worldconsole.log(hi); // undefined</code></pre><pre><code class="typescript">// Error - TS2355: A function whose declared type is neither &#39;void&#39; nor &#39;any&#39; must return a value.function hello(msg: string): undefined {  console.log(`Hello ${msg}`);}</code></pre><h3><span id="142cba8c-4539-4317-8114-ed552101bc6d">Never</span><a href="#142cba8c-4539-4317-8114-ed552101bc6d" class="header-anchor"></a></h3><p>Never은 <strong>절대 발생하지 않을 값</strong>을 나타내며, 어떠한 타입도 적용할 수 없습니다.</p><pre><code class="typescript">function error(message: string): never {  throw new Error(message);}</code></pre><p>보통 다음과 같이 빈 배열을 타입으로 잘못 선언한 경우, Never를 볼 수 있습니다.</p><pre><code class="typescript">const never: [] = [];never.push(3); // Error - TS2345: Argument of type &#39;3&#39; is not assignable to parameter of type &#39;never&#39;.</code></pre><h3><span id="615d1025-1a96-435b-bb8a-05882e6019f9">유니언(Union)</span><a href="#615d1025-1a96-435b-bb8a-05882e6019f9" class="header-anchor"></a></h3><p>2개 이상의 타입을 허용하는 경우, 이를 유니언(Union)이라고 합니다.<br><code>|</code>(vertical bar)를 통해 타입을 구분하며, <code>()</code>는 선택 사항입니다.</p><pre><code class="typescript">let union: (string | number);union = &#39;Hello type!&#39;;union = 123;union = false; // Error - TS2322: Type &#39;false&#39; is not assignable to type &#39;string | number&#39;.</code></pre><h3><span id="56514367-689d-4ace-b0f8-3e34a4a90dce">인터섹션(Intersection)</span><a href="#56514367-689d-4ace-b0f8-3e34a4a90dce" class="header-anchor"></a></h3><p><code>&amp;</code>(ampersand)를 사용해 2개 이상의 타입을 조합하는 경우, 이를 인터섹션(Intersection)이라고 합니다.<br>인터섹션은 새로운 타입을 생성하지 않고 기존의 타입들을 조합할 수 있기 때문에 유용하지만, 자주 사용되는 방법은 아닙니다.</p><blockquote><p>위에서 살펴본 유니언을 마치 ‘또는(Or)’과 같이 이해할 수 있다면, 인터섹션은 ‘그리고(And)’와 같이 이해할 수 있습니다.</p></blockquote><pre><code class="typescript">// 기존 타입들이 조합 가능하다면 인터섹션을 활용할 수 있습니다.interface IUser {  name: string,  age: number}interface IValidation {  isValid: boolean}const heropy: IUser = {  name: &#39;Heropy&#39;,  age: 36,  isValid: true // Error -  TS2322: Type &#39;{ name: string; age: number; isValid: boolean; }&#39; is not assignable to type &#39;IUser&#39;.};const neo: IUser &amp; IValidation = {  name: &#39;Neo&#39;,  age: 85,  isValid: true};// 혹은 기존 타입(IUser, IValidation)과 비슷하지만, 정확히 일치하는 타입이 없다면 새로운 타입을 생성해야 합니다.interface IUserNew {  name: string,  age: number,  isValid: boolean}const evan: IUserNew = {  name: &#39;Evan&#39;,  age: 36,  isValid: false};</code></pre><h3><span id="596ddd2e-a100-403a-8cab-4872dcca0d66">함수(Function)</span><a href="#596ddd2e-a100-403a-8cab-4872dcca0d66" class="header-anchor"></a></h3><p>화살표 함수를 이용해 타입을 지정할 수 있습니다.<br>인수의 타입과 반환 값의 타입을 입력합니다.</p><pre><code class="typescript">// myFunc는 2개의 숫자 타입 인수를 가지고, 숫자 타입을 반환하는 함수.let myFunc: (arg1: number, arg2: number) =&gt; number;myFunc = function (x, y) {  return x + y;};// 인수가 없고, 반환도 없는 경우.let yourFunc: () =&gt; void;yourFunc = function () {  console.log(&#39;Hello world~&#39;);};</code></pre><h2><span id="6d8b6f3e-1fe6-4bc0-b713-4f4a9b8f94de">타입 추론(Inference)</span><a href="#6d8b6f3e-1fe6-4bc0-b713-4f4a9b8f94de" class="header-anchor"></a></h2><p>명시적으로 타입 선언이 되어있지 않은 경우, 타입스크립트는 타입을 추론해 제공합니다.<br>개념은 매우 단순합니다.</p><blockquote><p>[추론]: 어떠한 판단을 근거로 삼아 다른 판단을 이끌어 냄.</p></blockquote><pre><code class="typescript">let num = 12;num = &#39;Hello type!&#39;; // TS2322: Type &#39;&quot;Hello type!&quot;&#39; is not assignable to type &#39;number&#39;.</code></pre><p>변수 <code>num</code>을 초기화하면서 숫자 <code>12</code>를 할당해 Number 타입으로 추론되었고, 따라서 <code>&#39;Hello type!&#39;</code>이라는 String 타입의 값은 할당할 수 없기 때문에 에러가 발생합니다.</p><p>이렇게 타입스크립트가 타입을 추론하는 경우는 다음과 같습니다.</p><ul><li>초기화된 변수</li><li>기본값이 설정된 매개 변수</li><li>반환 값이 있는 함수</li></ul><pre><code class="typescript">// 초기화된 변수 `num`let num = 12;// 기본값이 설정된 매개 변수 `b`function add(a: number, b: number = 2): number {  // 반환 값(`a + b`)이 있는 함수  return a + b;}</code></pre><blockquote><p>타입 추론이 엄격하지 않은 타입 선언을 의미하는 것은 아닙니다.<br>따라서 이를 활용해 모든 곳에 타입을 명시할 필요는 없으며, 많은 경우 더 좋은 코드 가독성을 제공할 수 있습니다.</p></blockquote><h2><span id="99ea03b0-5e79-44b2-8b7e-9f5c60d39d96">타입 단언(Assertions)</span><a href="#99ea03b0-5e79-44b2-8b7e-9f5c60d39d96" class="header-anchor"></a></h2><p>타입스크립트가 타입 추론을 통해 판단할 수 있는 타입의 범주를 넘는 경우, 더 이상 추론하지 않도록 지시할 수 있습니다.<br>이를 ‘타입 단언’이라고 하며, 이는 프로그래머가 타입스크립트보다 타입에 대해 더 잘 이해하고 있는 상황을 의미합니다.</p><blockquote><p>[단언]: 주저하지 아니하고 딱 잘라 말함.</p></blockquote><p>다음 예제를 살펴봅시다.</p><p>함수의 매개 변수 <code>val</code>은 유니언 타입으로 문자열(String)이거나 숫자(Number)일 수 있습니다.<br>그리고 매개 변수 <code>isNumber</code>는 불린(Boolean)이며, 이름을 통해 숫자 여부를 확인하는 값이라는 것을 (우리는) 추론할 수 있습니다.<br>따라서 우리는 <code>isNumber</code>가 <code>true</code>일 경우 <code>val</code>은 숫자일 것이고, 이에 <code>toFixed</code>를 사용할 수 있음을 확실히 알 수 있습니다.<br>하지만 타입스크립트는 ‘isNumber’라는 이름만으로 위 내용을 추론할 수 없기 때문에 “<code>val</code>이 문자열인 경우 <code>toFixed</code>를 사용할 수 없다”고 (컴파일 단계에서) 다음과 같은 에러를 반환합니다.</p><pre><code class="typescript">function someFunc(val: string | number, isNumber: boolean) {  // some logics  if (isNumber) {    val.toFixed(2); // Error - TS2339: ... Property &#39;toFixed&#39; does not exist on type &#39;string&#39;.  }}</code></pre><p>따라서 우리는 <code>isNumber</code>가 <code>true</code>일 때 <code>val</code>이 숫자임을 다음과 같이 2가지 방식으로 단언할 수 있습니다.<br>두 번째 방식(<code>&lt;number&gt;val</code>)은 JSX를 사용하는 경우 특정 구문 파싱에서 문제가 발생할 수 있으며, 결과적으로 <code>.tsx</code> 파일에서는 전혀 사용할 수 없습니다.</p><pre><code class="typescript">function someFunc(val: string | number, isNumber: boolean) {  // some logics  if (isNumber) {    // 1. 변수 as 타입    (val as number).toFixed(2);    // Or    // 2. &lt;타입&gt;변수    // (&lt;number&gt;val).toFixed(2);  }}</code></pre><blockquote><p>타입 단언은 마치 프로그래머가 타입스크립트에게 “나는 알고 있으니까 나를 믿어!”라고 알려주는 것과 같습니다.</p></blockquote><h3><span id="609cc130-b1a8-4886-b83e-0b510d3230de">Non-null 단언 연산자</span><a href="#609cc130-b1a8-4886-b83e-0b510d3230de" class="header-anchor"></a></h3><p><code>!</code>를 사용하는 Non-null 단언 연산자(Non-null assertion operator)를 통해 피연산자가 Nullish(<code>null</code>이나 <code>undefined</code>) 값이 아님을 단언할 수 있는데, 변수나 속성에서 간단하게 사용할 수 있기 때문에 유용합니다.</p><p>다음 예제 중 <code>fnA</code> 함수를 살펴보면, 매개 변수 <code>x</code>는 함수 내에서 <code>toFixed</code>를 사용하는 숫자 타입으로 처리되지만 <code>null</code>이나 <code>undefined</code>일 수 있기 때문에 에러가 발생합니다.<br>이를 타입 단언이나 <code>if</code> 조건문으로 해결할 수도 있지만, 마지막 함수와 같이 <code>!</code>를 사용하는 Non-null 단언 연산자를 이용해 간단하게 정리할 수 있습니다.</p><pre><code class="typescript">// Error - TS2533: Object is possibly &#39;null&#39; or &#39;undefined&#39;.function fnA(x: number | null | undefined) {  return x.toFixed(2);}// if statementfunction fnD(x: number | null | undefined) {  if (x) {    return x.toFixed(2);  }}// Type assertionfunction fnB(x: number | null | undefined) {  return (x as number).toFixed(2);}function fnC(x: number | null | undefined) {  return (&lt;number&gt;x).toFixed(2);}// Non-null assertion operatorfunction fnE(x: number | null | undefined) {  return x!.toFixed(2);}</code></pre><p>특히 컴파일 환경에서 체크하기 어려운 DOM 사용에서 유용합니다.<br>물론 일반적인 타입 단언을 사용할 수도 있습니다.</p><pre><code class="typescript">// Error - TS2531: Object is possibly &#39;null&#39;.document.querySelector(&#39;.menu-item&#39;).innerHTML;// Type assertion(document.querySelector(&#39;.menu-item&#39;) as HTMLDivElement).innerHTML;(&lt;HTMLDivElement&gt;document.querySelector(&#39;.menu-item&#39;)).innerHTML;// Non-null assertion operatordocument.querySelector(&#39;.menu-item&#39;)!.innerHTML;</code></pre><h2><span id="435ddba7-cdc0-4b4f-97f2-9e59ad54ac16">타입 가드(Guards)</span><a href="#435ddba7-cdc0-4b4f-97f2-9e59ad54ac16" class="header-anchor"></a></h2><p>다음 예제와 같이 <code>val</code>의 타입을 매번 보장하기 위해 타입 단언을 여러 번 사용하게 되는 경우가 있습니다.</p><pre><code class="typescript">function someFunc(val: string | number, isNumber: boolean) {  if (isNumber) {    (val as number).toFixed(2);    isNaN(val as number);  } else {    (val as string).split(&#39;&#39;);    (val as string).toUpperCase();    (val as string).length;  }}</code></pre><p>이 경우 타입 가드를 제공하면 타입스크립트가 추론 가능한 특정 범위(scope)에서 타입을 보장할 수 있습니다.<br>타입 가드는 <code>NAME is TYPE</code> 형태의 <strong>타입 술부(Predicate)를 반환 타입으로 명시한 함수</strong>입니다.<br>다음 예제에서 타입 술부는 <code>val is number</code>입니다.<br>타입 단언이 없어지니 훨씬 깔끔합니다.</p><blockquote><p>[술부]: 주어의 상태, 성질 따위를 서술하는 말.</p></blockquote><pre><code class="typescript">// 타입 가드function isNumber(val: string | number): val is number {  return typeof val === &#39;number&#39;;}function someFunc(val: string | number) {  if (isNumber(val)) {    val.toFixed(2);    isNaN(val);  } else {    val.split(&#39;&#39;);    val.toUpperCase();    val.length;  }}</code></pre><p>위 방식뿐만 아니라 제공 가능한 타입 가드가 더 있습니다.<br><code>typeof</code>, <code>in</code> 그리고 <code>instanceof</code> 연산자를 직접 사용하는 타입 가드입니다.<br>비교적 단순한 로직에서 추천되는 방식입니다.</p><blockquote><p><code>typeof</code> 연산자는 <code>number</code>, <code>string</code>, <code>boolean</code>, 그리고 <code>symbol</code>만 타입 가드로 인식할 수 있습니다.<br><code>in</code> 연산자의 우변 객체(<code>val</code>)는 <code>any</code> 타입이어야 합니다.</p></blockquote><pre><code class="typescript">// 기존 예제와 같이 `isNumber`를 제공(추상화)하지 않아도 `typeof` 연산자를 직접 사용하면 타입 가드로 동작합니다.// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/typeoffunction someFuncTypeof(val: string | number) {  if (typeof val === &#39;number&#39;) {    val.toFixed(2);    isNaN(val);  } else {    val.split(&#39;&#39;);    val.toUpperCase();    val.length;  }}// 별도의 추상화 없이 `in` 연산자를 사용해 타입 가드를 제공합니다.// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/infunction someFuncIn(val: any) {  if (&#39;toFixed&#39; in val) {    val.toFixed(2);    isNaN(val);  } else if (&#39;split&#39; in val) {    val.split(&#39;&#39;);    val.toUpperCase();    val.length;  }}// 역시 별도의 추상화 없이 `instanceof` 연산자를 사용해 타입 가드를 제공합니다.// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/instanceofclass Cat {  meow() {}}class Dog {  woof() {}}function sounds(ani: Cat | Dog) {  if (ani instanceof Cat) {    ani.meow();  } else {    ani.woof();  }}</code></pre><h1><span id="a40c1a59-3c02-44c0-929d-f10f8755c2e4">인터페이스(interface)</span><a href="#a40c1a59-3c02-44c0-929d-f10f8755c2e4" class="header-anchor"></a></h1><p>인터페이스(Interface)는 타입스크립트 여러 객체를 정의하는 일종의 규칙이며 구조입니다.<br>다음과 같이 <code>interface</code> 키워드와 함께 사용합니다.</p><blockquote><p>‘IUser’에서 ‘I’는 Interface를 의미하는 별칭으로 사용했습니다.</p></blockquote><pre><code class="typescript">interface IUser {  name: string,  age: number,  isAdult: boolean}let user1: IUser = {  name: &#39;Neo&#39;,  age: 123,  isAdult: true};// Error - TS2741: Property &#39;isAdult&#39; is missing in type &#39;{ name: string; age: number; }&#39; but required in type &#39;IUser&#39;.let user2: IUser = {  name: &#39;Evan&#39;,  age: 456};</code></pre><p><code>:</code>(colon), <code>,</code>(comma) 혹은 기호를 사용하지 않을 수 있습니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number}// Orinterface IUser {  name: string;  age: number;}// Orinterface IUser {  name: string  age: number}</code></pre><p>다음과 같이 속성에 <code>?</code>를 사용하면 선택적 속성으로 정의할 수 있습니다.<br>선택적 속성(Optional properties)에 대해선 Optional 파트에서 따로 설명하지만, 간단하게 표현하면 ‘필수가 아닌 속성으로 정의’하는 방법을 말합니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number,  isAdult?: boolean // Optional property}// `isAdult`를 초기화하지 않아도 에러가 발생하지 않습니다.let user: IUser = {  name: &#39;Neo&#39;,  age: 123};</code></pre><h2><span id="4c526ed8-1a5a-4283-b452-141a6cdbb6e4">읽기 전용 속성(Readonly properties)</span><a href="#4c526ed8-1a5a-4283-b452-141a6cdbb6e4" class="header-anchor"></a></h2><p><code>readonly</code> 키워드를 사용하면 초기화된 값을 유지해야 하는 <strong>읽기 전용 속성을 정의</strong>할 수 있습니다.</p><pre><code class="typescript">interface IUser {  readonly name: string,  age: number}// 초기화let user: IUser = {  name: &#39;Neo&#39;,  age: 36};user.age = 85; // Okuser.name = &#39;Evan&#39;; // Error - TS2540: Cannot assign to &#39;name&#39; because it is a read-only property.</code></pre><p>만약 모든 속성이 <code>readonly</code>일 경우, <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlyt" target="_blank" rel="noopener">유틸리티(Utility)</a>나 단언(Assertion) 타입을 활용할 수 있습니다.</p><pre><code class="typescript">// All readonly propertiesinterface IUser {  readonly name: string,  readonly age: number}let user: IUser = {  name: &#39;Neo&#39;,  age: 36};user.age = 85; // Erroruser.name = &#39;Evan&#39;; // Error// Readonly Utilityinterface IUser {  name: string,  age: number}let user: Readonly&lt;IUser&gt; = {  name: &#39;Neo&#39;,  age: 36};user.age = 85; // Erroruser.name = &#39;Evan&#39;; // Error// Type assertionlet user = {  name: &#39;Neo&#39;,  age: 36} as const;user.age = 85; // Erroruser.name = &#39;Evan&#39;; // Error</code></pre><h2><span id="8d103835-6101-42d3-a4b2-7b3ad57c081b">함수 타입</span><a href="#8d103835-6101-42d3-a4b2-7b3ad57c081b" class="header-anchor"></a></h2><p>함수 타입을 인터페이스로 정의하는 경우, 호출 시그니처(Call signature)라는 것을 사용합니다.<br>호출 시그니처는 다음과 같이 함수의 매개 변수(parameter)와 반환 타입을 지정합니다.</p><pre><code class="typescript">interface IName {  (PARAMETER: PARAM_TYPE): RETURN_TYPE // Call signature}</code></pre><p>간단한 예시를 살펴봅시다.<br>인터페이스 <code>IGetUser</code>를 통해 함수 타입을 정의했으며, 이는 <code>name</code> 매개 변수를 하나 가지며(이름이 일치할 필요는 없습니다), <code>IUser</code> 타입을 반환해야 합니다.</p><pre><code class="typescript">interface IUser {  name: string}interface IGetUser {  (name: string): IUser}// 매개 변수 이름이 인터페이스와 일치할 필요가 없습니다.// 또한 타입 추론을 통해 매개 변수를 순서에 맞게 암시적 타입으로 제공할 수 있습니다.const getUser: IGetUser = function (n) { // n is name: string  // Find user logic..  // ...  return user;};getUser(&#39;Heropy&#39;);</code></pre><h2><span id="e07aeb19-b5bc-4ea2-a180-dcdc7edd2641">클래스 타입</span><a href="#e07aeb19-b5bc-4ea2-a180-dcdc7edd2641" class="header-anchor"></a></h2><p>인터페이스로 클래스를 정의하는 경우, <code>implements</code> 키워드를 사용합니다.</p><pre><code class="typescript">interface IUser {  name: string,  getName(): string}class User implements IUser {  constructor(public name: string) {}  getName() {    return this.name;  }}const neo = new User(&#39;Neo&#39;);neo.getName(); // Neo</code></pre><p>기본적인 사용법은 어렵지 않습니다.<br>그런데 만약 정의한 클래스를 인수로 사용하는 경우 다음과 같은 문제가 발생할 수 있습니다.<br>다음 예제에서 인터페이스 <code>ICat</code>은 호출 가능한 구조가 아니기 때문입니다.</p><pre><code class="typescript">interface ICat {  name: string}class Cat implements ICat {  constructor(public name: string) {}}function makeKitten(c: ICat, n: string) {  return new c(n); // Error - TS2351: This expression is not constructable. Type &#39;ICat&#39; has no construct signatures.}const kitten = makeKitten(Cat, &#39;Lucy&#39;);console.log(kitten);</code></pre><p>이를 위해 구성 시그니처(Construct signature)를 제공할 수 있습니다.<br>구성 시그니처는 위에서 살펴본 호출 시그니처와 비슷하지만, <code>new</code> 키워드를 사용해야 합니다.</p><pre><code class="typescript">interface IName {  new (PARAMETER: PARAM_TYPE): RETURN_TYPE // Construct signature}</code></pre><p>위에서 봤던 예제를 다음과 같이 수정합니다.<br><code>ICatConstructor</code>라는 구성 시그니처를 가지는 호출 가능한 인터페이스를 정의하면, 문제없이 동작하는 것을 확인할 수 있습니다.</p><pre><code class="typescript">interface ICat {  name: string}interface ICatConstructor {  new (name: string): ICat;}class Cat implements ICat {  constructor(public name: string) {}}function makeKitten(c: ICatConstructor, n: string) {  return new c(n); // ok}const kitten = makeKitten(Cat, &#39;Lucy&#39;);console.log(kitten);</code></pre><p>비슷하지만 좀 더 재미있는 예제를 준비했습니다.<br>에러가 발생하는 부분을 확인하고 내용을 이해했다면 충분합니다.</p><pre><code class="typescript">interface IFullName {  firstName: string,  lastName: string}interface IFullNameConstructor {  new(firstName: string): IFullName; // Construct signature}function makeSon(c: IFullNameConstructor, firstName: string) {  return new c(firstName);}function getFullName(son: IFullName) {  return `${son.firstName} ${son.lastName}`;}// Anderson familyclass Anderson implements IFullName {  public lastName: string;  constructor (public firstName: string) {    this.lastName = &#39;Anderson&#39;;  }}const tomas = makeSon(Anderson, &#39;Tomas&#39;);const jack = makeSon(Anderson, &#39;Jack&#39;);getFullName(tomas); // Tomas AndersongetFullName(jack); // Jack Anderson// Smith family?class Smith implements IFullName {  public lastName: string;  constructor (public firstName: string, agentCode: number) {    this.lastName = `Smith ${agentCode}`;  }}const smith = makeSon(Smith, 7); // Error - TS2345: Argument of type &#39;typeof Smith&#39; is not assignable to parameter of type &#39;IFullNameConstructor&#39;.getFullName(smith);</code></pre><h2><span id="6159c981-ebd9-4c67-94ed-75b2a65ddcac">인덱싱 가능 타입(Indexable types)</span><a href="#6159c981-ebd9-4c67-94ed-75b2a65ddcac" class="header-anchor"></a></h2><p>우리는 인터페이스를 통해 특정 속성(메소드 등)의 타입을 정의할 순 있지만, 수많은 속성을 가지거나 단언할 수 없는 임의의 속성이 포함되는 구조에서는 기존의 방식만으론 한계가 있습니다. 이런 상황에서 유용한 인덱스 시그니처(Index signature)에 대해서 살펴봅시다.</p><p><code>arr[2]</code>와 같이 ‘숫자’로 인덱싱하거나 <code>obj[&#39;name&#39;]</code>과 같이 ‘문자’로 인덱싱하는, 인덱싱 가능 타입(Indexable types)들이 있습니다.<br>이런 인덱싱 가능 타입들을 정의하는 인터페이스는 인덱스 시그니처(Index signature)라는 것을 가질 수 있습니다.<br>인덱스 시그니처는 다음 구조와 같이, 인덱싱에 사용할 인덱서(Indexer)의 이름과 타입 그리고 인덱싱 결과의 반환 값을 지정합니다.<br><strong>인덱서의 타입은 <code>string</code>과 <code>number</code>만 지정</strong>할 수 있습니다.</p><pre><code class="typescript">interface INAME {  [INDEXER_NAME: INDEXER_TYPE]: RETURN_TYPE // Index signature}</code></pre><blockquote><p>배열(객체)에서 위치를 가리키는 숫자(문자)를 인덱스(index)라고 하며, 각 배열 요소(객체 속성)에 접근하기 위하여 인덱스를 사용하는 것을 인덱싱(indexing)이라고 합니다.(배열을 구성하는 각각의 값은 배열 요소(element)라고 합니다)</p></blockquote><p>이해를 돕기 위해 다음 예제를 살펴보면,<br>인터페이스 <code>IItem</code>은 인덱스 시그니처를 가지고 있으며, 그 <code>IItem</code>을 타입(인터페이스)으로 하는 <code>item</code>이 있고, 그 <code>item</code>을 <code>item[0]</code>이나 <code>item[1]</code>과 같이 숫자로 인덱싱할 때 반환되는 값은 <code>&#39;a&#39;</code>나 <code>b&#39;</code> 같은 문자여야 합니다.<br><code>item</code>을 <code>item[&#39;0&#39;]</code>과 같이 문자로 인덱싱하는 경우 에러가 발생합니다.</p><pre><code class="typescript">interface IItem {  [itemIndex: number]: string // Index signature}let item: IItem = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]; // Indexable typeconsole.log(item[0]); // &#39;a&#39; is string.console.log(item[1]); // &#39;b&#39; is string.console.log(item[&#39;0&#39;]); // Error - TS7015: Element implicitly has an &#39;any&#39; type because index expression is not of type &#39;number&#39;.</code></pre><p>참고로 인덱싱 결과의 반환 타입으로 유니온을 사용하면 다음과 같이 활용할 수 있습니다.</p><pre><code class="typescript">interface IItem {  [itemIndex: number]: string | boolean | number[]}let item: IItem = [&#39;Hello&#39;, false, [1, 2, 3]];console.log(item[0]); // Helloconsole.log(item[1]); // falseconsole.log(item[2]); // [1, 2, 3]</code></pre><p>이번에는 문자로 인덱싱하는 예제를 살펴봅시다.<br>인터페이스 <code>IUser</code>는 인덱스 시그니처를 가지고 있으며, 그 <code>IUser</code>를 타입(인터페이스)로 하는 <code>user</code>가 있고, 그 <code>user</code>를 <code>user[&#39;name&#39;]</code>, <code>user[&#39;email&#39;]</code> 또는 <code>user[&#39;isValid&#39;]</code>와 같이 문자로 인덱싱할 때 반환되는 값은 <code>&#39;Neo&#39;</code>나 <code>&#39;thesecon@gmail.com&#39;</code> 같은 문자 혹은 <code>true</code> 같은 불린이어야 합니다.<br>또한 <code>user[0]</code>과 같은 숫자로 인덱싱하는 경우나 <code>user[&#39;0&#39;]</code>과 같이 문자로 인덱싱하는 경우 모두 인덱싱 전에 숫자가 문자열로 변환되기 때문에 다음과 같이 값을 반환할 수 있습니다.</p><pre><code class="typescript">interface IUser {  [userProp: string]: string | boolean}let user: IUser = {  name: &#39;Neo&#39;,  email: &#39;thesecon@gmail.com&#39;,  isValid: true,  0: false};console.log(user[&#39;name&#39;]); // &#39;Neo&#39; is string.console.log(user[&#39;email&#39;]); // &#39;thesecon@gmail.com&#39; is string.console.log(user[&#39;isValid&#39;]); // true is boolean.console.log(user[0]); // false is booleanconsole.log(user[1]); // undefinedconsole.log(user[&#39;0&#39;]); // false is boolean</code></pre><p>인덱스 시그니처를 사용하면 다음 예제와 같이 인터페이스에 정의되지 않은 속성들을 사용할 때 유용합니다.<br>단, 해당 속성이 인덱스 시그니처에 정의된 반환 값을 가져야 함에 주의해야 합니다.<br>다음 예제에서 <code>isAdult</code> 속성은 정의된 <code>string</code>이나 <code>number</code> 타입을 반환하지 않지 않기 때문에 에러가 발생합니다.</p><pre><code class="typescript">interface IUser {  [userProp: string]: string | number  name: string,  age: number}let user: IUser = {  name: &#39;Neo&#39;,  age: 123,  email: &#39;thesecon@gmail.com&#39;,  isAdult: true // Error - TS2322: Type &#39;true&#39; is not assignable to type &#39;string | number&#39;.};console.log(user[&#39;name&#39;]); // &#39;Neo&#39;console.log(user[&#39;age&#39;]); // 123console.log(user[&#39;email&#39;]); // thesecon@gmail.com</code></pre><h3><span id="037dda6b-6fe6-4ba3-9241-b17376d7c9c7">keyof</span><a href="#037dda6b-6fe6-4ba3-9241-b17376d7c9c7" class="header-anchor"></a></h3><p>인덱싱 가능 타입에서 <code>keyof</code>를 사용하면 속성 이름을 타입으로 사용할 수 있습니다.<br>인덱싱 가능 타입의 속성 이름들이 <strong>유니온 타입으로 적용</strong>됩니다.<br>간단한 예제를 살펴보겠습니다.</p><pre><code class="typescript">interface ICountries {  KR: &#39;대한민국&#39;,  US: &#39;미국&#39;,  CP: &#39;중국&#39;}let country: keyof ICountries; // &#39;KR&#39; | &#39;US&#39; | &#39;CP&#39;country = &#39;KR&#39;; // okcountry = &#39;RU&#39;; // Error - TS2322: Type &#39;&quot;RU&quot;&#39; is not assignable to type &#39;&quot;KR&quot; | &quot;US&quot; | &quot;CP&quot;&#39;.</code></pre><p>또한 <code>keyof</code>를 통한 인덱싱으로 타입의 개별 값에도 접근할 수 있습니다.</p><pre><code class="typescript">interface ICountries {  KR: &#39;대한민국&#39;,  US: &#39;미국&#39;,  CP: &#39;중국&#39;}let country: ICountries[keyof ICountries]; // ICountries[&#39;KR&#39; | &#39;US&#39; | &#39;CP&#39;]country = &#39;대한민국&#39;;country = &#39;러시아&#39;; // Error - TS2322: Type &#39;&quot;러시아&quot;&#39; is not assignable to type &#39;&quot;대한민국&quot; | &quot;미국&quot; | &quot;중국&quot;&#39;.</code></pre><h2><span id="1bb79d5a-43f1-4f35-8569-b8229e019f0c">인터페이스 확장</span><a href="#1bb79d5a-43f1-4f35-8569-b8229e019f0c" class="header-anchor"></a></h2><p>인터페이스도 클래스처럼 <code>extends</code> 키워드를 활용해 상속할 수 있습니다.</p><pre><code class="typescript">interface IAnimal {  name: string}interface ICat extends IAnimal {  meow(): string}class Cat implements ICat { // Error - TS2420: Class &#39;Cat&#39; incorrectly implements interface &#39;ICat&#39;. Property &#39;name&#39; is missing in type &#39;Cat&#39; but required in type &#39;ICat&#39;.  meow() {    return &#39;MEOW~&#39;  }}</code></pre><p>그리고 같은 이름의 인터페이스를 여러 개 만들 수도 있습니다.<br>기존에 만들어진 인터페이스에 내용을 추가하는 경우에 유용합니다.</p><pre><code class="typescript">interface IFullName {  firstName: string,  lastName: string}interface IFullName {  middleName: string}const fullName: IFullName = {  firstName: &#39;Tomas&#39;,  middleName: &#39;Sean&#39;,  lastName: &#39;Connery&#39;};</code></pre><h1><span id="1e186f94-561d-44f0-a5a7-485f24d87550">타입 별칭(Type Aliases)</span><a href="#1e186f94-561d-44f0-a5a7-485f24d87550" class="header-anchor"></a></h1><p><code>type</code> 키워드를 사용해 새로운 타입 조합을 만들 수 있습니다.<br>하나 이상의 타입을 조합해 별칭(이름)을 부여하며, 정확히는 조합한 각 타입들을 참조하는 별칭을 만드는 것입니다.<br>일반적인 경우 둘 이상의 조합으로 구성하기 위해 유니온을 많이 사용합니다.</p><blockquote><p>TUser에서 T는 Type를 의미하는 별칭으로 사용했습니다.</p></blockquote><pre><code class="typescript">type MyType = string;type YourType = string | number | boolean;type TUser = {  name: string,  age: number,  isValid: boolean} | [string, number, boolean];let userA: TUser = {  name: &#39;Neo&#39;,  age: 85,  isValid: true};let userB: TUser = [&#39;Evan&#39;, 36, false];function someFunc(arg: MyType): YourType {  switch (arg) {    case &#39;s&#39;:      return arg.toString(); // string    case &#39;n&#39;:      return parseInt(arg); // number    default:      return true; // boolean  }}</code></pre><h1><span id="fa4c8277-05d5-46de-96bc-08829f8a2730">제네릭(Generic)</span><a href="#fa4c8277-05d5-46de-96bc-08829f8a2730" class="header-anchor"></a></h1><p>Generic은 재사용을 목적으로 함수나 클래스의 선언 시점이 아닌, <strong>사용 시점에 타입을 선언</strong>할 수 있는 방법을 제공합니다.</p><blockquote><p>타입을 인수로 받아서 사용한다고 이해하면 쉽습니다.</p></blockquote><p>다음 예제는 <code>toArray</code> 함수가 인수로 받은 값을 배열로 반환하도록 작성되었습니다.<br>매개 변수가 Number 타입만 허용하기 때문에 String 타입을 인수로 하는 함수 호출에서 에러가 발생합니다.</p><pre><code class="typescript">function toArray(a: number, b: number): number[] {  return [a, b];}toArray(1, 2);toArray(&#39;1&#39;, &#39;2&#39;); // Error - TS2345: Argument of type &#39;&quot;1&quot;&#39; is not assignable to parameter of type &#39;number&#39;.</code></pre><p>조금 더 범용적으로 만들기 위해 유니언 방식을 사용했습니다.<br>이제 String 타입을 인수로 받을 수 있지만, 가독성이 떨어지고 새로운 문제도 발생했습니다.<br>세 번째 호출을 보면 의도치 않게 Number와 String 타입을 동시에 받을 수 있게 되었습니다.</p><pre><code class="typescript">function toArray(a: number | string, b: number | string): (number | string)[] {  return [a, b];}toArray(1, 2); // Only NumbertoArray(&#39;1&#39;, &#39;2&#39;); // Only StringtoArray(1, &#39;2&#39;); // Number &amp; String</code></pre><p>이번에는 Generic을 사용합니다.<br>함수 이름 우측에 <code>&lt;T&gt;</code>를 작성해 시작합니다.<br><code>T</code>는 타입 변수(Type variable)로 사용자가 제공한 타입으로 변환될 식별자입니다.<br>이제 세 번째 호출은 의도적으로 Number와 String 타입을 동시에 받을 수 있습니다.(혹은 유니언을 사용하지 않으면 에러가 발생합니다)</p><blockquote><p>타입 변수는 매개 변수처럼 원하는 이름으로 지정할 수 있습니다.</p></blockquote><pre><code class="typescript">function toArray&lt;T&gt;(a: T, b: T): T[] {  return [a, b];}toArray&lt;number&gt;(1, 2);toArray&lt;string&gt;(&#39;1&#39;, &#39;2&#39;);toArray&lt;string | number&gt;(1, &#39;2&#39;);toArray&lt;number&gt;(1, &#39;2&#39;); // Error</code></pre><p>타입 추론을 활용해, 사용 시점에 타입을 제공하지 않을 수 있습니다.</p><pre><code class="typescript">function toArray&lt;T&gt;(a: T, b: T): T[] {  return [a, b];}toArray(1, 2);toArray(&#39;1&#39;, &#39;2&#39;);toArray(1, &#39;2&#39;); // Error</code></pre><h2><span id="4e85e0e2-c766-4b71-9e0a-bc54404149e6">제약 조건(Constraints)</span><a href="#4e85e0e2-c766-4b71-9e0a-bc54404149e6" class="header-anchor"></a></h2><p>인터페이스나 타입 별칭을 사용하는 제네릭을 작성할 수도 있습니다.<br>다음 예제는 별도의 제약 조건(Constraints)이 없어서 모든 타입이 허용됩니다.</p><pre><code class="typescript">interface MyType&lt;T&gt; {  name: string,  value: T}const dataA: MyType&lt;string&gt; = {  name: &#39;Data A&#39;,  value: &#39;Hello world&#39;};const dataB: MyType&lt;number&gt; = {  name: &#39;Data B&#39;,  value: 1234};const dataC: MyType&lt;boolean&gt; = {  name: &#39;Data C&#39;,  value: true};const dataD: MyType&lt;number[]&gt; = {  name: &#39;Data D&#39;,  value: [1, 2, 3, 4]};</code></pre><p>만약 타입 변수 <code>T</code>가 <code>string</code>과 <code>number</code>인 경우만 허용하려면 아래 예제와 같이 <strong><code>extends</code> 키워드를 사용하는 제약 조건</strong>을 추가할 수 있습니다.<br>기본 문법은 다음과 같습니다.</p><pre><code class="typescript">T extends U</code></pre><pre><code class="typescript">interface MyType&lt;T extends string | number&gt; {  name: string,  value: T}const dataA: MyType&lt;string&gt; = {  name: &#39;Data A&#39;,  value: &#39;Hello world&#39;};const dataB: MyType&lt;number&gt; = {  name: &#39;Data B&#39;,  value: 1234};const dataC: MyType&lt;boolean&gt; = { // TS2344: Type &#39;boolean&#39; does not satisfy the constraint &#39;string | number&#39;.  name: &#39;Data C&#39;,  value: true};const dataD: MyType&lt;number[]&gt; = { // TS2344: Type &#39;number[]&#39; does not satisfy the constraint &#39;string | number&#39;.  name: &#39;Data D&#39;,  value: [1, 2, 3, 4]};</code></pre><p>대표적으로 <code>type</code>과 <code>interface</code> 키워드를 사용하는 타입 선언은 다음 예제와 같이 <code>=</code> 기호를 기준으로 ‘식별자’와 ‘타입 구현’으로 구분할 수 있습니다.<br>제약 조건은 ‘식별자’ 영역에서 사용하는 <code>extends</code>에 한합니다.</p><pre><code class="typescript">type U = string | number | boolean;// type 식별자 = 타입 구현type MyType&lt;T extends U&gt; = string | T;// interface 식별자 { 타입 구현 }interface IUser&lt;T extends U&gt; {  name: string,  age: T}</code></pre><h2><span id="b76a6645-d2be-4a59-beb1-e9b08c63a220">조건부 타입(Conditional Types)</span><a href="#b76a6645-d2be-4a59-beb1-e9b08c63a220" class="header-anchor"></a></h2><p>제약 조건과 다르게 ‘타입 구현’ 영역에서 사용하는 <code>extends</code>는 삼항 연산자(Conditional ternary operator)를 사용할 수 있습니다.<br>이를 조건부 타입(Conditional Types)이라고 하며 다음과 같은 문법을 가집니다.</p><pre><code class="typescript">T extends U ? X : Y</code></pre><pre><code class="typescript">type U = string | number | boolean;// type 식별자 = 타입 구현type MyType&lt;T&gt; = T extends U ? string : never;// interface 식별자 { 타입 구현 }interface IUser&lt;T&gt; {  name: string,  age: T extends U ? number : never}</code></pre><pre><code class="typescript">// `T`는 `boolean` 타입으로 제한.interface IUser&lt;T extends boolean&gt; {  name: string,  age: T extends true ? string : number, // `T`의 타입이 `true`인 경우 `string` 반환, 아닌 경우 `number` 반환.  isString: T}const str: IUser&lt;true&gt; = {  name: &#39;Neo&#39;,  age: &#39;12&#39;, // String  isString: true}const num: IUser&lt;false&gt; = {  name: &#39;Lewis&#39;,  age: 12, // Number  isString: false}</code></pre><p>다음과 같이 삼항 연산자를 연속해서 사용할 수도 있습니다.</p><pre><code class="typescript">type MyType&lt;T&gt; =  T extends string ? &#39;Str&#39; :  T extends number ? &#39;Num&#39; :  T extends boolean ? &#39;Boo&#39; :  T extends undefined ? &#39;Und&#39; :  T extends null ? &#39;Nul&#39; :  &#39;Obj&#39;;</code></pre><h3><span id="6e48ee69-e26f-4ab5-aed2-8a940a904f01">infer</span><a href="#6e48ee69-e26f-4ab5-aed2-8a940a904f01" class="header-anchor"></a></h3><p><code>infer</code> 키워드를 사용해 <strong>타입 변수의 타입 추론(Inference) 여부</strong>를 확인할 수 있습니다.<br>기본 문법은 다음과 같습니다.</p><blockquote><p><code>U</code>가 추론 가능한 타입이면 참, 아니면 거짓</p></blockquote><pre><code class="typescript">T extends infer U ? X : Y</code></pre><p>유용하진 않지만, 이해를 위한 아주 간단한 예제를 살펴봅시다.<br>기본 구조는 위에서 살펴본 조건부 타입과 같습니다.</p><pre><code class="typescript">type MyType&lt;T&gt; = T extends infer R ? R : null;const a: MyType&lt;number&gt; = 123;</code></pre><p>여기서 타입 변수 <code>R</code>은 <code>MyType&lt;number&gt;</code>에서 받은 타입 <code>number</code>가 되고 <code>infer</code> 키워드를 통해 타입 추론이 가능한지 확인합니다.<br><code>number</code> 타입은 당연히 타입 추론이 가능하니 <code>R</code>을 반환하게 됩니다.(만약 <code>R</code>을 타입 추론할 수 없다면 <code>null</code>이 반환됩니다)<br>결과적으로 <code>MyType&lt;number&gt;</code>는 <code>number</code>를 반환하고 변수 <code>a</code>는 <code>123</code>을 할당할 수 있습니다.</p><p>이번에는 조금 더 복잡하지만 유용한 예제를 하나 살펴봅시다.<br><code>ReturnType</code>는 함수의 반환 값이 어떤 타입인지 반환합니다.</p><blockquote><p>‘TS 유틸리티 타입 &gt; ReturnType’ 파트를 참고하세요.</p></blockquote><pre><code class="typescript">type ReturnType&lt;T extends (...args: any) =&gt; any&gt; = T extends (...args: any) =&gt; infer R ? R : any;function fn(num: number) {  return num.toString();}const a: ReturnType&lt;typeof fn&gt; = &#39;Hello&#39;;</code></pre><p>위 예제에서 <code>typeof fn</code>은 <code>(num: number) =&gt; string</code>으로 반환 타입은 <code>string</code>입니다.<br>따라서 <code>R</code>은 <code>string</code>이고 역시 <code>infer</code> 키워드를 통해서 타입 추론이 가능하기 때문에 <code>R</code>을 반환합니다.<br>즉, <code>string</code>을 반환합니다.</p><p><code>infer</code> 키워드에 대한 더 자세한 내용은 공식 문서의 <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types" target="_blank" rel="noopener">Type inference in conditional types</a> 파트를 참고하세요.<br>문서 내용은 다음과 같이 간단히 정리했습니다.</p><ul><li><code>infer</code> 키워드는 제약 조건 <code>extends</code>가 아닌 조건부 타입 <code>extends</code> 절에서만 사용 가능</li><li><code>infer</code> 키워드는 같은 타입 변수를 여러 위치에서 사용 가능<ul><li>일반적인 공변성(co-variant) 위치에선 유니언 타입으로 추론</li><li>함수 인수인 반공변성(contra-variant) 위치에선 인터섹션 타입으로 추론</li></ul></li><li>여러 호출 시그니처(함수 오버로드)의 경우 마지막 시그니처에서 추론</li></ul><blockquote><p>공변성과 반공변성에 대한 자세한 내용은 다음 포스트를 참고하세요.<br><a href="https://medium.com/@iamssen/typescript-%EC%97%90%EC%84%9C%EC%9D%98-%EA%B3%B5%EB%B3%80%EC%84%B1%EA%B3%BC-%EB%B0%98%EA%B3%B5%EB%B3%80%EC%84%B1-strictfunctiontypes-a82400e67f2" target="_blank" rel="noopener">TypeScript에서의 공변성과 반공변성 (strictFunctionTypes)</a></p></blockquote><h1><span id="6ad05084-17a4-472c-b143-9e30a3d10913">함수</span><a href="#6ad05084-17a4-472c-b143-9e30a3d10913" class="header-anchor"></a></h1><p>기본적인 함수 사용에 대해선 위에서 살펴봤습니다.<br>여기서는 타입스크립트 함수의 주요 특징들에 대해서 살펴봅시다.</p><h2><span id="99be3e90-c8b5-49c7-9647-e45ad955029f">this</span><a href="#99be3e90-c8b5-49c7-9647-e45ad955029f" class="header-anchor"></a></h2><p>함수를 다루는 데 있어 가장 중요한 내용 중 하나가 바로 <code>this</code>입니다.<br>함수 내 <code>this</code>는 전역 객체를 참조하거나(sloppy mode), <code>undefined</code>(strict mode)가 되는 등 우리가 원하는 콘텍스트(context)를 잃고 다른 값이 되는 경우들이 있습니다.</p><pre><code class="typescript">const obj = {  a: &#39;Hello~&#39;,  b: function () {    console.log(this.a); // obj.a    // Inner function    function b() {      console.log(this.a); // global.a    }  }};</code></pre><p>특히 ‘호출하지 않는 메소드’를 사용하는 경우에 <code>this</code>로 인한 문제가 발생합니다.<br>우선, 다음 예제를 살펴봅시다.<br>객체 데이터 <code>obj</code>에서 <code>b</code> 메소드는 <code>a</code> 속성을 <code>this</code>를 통해 참조하고 있습니다.</p><pre><code class="typescript">const obj = {  a: &#39;Hello~&#39;,  b: function () {    console.log(this.a);  }};</code></pre><p>위 객체를 기준으로 아래 예제와 같이 ‘호출하지 않는 메소드’를 사용(할당)하는 경우, <code>this</code>가 유효한 콘텍스트를 잃어버리고 <code>a</code>를 참조할 수 없게 됩니다.</p><blockquote><p>많은 경우 콜백 함수가 해당합니다.</p></blockquote><pre><code class="typescript">obj.b(); // Hello~const b = obj.b;b(); // Cannot read property &#39;a&#39; of undefinedfunction someFn(cb: any) {  cb();}someFn(obj.b); // Cannot read property &#39;a&#39; of undefinedsetTimeout(obj.b, 100); // undefined</code></pre><p>이런 상황에서 <code>this</code> 콘텍스트가 정상적으로 유지되어 <code>a</code> 속성을 참조할 수 있는 방법을 알아봅시다.</p><p>첫 번째는 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind" target="_blank" rel="noopener">bind 메소드</a>를 사용해 <code>this</code>를 <strong>직접 연결</strong>해 주는 방법입니다.</p><blockquote><p>타입스크립트에서 bind, call, apply 메소드는 기본적으로 인수 타입 체크를 하지 않기 때문에, 컴파일러 옵션에서 <code>strict: true</code>(혹은 <code>strictBindCallApply: true</code>)를 지정해 줘야 정상적으로 타입 체크를 하게 됩니다.</p></blockquote><pre><code class="typescript">obj.b(); // Hello~const b = obj.b.bind(obj);b(); // Hello~function someFn(cb: any) {  cb();}someFn(obj.b.bind(obj)); // Hello~setTimeout(obj.b.bind(obj), 100); // Hello~</code></pre><p>두 번째는 화살표 함수를 사용하는 방법입니다.<br>다음과 같이 화살표 함수를 이용해 유효한 콘텍스트를 유지하면서 메소드를 <strong>호출</strong>합니다.</p><blockquote><p>화살표 함수는 호출된 곳이 아닌 함수가 생성된 곳에서 <code>this</code>를 캡처합니다.</p></blockquote><pre><code class="typescript">obj.b(); // Hello~const b = () =&gt; obj.b();b(); // Hello~function someFn(cb: any) {  cb();}someFn(() =&gt; obj.b()); // Hello~setTimeout(() =&gt; obj.b(), 100); // Hello~</code></pre><p>만약 클래스의 메소드 멤버를 정의하는 경우, 프로토타입(prototype) 메소드가 아닌 화살표 함수를 사용할 수 있습니다.</p><pre><code class="typescript">class Cat {  constructor(private name: string) {}  getName = () =&gt; {    console.log(this.name);  }}const cat = new Cat(&#39;Lucy&#39;);cat.getName(); // Lucyconst getName = cat.getName;getName(); // Lucyfunction someFn(cb: any) {  cb();}someFn(cat.getName); // Lucy</code></pre><p>여기서 주의할 점은 인스턴스를 생성할 때마다 개별적인 <code>getName</code>이 만들어지게 되는데, 일반적인 메소드 호출에서의 화살표 함수 사용은 비효율적이지만 만약에 메소드를 주로 콜백으로 사용하는 경우엔 프로토타입의 새로운 클로져 호출보다 화살표 함수의 생성된 <code>getName</code> 참조가 훨씬 효율적일 수 있습니다.</p><p><img src="/images/screenshot/typescript/typescript-compare-prototype-and-arrow-function.jpg" alt="prototype method member &amp; arrow function"></p><blockquote><p>각 방법은 메모리와 성능에 대한 트레이드-오프(trade-off)입니다.<br>상황에 맞게 선택하는 것이 좋습니다.</p></blockquote><h3><span id="c3d829e4-7c79-4a24-9ae3-73dd19b7fbe5">명시적 this</span><a href="#c3d829e4-7c79-4a24-9ae3-73dd19b7fbe5" class="header-anchor"></a></h3><p>다음 예제를 살펴보면, <code>someFn</code> 함수 내 <code>this</code>가 캡처할 수 있는 <code>cat</code> 객체를 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call" target="_blank" rel="noopener">call 메소드</a>를 통해 전달 및 실행했지만, 엄격 모드에서 <code>this</code>는 암시적인(implicitly) <code>any</code> 타입이기 때문에 에러가 발생합니다.</p><blockquote><p>‘엄격 모드’는 컴파일러 옵션에서 <code>strict: true</code>(혹은 <code>noImplicitThis: true</code>)인 경우를 말합니다.</p></blockquote><pre><code class="typescript">interface ICat {  name: string}const cat: ICat = {  name: &#39;Lucy&#39;};function someFn(greeting: string) {  console.log(`${greeting} ${this.name}`); // Error - TS2683: &#39;this&#39; implicitly has type &#39;any&#39; because it does not have a type annotation.}someFn.call(cat, &#39;Hello&#39;); // Hello Lucy</code></pre><p>이 경우 <code>this</code>의 타입을 명시적으로(explicitly) 선언할 수 있습니다.<br>다음과 같이 첫 번째 가짜(fake) 매개변수로 <code>this</code>를 선언합니다.</p><pre><code class="typescript">interface ICat {  name: string}const cat: ICat = {  name: &#39;Lucy&#39;};function someFn(this: ICat, greeting: string) {  console.log(`${greeting} ${this.name}`); // ok}someFn.call(cat, &#39;Hello&#39;); // Hello Lucy</code></pre><h2><span id="134bab2c-5ca7-4df6-b727-11d9be6ce1bd">오버로드(Overloads)</span><a href="#134bab2c-5ca7-4df6-b727-11d9be6ce1bd" class="header-anchor"></a></h2><p>타입스크립트의 ‘함수 오버로드(Overloads)’는 이름은 같지만 매개변수 타입과 반환 타입이 다른 여러 함수를 가질 수 있는 것을 말합니다.<br>함수 오버로드를 통해 다양한 구조의 함수를 생성하고 관리할 수 있습니다.</p><p>아래 예제에서 <code>add</code> 함수는 2개의 선언부와 1개의 구현부를 가지고 있습니다.<br>주의할 점은 함수 선언부와 구현부의 매개변수 개수가 같아야 합니다.</p><blockquote><p>함수 구현부에 <code>any</code>가 자주 사용됩니다.</p></blockquote><pre><code class="typescript">function add(a: string, b: string): string; // 함수 선언function add(a: number, b: number): number; // 함수 선언function add(a: any, b: any): any { // 함수 구현  return a + b;}add(&#39;hello &#39;, &#39;world~&#39;);add(1, 2);add(&#39;hello &#39;, 2); // Error - No overload matches this call.</code></pre><p>인터페이스나 타입 별칭 등의 메소드 정의에서도 오버로드를 활용할 수 있습니다.<br>타입 단언이나 타입 가드를 통해 함수 선언부의 동적인 매개변수와 반환 값을 정의할 수 있습니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number,  getData(x: string): string[];  getData(x: number): string;}let user: IUser = {  name: &#39;Neo&#39;,  age: 36,  getData: (data: any) =&gt; {    if (typeof data === &#39;string&#39;) {      return data.split(&#39;&#39;);    } else {      return data.toString();    }  }};user.getData(&#39;Hello&#39;); // [&#39;h&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]user.getData(123); // &#39;123&#39;</code></pre><p><code>HTMLDivElement</code> 같이 DOM 타입의 선언부를 살펴보면 다음과 같습니다.</p><pre><code class="typescript">/** Provides special properties (beyond the regular HTMLElement interface it also has available to it by inheritance) for manipulating &lt;div&gt; elements. */interface HTMLDivElement extends HTMLElement {    /**     * Sets or retrieves how the object is aligned with adjacent text.     */    /** @deprecated */    align: string;    addEventListener&lt;K extends keyof HTMLElementEventMap&gt;(type: K, listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) =&gt; any, options?: boolean | AddEventListenerOptions): void;    addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;    removeEventListener&lt;K extends keyof HTMLElementEventMap&gt;(type: K, listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) =&gt; any, options?: boolean | EventListenerOptions): void;    removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;}</code></pre><p><img src="/images/screenshot/typescript/typescript-webstorm-declaration-of-usages.jpg" alt="WebStorm go to declaration or usages"></p><div class="image-caption">Declaration or Usages in WebStorm</div><p><img src="/images/screenshot/typescript/typescript-vscode-go-to-definition.jpg" alt="VSCode go to definition"></p><div class="image-caption">Go to Definition in VSCode</div><h1><span id="bbbfc116-8ff2-42ca-87ad-e96452856f80">클래스</span><a href="#bbbfc116-8ff2-42ca-87ad-e96452856f80" class="header-anchor"></a></h1><p>클래스의 생성자 메소드(<code>constructor</code>)와 일반 메소드(Methods) 멤버(Class member)와는 다르게, 속성(Properties)은 <code>name: string;</code>와 같이 클래스 바디(Class body)에 별도로 타입을 선언합니다.</p><blockquote><p>클래스 바디(Class body)는 중괄호 <code>{}</code>로 묶여 있는 영역을 의미합니다.</p></blockquote><pre><code class="typescript">class Animal {  name: string;  constructor(name: string) {    this.name = name;  }}class Cat extends Animal {  getName(): string {    return `Cat name is ${this.name}.`;  }}let cat: Cat;cat = new Cat(&#39;Lucy&#39;);console.log(cat.getName()); // Cat name is Lucy.</code></pre><h2><span id="a2e52b48-574b-4c7f-90c3-8b5b2310fcd2">클래스 수식어(Modifiers)</span><a href="#a2e52b48-574b-4c7f-90c3-8b5b2310fcd2" class="header-anchor"></a></h2><p>타입스크립트와 관련된 클래스 수식어들을 살펴봅시다.</p><p>클래스 멤버(속성, 메소드)에서 사용할 수 있는 접근 제어자(Access Modifiers)들이 있습니다.<br>각 접근 제어자들의 차이점을 이해해 봅시다.</p><blockquote><p>접근 제어자(Access Modifiers)는 클래스, 메서드 및 기타 멤버의 접근 가능성을 설정하는 객체 지향 언어의 키워드입니다.</p></blockquote><table><thead><tr><th>접근 제어자</th><th>의미</th><th>범위</th></tr></thead><tbody><tr><td><code>public</code></td><td>어디서나 자유롭게 접근 가능(생략 가능)</td><td>속성, 메소드</td></tr><tr><td><code>protected</code></td><td>나와 파생된 후손 클래스 내에서 접근 가능</td><td>속성, 메소드</td></tr><tr><td><code>private</code></td><td>내 클래스에서만 접근 가능</td><td>속성, 메소드</td></tr></tbody></table><p>다음 수식어들은 위 접근 제어자와 함께 사용할 수 있습니다.<br><code>static</code>의 경우 타입스크립트에서는 정적 메소드뿐만 아니라 정적 속성도 사용할 수 있습니다.</p><table><thead><tr><th>수식어</th><th>의미</th><th>범위</th></tr></thead><tbody><tr><td><code>static</code></td><td>정적으로 사용</td><td>속성, 일반 메소드</td></tr><tr><td><code>readonly</code></td><td>읽기 전용으로 사용</td><td>속성</td></tr></tbody></table><p>그럼 우선, 각 접근 제어자들의 차이점에 대해서 살펴봅시다.</p><p>다음 예제의 <code>Animal</code> 클래스의 <code>name</code> 속성은 <code>public</code>이기 때문에 파생된 자식 클래스(<code>Cat</code>)에서 <code>this.name</code>으로 참조하거나 인스턴스에서 <code>cat.name</code>으로 접근하는데 아무런 문제가 없습니다.<br>(어디서나 자유롭게 접근 가능(생략 가능))</p><pre><code class="typescript">class Animal {  // public 수식어 사용(생략 가능)  public name: string;  constructor(name: string) {    this.name = name;  }}class Cat extends Animal {  getName(): string {    return `Cat name is ${this.name}.`;  }}let cat = new Cat(&#39;Lucy&#39;);console.log(cat.getName()); // Cat name is Lucy.cat.name = &#39;Tiger&#39;;console.log(cat.getName()); // Cat name is Tiger.</code></pre><p>다음 예제의 <code>Animal</code> 클래스의 <code>name</code> 속성은 <code>protected</code>이기 때문에 파생된 자식 클래스(<code>Cat</code>)에서 <code>this.name</code>으로 참조할 수는 있지만, 인스턴스에서 <code>cat.name</code>으로는 접근할 수 없습니다.<br>(나와 파생된 후손 클래스 내에서 접근 가능)</p><pre><code class="typescript">class Animal {  // protected 수식어 사용  protected name: string;  constructor(name: string) {    this.name = name;  }}class Cat extends Animal {  getName(): string {    return `Cat name is ${this.name}.`;  }}let cat = new Cat(&#39;Lucy&#39;);console.log(cat.getName()); // Cat name is Lucy.console.log(cat.name); // Error - TS2445: Property &#39;name&#39; is protected and only accessible within class &#39;Animal&#39; and its subclasses.cat.name = &#39;Tiger&#39;; // Error - TS2445: Property &#39;name&#39; is protected and only accessible within class &#39;Animal&#39; and its subclasses.console.log(cat.getName());</code></pre><p>다음 예제의 <code>Animal</code> 클래스의 <code>name</code> 속성은 <code>private</code>이기 때문에 파생된 자식 클래스(<code>Cat</code>)에서 <code>this.name</code>으로 참조할 수 없고, 인스턴스에서도 <code>cat.name</code>으로 접근할 수도 없습니다.<br>(내 클래스에서만 접근 가능)</p><pre><code class="typescript">class Animal {  // private 수식어 사용  private name: string;  constructor(name: string) {    this.name = name;  }}class Cat extends Animal {  getName(): string {    return `Cat name is ${this.name}.`; // Error - TS2341: Property &#39;name&#39; is private and only accessible within class &#39;Animal&#39;  }}let cat = new Cat(&#39;Lucy&#39;);console.log(cat.getName());console.log(cat.name); // Error - TS2341: Property &#39;name&#39; is private and only accessible within class &#39;Animal&#39;.cat.name = &#39;Tiger&#39;; // Error - TS2341: Property &#39;name&#39; is private and only accessible within class &#39;Animal&#39;.console.log(cat.getName());</code></pre><p>다음은 생성자 메소드(<code>constructor</code>)에 <code>protected</code>를 사용했기 때문에 인스턴스 생성에서 에러가 발생하는 예제입니다.</p><pre><code class="typescript">class Animal {  name: string;  protected constructor(name: string) {    this.name = name;  }}const cat = new Animal(&#39;Dog&#39;); // Error - TS2674: Constructor of class &#39;Animal&#39; is protected and only accessible within the class declaration.</code></pre><p>그리고 흥미로운 부분은 생성자 메소드에서 인수 타입 선언과 동시에 접근 제어자를 사용하면 바로 속성 멤버로 정의할 수 있습니다.<br>접근 제어자를 생략하지 않도록 주의하세요.</p><pre><code class="typescript">class Cat {  constructor(public name: string, protected age: number) {}  getName() {    return this.name;  }  getAge() {    return this.age;  }}const cat = new Cat(&#39;Neo&#39;, 2);console.log(cat.getName()); // Neoconsole.log(cat.getAge()); // 2</code></pre><p>이번엔 <code>static</code>과 <code>readonly</code>에 대해서 살펴봅시다.</p><p>ES6에서는 <code>static</code>으로 정적 메소드만 생성할 수 있었는데, 타입스크립트에서는 정적 속성도 생성할 수 있습니다.<br>정적 속성은 클래스 바디에서 속성의 타입 선언과 같이 사용하며, 정적 메소드와 다르게 클래스 바디에서 값을 초기화할 수 없기 때문에 <code>constructor</code> 혹은 메소드에서 초기화가 필요합니다.</p><pre><code class="typescript">class Cat {  static legs: number;  constructor() {    Cat.legs = 4; // Init static property.  }}console.log(Cat.legs); // undefinednew Cat();console.log(Cat.legs); // 4class Dog {  // Init static method.  static getLegs() {    return 4;  }}console.log(Dog.getLegs()); // 4</code></pre><p><code>readonly</code>을 사용하면 해당 속성은 ‘읽기 전용’입니다.</p><pre><code class="typescript">class Animal {  readonly name: string;  constructor(n: string) {    this.name = n;  }}let dog = new Animal(&#39;Charlie&#39;);console.log(dog.name); // Charliedog.name = &#39;Tiger&#39;; // Error - TS2540: Cannot assign to &#39;name&#39; because it is a read-only property.</code></pre><p>그리고 <code>static</code>과 <code>readonly</code>는 접근 제어자와 같이 사용할 수 있습니다.<br>접근 제어자를 먼저 작성해야 합니다.</p><pre><code class="typescript">class Cat {  public readonly name: string;  protected static eyes: number;  constructor(n: string) {    this.name = n;    Cat.eyes = 2;  }  private static getLegs() {    return 4;  }}</code></pre><h2><span id="52b81c3d-1be2-4ff9-b1d7-6a417060e9de">추상(Abstract) 클래스</span><a href="#52b81c3d-1be2-4ff9-b1d7-6a417060e9de" class="header-anchor"></a></h2><p>추상(Abstract) 클래스는 다른 클래스가 파생될 수 있는 기본 클래스로, 인터페이스와 굉장히 유사합니다.<br><code>abstract</code>는 클래스뿐만 아니라 속성과 메소드에도 사용할 수 있습니다.<br>추상 클래스는 직접 인스턴스를 생성할 수 없기 때문에 파생된 후손 클래스에서 인스턴스를 생성해야 합니다.</p><pre><code class="typescript">// Abstract Classabstract class Animal {  abstract name: string; // 파생된 클래스에서 구현해야 합니다.  abstract getName(): string; // 파생된 클래스에서 구현해야 합니다.}class Cat extends Animal {  constructor(public name: string) {    super();  }  getName() {    return this.name;  }}new Animal(); // Error - TS2511: Cannot create an instance of an abstract class.const cat = new Cat(&#39;Lucy&#39;);console.log(cat.getName()); // Lucy// Interfaceinterface IAnimal {  name: string;  getName(): string;}class Dog implements IAnimal {  constructor(public name: string) {}  getName() {    return this.name;  }}</code></pre><p>추상 클래스가 인터페이스와 다른 점은 속성이나 메소드 멤버에 대한 세부 구현이 가능하다는 점입니다.</p><pre><code class="typescript">abstract class Animal {  abstract name: string;  abstract getName(): string;  // Abstract class constructor can be made protected.  protected constructor(public legs: string) {}  getLegs() {    return this.legs  }}</code></pre><h1><span id="2bc73bb2-5866-430a-9317-1f02743f5208">Optional</span><a href="#2bc73bb2-5866-430a-9317-1f02743f5208" class="header-anchor"></a></h1><p><code>?</code> 키워드를 사용하는 여러 선택적(Optional) 개념에 대해서 살펴봅시다.</p><h2><span id="d873cc52-2596-4210-8e71-d9ef89b5559f">매개 변수(Parameters)</span><a href="#d873cc52-2596-4210-8e71-d9ef89b5559f" class="header-anchor"></a></h2><p>우선, 타입을 선언할 때 선택적 매개 변수(Optional Parameter)를 지정할 수 있습니다.<br>다음 예제를 보면 <code>?</code> 키워드를 사용해 <code>y</code>를 선택적 매개 변수로 지정했습니다.<br>따라서 <code>y</code>가 받을 인수가 없어도 에러가 발생하지 않습니다.</p><pre><code class="typescript">function add(x: number, y?: number): number {  return x + (y || 0);}const sum = add(2);console.log(sum);</code></pre><p>위 예제는 정확히 다음 예제와 같습니다.<br>즉, <code>?</code> 키워드 사용은 <code>| undefined</code>를 추가하는 것과 같습니다.</p><pre><code class="typescript">function add(x: number, y: number | undefined): number {  return x + (y || 0);}const sum = add(2, undefined);console.log(sum);</code></pre><h2><span id="cedbabd1-f4ba-4cff-89a2-ee6034c461ea">속성과 매소드(Properties and Methods)</span><a href="#cedbabd1-f4ba-4cff-89a2-ee6034c461ea" class="header-anchor"></a></h2><p><code>?</code> 키워드를 속성(Properties)과 메소드(Methods) 타입 선언에도 사용할 수 있습니다.<br>다음은 인터페이스 파트에서 살펴봤던 예제입니다.<br><code>isAdult</code>를 선택적 속성으로 선언하면서 더 이상 에러가 발생하지 않습니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number,  isAdult?: boolean}let user1: IUser = {  name: &#39;Neo&#39;,  age: 123,  isAdult: true};let user2: IUser = {  name: &#39;Evan&#39;,  age: 456};</code></pre><p>Type이나 Class에서도 사용할 수 있습니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number,  isAdult?: boolean,  validate?(): boolean}type TUser = {  name: string,  age: number,  isAdult?: boolean,  validate?(): boolean}abstract class CUser {  abstract name: string;  abstract age: number;  abstract isAdult?: boolean;  abstract validate?(): boolean;}</code></pre><h2><span id="979e3adb-e58b-42bc-85c7-630bccce8bff">체이닝(Chaining)</span><a href="#979e3adb-e58b-42bc-85c7-630bccce8bff" class="header-anchor"></a></h2><p>다음 예제는 <code>str</code> 속성이 <code>undefined</code>일 경우 <code>toString</code> 메소드를 사용할 수 없기 때문에 에러가 발생합니다.<br><code>str</code> 속성이 문자열이라는 것을 단언하면 문제를 해결할 수 있지만, 더 간단하게 선택적 체이닝(Optional Chaining) 연산자 <code>?.</code>를 사용할 수 있습니다.</p><p>자세한 사용법은 MDN 문서를 참고하세요.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Optional_chaining" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Optional_chaining</a></p><pre><code class="typescript">obj?.prop;obj?.[expr];arr?.[index];func?.(args);</code></pre><pre><code class="typescript">// Error - TS2532: Object is possibly &#39;undefined&#39;.function toString(str: string | undefined) {  return str.toString();}// Type Assertionfunction toString(str: string | undefined) {  return (str as string).toString();}// Optional Chainingfunction toString(str: string | undefined) {  return str?.toString();}</code></pre><p>특히 <code>&amp;&amp;</code> 연산자를 사용해 각 속성을 Nullish 체크(<code>null</code>이나 <code>undefined</code>를 확인)하는 부분에서 유용합니다.</p><pre><code class="typescript">// Beforeif (foo &amp;&amp; foo.bar &amp;&amp; foo.bar.baz) {}// After-ishif (foo?.bar?.baz) {}</code></pre><h2><span id="836a4610-2545-4cf4-971a-e729850ba7cf">Nullish 병합 연산자</span><a href="#836a4610-2545-4cf4-971a-e729850ba7cf" class="header-anchor"></a></h2><p>일반적으로 논리 연산자 <code>||</code>를 사용해 Falsy 체크(<code>0</code>, <code>&quot;&quot;</code>, <code>NaN</code>, <code>null</code>, <code>undefined</code>를 확인)하는 경우가 많습니다.<br>여기서 <code>0</code>이나 <code>&quot;&quot;</code> 값을 유효 값으로 사용하는 경우 원치 않는 결과가 발생할 수 있는데, 이럴 때 유용한 Nullish 병합(Nullish Coalescing) 연산자 <code>??</code>를 타입스크립트에서 사용할 수 있습니다.</p><p>자세한 사용법은 MDN 문서를 참고하세요.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator</a></p><pre><code class="typescript">const foo = null ?? &#39;Hello nullish.&#39;;console.log(foo); // Hello nullish.const bar = false ?? true;console.log(bar); // falseconst baz = 0 ?? 12;console.log(baz); // 0</code></pre><h1><span id="436b5c21-5d10-4d75-bc83-2ce182ccec29">모듈</span><a href="#436b5c21-5d10-4d75-bc83-2ce182ccec29" class="header-anchor"></a></h1><p>타입스크립트의 모듈을 이해하기 위해선 자바스크립트 모듈에 대한 이해가 선행되어야 합니다.<br>타입스크립트 공식 문서의 많은 부분이 이 자바스크립트 모듈에 대한 설명을 포함하고 있는데, 여기서는 타입스크립트가 가지는 모듈 개념의 차이점에 대해서만 살펴보려고 합니다.</p><h2><span id="935a20d1-740c-4b56-bf67-7e21274deab3">내보내기(export)와 가져오기(import)</span><a href="#935a20d1-740c-4b56-bf67-7e21274deab3" class="header-anchor"></a></h2><p>자바스크립트 모듈의 내보내기(export)와 가져오기(import)에 대해 이해가 부족하다면 다음 MDN 문서를 우선 참고하시길 바랍니다.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/export" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/export</a><br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import</a></p><p>타입스크립트는 일반적인 변수나 함수, 클래스뿐만 아니라 다음과 같이 인터페이스나 타입 별칭도 모듈로 내보낼 수 있습니다.</p><pre><code class="typescript">// myTypes.ts// 인터페이스 내보내기export interface IUser {  name: string,  age: number}// 타입 별칭 내보내기export type MyType = string | number;</code></pre><pre><code class="typescript">// 선언한 모듈(myTypes.ts) 가져오기import { IUser, MyType } from &#39;./myTypes&#39;;const user: IUser = {  name: &#39;HEROPY&#39;,  age: 85};const something: MyType = true; // Error - TS2322: Type &#39;true&#39; is not assignable to type &#39;MyType&#39;.</code></pre><p>타입스크립트는 CommonJS/AMD/UMD 모듈을 위해 <code>export = ABC;</code>, <code>import ABC = require(&#39;abc&#39;);</code>와 같은 내보내기와 가져오기 문법을 제공합니다.<br>이는 ES6 모듈의 <code>export default</code> 같이 하나의 모듈에서 하나의 객체만 내보내는 Default Export 기능을 제공합니다.</p><p>결국 타입스크립트에서 CommonJS/AMD/UMD 모듈은 다음과 같이 가져올 수 있습니다.<br>추가로, 컴파일 옵션에 <code>&quot;esModuleInterop&quot;: true</code>를 제공하면, ES6 모듈의 Default Import 방식도 같이 사용할 수 있습니다.</p><pre><code class="typescript">// CommonJS/AMD/UMDimport ABC = require(&#39;abc&#39;);// orimport * as ABC from &#39;abc&#39;;// or `&quot;esModuleInterop&quot;: true`import ABC from &#39;abc&#39;;</code></pre><h2><span id="cdf941c1-19d9-4398-8d06-04d047f1d3c2">모듈의 타입 선언(Ambient module declaration)</span><a href="#cdf941c1-19d9-4398-8d06-04d047f1d3c2" class="header-anchor"></a></h2><p>타입스크립트의 외부 자바스크립트 모듈 사용에 대해서 알아봅시다.<br>간단한 프로젝트를 생성하고 외부 모듈로 <a href="https://lodash.com/" target="_blank" rel="noopener">Lodash</a>를 설치해 사용해 보겠습니다.</p><blockquote><p>‘모듈’ 파트의 타입스크립트 프로젝트 생성은 ‘개발환경 / TS Node’ 파트를 참고하세요.</p></blockquote><pre><code class="bash">$ npm install lodash</code></pre><p><code>main.ts</code>에서 Lodash 모듈의 <code>camelCase</code> API를 사용해 콘솔 출력하는 아주 단순한 코드를 작성합니다.<br>하지만 다음과 같이 ‘가져오기(import)’ 단계에서 에러가 발생합니다.<br>이는 타입스크립트 컴파일러가 확인할 수 있는 모듈의 타입 선언(Ambient module declaration)이 없기 때문입니다.</p><pre><code class="typescript">// main.tsimport * as _ from &#39;lodash&#39;; // Error - TS2307: Cannot find module &#39;lodash&#39;.console.log(_.camelCase(&#39;import lodash module&#39;));</code></pre><p><strong>모듈 구현(implement)과 타입 선언(declaration)이 동시에 이뤄지는 타입스크립트</strong>와 달리, 구현만 존재하는 자바스크립트 모듈(E.g. Lodash)을 사용하는 경우, 컴파일러가 이해할 수 있는 모듈의 타입 선언이 필요하며, 이를 대부분 <code>.d.ts</code>파일로 만들어 제공하게 됩니다.</p><p>그럼 이제 Lodash에 대한 타입 선언을 해봅시다.<br>다음과 같이 루트 경로에 <code>lodash.d.ts</code> 파일을 생성합니다.</p><p><img src="/images/screenshot/typescript/typescript-module-ambient1.jpg" alt="Ambient modules"></p><p>기본 구조는 단순합니다.<br>모듈 가져오기(Import)가 가능하도록 <code>module</code> 키워드를 사용해 모듈 이름을 명시합니다.<br>그리고 그 범위 안에서, 타입(<code>interface</code>)을 가진 변수(<code>_</code>)를 선언하고 내보내기(Export)만 하면 됩니다.</p><blockquote><p>타입스크립트 컴파일러가 이해할 수 있도록 <code>declare</code> 키워드를 통해 선언해야 합니다!</p></blockquote><pre><code class="typescript">// lodash.d.ts// 모듈의 타입 선언(Ambient module declaration)declare module &#39;lodash&#39; {  // 1. 타입(인터페이스) 선언  interface ILodash {    camelCase(str?: string): string  }  // 2. 타입(인터페이스)을 가지는 변수 선언  const _: ILodash;  // 3. 내보내기(CommonJS)  export = _;}</code></pre><p>그리고 이 타입 선언이 컴파일 과정에 포함될 수 있도록 다음과 같이 <code>///</code>(삼중 슬래시 지시자, Triple-slash directive)를 사용하는 <strong>참조 태그(<code>&lt;reference /&gt;</code>)</strong>와 <code>path</code> 속성을 사용합니다.<br>넘어가기 전, 참조 태그의 특징에 대해서 몇 가지 살펴보면,</p><ul><li>참조 태그로 가져오는 것은 모듈 구현이 아니라 타입 선언이기 때문에, <code>import</code> 키워드로 가져오지 않아야 합니다.</li><li>삼중 슬래시 지시자는 자바스크립트로 컴파일되면 단순 주석입니다.</li><li><code>path</code> 속성은 가져올 타입 선언의 상대 경로를 지정하며, 확장자를 꼭 입력해야 합니다.</li><li><code>types</code> 속성은 <code>/// &lt;reference types=&quot;lodash&quot; /&gt;</code>와 같이 모듈 이름을 지정하며, 이는 컴파일 옵션 <code>typeRoots</code> 와 Definitely Typed(<code>@types</code>)를 기준으로 합니다.</li></ul><blockquote><p>컴파일 옵션 <code>typeRoots</code>와 Definitely Typed(<code>@types</code>)는 뒤에서 살펴봅니다.</p></blockquote><pre><code class="typescript">// 참조 태그(Triple-slash directive)/// &lt;reference path=&quot;./lodash.d.ts&quot; /&gt;import * as _ from &#39;lodash&#39;;console.log(_.camelCase(&#39;import lodash module&#39;));</code></pre><p>정상적으로 콘솔 출력되는지 확인합니다.</p><pre><code class="bash">$ npx ts-node main.ts# importLodashModule</code></pre><h3><span id="0c7ffebc-a75e-47d0-be14-5b917c5e717a">Definitely Typed(@types)</span><a href="#0c7ffebc-a75e-47d0-be14-5b917c5e717a" class="header-anchor"></a></h3><p>이전 파트에서 Lodash의 <code>camelCase</code> 메소드를 사용했고, 이번엔 추가로 <code>snakeCase</code>도 사용하려고 합니다.<br>하지만 우리는 <code>lodash.d.ts</code>에 <code>snakeCase</code>에 대한 타입 선언을 하지 않았기 때문에 다음과 같이 에러가 발생합니다.</p><pre><code class="typescript">// main.ts/// &lt;reference path=&quot;./lodash.d.ts&quot; /&gt;import * as _ from &#39;lodash&#39;;console.log(_.camelCase(&#39;import lodash module&#39;));console.log(_.snakeCase(&#39;import lodash module&#39;)); // Error - TS2339: Property &#39;snakeCase&#39; does not exist on type &#39;ILodash&#39;.</code></pre><p>이는 <code>lodash.d.ts</code>에 <code>snakeCase</code>에 대한 타입 선언을 하면 간단히 해결할 수 있습니다.</p><pre><code class="typescript">// lodash.d.tsdeclare module &#39;lodash&#39; {  interface ILodash {    camelCase(str?: string): string,    snakeCase(str?: string): string // 타입 선언 추가  }  const _: ILodash;  export = _;}</code></pre><p>하지만, 프로젝트에서 사용하는 모든 모듈에 대해 매번 직접 타입 선언을 작성하는 것(타이핑, Typing)은 매우 비효율적입니다.<br>그래서 우리는 여러 사용자들의 기여로 만들어진 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped" target="_blank" rel="noopener">Definitely Typed</a>을 사용할 수 있습니다.<br>수 많은 모듈의 타입이 정의되어 있으며, 지속적으로 추가되고 있습니다.</p><p><code>npm install -D @types/모듈이름</code>으로 설치해 사용합니다.<br><code>npm info @types/모듈이름</code>으로 검색하면 원하는 모듈의 타입 선언이 존재하는지 확인할 수 있습니다.</p><p>다음과 같이 Lodash 타입 선언을 설치합니다.</p><pre><code class="bash">$ npm i -D @types/lodash</code></pre><p>이제, 더 이상 필요치 않으니 <code>lodash.d.ts</code>를 삭제합니다!<br><code>main.ts</code>의 참조 태그(Triple-slash directive)도 같이 삭제합니다!<br>별도 설정이 없어도, 다양한 Lodash API를 사용할 수 있습니다.</p><pre><code class="typescript">// main.tsimport * as _ from &#39;lodash&#39;;console.log(_.camelCase(&#39;import lodash module&#39;));console.log(_.snakeCase(&#39;import lodash module&#39;));console.log(_.kebabCase(&#39;import lodash module&#39;));</code></pre><pre><code class="bash">$ npx ts-node main.ts# importLodashModule# import_lodash_module# import-lodash-module</code></pre><p>동작 원리는 간단합니다.<br>타입 선언 모듈(<code>@types/lodash</code>)은 <code>node_modules/@types</code>경로에 설치되며,<br>이 경로의 모든 타입 선언은 <strong>모듈 가져오기(Import)를 통해 컴파일에 자동으로 포함</strong>됩니다.</p><p><img src="/images/screenshot/typescript/typescript-module-declaration-for-camelcase-in-lodash.jpg" alt="camelCase type declaration in @types/lodash"></p><h3><span id="9901349e-5ff2-4194-903d-c9b95b5e5bb7">typeRoots와 types 옵션</span><a href="#9901349e-5ff2-4194-903d-c9b95b5e5bb7" class="header-anchor"></a></h3><p>위에서 살펴본 것과 같이, 자바스크립트 모듈을 사용할 때 다음과 같이 타입 선언을 고민하지 않아도 되는 상황들이 있습니다.</p><ul><li>처음부터 타입스크립트로 작성된 모듈</li><li>타입 선언(<code>.d.ts</code> 파일 등)을 같이 제공하는 자바스크립트 모듈</li><li>Definitely Typed(<code>@types/모듈</code>)에 타입 선언이 기여된 자바스크립트 모듈</li></ul><p>하지만 어쩔 수 없이 직접 타입 선언을 작성(타이핑, Typing)해야 하는 다음과 같은 상황들도 고려해야 합니다.</p><ul><li>타입 선언을 찾을 수 없는 자바스크립트 모듈</li><li>가지고 있는 타입 선언을 수정해야 하는 경우</li></ul><p>위에서 작성했던 <code>lodash.d.ts</code>와 같이 직접 타입 선언을 작성해서 제공할 수 있으며, 이를 좀 더 쉽게 관리할 방법으로 컴파일 옵션 <code>typeRoots</code>를 사용할 수 있습니다.</p><p><code>typeRoots</code> 옵션을 테스트하기 위해, 새로운 프로젝트를 만들어 아래와 같이 Lodash를 설치하고 <code>main.ts</code> 파일을 생성합니다.</p><blockquote><p>‘모듈’ 파트의 타입스크립트 프로젝트 생성은 ‘개발환경 / TS Node’ 파트를 참고하세요.</p></blockquote><pre><code class="bash">$ npm install lodash</code></pre><p>역시 ‘가져오기(Import)’ 단계에서 에러가 발생하네요.</p><pre><code class="typescript">// main.tsimport * as _ from &#39;lodash&#39;; // Error - TS2307: Cannot find module &#39;lodash&#39;.console.log(_.camelCase(&#39;import lodash module&#39;));</code></pre><p>이를 해결하기 위해,<br>아래와 같이 <code>index.d.ts</code> 파일을 <code>types/lodash</code> 경로에 생성하고,<br><code>tsconfig.json</code> 파일 컴파일 옵션으로 <code>&quot;typeRoots&quot;: [&quot;./types&quot;]</code>를 제공합니다.<br>넘어가기 전, <code>typeRoots</code> 옵션의 특징에 대해서 몇 가지 살펴보면,</p><ul><li>기본값은 <code>&quot;typeRoots&quot;: [&quot;./node_modules/@types&quot;]</code>입니다.</li><li><code>typeRoots</code> 옵션은 지정된 경로에서 <code>index.d.ts</code> 파일을 우선 탐색합니다.</li><li><code>index.d.ts</code> 파일이 없다면 <code>package.json</code>의 <code>types</code> 혹은 <code>typings</code> 속성에 작성된 경로와 파일 이름을 탐색합니다.</li><li>타입 선언을 찾을 수 없으면 컴파일 오류가 발생합니다.</li></ul><pre><code class="typescript">// types/lodash/index.d.tsdeclare module &#39;lodash&#39; {  interface ILodash {    camelCase(str?: string): string  }  const _: ILodash;  export = _;}</code></pre><p><img src="/images/screenshot/typescript/typescript-module-ambient2.jpg" alt="types directory"><br><img src="/images/screenshot/typescript/typescript-module-ambient3.jpg" alt="typeRoots compile option"></p><p>이제 정상적으로 동작합니다.</p><pre><code class="bash">$ npx ts-node main.ts# importLodashModule</code></pre><p>이렇게 <code>typeRoots</code> 옵션을 통해 <code>types</code> 디렉터리에서 여러 모듈의 타입 선언을 관리할 수 있으며,<br>디렉터리 이름은 <code>types</code> 뿐만 아니라 <code>@types</code>, <code>_types</code>, <code>typings</code> 등 자유롭게 사용할 수 있습니다.</p><p>추가로 컴파일러 옵션 <code>types</code>를 통해 화이트리스트(Whitelist) 방식으로 사용할 모듈 이름만을 작성할 수 있는데,<br><code>&quot;types&quot;: [&quot;lodash&quot;]</code>로 작성하면 <code>types</code> 디렉터리에서 <strong>Lodash의 타입 선언만을 사용</strong>하며,<br><code>&quot;types&quot;: []</code>로 작성하면 <code>types</code> 디렉터리의 <strong>모든 모듈의 타입 선언을 사용하지 않음</strong>을 의미하며,<br><code>types</code> 옵션을 사용하지 않으면 <code>types</code> 디렉터리의 <strong>모든 모듈의 타입 선언을 사용</strong>하게 됩니다.</p><p>모듈 이름을 추가하고 삭제하면서 어떻게 동작하는지 확인하면 이해하는 데 도움이 될 것입니다.</p><blockquote><p>일반적인 경우 <code>types</code> 옵션을 작성할 필요가 없습니다.</p></blockquote><p><img src="/images/screenshot/typescript/typescript-module-types-compile-option.jpg" alt="types compile option"></p><h1><span id="0b1cf61f-78d7-41ad-bad0-04f503369cfe">TS 유틸리티 타입</span><a href="#0b1cf61f-78d7-41ad-bad0-04f503369cfe" class="header-anchor"></a></h1><p>타입스크립트에서 제공하는 여러 전역 유틸리티 타입이 있습니다.<br>이해를 돕기 위한 간단한 예제를 포함했습니다.<br>더 자세한 내용은 <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html" target="_blank" rel="noopener">Utility Types</a>를 참고하세요.</p><blockquote><p>타입 변수 <code>T</code>는 타입(Type), <code>U</code>는 또 다른 타입, <code>K</code>는 속성(key)을 의미하는 약어입니다.<br>이해를 돕기 위해 타입 변수를 <code>T</code>는 <code>TYPE</code> 또는 <code>TYPE1</code>, <code>U</code>는 <code>TYPE2</code>, <code>K</code>는 <code>KEY</code>로 명시했습니다.</p></blockquote><table><thead><tr><th>유틸리티 이름</th><th>설명 (대표 타입)</th><th>타입 변수</th></tr></thead><tbody><tr><td><code>Partial</code></td><td><code>TYPE</code>의 모든 속성을 선택적으로 변경한 새로운 타입 반환 (인터페이스)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>Required</code></td><td><code>TYPE</code>의 모든 속성을 필수로 변경한 새로운 타입 반환 (인터페이스)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>Readonly</code></td><td><code>TYPE</code>의 모든 속성을 읽기 전용으로 변경한 새로운 타입 반환 (인터페이스)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>Record</code></td><td><code>KEY</code>를 속성으로, <code>TYPE</code>를 그 속성값의 타입으로 지정하는 새로운 타입 반환 (인터페이스)</td><td><code>&lt;KEY, TYPE&gt;</code></td></tr><tr><td><code>Pick</code></td><td><code>TYPE</code>에서 <code>KEY</code>로 속성을 선택한 새로운 타입 반환 (인터페이스)</td><td><code>&lt;TYPE, KEY&gt;</code></td></tr><tr><td><code>Omit</code></td><td><code>TYPE</code>에서 <code>KEY</code>로 속성을 생략하고 나머지를 선택한 새로운 타입 반환 (인터페이스)</td><td><code>&lt;TYPE, KEY&gt;</code></td></tr><tr><td><code>Exclude</code></td><td><code>TYPE1</code>에서 <code>TYPE2</code>를 제외한 새로운 타입 반환 (유니언)</td><td><code>&lt;TYPE1, TYPE2&gt;</code></td></tr><tr><td><code>Extract</code></td><td><code>TYPE1</code>에서 <code>TYPE2</code>를 추출한 새로운 타입 반환 (유니언)</td><td><code>&lt;TYPE1, TYPE2&gt;</code></td></tr><tr><td><code>NonNullable</code></td><td><code>TYPE</code>에서 <code>null</code>과 <code>undefined</code>를 제외한 새로운 타입 반환 (유니언)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>Parameters</code></td><td><code>TYPE</code>의 매개변수 타입을 새로운 튜플 타입으로 반환 (함수, 튜플)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>ConstructorParameters</code></td><td><code>TYPE</code>의 매개변수 타입을 새로운 튜플 타입으로 반환 (클래스, 튜플)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>ReturnType</code></td><td><code>TYPE</code>의 반환 타입을 새로운 타입으로 반환 (함수)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>InstanceType</code></td><td><code>TYPE</code>의 인스턴스 타입을 반환 (클래스)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>ThisParameterType</code></td><td><code>TYPE</code>의 명시적 <code>this</code> 매개변수 타입을 새로운 타입으로 반환 (함수)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>OmitThisParameter</code></td><td><code>TYPE</code>의 명시적 <code>this</code> 매개변수를 제거한 새로운 타입을 반환 (함수)</td><td><code>&lt;TYPE&gt;</code></td></tr><tr><td><code>ThisType</code></td><td><code>TYPE</code>의 <code>this</code> 컨텍스트(Context)를 명시, 별도 반환 없음! (인터페이스)</td><td><code>&lt;TYPE&gt;</code></td></tr></tbody></table><script src="https://gist.github.com/ParkYoungWoong/d033592b4b3cb09c182033d783dd0e5a.js"></script><h3><span id="a066a2de-5158-4ecf-b81a-5e7b1959e044">Partial</span><a href="#a066a2de-5158-4ecf-b81a-5e7b1959e044" class="header-anchor"></a></h3><p><code>TYPE</code>의 모든 속성을 선택적(<code>?</code>)으로 변경한 새로운 타입을 반환합니다.</p><blockquote><p>‘Optional &gt; 속성과 메소드’ 파트를 참고하세요.</p></blockquote><pre><code class="typescript">Partial&lt;TYPE&gt;</code></pre><pre><code class="typescript">interface IUser {  name: string,  age: number}const userA: IUser = { // TS2741: Property &#39;age&#39; is missing in type &#39;{ name: string; }&#39; but required in type &#39;IUser&#39;.  name: &#39;A&#39;};const userB: Partial&lt;IUser&gt; = {  name: &#39;B&#39;};</code></pre><p>위 예제의 <code>Partial&lt;IUser&gt;</code>은 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">interface INewType {  name?: string,  age?: number}</code></pre><h3><span id="4108ecb6-8291-4f4d-b95e-98144e0dc520">Required</span><a href="#4108ecb6-8291-4f4d-b95e-98144e0dc520" class="header-anchor"></a></h3><p><code>TYPE</code>의 모든 속성을 필수로 변경한 새로운 타입을 반환합니다.</p><pre><code class="typescript">Required&lt;TYPE&gt;</code></pre><pre><code class="typescript">interface IUser {  name?: string,  age?: number}const userA: IUser = {  name: &#39;A&#39;};const userB: Required&lt;IUser&gt; = { // TS2741: Property &#39;age&#39; is missing in type &#39;{ name: string; }&#39; but required in type &#39;Required&lt;IUser&gt;&#39;.  name: &#39;B&#39;};</code></pre><p>위 예제의 <code>Required&lt;IUser&gt;</code>은 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">interface IUser {  name: string,  age: number}</code></pre><h3><span id="b300c80a-b730-47f0-9947-be63f2ccf472">Readonly</span><a href="#b300c80a-b730-47f0-9947-be63f2ccf472" class="header-anchor"></a></h3><p><code>TYPE</code>의 모든 속성을 읽기 전용(<code>readonly</code>)으로 변경한 새로운 타입을 반환합니다.</p><blockquote><p>‘인터페이스 &gt; 읽기 전용 속성’ 파트를 참고하세요.</p></blockquote><pre><code class="typescript">Readonly&lt;TYPE&gt;</code></pre><pre><code class="typescript">interface IUser {  name: string,  age: number}const userA: IUser = {  name: &#39;A&#39;,  age: 12};userA.name = &#39;AA&#39;;const userB: Readonly&lt;IUser&gt; = {  name: &#39;B&#39;,  age: 13};userB.name = &#39;BB&#39;; // TS2540: Cannot assign to &#39;name&#39; because it is a read-only property.</code></pre><p>위 예제의 <code>Readonly&lt;IUser&gt;</code>는 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">interface INewType {  readonly name: string,  readonly age: number}</code></pre><h3><span id="40c37b29-aa4d-4074-8a41-1a39dbf80d88">Record</span><a href="#40c37b29-aa4d-4074-8a41-1a39dbf80d88" class="header-anchor"></a></h3><p><code>KEY</code>를 속성(Key)으로, <code>TYPE</code>를 그 속성값의 타입(Type)으로 지정하는 새로운 타입을 반환합니다.</p><pre><code class="typescript">Record&lt;KEY, TYPE&gt;</code></pre><pre><code class="typescript">type TName = &#39;neo&#39; | &#39;lewis&#39;;const developers: Record&lt;TName, number&gt; = {  neo: 12,  lewis: 13};</code></pre><p>위 예제의 <code>Record&lt;TName, number&gt;</code>는 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">interface INewType {  neo: number,  lewis: number}</code></pre><h3><span id="10d82899-caf8-4a70-a91e-01d91005f433">Pick</span><a href="#10d82899-caf8-4a70-a91e-01d91005f433" class="header-anchor"></a></h3><p><code>TYPE</code>에서 <code>KEY</code>로 속성을 선택한 새로운 타입을 반환합니다.<br><code>TYPE</code>은 속성을 가지는 인터페이스나 객체 타입이어야 합니다.</p><pre><code class="typescript">Pick&lt;TYPE, KEY&gt;</code></pre><pre><code class="typescript">interface IUser {  name: string,  age: number,  email: string,  isValid: boolean}type TKey = &#39;name&#39; | &#39;email&#39;;const user: Pick&lt;IUser, TKey&gt; = {  name: &#39;Neo&#39;,  email: &#39;thesecon@gmail.com&#39;,  age: 22 // TS2322: Type &#39;{ name: string; email: string; age: number; }&#39; is not assignable to type &#39;Pick&lt;IUser, TKey&gt;&#39;.};</code></pre><p>위 예제의 <code>Pick&lt;IUser, TKey&gt;</code>은 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">interface INewType {  name: string,  email: string}</code></pre><h3><span id="a65891ae-83f3-4378-9fe3-f635f084add4">Omit</span><a href="#a65891ae-83f3-4378-9fe3-f635f084add4" class="header-anchor"></a></h3><p>위에서 살펴본 <code>Pick</code>과 반대로,<br><code>TYPE</code>에서 <code>KEY</code>로 속성을 생략하고 나머지를 선택한 새로운 타입을 반환합니다.<br><code>TYPE</code>은 속성을 가지는 인터페이스나 객체 타입이어야 합니다.</p><pre><code class="typescript">Omit&lt;TYPE, KEY&gt;</code></pre><pre><code class="typescript">interface IUser {  name: string,  age: number,  email: string,  isValid: boolean}type TKey = &#39;name&#39; | &#39;email&#39;;const user: Omit&lt;IUser, TKey&gt; = {  age: 22,  isValid: true,  name: &#39;Neo&#39; // TS2322: Type &#39;{ age: number; isValid: true; name: string; }&#39; is not assignable to type &#39;Pick&lt;IUser, &quot;age&quot; | &quot;isValid&quot;&gt;&#39;.};</code></pre><p>위 예제의 <code>Omit&lt;IUser, TKey&gt;</code>은 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">interface INewType {  // name: string,  age: number,  // email: string,  isValid: boolean}</code></pre><h3><span id="3e647077-1830-431e-a024-8f77a5022fd9">Exclude</span><a href="#3e647077-1830-431e-a024-8f77a5022fd9" class="header-anchor"></a></h3><p>유니언 <code>TYPE1</code>에서 유니언 <code>TYPE2</code>를 제외한 새로운 타입을 반환합니다.</p><pre><code class="typescript">Exclude&lt;TYPE1, TYPE2&gt;</code></pre><pre><code class="typescript">type T = string | number;const a: Exclude&lt;T, number&gt; = &#39;Only string&#39;;const b: Exclude&lt;T, number&gt; = 1234; // TS2322: Type &#39;123&#39; is not assignable to type &#39;string&#39;.const c: T = &#39;String&#39;;const d: T = 1234;</code></pre><h3><span id="881c8f60-13cb-41dc-93fb-5e766941c206">Extract</span><a href="#881c8f60-13cb-41dc-93fb-5e766941c206" class="header-anchor"></a></h3><p>유니언 <code>TYPE1</code>에서 유니언 <code>TYPE2</code>를 추출한 새로운 타입을 반환합니다.</p><pre><code class="typescript">Extract&lt;TYPE1, TYPE2&gt;</code></pre><pre><code class="typescript">type T = string | number;type U = number | boolean;const a: Extract&lt;T, U&gt; = 123;const b: Extract&lt;T, U&gt; = &#39;Only number&#39;; // TS2322: Type &#39;&quot;Only number&quot;&#39; is not assignable to type &#39;number&#39;.</code></pre><h3><span id="58802eb2-b33b-4eef-8527-412e21ed9155">NonNullable</span><a href="#58802eb2-b33b-4eef-8527-412e21ed9155" class="header-anchor"></a></h3><p>유니언 <code>TYPE</code>에서 <code>null</code>과 <code>undefined</code>를 제외한 새로운 타입을 반환합니다.</p><pre><code class="typescript">NonNullable&lt;TYPE&gt;</code></pre><pre><code class="typescript">type T = string | number | undefined;const a: T = undefined;const b: NonNullable&lt;T&gt; = null; // TS2322: Type &#39;null&#39; is not assignable to type &#39;string | number&#39;.</code></pre><h3><span id="f4f1bf6d-6b8c-42cf-81ca-c3a94ac67413">Parameters</span><a href="#f4f1bf6d-6b8c-42cf-81ca-c3a94ac67413" class="header-anchor"></a></h3><p>함수 <code>TYPE</code>의 매개변수 타입을 새로운 튜플(Tuple) 타입으로 반환합니다.</p><pre><code class="typescript">Parameters&lt;TYPE&gt;</code></pre><pre><code class="typescript">function fn(a: string | number, b: boolean) {  return `[${a}, ${b}]`;}const a: Parameters&lt;typeof fn&gt; = [&#39;Hello&#39;, 123]; // Type &#39;number&#39; is not assignable to type &#39;boolean&#39;.</code></pre><p>위 예제의 <code>Parameters&lt;typeof fn&gt;</code>은 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">[string | number, boolean]</code></pre><h3><span id="f6dadbcc-5a9f-4de4-a7b1-2103dc6364e0">ConstructorParameters</span><a href="#f6dadbcc-5a9f-4de4-a7b1-2103dc6364e0" class="header-anchor"></a></h3><p>클래스 <code>TYPE</code>의 매개변수 타입을 새로운 튜플 타입으로 반환합니다.</p><pre><code class="typescript">ConstructorParameters&lt;TYPE&gt;</code></pre><pre><code class="typescript">class User {  constructor (public name: string, private age: number) {}}const neo = new User(&#39;Neo&#39;, 12);const a: ConstructorParameters&lt;typeof User&gt; = [&#39;Neo&#39;, 12];const b: ConstructorParameters&lt;typeof User&gt; = [&#39;Lewis&#39;]; // TS2741: Property &#39;1&#39; is missing in type &#39;[string]&#39; but required in type &#39;[string, number]&#39;.</code></pre><p>위 예제의 <code>ConstructorParameters&lt;typeof User&gt;</code>은 다음과 같이 이해할 수 있습니다.</p><pre><code class="typescript">[string, number]</code></pre><h3><span id="ca2c341c-15d6-46ca-bbae-afbeca069200">ReturnType</span><a href="#ca2c341c-15d6-46ca-bbae-afbeca069200" class="header-anchor"></a></h3><p>함수 <code>TYPE</code>의 반환(Return) 타입을 새로운 타입으로 반환합니다.</p><pre><code class="typescript">ReturnType&lt;TYPE&gt;</code></pre><pre><code class="typescript">function fn(str: string) {  return str;}const a: ReturnType&lt;typeof fn&gt; = &#39;Only string&#39;;const b: ReturnType&lt;typeof fn&gt; = 1234; // TS2322: Type &#39;123&#39; is not assignable to type &#39;string&#39;.</code></pre><h3><span id="a86cfff2-09ed-40d1-931f-bb3d637174d5">InstanceType</span><a href="#a86cfff2-09ed-40d1-931f-bb3d637174d5" class="header-anchor"></a></h3><p>클래스 <code>TYPE</code>의 인스턴스 타입을 반환합니다.</p><pre><code class="typescript">InstanceType&lt;TYPE&gt;</code></pre><pre><code class="typescript">class User {  constructor(public name: string) {}}const neo: InstanceType&lt;typeof User&gt; = new User(&#39;Neo&#39;);</code></pre><h3><span id="32b8bb23-cfd8-4fcc-9b42-fa34473672a8">ThisParameterType</span><a href="#32b8bb23-cfd8-4fcc-9b42-fa34473672a8" class="header-anchor"></a></h3><p>함수 <code>TYPE</code>의 명시적 <code>this</code> 매개변수 타입을 새로운 타입으로 반환합니다.<br>함수 <code>TYPE</code>에 명시적 <code>this</code> 매개변수가 없는 경우 알 수 없는 타입(Unknown)을 반환합니다.</p><blockquote><p>‘함수 &gt; this &gt; 명시적 this’ 파트를 참고하세요.</p></blockquote><pre><code class="typescript">ThisParameterType&lt;TYPE&gt;</code></pre><pre><code class="typescript">// https://www.typescriptlang.org/docs/handbook/utility-types.html#thisparametertypefunction toHex(this: Number) {    return this.toString(16);}function numberToString(n: ThisParameterType&lt;typeof toHex&gt;) {    return toHex.apply(n);}</code></pre><p>위 예제에서 함수 <code>toHex</code>의 명시적 <code>this</code> 타입은 <code>Number</code>이고,<br>그 타입을 참고해서 함수 <code>numberToString</code>의 매개변수 <code>n</code>의 타입을 선언합니다.<br>따라서 <code>toHex</code>에 다른 타입의 <code>this</code>가 바인딩 되는 것을 방지할 수 있습니다.</p><h3><span id="d953821c-5ccb-4c95-bf49-a227c28a67de">OmitThisParameter</span><a href="#d953821c-5ccb-4c95-bf49-a227c28a67de" class="header-anchor"></a></h3><p>함수 <code>TYPE</code>의 명시적 <code>this</code> 매개변수를 제거한 새로운 타입을 반환합니다.</p><pre><code class="typescript">OmitThisParameter&lt;TYPE&gt;</code></pre><pre><code class="typescript">function getAge(this: typeof cat) {  return this.age;}// 기존 데이터const cat = {  age: 12 // Number};getAge.call(cat); // 12// 새로운 데이터const dog = {  age: &#39;13&#39; // String};getAge.call(dog); // TS2345: Argument of type &#39;{ age: string; }&#39; is not assignable to parameter of type &#39;{ age: number; }&#39;.</code></pre><p>위 예제에서 데이터 <code>cat</code>을 기준으로 설계한 함수 <code>getAge</code>는 일부 다른 타입을 가지는 새로운 데이터 <code>dog</code>를 <code>this</code>로 사용할 수 없습니다.<br>하지만 <code>OmitThisParameter</code>를 통해 명시적 <code>this</code>를 제거한 새로운 타입의 함수를 만들 수 있기 때문에,<br><code>getAge</code>를 직접 수정하지 않고 데이터 <code>dog</code>를 사용할 수 있습니다.</p><pre><code class="typescript">const getAgeForDog: OmitThisParameter&lt;typeof getAge&gt; = getAge;getAgeForDog.call(dog); // &#39;13&#39;</code></pre><blockquote><p><code>this.age</code>에는 이제 어떤 값도 들어갈 수 있음을 주의합니다.</p></blockquote><h3><span id="d44a1010-ee24-4ed7-a879-51dabcaff1dd">ThisType</span><a href="#d44a1010-ee24-4ed7-a879-51dabcaff1dd" class="header-anchor"></a></h3><p><code>TYPE</code>의 <code>this</code> 컨텍스트(Context)를 명시하고 별도의 타입을 반환하지 않습니다.</p><pre><code class="typescript">ThisType&lt;TYPE&gt;</code></pre><pre><code class="typescript">interface IUser {  name: string,  getName: () =&gt; string}function makeNeo(methods: ThisType&lt;IUser&gt;) {  return { name: &#39;Neo&#39;, ...methods } as IUser;}const neo = makeNeo({  getName() {    return this.name;  }});neo.getName(); // Neo</code></pre><p>함수 <code>makeNeo</code>의 인수로 사용되는 메소드 <code>getName</code>은 내부에서 <code>this.name</code>을 사용하고 있기 때문에 <code>ThisType</code>을 통해 명시적으로 <code>this</code> 컨텍스트를 설정해 줍니다.<br>단, <code>ThisType</code>은 별도의 타입을 반환하지 않기 때문에 <code>makeNeo</code> 반환 값(<code>{ name: &#39;Neo&#39;, ...methods }</code>)에 대한 타입이 정상적으로 추론(Inference)되지 않습니다.<br>따라서 <code>as IUser</code>와 같이 따로 타입을 단언(Assertions)해야 <code>neo.getName</code>을 정상적으로 호출할 수 있습니다.</p><h1><span id="dc7f7ff0-7a42-47ff-9cba-f3676988fd58">참고 자료(References)</span><a href="#dc7f7ff0-7a42-47ff-9cba-f3676988fd58" class="header-anchor"></a></h1><p><a href="https://www.typescriptlang.org/docs/home.html" target="_blank" rel="noopener">https://www.typescriptlang.org/docs/home.html</a><br><a href="https://www.tutorialsteacher.com/typescript" target="_blank" rel="noopener">https://www.tutorialsteacher.com/typescript</a><br><a href="https://hyunseob.github.io/2017/12/12/typescript-type-inteference-and-type-assertion/" target="_blank" rel="noopener">https://hyunseob.github.io/2017/12/12/typescript-type-inteference-and-type-assertion/</a><br><a href="https://jsdev.kr/t/typescript-interface/3168" target="_blank" rel="noopener">https://jsdev.kr/t/typescript-interface/3168</a><br><a href="https://github.com/microsoft/TypeScript/wiki/&#39;this&#39;-in-TypeScript" target="_blank" rel="noopener">https://github.com/microsoft/TypeScript/wiki/‘this’-in-TypeScript</a><br><a href="https://github.com/Microsoft/TypeScript-Handbook/issues/180" target="_blank" rel="noopener">https://github.com/Microsoft/TypeScript-Handbook/issues/180</a><br><a href="https://medium.com/naver-fe-platform/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EA%B0%80-%EB%AA%A8%EB%93%88-%ED%83%80%EC%9E%85-%EC%84%A0%EC%96%B8%EC%9D%84-%EC%B0%B8%EC%A1%B0%ED%95%98%EB%8A%94-%EA%B3%BC%EC%A0%95-5bfc55a88bb6" target="_blank" rel="noopener">https://medium.com/naver-fe-platform/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EA%B0%80-%EB%AA%A8%EB%93%88-%ED%83%80%EC%9E%85-%EC%84%A0%EC%96%B8%EC%9D%84-%EC%B0%B8%EC%A1%B0%ED%95%98%EB%8A%94-%EA%B3%BC%EC%A0%95-5bfc55a88bb6</a></p>]]></content>
    
    <summary type="html">
    
      타입스크립트는 Microsoft에서 개발하고 유지/관리하는 Apache 라이센스가 부여된 오픈 소스로, 자바스크립트에 강한 타입 시스템을 적용해 대부분의 에러를 컴파일 환경에서 코드를 입력하는 동안 체크할 수 있습니다.
    
    </summary>
    
    
      <category term="typescript" scheme="https://heropy.blog/tags/typescript/"/>
    
  </entry>
  
  <entry>
    <title>Firebase 인증으로 Sapper(Svelte)에 구글 로그인 구현</title>
    <link href="https://heropy.blog/2019/12/29/firebase-auth-with-sapper/"/>
    <id>https://heropy.blog/2019/12/29/firebase-auth-with-sapper/</id>
    <published>2019-12-29T14:59:59.000Z</published>
    <updated>2020-01-27T07:47:05.000Z</updated>
    
    <content type="html"><![CDATA[<div class="toc"><ul><li><a href="aa970f58-e014-4dd4-87a2-418e443c67c7">Sapper 설치</a></li><li><a href="a05a7aa0-ea6f-4126-af72-3ec3957adeb3">Firebase 프로젝트 생성</a></li><li><a href="e5f5053d-fd37-4c21-8fac-ab0d4a487508">Firebase 설치 및 초기화</a></li><li><a href="ff2c1718-9c47-46aa-bde9-83fb4decf1c7">Firebase 내 앱 추가</a></li><li><a href="304a7c2c-491f-439a-a392-a2477c345db3">컴포넌트 및 스토어</a><ul><li><a href="a42b1862-879b-4241-88a7-b83174dd6d02">user 스토어</a></li><li><a href="b663d08f-1a8b-4e9c-91a4-00c35f494ceb">SignInWithGoogle 컴포넌트</a></li><li><a href="bbb3e5a6-d9a7-49e7-8a72-ec64e9a02a56">UserObserver 컴포넌트</a></li></ul></li><li><a href="bb3c905c-5b98-4778-986f-7fe9bfe8c732">FirebaseUI로 로그인 UI 추가</a></li><li><a href="23b99d12-9f6b-41c4-a103-98a42103f4f7">참고 자료(References)</a></li></ul></div><p><a href="https://firebase.google.com/docs/auth?hl=ko" target="_blank" rel="noopener">Firebase 인증</a>을 통해 구글 및 페이스북, 트위터, GitHub 등의 계정으로 쉽게 로그인할 수 있는 서비스를 만들 수 있습니다.<br>최근 관심이 있는 <a href="https://sapper.svelte.dev/" target="_blank" rel="noopener">Sapper</a>(<a href="https://svelte.dev/" target="_blank" rel="noopener">Svelte</a>)를 사용해 간단하게 구글 로그인을 구현해 보려고 합니다.</p><p>예제 결과는 위 <a href="https://github.com/ParkYoungWoong/firebase-auth-with-sapper" target="_blank" rel="noopener">GitHub Repository 버튼</a>을 클릭하면 확인할 수 있습니다.</p><pre><code class="bash">$ npx degit &quot;ParkYoungWoong/firebase-auth-with-sapper&quot; YOUR_PROJECT_NAME</code></pre><h1><span id="aa970f58-e014-4dd4-87a2-418e443c67c7">Sapper 설치</span><a href="#aa970f58-e014-4dd4-87a2-418e443c67c7" class="header-anchor"></a></h1><p>Sapper(Svelte APP makER)는 Svelte를 기반으로 동작하는 다음과 같은 기능들을 지원하는 프레임워크입니다.<br>React의 <a href="https://nextjs.org/" target="_blank" rel="noopener">Next.js</a>, Vue의 <a href="https://nuxtjs.org/" target="_blank" rel="noopener">Nuxt.js</a>와 같은 라인업이라고 볼 수 있습니다.</p><ul><li>라우팅</li><li>서버 사이드 렌더링(SSR)</li><li>자동 코드 분할</li><li>오프라인 지원(Service Worker)</li><li>고급 프로젝트 구조 관리</li></ul><p>다음과 같이 <a href="https://github.com/Rich-Harris/degit" target="_blank" rel="noopener">Degit</a>을 이용해 새로운 프로젝트를 생성합니다.</p><pre><code class="bash">$ npx degit &quot;sveltejs/sapper-template#rollup&quot; YOUR_PROJECT_NAME$ cd YOUR_PROJECT_NAME$ npm install</code></pre><p>설치 후 Sapper는 다음과 같은 구조를 가집니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/sapper-project-structure-1.jpg" alt="Sapper structure"></p><h1><span id="a05a7aa0-ea6f-4126-af72-3ec3957adeb3">Firebase 프로젝트 생성</span><a href="#a05a7aa0-ea6f-4126-af72-3ec3957adeb3" class="header-anchor"></a></h1><p>Firebase는 인증, 실시간 데이터베이스, 스토리지, 푸시, 호스팅 등 다양한 기능을 지원하는 통합 플랫폼입니다.<br><a href="https://firebase.google.com/" target="_blank" rel="noopener">Firebase 페이지</a>에 접속에 로그인 후 다음과 같이 진행합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/create-firebase-project-1.jpg" alt="Create Firebase project"></p><p>1) Firebase 콘솔에 로그인한 후 새로운 프로젝트 생성합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/create-firebase-project-2.jpg" alt="Create Firebase project"></p><p>2) 프로젝트의 이름을 지정하는 등 ‘프로젝트 만들기’의 단계를 수행합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/create-firebase-project-3.jpg" alt="Create Firebase project"></p><p>3) 새로운 프로젝트가 준비되면 ‘계속’을 눌러 대시보드로 접근합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/create-firebase-project-4.jpg" alt="Create Firebase project"></p><p>4) ‘Authentication’의 ‘로그인 방법’에서 제공 업체로 ‘Google’을 선택합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/create-firebase-project-5.jpg" alt="Create Firebase project"></p><p>5) ‘사용 설정’을 활성화하고 ‘프로젝트 지원 이메일’을 설정한 후 저장합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/create-firebase-project-6.jpg" alt="Create Firebase project"></p><p>6) 제공 업체 상태가 ‘사용 설정됨’으로 표시되면 준비가 끝났습니다.</p><h1><span id="e5f5053d-fd37-4c21-8fac-ab0d4a487508">Firebase 설치 및 초기화</span><a href="#e5f5053d-fd37-4c21-8fac-ab0d4a487508" class="header-anchor"></a></h1><p>전역으로 <code>firebase</code> 명령어를 사용하기 위해 다음과 같이 ‘firebase-tools’을 전역 설치합니다.</p><pre><code class="bash">$ npm i -g firebase-tools</code></pre><p>설치가 완료되었다면 다음으로 구글에 로그인해야 합니다.</p><blockquote><p>이 명령어는 로컬 머신을 Firebase에 연결하고 Firebase 프로젝트에 대한 액세스 권한을 부여합니다.</p></blockquote><pre><code class="bash">$ firebase login</code></pre><p>다음은 Firebase 프로젝트를 초기화해야 합니다.<br>프로젝트 디렉터리의 루트에서 다음 명령어를 실행합니다.</p><pre><code class="bash">$ firebase init</code></pre><p><img src="/images/screenshot/firebase-auth-with-sapper/firebase-init.jpg" alt="Firebase Init"></p><p>원하는 Firebase CLI 기능을 선택(당장은 ‘인증’만 테스트하기 때문에 아무 기능이나 선택)하고,<br>위에서 생성한 Firebase 프로젝트(<code>auth-test-with-sapper</code>)를 연결합니다.</p><blockquote><p><a href="https://firebase.google.com/docs/hosting/quickstart?hl=ko" target="_blank" rel="noopener">Firebase 호스팅</a>에 필요한 firebase.json 파일을 생성합니다.<br>Firebase가 찾는 디렉터리의 기본 이름은 ‘public’입니다.<br>나중에 firebase.json 파일을 직접 수정하여 <a href="https://firebase.google.com/docs/hosting/full-config?hl=ko#public" target="_blank" rel="noopener">공개 디렉터리</a>를 설정할 수도 있습니다.</p></blockquote><h1><span id="ff2c1718-9c47-46aa-bde9-83fb4decf1c7">Firebase 내 앱 추가</span><a href="#ff2c1718-9c47-46aa-bde9-83fb4decf1c7" class="header-anchor"></a></h1><p>Sapper 프로젝트에 Firebase 인증을 적용하기 전에,<br>우선 다음과 같이 Firebase가 지원할 내 앱(Sapper 프로젝트)의 플랫폼을 선택해 SDK 구성을 얻어야 합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/firebase-add-app-1.jpg" alt="Firebase Add App"></p><p>1) ‘프로젝트 설정’으로 접근합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/firebase-add-app-2.jpg" alt="Firebase Add App"></p><p>2) ‘내 앱’에서 ‘Web’으로 플랫폼을 선택합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/firebase-add-app-3.jpg" alt="Firebase Add App"></p><p>2) 혹은 ‘Project Overview’에서 바로 선택할 수도 있습니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/firebase-add-app-4.jpg" alt="Firebase Add App"></p><p>3) 내 웹앱의 이름을 설정합니다.<br>(하나의 Firebase 프로젝트 내 여러 앱을 추가할 수 있습니다)</p><p><img src="/images/screenshot/firebase-auth-with-sapper/firebase-add-app-5.jpg" alt="Firebase Add App"></p><p>4) Firebase SDK 추가를 위한 코드가 발급됩니다.<br>(공개 가능한 구성입니다)</p><blockquote><p>보안 규칙과 관련된 내용은 <a href="https://firebase.google.com/docs/rules" target="_blank" rel="noopener">Firebase Security Rules</a>를 참고하세요.</p></blockquote><p>이제 인증(Auth)을 위한 SDK를 연결하기 위해 위 구성에 적혀있는 것처럼 <a href="https://firebase.google.com/docs/web/setup#available-libraries" target="_blank" rel="noopener">https://firebase.google.com/docs/web/setup#available-libraries</a> 로 이동합니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/available-libraries.jpg" alt="Available Libraries"></p><p>우리는 인증을 사용하기 때문에 <code>firebase-auth.js</code>를 연결할 것입니다.<br>우리의 Sapper 프로젝트의 <code>src/template.html</code>에 다음과 같이 코드를 추가합니다.</p><pre><code class="html">&lt;!-- src/template.html --&gt;&lt;script src=&quot;https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js&quot;&gt;&lt;/script&gt;&lt;script src=&quot;https://www.gstatic.com/firebasejs/7.6.1/firebase-auth.js&quot;&gt;&lt;/script&gt;&lt;script&gt;  // Your web app&#39;s Firebase configuration  var firebaseConfig = {    apiKey: &quot;AIzaSyDEZzrvW7PlAgDmQWtw_hecYHTLi7NBWxA&quot;,    authDomain: &quot;auth-test-with-sapper.firebaseapp.com&quot;,    databaseURL: &quot;https://auth-test-with-sapper.firebaseio.com&quot;,    projectId: &quot;auth-test-with-sapper&quot;,    storageBucket: &quot;auth-test-with-sapper.appspot.com&quot;,    messagingSenderId: &quot;148687957530&quot;,    appId: &quot;1:148687957530:web:919b0b6018d8bbde5d2ba3&quot;  };  // Initialize Firebase  firebase.initializeApp(firebaseConfig);&lt;/script&gt;</code></pre><blockquote><p>이 프로젝트는 클라이언트 정적 파일에서 인증(로그인)만 사용하기 때문에 Sapper 서버에서 사용하지 않도록 CDN으로 설치합니다.<br><a href="https://stackoverflow.com/questions/56315901/how-to-import-firebase-only-on-client-in-sapper" target="_blank" rel="noopener">https://stackoverflow.com/questions/56315901/how-to-import-firebase-only-on-client-in-sapper</a></p></blockquote><h1><span id="304a7c2c-491f-439a-a392-a2477c345db3">컴포넌트 및 스토어</span><a href="#304a7c2c-491f-439a-a392-a2477c345db3" class="header-anchor"></a></h1><p>구글 로그인을 위해 다음과 같이 컴포넌트와 스토어 파일을 생성할 것입니다.</p><p><img src="/images/screenshot/firebase-auth-with-sapper/sapper-project-structure-2.jpg" alt="Sapper structure"></p><h2><span id="a42b1862-879b-4241-88a7-b83174dd6d02">user 스토어</span><a href="#a42b1862-879b-4241-88a7-b83174dd6d02" class="header-anchor"></a></h2><p>프로젝트 내 전역으로 현재 사용자(<code>currentUser</code>)에 접근하기 위해 쓰기 가능하도록 다음과 같이 작성합니다.</p><pre><code class="js">// src/store/user.jsimport { writable } from &#39;svelte/store&#39;export const currentUser = writable(null)</code></pre><h2><span id="b663d08f-1a8b-4e9c-91a4-00c35f494ceb">SignInWithGoogle 컴포넌트</span><a href="#b663d08f-1a8b-4e9c-91a4-00c35f494ceb" class="header-anchor"></a></h2><p>구글 로그인을 위한 컴포넌트를 다음과 같이 작성합니다.<br>Nav(Header)의 오른쪽에 위치하도록 스타일을 정의합니다.</p><pre><code class="svelte">&lt;!-- src/components/SignInWithGoogle.svelte --&gt;&lt;script&gt;  // 스토어에서 현재 사용자를 가져옵니다.  // `currentUser`는 쓰기용 객체 데이터(writable)이기 때문에 `$` 접두사를 사용해 스토어를 참조해야 합니다.  // `$currentUser`  import { currentUser } from &#39;../store/user&#39;;  // 구글로 로그인  const signInWithGoogle = async () =&gt; {    await firebase.auth().signInWithPopup(      new firebase.auth.GoogleAuthProvider()      // 페이스북이나 트위터 등으로도 로그인할 수 있습니다.      // new firebase.auth.FacebookAuthProvider()      // new firebase.auth.TwitterAuthProvider()      // new firebase.auth.GithubAuthProvider()    );  };  // 로그아웃  const signOut = async () =&gt; {    await firebase.auth().signOut();  };&lt;/script&gt;&lt;div class=&quot;sign-in-with-google&quot;&gt;  {#if $currentUser}    &lt;div class=&quot;user&quot;&gt;      &lt;div class=&quot;user__name&quot;&gt;{$currentUser.displayName}&lt;/div&gt;      &lt;div class=&quot;user__photo&quot;&gt;        &lt;img          src={$currentUser.photoURL}          alt={$currentUser.displayName}          width=&quot;28&quot; /&gt;      &lt;/div&gt;    &lt;/div&gt;    &lt;div      class=&quot;sign-out&quot;      on:click={signOut}&gt;      Sign out    &lt;/div&gt;  {:else}    &lt;div      class=&quot;sign-in&quot;      on:click={signInWithGoogle}&gt;      Sign in with Google    &lt;/div&gt;  {/if}&lt;/div&gt;&lt;style&gt;  .sign-in-with-google {    height: 56px;    position: absolute;    top: 0;    right: 1em;    display: flex;    align-items: center;  }  .sign-in-with-google .user {    display: flex;    align-items: center;  }  .sign-in-with-google .user .user__name {    font-size: 13px;    font-weight: 700;    margin-right: 6px;  }  .sign-in-with-google .user .user__photo {    width: 28px;    height: 28px;    border: 1px solid lightgray;    border-radius: 50%;    overflow: hidden;    margin-right: 10px;  }  .sign-in-with-google .sign-out {    font-size: 13px;    cursor: pointer;  }  .sign-in-with-google .sign-out:hover {    text-decoration: underline;  }&lt;/style&gt;</code></pre><p><img src="/images/screenshot/firebase-auth-with-sapper/user-information.jpg" alt="User Information"></p><div class="image-caption">구글 로그인 후 반환된 사용자 객체</div><p>작성한 <code>SignInWithGoogle.svelte</code> 컴포넌트는 <code>src/components/Nav.svelte</code>에서 다음과 같이 사용합니다.<br><code>nav</code> 요소에 <code>position</code> 속성이 있어야 정상적으로 배치될 수 있습니다.</p><pre><code class="svelte">&lt;!-- src/components/Nav.svelte --&gt;&lt;script&gt;  import SignInWithGoogle from &#39;./SignInWithGoogle.svelte&#39;;  // ...&lt;/script&gt;&lt;style&gt;  nav {    /* ... */    position: relative;  }&lt;/style&gt;&lt;nav&gt;  &lt;!-- ... --&gt;  &lt;SignInWithGoogle /&gt;&lt;/nav&gt;</code></pre><h2><span id="bbb3e5a6-d9a7-49e7-8a72-ec64e9a02a56">UserObserver 컴포넌트</span><a href="#bbb3e5a6-d9a7-49e7-8a72-ec64e9a02a56" class="header-anchor"></a></h2><p>사용자의 상태(로그인, 로그아웃)를 감지하기 위한 컴포넌트를 다음과 같이 작성합니다.</p><pre><code class="svelte">&lt;!-- src/components/UserObserver.svelte --&gt;&lt;script&gt;  import { onMount } from &#39;svelte&#39;;  import { currentUser } from &#39;../store/user&#39;;  // Client 환경에서 동작하도록 `onMount` 훅에서 실행합니다.  onMount(() =&gt; {    // 로그인 사용자의 상태 변환(로그인, 로그아웃)에 따라 콜백을 실행합니다.    firebase.auth().onAuthStateChanged(user =&gt; {      if (user) {        // 쓰기 가능한 객체이기 때문에 바로 사용자를 할당할 수 있습니다.        $currentUser = user      } else {        // 사용자가 없는 경우 초기화합니다.        $currentUser = null      }    });  });&lt;/script&gt;</code></pre><p>작성한 <code>UserObserver.svelte</code> 컴포넌트는 항시 동작할 수 있도록 <code>src/routes/_layout.svelte</code>에서 다음과 같이 사용합니다.</p><pre><code class="svelte">&lt;!-- src/routes/_layout.svelte --&gt;&lt;script&gt;  import UserObserver from &#39;../components/UserObserver.svelte&#39;;  // ...&lt;/script&gt;&lt;UserObserver /&gt;</code></pre><p><img src="/images/screenshot/firebase-auth-with-sapper/sign-in.jpg" alt="Sign In"></p><h1><span id="bb3c905c-5b98-4778-986f-7fe9bfe8c732">FirebaseUI로 로그인 UI 추가</span><a href="#bb3c905c-5b98-4778-986f-7fe9bfe8c732" class="header-anchor"></a></h1><p><a href="https://github.com/firebase/firebaseui-web" target="_blank" rel="noopener">FirebaseUI</a>는 Firebase 인증 UI를 제공하는 공식 라이브러리입니다.<br>별도의 UI 제작 없이 손쉽게 구글, 페이스북, 트위터 등의 로그인 UI를 제작할 수 있습니다.</p><p>먼저 다음과 같이 FirebaseUI를 설치합니다.</p><pre><code class="html">&lt;!-- src/template.html --&gt;&lt;!-- ... --&gt;&lt;script src=&quot;https://www.gstatic.com/firebasejs/ui/4.3.0/firebase-ui-auth.js&quot;&gt;&lt;/script&gt;&lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;https://www.gstatic.com/firebasejs/ui/4.3.0/firebase-ui-auth.css&quot; /&gt;&lt;!-- ... --&gt;</code></pre><p><code>SignInWithGoogle.svelte</code> 컴포넌트를 FirebaseUI가 적용될 수 있도록 다음과 같이 수정합니다.</p><pre><code class="svelte">&lt;!-- src/components/SignInWithGoogle.svelte --&gt;&lt;script&gt;  import { onMount, tick } from &#39;svelte&#39;  import { currentUser } from &#39;../store/user&#39;;  // 컴포넌트가 마운트되면 현재 사용자를 체크하고,  // 사용자가 없으면 구글 로그인 버튼을 생성합니다.  onMount(() =&gt; {    if (!$currentUser) {      createLoginButton();    }  });  // 로그인 버튼 생성  function createLoginButton() {    // FirebaseUI config.    const uiConfig = {      signInOptions: [        firebase.auth.GoogleAuthProvider.PROVIDER_ID,        // 페이스북이나 트위터 등의 로그인 UI도 같이 제공할 수 있습니다.        // firebase.auth.FacebookAuthProvider.PROVIDER_ID,        // firebase.auth.TwitterAuthProvider.PROVIDER_ID,        // firebase.auth.GithubAuthProvider.PROVIDER_ID      ],      callbacks: {        signInSuccessWithAuthResult: (authResult, redirectUrl) =&gt; false      }    };    // FirebaseUI 초기화    const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth());    // 해당 요소를 검색(아이디 선택자)하고 UI를 렌더링합니다.    ui.start(&quot;#firebaseui-auth-container&quot;, uiConfig);  }  // ...  const signOut = async () =&gt; {    await firebase.auth().signOut();    // 현재 사용자 상태 변화(로그아웃)에 따라 렌더링을 대기합니다.    await tick();    // 로그인 버튼을 생성합니다.    createLoginButton();  };&lt;/script&gt;&lt;div class=&quot;sign-in-with-google&quot;&gt;  &lt;!-- ... --&gt;  {:else}    &lt;!-- `.sign-in`에 있던 클릭 이벤트를 제거해야 합니다. --&gt;    &lt;div class=&quot;sign-in&quot;&gt;      &lt;!-- FirebaseUI가 렌더링되는 위치 --&gt;      &lt;div id=&quot;firebaseui-auth-container&quot;&gt;&lt;/div&gt;    &lt;/div&gt;  {/if}&lt;/div&gt;&lt;style&gt;  /* ... */  /* reset firebaseui margin &amp; padding */  /* 현재 화면에 맞지 않는 스타일들을 초기화합니다. */  /* Svelte에서는 유효범위 없이 스타일을 선언하려면 :global(선택자) 수정자(Modifier)를 사용해야 합니다. */  :global(.firebaseui-card-content) {    padding: 0 !important;  }  :global(.firebaseui-idp-list),  :global(.firebaseui-list-item) {    margin: 8px 0 !important;  }&lt;/style&gt;</code></pre><p><img src="/images/screenshot/firebase-auth-with-sapper/sign-out.jpg" alt="Sign Out"></p><div class="image-caption">FirebaseUI에서 제공하는 구글 로그인 버튼</div><h1><span id="23b99d12-9f6b-41c4-a103-98a42103f4f7">참고 자료(References)</span><a href="#23b99d12-9f6b-41c4-a103-98a42103f4f7" class="header-anchor"></a></h1><p><a href="https://firebase.google.com/docs/web/setup" target="_blank" rel="noopener">https://firebase.google.com/docs/web/setup</a></p>]]></content>
    
    <summary type="html">
    
      Firebase 인증을 통해 구글 및 페이스북, 트위터, GitHub 등의 계정으로 쉽게 로그인할 수 있는 서비스를 만들 수 있습니다. 최근 관심이 있는 Sapper(Svelte)를 사용해 간단하게 구글 로그인을 구현해 보려고 합니다.
    
    </summary>
    
    
      <category term="firebase" scheme="https://heropy.blog/tags/firebase/"/>
    
      <category term="sapper" scheme="https://heropy.blog/tags/sapper/"/>
    
  </entry>
  
  <entry>
    <title>Resize Observer - 요소의 크기 변화 관찰</title>
    <link href="https://heropy.blog/2019/11/30/resize-observer/"/>
    <id>https://heropy.blog/2019/11/30/resize-observer/</id>
    <published>2019-11-29T15:00:00.000Z</published>
    <updated>2020-10-25T09:57:22.685Z</updated>
    
    <content type="html"><![CDATA[<div class="toc"><ul><li><a href="794042f6-d5f7-4d2f-bdaf-d6b2471c3c8d">Polyfill</a><ul><li><a href="215639b6-4f91-4de6-900b-ce5d85ecf887">Installation and Basic usage</a></li><li><a href="53973199-1094-4aae-9d5b-99074570a124">Tested Browsers</a><ul><li><a href="ae9ecea1-9d24-452e-a10d-0c57f1a9f907">Desktop</a></li><li><a href="48487815-b045-4aa4-aea4-1d16f8e884e0">Mobile</a></li></ul></li></ul></li><li><a href="559195ac-f2f1-4817-a1d6-f89324f57018">ResizeObserver</a><ul><li><a href="326300ca-02ad-4f40-ad77-1060b97fcd31">callback</a><ul><li><a href="22e9ba61-5cf1-4369-aa0b-872e930977d5">entries</a></li><li><a href="5b88532e-a9c9-4198-8cd2-c2170c44b19a">observer</a></li></ul></li><li><a href="296c98f1-2ea4-49fd-9f84-f0e6e0b7859e">Methods</a><ul><li><a href="8f7880ec-1c40-4f5f-8e5e-af58a3f67001">observe()</a></li><li><a href="1c902308-7aac-4edf-ab2d-d0c11ba038c1">unobserve()</a></li><li><a href="2097847c-2a8e-4571-9d41-b9fb0482e9b5">disconnect()</a></li></ul></li></ul></li><li><a href="16fe454f-c708-469a-8250-88b51b8ad330">예제</a></li><li><a href="eec0696b-6f8f-4f09-b15a-d34f5d8de1d0">참고 자료(References)</a></li></ul></div><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver" target="_blank" rel="noopener">Resize Observer</a>는 설정한 요소의 크기 변화를 관찰하며,<br>크기 변화를 제어할 경우 발생할 수 있는 무한 콜백 루프나 <a href="https://en.wikipedia.org/wiki/Circular_dependency" target="_blank" rel="noopener">순환 종속성(Circular dependency)</a> 등의 다양한 문제 없이 사용할 수 있습니다.</p><iframe height="488" style="width:100%" scrolling="no" title="ResizeObserver example(textarea)" src="https://codepen.io/heropark/embed/ExaYWBa?height=488&theme-id=default&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen><br>See the Pen <a href="https://codepen.io/heropark/pen/ExaYWBa" target="_blank" rel="noopener">ResizeObserver example(textarea)</a> by park young woong<br>(<a href="https://codepen.io/heropark" target="_blank" rel="noopener">@heropark</a>) on <a href="https://codepen.io" target="_blank" rel="noopener">CodePen</a>.<br></iframe><h1><span id="794042f6-d5f7-4d2f-bdaf-d6b2471c3c8d">Polyfill</span><a href="#794042f6-d5f7-4d2f-bdaf-d6b2471c3c8d" class="header-anchor"></a></h1><p>ResizeObserver는 <a href="https://drafts.csswg.org/resize-observer-1/" target="_blank" rel="noopener">표준화 초안 단계(ED, Editor’s Draft)</a>이므로 브라우저 버전에 따라 네이티브 ResizeObserver의 사용법이 다를 수 있으니 폴리필(Polyfill) 사용을 <strong>적극 추천</strong>합니다.</p><p>이 포스트에서는 <a href="https://github.com/juggle/resize-observer" target="_blank" rel="noopener">@juggle/resize-observer</a>를 사용합니다.</p><blockquote><p>ResizeObserver의 사양은 권고 단계(REC)가 아니기 때문에 변경될 수 있습니다.<br>표준 사양이 크게 변경되면 이 라이브러리(@juggle/resize-observer)에 변경 사항이 있을 수 있음으로 주 버전(Major) 충돌에 주의합시다.</p></blockquote><h2><span id="215639b6-4f91-4de6-900b-ce5d85ecf887">Installation and Basic usage</span><a href="#215639b6-4f91-4de6-900b-ce5d85ecf887" class="header-anchor"></a></h2><pre><code class="bash">$ npm i @juggle/resize-observer</code></pre><pre><code class="js">import ResizeObserver from &#39;@juggle/resize-observer&#39;const ro = new ResizeObserver(callback)ro.observe(element)</code></pre><h2><span id="53973199-1094-4aae-9d5b-99074570a124">Tested Browsers</span><a href="#53973199-1094-4aae-9d5b-99074570a124" class="header-anchor"></a></h2><h3><span id="ae9ecea1-9d24-452e-a10d-0c57f1a9f907">Desktop</span><a href="#ae9ecea1-9d24-452e-a10d-0c57f1a9f907" class="header-anchor"></a></h3><table><thead><tr><th><img src="https://github.com/alrra/browser-logos/raw/master/src/chrome/chrome_64x64.png" alt="chrome"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/safari/safari_64x64.png" alt="safari"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/firefox/firefox_64x64.png" alt="ff"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/opera/opera_64x64.png" alt="opera"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/edge/edge_64x64.png" alt="edge"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/archive/edge_12-18/edge_12-18_64x64.png" alt="edge"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_64x64.png" alt="IE"></th></tr></thead><tbody><tr><td>Chrome</td><td>Safari</td><td>Firefox</td><td>Opera</td><td>Edge</td><td>Edge 12-18</td><td>IE11<br>IE 9-10 (with polyfills)**</td></tr></tbody></table><h3><span id="48487815-b045-4aa4-aea4-1d16f8e884e0">Mobile</span><a href="#48487815-b045-4aa4-aea4-1d16f8e884e0" class="header-anchor"></a></h3><table><thead><tr><th><img src="https://github.com/alrra/browser-logos/raw/master/src/chrome/chrome_64x64.png" alt="chrome"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/safari/safari_64x64.png" alt="safari"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/firefox/firefox_64x64.png" alt="ff"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/opera/opera_64x64.png" alt="opera"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/opera-mini/opera-mini_64x64.png" alt="opera mini"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/archive/edge_12-18/edge_12-18_64x64.png" alt="edge"></th><th><img src="https://github.com/alrra/browser-logos/raw/master/src/samsung-internet/samsung-internet_64x64.png" alt="samsung internet"></th></tr></thead><tbody><tr><td>Chrome</td><td>Safari</td><td>Firefox</td><td>Opera</td><td>Opera Mini</td><td>Edge</td><td>Samsung Internet</td></tr></tbody></table><h1><span id="559195ac-f2f1-4817-a1d6-f89324f57018">ResizeObserver</span><a href="#559195ac-f2f1-4817-a1d6-f89324f57018" class="header-anchor"></a></h1><p><code>new ResizeObserver</code>를 통해 생성한 인스턴스(<code>ro</code>)로 관찰자(Observer)를 초기화하고 관찰할 대상(<a href="https://developer.mozilla.org/ko/docs/Web/API/Element" target="_blank" rel="noopener">Element</a>)을 지정합니다.<br>생성자는 1개의 인수(<code>callback</code>)를 가집니다.</p><pre><code class="js">// 관찰자 초기화const ro = new ResizeObserver(callback)// 관찰할 대상(요소) 등록ro.observe(element)</code></pre><h2><span id="326300ca-02ad-4f40-ad77-1060b97fcd31">callback</span><a href="#326300ca-02ad-4f40-ad77-1060b97fcd31" class="header-anchor"></a></h2><p>관찰할 대상이 등록되거나 크기에 변화가 생기면 관찰자는 콜백을 실행합니다.<br>콜백은 2개의 인수(<code>entries</code>, <code>observer</code>)를 가집니다.</p><pre><code class="js">const ro = new ResizeObserver((entries, observer) =&gt; {})ro.observe(element)</code></pre><h3><span id="22e9ba61-5cf1-4369-aa0b-872e930977d5">entries</span><a href="#22e9ba61-5cf1-4369-aa0b-872e930977d5" class="header-anchor"></a></h3><p><code>entries</code>는 ResizeObserverEntry 인스턴스의 <strong>배열</strong>입니다.<br>ResizeObserverEntry는 다음 속성들을 포함합니다.</p><ul><li><code>contentRect</code>(legacy): 관찰 대상의 사각형 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly" target="_blank" rel="noopener">DOMRectReadOnly</a>)</li><li><code>target</code>(legacy): 관찰 대상 요소(<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element" target="_blank" rel="noopener">Element</a>)</li><li><code>contentBoxSize</code>: 관찰 대상의 <code>content-box</code>(content) 크기</li><li><code>borderBoxSize</code>: 관찰 대상의 <code>border-box</code>(content + padding + border) 크기</li></ul><pre><code class="js">const ro = new ResizeObserver((entries, observer) =&gt; {  entries.forEach(entry =&gt; {    console.log(entry)  })})ro.observe(element)</code></pre><p><img src="/images/screenshot/resize-observer/resize-observer-entry-object.jpg" alt="resize observer entry object"></p><div class="image-caption">ResizeObserverEntry 구조(after polyfill)</div><h3><span id="5b88532e-a9c9-4198-8cd2-c2170c44b19a">observer</span><a href="#5b88532e-a9c9-4198-8cd2-c2170c44b19a" class="header-anchor"></a></h3><p>콜백이 실행되는 해당 인스턴스를 참조합니다.</p><p><img src="/images/screenshot/resize-observer/resize-observer-object.jpg" alt="resize observer entry object"></p><div class="image-caption">ResizeObserver 구조</div><h2><span id="296c98f1-2ea4-49fd-9f84-f0e6e0b7859e">Methods</span><a href="#296c98f1-2ea4-49fd-9f84-f0e6e0b7859e" class="header-anchor"></a></h2><h3><span id="8f7880ec-1c40-4f5f-8e5e-af58a3f67001">observe()</span><a href="#8f7880ec-1c40-4f5f-8e5e-af58a3f67001" class="header-anchor"></a></h3><p>대상 요소의 관찰을 시작합니다.</p><pre><code class="js">const ro1 = new ResizeObserver(callback1)const ro2 = new ResizeObserver(callback2)const div = document.querySelector(&#39;div&#39;)const button = document.querySelector(&#39;button&#39;)const textarea = document.querySelector(&#39;textarea&#39;)ro1.observe(div) // DIV 요소 관찰ro2.observe(button) // BUTTON 요소 관찰ro2.observe(textarea) // TEXTAREA 요소 관찰</code></pre><h3><span id="1c902308-7aac-4edf-ab2d-d0c11ba038c1">unobserve()</span><a href="#1c902308-7aac-4edf-ab2d-d0c11ba038c1" class="header-anchor"></a></h3><p>대상 요소의 관찰을 중지합니다.</p><pre><code class="js">const ro1 = new ResizeObserver(callback1)const ro2 = new ResizeObserver(callback2)// ...ro1.observe(div)ro2.observe(button)ro2.observe(textarea)ro1.unobserve(button) // Nothing..ro2.unobserve(button) // BUTTON 요소 관찰 중지</code></pre><h3><span id="2097847c-2a8e-4571-9d41-b9fb0482e9b5">disconnect()</span><a href="#2097847c-2a8e-4571-9d41-b9fb0482e9b5" class="header-anchor"></a></h3><p>ResizeObserver 인스턴스가 관찰하는 모든 요소의 관찰을 중지합니다.</p><pre><code class="js">const ro1 = new ResizeObserver(callback1)const ro2 = new ResizeObserver(callback2)// ...ro1.observe(div)ro2.observe(button)ro2.observe(textarea)ro2.disconnect() // `ro2`가 관찰하는 모든 요소(BUTTON, TEXTAREA) 관찰 중지</code></pre><h1><span id="16fe454f-c708-469a-8250-88b51b8ad330">예제</span><a href="#16fe454f-c708-469a-8250-88b51b8ad330" class="header-anchor"></a></h1><iframe height="516" style="width:100%" scrolling="no" title="Resize Observer example - Text overflow" src="https://codepen.io/heropark/embed/MWYgoVv?height=516&theme-id=default&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen><br>See the Pen <a href="https://codepen.io/heropark/pen/MWYgoVv" target="_blank" rel="noopener">Resize Observer example - Text overflow</a> by park young woong<br>(<a href="https://codepen.io/heropark" target="_blank" rel="noopener">@heropark</a>) on <a href="https://codepen.io" target="_blank" rel="noopener">CodePen</a>.<br></iframe><h1><span id="eec0696b-6f8f-4f09-b15a-d34f5d8de1d0">참고 자료(References)</span><a href="#eec0696b-6f8f-4f09-b15a-d34f5d8de1d0" class="header-anchor"></a></h1><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver</a><br><a href="https://github.com/juggle/resize-observer" target="_blank" rel="noopener">https://github.com/juggle/resize-observer</a></p>]]></content>
    
    <summary type="html">
    
      Resize Observer는 요소(Element)의 크기를 관찰하며, 요소의 크기가 변화할 때 실행할 최적화 콜백(callback)을 제공할 수 있습니다. 활용도를 높이기 위한 폴리필(polyfill) 사용법도 같이 알아봅니다.
    
    </summary>
    
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>Intersection Observer - 요소의 가시성 관찰</title>
    <link href="https://heropy.blog/2019/10/27/intersection-observer/"/>
    <id>https://heropy.blog/2019/10/27/intersection-observer/</id>
    <published>2019-10-26T15:00:00.000Z</published>
    <updated>2019-11-30T07:48:45.000Z</updated>
    
    <content type="html"><![CDATA[<div class="toc"><ul><li><a href="2aaf37a0-ffc6-4ccc-a180-deb871ea66d6">IntersectionObserver</a><ul><li><a href="8b204cd7-1484-4969-9fdd-0a8dd3cfd613">callback</a><ul><li><a href="c4e1ee37-c835-449b-a843-4587b6e002c2">entries</a><ul><li><a href="32db3466-bf2f-4f88-a8fb-ff767969d733">boundingClientRect</a></li><li><a href="20db7392-1249-499d-ae3b-e95a4b2b7a96">intersectionRect</a></li><li><a href="fbaf8015-f14f-4129-89f3-edfd9775cdca">intersectionRatio</a></li><li><a href="5231f9ac-2323-45b7-89f2-3339c329548a">isIntersecting</a></li><li><a href="074b5573-df67-4d56-89ac-301623448517">rootBounds</a></li><li><a href="7a0b8b0b-5e12-4752-a702-42b8acc61180">target</a></li><li><a href="25bb7f70-b275-42eb-84ab-312cc2c24880">time</a></li></ul></li><li><a href="eecbf4d7-8f77-4226-a95f-b1981d827e3e">observer</a></li></ul></li><li><a href="c1ac88c8-b78d-4a14-91de-cdc7b7dfbc5f">options</a><ul><li><a href="cb564258-a65e-4b16-bddf-2c260e1e9f31">root</a></li><li><a href="ce43f133-3726-4fd2-9f8a-deedec2db91d">rootMargin</a></li><li><a href="869c70a3-6499-4cf4-b128-0f95f5885b1b">threshold</a></li></ul></li><li><a href="fdbed977-2680-402f-83ee-6dd7ea4c7d69">Methods</a><ul><li><a href="5cf21192-8db0-45e7-b294-f87415572ae6">observe()</a></li><li><a href="8e2e5cab-69c2-40a5-a21f-84519f318ada">unobserve()</a></li><li><a href="d777c764-a613-409b-8f3e-cccb6f6a4ed3">disconnect()</a></li><li><a href="fae9c5fe-c93f-4215-9650-c24256a05537">takeRecords()</a></li></ul></li></ul></li><li><a href="8186d8e7-447d-440d-b941-9dbf557b2ee0">IE 지원(polyfill)</a></li><li><a href="fc29e31f-7904-4137-a32d-99a6ffbeb8c1">예제</a><ul><li><a href="32ec28ba-90f6-4911-9b2e-4c82d513718d">Infinite scroll</a></li></ul></li><li><a href="2eb64cb8-c94f-41ea-8a64-2af1a10b9e2b">참고 자료(References)</a></li></ul></div><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" target="_blank" rel="noopener">Intersection observer</a>는 기본적으로 브라우저 뷰포트(Viewport)와 설정한 요소(Element)의 교차점을 관찰하며, 요소가 뷰포트에 포함되는지 포함되지 않는지, 더 쉽게는 사용자 화면에 지금 보이는 요소인지 아닌지를 구별하는 기능을 제공합니다.</p><p>이 기능은 비동기적으로 실행되기 때문에, <code>scroll</code> 같은 이벤트 기반의 요소 관찰에서 발생하는 <a href="https://developers.google.com/web/fundamentals/performance/rendering/?hl=ko" target="_blank" rel="noopener">렌더링 성능</a>이나 이벤트 연속 호출 같은 문제 없이 사용할 수 있습니다.</p><h1><span id="2aaf37a0-ffc6-4ccc-a180-deb871ea66d6">IntersectionObserver</span><a href="#2aaf37a0-ffc6-4ccc-a180-deb871ea66d6" class="header-anchor"></a></h1><p><img src="/images/screenshot/intersection-observer/intersection-observer-summary.jpg" alt="intersection observer summary"></p><p><code>new IntersectionObserver()</code>를 통해 생성한 인스턴스(<code>io</code>)로 관찰자(Observer)를 초기화하고 관찰할 대상(<a href="https://developer.mozilla.org/ko/docs/Web/API/Element" target="_blank" rel="noopener">Element</a>)을 지정합니다.<br>생성자는 2개의 인수(<code>callback</code>, <code>options</code>)를 가집니다.</p><pre><code class="js">const io = new IntersectionObserver(callback, options) // 관찰자 초기화io.observe(element) // 관찰할 대상(요소) 등록</code></pre><h2><span id="8b204cd7-1484-4969-9fdd-0a8dd3cfd613">callback</span><a href="#8b204cd7-1484-4969-9fdd-0a8dd3cfd613" class="header-anchor"></a></h2><p>관찰할 대상(Target)이 등록되거나 가시성(Visibility, 보이는지 보이지 않는지)에 변화가 생기면 관찰자는 콜백(Callback)을 실행합니다.<br>콜백은 2개의 인수(<code>entries</code>, <code>observer</code>)를 가집니다.</p><pre><code class="js">const io = new IntersectionObserver((entries, observer) =&gt; {}, options)io.observe(element)</code></pre><h3><span id="c4e1ee37-c835-449b-a843-4587b6e002c2">entries</span><a href="#c4e1ee37-c835-449b-a843-4587b6e002c2" class="header-anchor"></a></h3><p><code>entries</code>는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" rel="noopener">IntersectionObserverEntry</a> 인스턴스의 <strong>배열</strong>입니다.<br>IntersectionObserverEntry는 읽기 전용(Read only)의 다음 속성들을 포함합니다.</p><ul><li><code>boundingClientRect</code>: 관찰 대상의 사각형 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly" target="_blank" rel="noopener">DOMRectReadOnly</a>)</li><li><code>intersectionRect</code>: 관찰 대상의 교차한 영역 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly" target="_blank" rel="noopener">DOMRectReadOnly</a>)</li><li><code>intersectionRatio</code>: 관찰 대상의 교차한 영역 백분율(<code>intersectionRect</code> 영역에서 <code>boundingClientRect</code> 영역까지 비율, Number)</li><li><code>isIntersecting</code>: 관찰 대상의 교차 상태(Boolean)</li><li><code>rootBounds</code>: 지정한 루트 요소의 사각형 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly" target="_blank" rel="noopener">DOMRectReadOnly</a>)</li><li><code>target</code>: 관찰 대상 요소(<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element" target="_blank" rel="noopener">Element</a>)</li><li><code>time</code>: 변경이 발생한 시간 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp" target="_blank" rel="noopener">DOMHighResTimeStamp</a>)</li></ul><pre><code class="js">const io = new IntersectionObserver((entries, observer) =&gt; {  entries.forEach(entry =&gt; {    console.log(entry) // entry is &#39;IntersectionObserverEntry&#39;  })}, options)io.observe(element)</code></pre><p><img src="/images/screenshot/intersection-observer/intersection-observer-entry-object.jpg" alt="intersection observer entry object"></p><div class="image-caption">IntersectionObserverEntry 구조</div><h4><span id="32db3466-bf2f-4f88-a8fb-ff767969d733">boundingClientRect</span><a href="#32db3466-bf2f-4f88-a8fb-ff767969d733" class="header-anchor"></a></h4><p>관찰 대상의 사각형 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly" target="_blank" rel="noopener">DOMRectReadOnly</a>)를 반환합니다.<br>이 값은, <code>Element.getBoundingClientRect()</code>를 사용해 동일하게 얻을 수 있습니다.(<code>getBoundingClientRect</code> 호출에서 <a href="https://developers.google.com/speed/docs/insights/browser-reflow?hl=ko" target="_blank" rel="noopener">Reflow 현상</a>이 발생합니다.)</p><p><img src="/images/screenshot/intersection-observer/intersection-observer-bounding-client-rect.jpg" alt="intersection observer boundingClientRect"><br><img src="/images/screenshot/intersection-observer/intersection-observer-dom-rect-object.jpg" alt="intersection observer DOM rect"><br><img src="/images/screenshot/intersection-observer/intersection-observer-dom-rect.jpg" alt="intersection observer DOM rect"></p><div class="image-caption">DOMRectReadOnly 구조</div><h4><span id="20db7392-1249-499d-ae3b-e95a4b2b7a96">intersectionRect</span><a href="#20db7392-1249-499d-ae3b-e95a4b2b7a96" class="header-anchor"></a></h4><p>관찰 대상과 루트 요소와의 교차하는(겹치는) 영역에 대한 사각형 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly" target="_blank" rel="noopener">DOMRectReadOnly</a>)를 반환합니다.</p><p><img src="/images/screenshot/intersection-observer/intersection-observer-intersection-rect.jpg" alt="intersection observer intersectionRect"></p><h4><span id="fbaf8015-f14f-4129-89f3-edfd9775cdca">intersectionRatio</span><a href="#fbaf8015-f14f-4129-89f3-edfd9775cdca" class="header-anchor"></a></h4><p>관찰 대상이 루트 요소와 얼마나 교차하는(겹치는)지의 수치를 <code>0.0</code>과 <code>1.0</code> 사이의 숫자로 반환합니다.<br>이는 <code>intersectionRect</code> 영역과 <code>boundingClientRect</code> 영역의 비율을 의미합니다.</p><h4><span id="5231f9ac-2323-45b7-89f2-3339c329548a">isIntersecting</span><a href="#5231f9ac-2323-45b7-89f2-3339c329548a" class="header-anchor"></a></h4><p>관찰 대상이 루트 요소와 교차 상태로 들어가거나(<code>true</code>) 교차 상태에서 나가는지(<code>false</code>) 여부를 나타내는 값(Boolean)입니다.</p><p><img src="/images/screenshot/intersection-observer/intersection-observer-is-intersecting.jpg" alt="intersection observer isIntersecting"></p><h4><span id="074b5573-df67-4d56-89ac-301623448517">rootBounds</span><a href="#074b5573-df67-4d56-89ac-301623448517" class="header-anchor"></a></h4><p>루트 요소에 대한 사각형 정보(<a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly" target="_blank" rel="noopener">DOMRectReadOnly</a>)를 반환합니다.<br>이는 옵션 <code>rootMargin</code>에 의해 값이 변경되며, 만약 별도의 루트 요소(옵션 <code>root</code>)를 선언하지 않았을 경우 <code>null</code>을 반환합니다.</p><h4><span id="7a0b8b0b-5e12-4752-a702-42b8acc61180">target</span><a href="#7a0b8b0b-5e12-4752-a702-42b8acc61180" class="header-anchor"></a></h4><p>관찰 대상(<a href="https://developer.mozilla.org/ko/docs/Web/API/Element" target="_blank" rel="noopener">Element</a>)을 반환합니다.</p><h4><span id="25bb7f70-b275-42eb-84ab-312cc2c24880">time</span><a href="#25bb7f70-b275-42eb-84ab-312cc2c24880" class="header-anchor"></a></h4><p>문서가 작성된 시간을 기준으로 교차 상태 변경이 발생한 시간을 나타내는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp" target="_blank" rel="noopener">DOMHighResTimeStamp</a>를 반환합니다.</p><h3><span id="eecbf4d7-8f77-4226-a95f-b1981d827e3e">observer</span><a href="#eecbf4d7-8f77-4226-a95f-b1981d827e3e" class="header-anchor"></a></h3><p>콜백이 실행되는 해당 인스턴스를 참조합니다.</p><pre><code class="js">const io = new IntersectionObserver((entries, observer) =&gt; {  console.log(observer)}, options)io.observe(element)</code></pre><p><img src="/images/screenshot/intersection-observer/intersection-observer-observer.jpg" alt="intersection observer observer"></p><div class="image-caption">IntersectionObserver 구조</div><h2><span id="c1ac88c8-b78d-4a14-91de-cdc7b7dfbc5f">options</span><a href="#c1ac88c8-b78d-4a14-91de-cdc7b7dfbc5f" class="header-anchor"></a></h2><h3><span id="cb564258-a65e-4b16-bddf-2c260e1e9f31">root</span><a href="#cb564258-a65e-4b16-bddf-2c260e1e9f31" class="header-anchor"></a></h3><p>타겟의 가시성을 검사하기 위해 뷰포트 대신 사용할 요소 객체(루트 요소)를 지정합니다.<br>타켓의 조상 요소이어야 하며 지정하지 않거나 <code>null</code>일 경우 브라우저의 뷰포트가 기본 사용됩니다.<br>기본값은 <code>null</code>입니다.</p><pre><code class="js">const io = new IntersectionObserver(callback, {  root: document.getElementById(&#39;my-viewport&#39;)})</code></pre><p><img src="/images/screenshot/intersection-observer/intersection-observer-root.jpg" alt="intersection observer root"></p><h3><span id="ce43f133-3726-4fd2-9f8a-deedec2db91d">rootMargin</span><a href="#ce43f133-3726-4fd2-9f8a-deedec2db91d" class="header-anchor"></a></h3><p>바깥 여백(Margin)을 이용해 Root 범위를 확장하거나 축소할 수 있습니다.<br>CSS의 <code>margin</code>과 같이 4단계로 여백을 설정할 수 있으며, <code>px</code> 또는 <code>%</code>로 나타낼 수 있습니다.<br>기본값은 <code>0px 0px 0px 0px</code>이며 <strong>단위를 꼭 입력</strong>해야 합니다.</p><ul><li>TOP, RIGHT, BOTTOM, LEFT / e.g. <code>10px 0px 30px 0px</code></li><li>TOP, (LEFT, RIGHT), BOTTOM / e.g. <code>10px 0px 30px</code></li><li>(TOP, BOTTOM), (LEFT, RIGHT) / e.g. <code>30px 0px</code></li><li>(TOP, BOTTOM, LEFT, RIGHT) / e.g. <code>30px</code></li></ul><pre><code class="js">const io = new IntersectionObserver(callback, {  rootMargin: &#39;200px 0px&#39;})</code></pre><p><img src="/images/screenshot/intersection-observer/intersection-observer-root-margin-0.jpg" alt="intersection observer rootMargin"><br><img src="/images/screenshot/intersection-observer/intersection-observer-root-margin+100.jpg" alt="intersection observer rootMargin"><br><img src="/images/screenshot/intersection-observer/intersection-observer-root-margin-100.jpg" alt="intersection observer rootMargin"></p><h3><span id="869c70a3-6499-4cf4-b128-0f95f5885b1b">threshold</span><a href="#869c70a3-6499-4cf4-b128-0f95f5885b1b" class="header-anchor"></a></h3><p>옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지 백분율로 표시합니다.<br>기본값은 Array 타입의 <code>[0]</code>이지만 Number 타입의 단일 값으로도 작성할 수 있습니다.</p><ul><li><code>0</code>: 타겟의 가장자리 픽셀이 Root 범위를 교차하는 순간(타겟의 가시성이 0%일 때) 옵저버가 실행됩니다.</li><li><code>0.3</code>: 타겟의 가시성 30%일 때 옵저버가 실행됩니다.</li><li><code>[0, 0.3, 1]</code>: 타겟의 가시성이 0%, 30%, 100%일 때 모두 옵저버가 실행됩니다.</li></ul><pre><code class="js">const io = new IntersectionObserver(callback, {  threshold: 0.3 // or `threshold: [0.3]`})</code></pre><p><img src="/images/screenshot/intersection-observer/intersection-observer-threshold-0.jpg" alt="intersection observer threshold"><br><img src="/images/screenshot/intersection-observer/intersection-observer-threshold-0.3.jpg" alt="intersection observer threshold"><br><img src="/images/screenshot/intersection-observer/intersection-observer-threshold-1.jpg" alt="intersection observer threshold"></p><h2><span id="fdbed977-2680-402f-83ee-6dd7ea4c7d69">Methods</span><a href="#fdbed977-2680-402f-83ee-6dd7ea4c7d69" class="header-anchor"></a></h2><h3><span id="5cf21192-8db0-45e7-b294-f87415572ae6">observe()</span><a href="#5cf21192-8db0-45e7-b294-f87415572ae6" class="header-anchor"></a></h3><p>대상 요소의 관찰을 시작합니다.</p><pre><code class="js">const io1 = new IntersectionObserver(callback, options)const io2 = new IntersectionObserver(callback, options)const div = document.querySelector(&#39;div&#39;)const li = document.querySelector(&#39;li&#39;)const h2 = document.querySelector(&#39;h2&#39;)io1.observe(div) // DIV 요소 관찰io2.observe(li) // LI 요소 관찰io2.observe(h2) // h2 요소 관찰</code></pre><h3><span id="8e2e5cab-69c2-40a5-a21f-84519f318ada">unobserve()</span><a href="#8e2e5cab-69c2-40a5-a21f-84519f318ada" class="header-anchor"></a></h3><p>대상 요소의 관찰을 중지합니다.<br>관찰을 중지할 하나의 대상 요소를 인수로 지정해야 합니다.<br>단, IntersectionObserver 인스턴스가 관찰하고 있지 않은 대상 요소가 인수로 지정된 경우 아무런 동작도 하지 않습니다.</p><pre><code class="js">const io1 = new IntersectionObserver(callback, options)const io2 = new IntersectionObserver(callback, options)// ...io1.observe(div)io2.observe(li)io2.observe(h2)io1.unobserve(h2) // nothing..io2.unobserve(h2) // H2 요소 관찰 중지</code></pre><p>콜백의 두 번째 인수 <code>observer</code>가 해당 인스턴스를 참조하므로, 다음과 같이 작성할 수도 있습니다.</p><pre><code class="js">const io1 = new IntersectionObserver((entries, observer) =&gt; {  entries.forEach(entry =&gt; {    // 가시성의 변화가 있으면 관찰 대상 전체에 대한 콜백이 실행되므로,    // 관찰 대상의 교차 상태가 false일(보이지 않는) 경우 실행하지 않음.    if (!entry.isIntersecting) {      return    }    // 관찰 대상의 교차 상태가 true일(보이는) 경우 실행.    // ...    // 위 실행을 처리하고(1회) 관찰 중지    observer.unobserve(entry.target)  })}, options)</code></pre><h3><span id="d777c764-a613-409b-8f3e-cccb6f6a4ed3">disconnect()</span><a href="#d777c764-a613-409b-8f3e-cccb6f6a4ed3" class="header-anchor"></a></h3><p>IntersectionObserver 인스턴스가 관찰하는 모든 요소의 관찰을 중지합니다.</p><pre><code class="js">const io1 = new IntersectionObserver(callback, options)const io2 = new IntersectionObserver(callback, options)// ...io1.observe(div)io2.observe(li)io2.observe(h2)io2.disconnect() // io2가 관찰하는 모든 요소(LI, H2) 관찰 중지</code></pre><h3><span id="fae9c5fe-c93f-4215-9650-c24256a05537">takeRecords()</span><a href="#fae9c5fe-c93f-4215-9650-c24256a05537" class="header-anchor"></a></h3><p>IntersectionObserverEntry 객체의 배열을 반환합니다.</p><blockquote><p>일반적인 상황에서 이 메서드를 호출할 필요가 없습니다.</p></blockquote><h1><span id="8186d8e7-447d-440d-b941-9dbf557b2ee0">IE 지원(polyfill)</span><a href="#8186d8e7-447d-440d-b941-9dbf557b2ee0" class="header-anchor"></a></h1><p>IntersectionObserver API를 지원하지 않는 브라우저(MSIE)에서도 사용할 수 있도록 공식적으로 <a href="https://github.com/w3c/IntersectionObserver/tree/master/polyfill" target="_blank" rel="noopener">라이브러리</a>가 지원됩니다.<br>따로 설정할 것 없이 모듈을 가져오기만 하면 됩니다.</p><pre><code class="bash">$ npm i intersection-observer</code></pre><pre><code class="js">// For IEimport &#39;intersection-observer&#39;// 동일하게 사용하면 됩니다.const io = new IntersectionObserver(callback, options)const els = document.querySelectorAll(&#39;element&#39;)// For IEArray.prototype.slice.call(els).forEach(el =&gt; {  io.observe(el)})</code></pre><p>혹은 다음과 같이 전역으로 사용할 수도 있습니다.</p><pre><code class="html">&lt;script src=&quot;https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver&quot;&gt;&lt;/script&gt;</code></pre><h1><span id="fc29e31f-7904-4137-a32d-99a6ffbeb8c1">예제</span><a href="#fc29e31f-7904-4137-a32d-99a6ffbeb8c1" class="header-anchor"></a></h1><h2><span id="32ec28ba-90f6-4911-9b2e-4c82d513718d">Infinite scroll</span><a href="#32ec28ba-90f6-4911-9b2e-4c82d513718d" class="header-anchor"></a></h2><p><img src="/images/screenshot/intersection-observer/intersection-observer-infinite-scroll-example-chrome-network-tab-setting.jpg" alt="set Network tab in Chrome"></p><iframe height="700" style="width:100%" scrolling="no" title="Infinite scroll" src="https://codepen.io/heropark/embed/LYYjMQp?height=265&theme-id=0&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen><br>See the Pen <a href="https://codepen.io/heropark/pen/LYYjMQp" target="_blank" rel="noopener">Infinite scroll</a> by park young woong<br>(<a href="https://codepen.io/heropark" target="_blank" rel="noopener">@heropark</a>) on <a href="https://codepen.io" target="_blank" rel="noopener">CodePen</a>.<br></iframe><h1><span id="2eb64cb8-c94f-41ea-8a64-2af1a10b9e2b">참고 자료(References)</span><a href="#2eb64cb8-c94f-41ea-8a64-2af1a10b9e2b" class="header-anchor"></a></h1><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API</a><br><a href="https://github.com/w3c/IntersectionObserver/tree/master/polyfill" target="_blank" rel="noopener">https://github.com/w3c/IntersectionObserver/tree/master/polyfill</a></p>]]></content>
    
    <summary type="html">
    
      Intersection observer는 기본적으로 브라우저 뷰포트(Viewport)와 설정한 요소(Element)의 교차점을 관찰하며, 요소가 뷰포트에 포함되는지 포함되지 않는지, 더 쉽게는 사용자 화면에 지금 보이는 요소인지 아닌지를 구별하는 기능을 제공합니다.
    
    </summary>
    
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>Svelte.js 완벽 가이드(Renew)</title>
    <link href="https://heropy.blog/2019/09/29/svelte/"/>
    <id>https://heropy.blog/2019/09/29/svelte/</id>
    <published>2019-09-29T09:00:01.000Z</published>
    <updated>2020-12-31T13:55:48.384Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>이하 내용은 <a href="https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md" target="_blank" rel="noopener">Svelte@3.31.0</a>을 기준으로 작성했습니다.</p></blockquote><h1 id="변경사항"><a href="#변경사항" class="headerlink" title="변경사항"></a>변경사항</h1><h2 id="2020년-12월"><a href="#2020년-12월" class="headerlink" title="2020년 12월"></a>2020년 12월</h2><ul><li>&lt;컴포넌트 / Slot&gt; 파트를 &lt;슬롯(Slot)&gt;으로 변경하고 하위 파트를 추가했습니다.</li><li>다음의 파트들을 추가했습니다.<ul><li>키 블록</li><li>슬롯 포워딩 &lt;슬롯(Slot)&gt;</li><li>$$slots &lt;슬롯(Slot)&gt;</li></ul></li><li>일부 내용과 오타 등을 수정했습니다.</li></ul><h2 id="2020년-11월"><a href="#2020년-11월" class="headerlink" title="2020년 11월"></a>2020년 11월</h2><ul><li>별도 구성 없이 바로 프로젝트를 시작할 수 있는 <a href="https://github.com/ParkYoungWoong/svelte-snowpack-template" target="_blank" rel="noopener">Snowpack 기반의 Svelte 템플릿</a>을 추가했습니다.</li><li>‘시작하기’ 파트를 ‘개발환경 구성’으로 수정했습니다.</li><li>다음의 파트들을 추가했습니다.<ul><li>Snowpack template</li><li>Web Test Runner &lt;Unit Test&gt;</li></ul></li></ul><h2 id="2020년-10월"><a href="#2020년-10월" class="headerlink" title="2020년 10월"></a>2020년 10월</h2><ul><li>문서 제목을 ‘SvelteJS(스벨트) - 새로운 개념의 프론트엔드 프레임워크’에서 ‘Svelte.js 완벽 가이드’로 변경했습니다.</li><li>문서 전체를 Svelte 최신 버전에 맞게 재단장했습니다.</li><li>다음의 대주제(파트)들을 추가했습니다.<ul><li>Svelte 시작하기</li><li>Svelte Core API</li><li>Svelte Animation API</li><li>Router</li><li>기능</li><li>Unit Test</li><li>Tools</li></ul></li></ul><div class="toc"><ul><li><a href="38e18a02-ab3f-4b7a-88cc-b36d8b641b33">Svelte 온라인 강의</a><ul><li><a href="30320b32-ab6a-425a-81c4-4db577ab192a">Svelte Core API 완벽 가이드</a><ul><li><a href="0167e931-8f06-417d-8b9e-53e2e9c36cce">Trello clone app</a></li></ul></li><li><a href="eebef9ce-0827-4cef-ab9b-7774e5d6869d">Svelte SPA 영화 검색 프로젝트</a></li><li><a href="d189b3fa-a7af-48d6-9b0f-2441df9d34b5">Svelte 입문 가이드(무료)</a></li></ul></li><li><a href="1ff6a1c6-4665-4e56-b2e1-b753efaef7e6">Svelte?</a><ul><li><a href="ac88cadc-eef5-4c58-adb7-8a62e61137e6">간결한 코드</a></li><li><a href="fa73b371-7e6e-4850-bcbc-f42d00c26576">No virtual DOM</a></li><li><a href="d58f5137-0080-4dae-9ba9-2ecc4378dcec">반응성</a></li><li><a href="f7fdc7c0-945a-431a-ba1d-93a720e0e2cf">퍼포먼스</a></li></ul></li><li><a href="7c16c848-71bc-4b8f-8783-8975fa72b37e">Svelte 시작하기</a><ul><li><a href="30d39d67-2da3-4b10-bf48-30002962d058">개발환경 구성</a><ul><li><a href="d8d48955-90bd-48dc-8f55-bc6e7b7a60e8">Svelte REPL</a></li><li><a href="dcd9b9b2-5881-4c4d-b5f5-aaea4891a3d9">Svelte/template</a><ul><li><a href="81c65259-85ba-4846-adb3-9c3be431acdf">main.js</a></li></ul></li><li><a href="867d02d4-a133-4a68-8de3-53c3fd693c5a">Snowpack template</a></li></ul></li><li><a href="040316bd-8413-43f9-bfd5-c926793a19bd">선언적 렌더링</a></li><li><a href="fe22c51d-56f6-4a22-857b-064b436ca7fe">조건문과 반복문</a></li><li><a href="05af9371-b6a5-4b1f-a390-9813b4c3ff4b">이벤트 핸들링</a></li><li><a href="dc0f5888-2dab-4de2-ab59-e8c283d14056">컴포넌트</a></li><li><a href="f977c27b-7c57-4d7b-b12e-a4618f076a8f">스토어</a></li><li><a href="7d74a6c9-b2c9-4099-bb2c-98825cb28956">Todo 예제 만들기</a></li></ul></li><li><a href="6e854e14-6613-4192-a73d-7735b415fdd3">Svelte Core API</a><ul><li><a href="a9827601-7a04-4609-8cfb-6cf41f5f023a">라이프 사이클</a><ul><li><a href="114d1d75-2c62-46df-9050-8422de4ffea7">onMount, onDestroy, beforeUpdate, afterUpdate</a></li><li><a href="5493318d-5c94-49d5-b09f-ff79527ffa0b">tick</a></li><li><a href="88742375-8558-428c-b0ab-86e07436a87f">라이프 사이클 모듈화</a></li></ul></li><li><a href="c6d9cf12-af57-4719-90ba-e8b9b85f3c6c">기본 보간</a><ul><li><a href="6e7bb879-72a4-4adf-aa22-77f94a6e6531">원시 HTML</a></li><li><a href="aee65783-0c7f-4505-b2e7-2bc1e57ea72f">디버그</a></li></ul></li><li><a href="33287fcc-e192-46e3-816a-6555116fa659">반응성</a><ul><li><a href="8a23c17a-a04c-40ef-8d77-e7722f808656">할당</a></li><li><a href="fb64cfe9-76f8-4f23-bdfd-1dd8634f2b64">반응성 구문</a><ul><li><a href="51d9cdf1-ea7e-4bbc-9fb0-9f74c3983792">사용 패턴</a></li></ul></li></ul></li><li><a href="e3fed7c9-cc4a-40ff-b11f-ee87f0649e52">클래스와 스타일</a><ul><li><a href="bdc250f7-7df5-48fc-94ed-2bbb8ed36173">속성 바인딩</a><ul><li><a href="53f58ee6-61f9-46b5-990e-0223ab1b1441">사용 패턴</a></li></ul></li><li><a href="f0eebf0a-d21a-4c68-809b-5d7a923f6d0b">스타일 유효범위와 전역화</a></li><li><a href="2b0ec369-c8f8-4c00-becf-0c15a9216283">@keyframes 전역화</a></li></ul></li><li><a href="10df2e83-5b48-477e-9414-a840f53903cf">요소 바인딩</a><ul><li><a href="3d626d56-74b6-41b1-8b67-48489b661641">일반 요소</a></li><li><a href="3be528f0-343c-46fe-bcaf-d817ec786c73">입력 요소</a></li><li><a href="5b97fc02-0061-4324-884b-3eafdd466599">편집 가능 요소</a></li></ul></li><li><a href="6b515578-38e0-4555-ab0c-ef7e854d37ec">조건 블록</a><ul><li><a href="cdb48251-caac-4faa-baf7-0463b6fd819f">사용 패턴</a></li></ul></li><li><a href="6f993a12-4d52-40b6-a672-67c3e198d803">반복 블록</a><ul><li><a href="177a4d2c-5b18-4d61-82ad-d8feada43cbb">key</a></li><li><a href="a0cf5f22-80aa-40aa-9d56-7fb8f3c48ba5">사용 패턴</a></li></ul></li><li><a href="05486b13-90ab-4f2d-8c8e-4c1b45497dd5">키 블록</a></li><li><a href="c016884c-3bb2-4ddb-b8e5-b843862c91b9">비동기 블록</a></li><li><a href="0d35e7e5-bca6-4a62-b111-2e33ae7f713e">사용자 입력 핸들링</a><ul><li><a href="2ead5099-26c1-4674-a6b6-3f2b28ff7bfc">다중 이벤트 핸들러</a></li><li><a href="b324c25c-264c-482f-84b2-afba940f012a">이벤트 수식어</a></li></ul></li><li><a href="73aedf0e-a4e7-481c-8788-9f4daa6ac893">컴포넌트</a><ul><li><a href="f3c2ba04-705a-45b4-9b97-8228dd905be1">Props</a><ul><li><a href="234195d6-c0c1-4a2d-adcb-d87be5d91b4e">양방향 바인딩</a></li></ul></li><li><a href="3d80d4b0-5a8f-40d2-a042-5d853812d602">Event Dispatcher</a><ul><li><a href="1e048924-c4c4-4b5d-8b99-cb171360d4c1">Event Forwarding</a></li></ul></li><li><a href="490d2aba-18da-4a29-8482-579d2ff1e300">Context API</a></li><li><a href="d6f5eaca-6149-4b27-8e49-780bf9a4be9b">Module Context</a></li><li><a href="baa92077-3ffc-4482-afad-86f03be70877">$$props, $$restProps</a></li></ul></li><li><a href="2cf07b14-3b05-4011-beee-0e61a5078ece">슬롯(Slot)</a><ul><li><a href="224094c9-be50-42c5-8b8e-98e4445354a2">단일 슬롯과 Fallback Content</a></li><li><a href="a79eb398-278d-4cb3-8b09-cec1537d0ca8">이름을 가지는 슬롯</a></li><li><a href="2264d36d-4502-495e-8602-3ad22176bfec">범위를 가지는 슬롯</a></li><li><a href="a499ce4a-995b-46f6-8967-2ca68d9af87b">슬롯 포워딩</a></li><li><a href="3354dd9b-e3a9-4a67-9f9b-af5a73d0f964">$$slots</a></li></ul></li><li><a href="fd8320ea-176e-41c8-adde-d71e8e0337ba">스토어</a><ul><li><a href="e1776701-fef3-4a1a-bc18-977ae4bb622a">쓰기 가능 스토어</a><ul><li><a href="949035eb-6f51-4a25-9cf5-d03407185549">사용 패턴</a></li></ul></li><li><a href="e1700ff3-1c9a-4c13-bd48-2f84bd59fa00">읽기 전용 스토어</a><ul><li><a href="b9d39e0c-457b-4608-8486-22b067cab070">사용 패턴</a></li></ul></li><li><a href="58263a4e-c90c-416f-9f45-0018125b02e9">계산된 스토어</a><ul><li><a href="f0c13db7-bd0b-4d24-9b23-cf56c8434c4d">사용 패턴</a></li></ul></li><li><a href="b821d1ff-969a-4ade-9476-41078a14afe5">스토어 값 얻기</a></li><li><a href="dbf4a640-9885-4409-ae1d-d3d2a8f9cd63">커스텀 스토어</a></li></ul></li><li><a href="0a7c2b76-66ff-401e-aa19-9cf550abe248">액션</a></li><li><a href="cd7e6d07-d9b0-44f7-9037-9ff18f107ede">특별한 요소</a><ul><li><a href="9e00f28a-d082-4b1b-a61b-92e9c5da653e">self</a></li><li><a href="6fd571ae-5e5b-4d29-a7c2-edc80f0896c9">component</a></li><li><a href="a996236f-03c1-498d-940f-726b290cd8bc">window</a></li><li><a href="ba491066-8add-4c89-b255-b0ae81b03806">head, body</a></li><li><a href="19f4a07e-b858-4edf-beb5-44ff9bb19d61">options</a><ul><li><a href="84460d43-8c73-43ab-aa54-bd4fef9366d3">불변성 선언(immutable)</a></li><li><a href="f9237e30-b709-4b30-85be-c7624b7d081d">접근 허용(accessors)</a></li></ul></li></ul></li></ul></li><li><a href="2d4adc77-e425-4432-b26f-422bd53be1f7">Svelte Animation API</a><ul><li><a href="bb797741-0cac-4bf4-be03-279c5b5c7a51">모션</a><ul><li><a href="39e9885b-5b9e-489d-af47-ff7b0d8d36bd">tweened</a></li></ul></li></ul></li><li><a href="9fe1ff14-536a-450e-b631-36a617611bc8">Router</a><ul><li><a href="fa732cba-b87d-442c-89f2-101a87c0f433">svelte-spa-router</a></li></ul></li><li><a href="54744aff-c44e-487e-b539-5bc336d47d51">기능</a><ul><li><a href="91732805-1c75-49d7-9cef-8d74d17dda73">전/후 처리기(svelte-preprocess)</a><ul><li><a href="fc3d4ec7-f7d5-46ad-b353-24f6c604146d">자동 전처리 모드(Auto Preprocessing mode)</a></li><li><a href="2836c4ad-f9ac-4260-ac8c-1d148b4eea1c">구성</a><ul><li><a href="21a30f69-e792-416c-a50a-07222de66c50">Rollup</a></li><li><a href="7d5e9e2d-aaf7-469c-b720-f18efbde2fdf">Snowpack</a></li></ul></li></ul></li><li><a href="689d04d6-14ab-4228-a76c-3200be05b527">경로 별칭</a><ul><li><a href="49f30b17-1254-4dc2-bf40-3148970a135a">Rollup</a></li><li><a href="1c30f1af-10db-4a53-8b8d-696d06e8bdbe">Snowpack</a></li></ul></li></ul></li><li><a href="1dbad537-f3ec-4046-ba24-cb2e7e41e6e4">Unit Test</a><ul><li><a href="7ca6b33d-2565-4ce8-b954-290005878d6b">Jest</a></li><li><a href="e23e0db3-6050-491e-9c36-67d056a9e639">Web Test Runner</a></li></ul></li><li><a href="1652873e-c74c-4baf-ae73-60756d778b11">Tools</a><ul><li><a href="68347524-f433-4e41-a67e-2c72811ac535">WebStorm plugin</a></li><li><a href="e84e057b-2dc6-468a-8207-590d28b97241">VS Code plugin</a><ul><li><a href="c0a1dc27-bd57-41d6-ac65-cff2d69195cb">Sass(SCSS) 에러 해결</a></li></ul></li><li><a href="fb00a760-7e94-4223-8667-2bb52e411a6b">Svelte Devtools</a></li></ul></li><li><a href="189e5563-e77f-425a-8cd5-c05327aa44a9">참고 자료(References)</a></li></ul></div><h1><span id="38e18a02-ab3f-4b7a-88cc-b36d8b641b33">Svelte 온라인 강의</span><a href="#38e18a02-ab3f-4b7a-88cc-b36d8b641b33" class="header-anchor"></a></h1><h2><span id="30320b32-ab6a-425a-81c4-4db577ab192a">Svelte Core API 완벽 가이드</span><a href="#30320b32-ab6a-425a-81c4-4db577ab192a" class="header-anchor"></a></h2><p><strong>인프런 강의</strong> - <a href="https://www.inflearn.com/course/스벨트-완벽-가이드?inst=c1552804" target="_blank" rel="noopener">https://www.inflearn.com/course/스벨트-완벽-가이드?inst=c1552804</a><br><strong>유튜브 공개 목록</strong> - <a href="https://www.youtube.com/watch?v=QjaHjFlPa-g&amp;list=PL5v0w59YqSue9aPueJ15phdPv6lcvWzVy" target="_blank" rel="noopener">https://www.youtube.com/watch?v=QjaHjFlPa-g&amp;list=PL5v0w59YqSue9aPueJ15phdPv6lcvWzVy</a></p><p>이 강의는,</p><ul><li>21시간 분량의 강의로, 이론 및 기본 예제와 클론 프로젝트까지 한 번에 진행할 수 있어요!</li><li>최신 Svelte.js의 Core API를 기초부터 탄탄하게 학습해요!</li><li>자바스크립트 데이터의 불변성(Immutable)과 가변성(Mutable)에 대해서 이해할 수 있어요!</li><li>자바스크립트의 비동기에 대해서 이해하고 여러 비동기 패턴을 학습해요!</li><li>Rollup.js의 기본 구성을 이해하고 추가 구성으로 실제 프로젝트를 만들어요!</li><li>Snowpack의 기본 구성을 이해하고 프로젝트를 이전(Migration)하는 작업도 진행해요!</li><li>Sortable.js를 핵심 모듈로 ‘트렐로(Trello) 클론 앱’을 만들고, Netlify 서비스에 가입하고 프로젝트를 지속적으로 배포(CD)할 수 있어요!</li></ul><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/QjaHjFlPa-g?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><h3><span id="0167e931-8f06-417d-8b9e-53e2e9c36cce">Trello clone app</span><a href="#0167e931-8f06-417d-8b9e-53e2e9c36cce" class="header-anchor"></a></h3><p>Svelte Core API 완벽 가이드에 포함된 Rollup 기반의 Trello 클론 프로젝트입니다.<br>프로젝트 내 각 파일에 주석으로 자세한 설명을 명시했으니, 학습에 도움이 되실 거예요!</p><p>GitHub Repo - <a href="https://github.com/HeropCode/Svelte-Trello-app" target="_blank" rel="noopener">https://github.com/HeropCode/Svelte-Trello-app</a><br>DEMO - <a href="https://boring-agnesi-165a0d.netlify.app" target="_blank" rel="noopener">https://boring-agnesi-165a0d.netlify.app</a></p><p><img src="/images/screenshot/svelte/svelte-trello-example.gif" alt="Trello clone app"></p><h2><span id="eebef9ce-0827-4cef-ab9b-7774e5d6869d">Svelte SPA 영화 검색 프로젝트</span><a href="#eebef9ce-0827-4cef-ab9b-7774e5d6869d" class="header-anchor"></a></h2><p><strong>인프런 강의</strong> - <a href="https://www.inflearn.com/course/스벨트-실습-프로젝트?inst=0bd6a806" target="_blank" rel="noopener">https://www.inflearn.com/course/스벨트-실습-프로젝트?inst=0bd6a806</a><br><strong>유튜브 공개 목록</strong> - <a href="https://www.youtube.com/watch?v=yMOSlm667To&amp;list=PL5v0w59YqSueBr4Nwu2xHb_a32RRkDqH4" target="_blank" rel="noopener">https://www.youtube.com/watch?v=yMOSlm667To&amp;list=PL5v0w59YqSueBr4Nwu2xHb_a32RRkDqH4</a></p><p>이 강의는,</p><ul><li>Svelte.js의 SPA(Single Page Application)을 구성할 수 있어요!</li><li>SPA의 장점을 활용하고 단점을 보완할 수 있어요!</li><li>Netlify Serverless Functions를 사용해 손쉽게 백엔드 API를 구성할 수 있어요!</li></ul><p>GitHub Repo - <a href="https://github.com/HeropCode/Svelte-Movie-app" target="_blank" rel="noopener">https://github.com/HeropCode/Svelte-Movie-app</a><br>DEMO - <a href="https://competent-cori-258206.netlify.app" target="_blank" rel="noopener">https://competent-cori-258206.netlify.app</a></p><p><img src="/images/screenshot/svelte/svelte-movie-app.gif" alt="Movie app"></p><h2><span id="d189b3fa-a7af-48d6-9b0f-2441df9d34b5">Svelte 입문 가이드(무료)</span><a href="#d189b3fa-a7af-48d6-9b0f-2441df9d34b5" class="header-anchor"></a></h2><p><strong>인프런 강의</strong> - <a href="https://www.inflearn.com/course/스벨트-입문-가이드?inst=e4eed96c" target="_blank" rel="noopener">https://www.inflearn.com/course/스벨트-입문-가이드?inst=e4eed96c</a></p><p>이 강의는,</p><ul><li>Svelte.js 기본 사용법에 대해서 학습해요!</li><li>Store를 사용하는 간단한 Todo 예제를 만들어요!</li></ul><h1><span id="1ff6a1c6-4665-4e56-b2e1-b753efaef7e6">Svelte?</span><a href="#1ff6a1c6-4665-4e56-b2e1-b753efaef7e6" class="header-anchor"></a></h1><p><a href="https://svelte.dev/" target="_blank" rel="noopener">Svelte(스벨트)</a>는 <a href="https://twitter.com/rich_harris" target="_blank" rel="noopener">Rich Harris</a>가 제작한 새로운 접근 방식을 가지는 프론트엔드 프레임워크입니다.<br>Svelte는 자신을 ‘프레임워크가 없는 프레임워크’ 혹은 <strong>‘컴파일러’</strong>라고 소개합니다.<br>이는 Virtual(가상) DOM이 없고, Runtime(런타임)에 로드할 프레임워크가 없음을 의미합니다.<br>기본적으로 빌드 단계에서 구성 요소를 컴파일하는 도구이므로 페이지에 단일 번들(bundle.js)을 로드하여 앱을 렌더링할 수 있습니다.</p><p>최근까지 ‘The magical disappearing UI framework’라는 <a href="https://veriide.com/info/motto-slogan-catch-phrase-tagline/" target="_blank" rel="noopener">태그라인</a>을 사용했습니다.</p><blockquote><p>‘Cybernetically enhanced web apps’라는 태그라인으로 변경되었습니다.</p></blockquote><p>다른 프레임워크와 Svelte의 주요 차이점을 알아봅시다.</p><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/nV-cpUd5R7Y?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><h2><span id="ac88cadc-eef5-4c58-adb7-8a62e61137e6">간결한 코드</span><a href="#ac88cadc-eef5-4c58-adb7-8a62e61137e6" class="header-anchor"></a></h2><p>Svelte는 높은 가독성을 유지하며 더 적은 코드를 작성할 수 있습니다.<br>다음의 Svelte 코드를 살펴보세요.</p><pre><code class="svelte">&lt;!-- Svelte --&gt;&lt;script&gt;  let a = 1;  let b = 2;&lt;/script&gt;&lt;input type=&quot;number&quot; bind:value={a}&gt;&lt;input type=&quot;number&quot; bind:value={b}&gt;&lt;p&gt;{a} + {b} = {a + b}&lt;/p&gt;</code></pre><p>위 코드는 <a href="https://ko.reactjs.org/" target="_blank" rel="noopener">React</a>와 <a href="https://kr.vuejs.org/v2/guide/index.html" target="_blank" rel="noopener">Vue</a>에서 다음과 같이 작성할 수 있습니다.</p><pre><code class="react">// Reactimport React, { useState } from &#39;react&#39;;export default () =&gt; {  const [a, setA] = useState(1);  const [b, setB] = useState(2);  function handleChangeA(event) {    setA(+event.target.value);  }  function handleChangeB(event) {    setB(+event.target.value);  }  return (    &lt;div&gt;      &lt;input type=&quot;number&quot; value={a} onChange={handleChangeA}/&gt;      &lt;input type=&quot;number&quot; value={b} onChange={handleChangeB}/&gt;      &lt;p&gt;{a} + {b} = {a + b}&lt;/p&gt;    &lt;/div&gt;  );};</code></pre><pre><code class="vue">&lt;!-- Vue --&gt;&lt;template&gt;  &lt;div&gt;    &lt;input type=&quot;number&quot; v-model.number=&quot;a&quot;&gt;    &lt;input type=&quot;number&quot; v-model.number=&quot;b&quot;&gt;    &lt;p&gt;{{a}} + {{b}} = {{a + b}}&lt;/p&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {    data: function() {      return {        a: 1,        b: 2      };    }  };&lt;/script&gt;</code></pre><h2><span id="fa73b371-7e6e-4850-bcbc-f42d00c26576">No virtual DOM</span><a href="#fa73b371-7e6e-4850-bcbc-f42d00c26576" class="header-anchor"></a></h2><p>Svelte는 <a href="https://velopert.com/3236" target="_blank" rel="noopener">Virtual(가상) DOM</a>을 사용하지 않습니다.<br>Virtual DOM은 <strong>충분히 빠르고 유용하지만</strong> 이는 기능이 아닌 수단일 뿐이며, 이를 사용하지 않고도 유사한 프로그래밍 모델을 얻을 수 있다고 Svelte는 설명합니다.<br>새로운 Virtual DOM을 이전 Snapshot과 비교하거나(<a href="https://meetup.toast.com/posts/110" target="_blank" rel="noopener">Diffing</a>), 상태 변화에 따른 새로운 가상 요소 생성 등에 많은 <a href="https://ko.wikipedia.org/wiki/%EC%98%A4%EB%B2%84%ED%97%A4%EB%93%9C" target="_blank" rel="noopener">오버헤드</a>가 있을 수 있으며 최종적으론 실제 DOM을 업데이트해야 하므로 그 과정을 생략하는 것이 더욱 빠를 수 있다고 합니다.</p><p>이에 대한 더 자세한 내용은 <a href="https://svelte.dev/blog/virtual-dom-is-pure-overhead#Where_does_the_overhead_come_from" target="_blank" rel="noopener">Virtual DOM is pure overhead</a>에서 더 자세하게 확인할 수 있습니다.</p><blockquote><p>기존 UI 프레임워크와 달리, Svelte는 런타임에 작업을 기다리지 않고 빌드 시간에 앱에서 변경 사항이 어떻게 발생하는지 알고 있는 컴파일러입니다.</p></blockquote><h2><span id="d58f5137-0080-4dae-9ba9-2ecc4378dcec">반응성</span><a href="#d58f5137-0080-4dae-9ba9-2ecc4378dcec" class="header-anchor"></a></h2><p>반응성은 변경된 값이 DOM에 자동으로 반영됨을 의미합니다.<br>Svelte는 별도의 Setter 없이 값의 할당(assignments)만으로 업데이트를 트리거(Trigger)할 수 있습니다.<br>실제로 상당히 편리합니다!</p><pre><code class="svelte">&lt;script&gt;  let count = 0;&lt;/script&gt;&lt;button on:click={() =&gt; count += 1}&gt;  {count}&lt;/button&gt;</code></pre><p>컴파일 결과가 할당을 계측하고 DOM을 갱신합니다.</p><pre><code class="js">// JS output$$invalidate(&#39;count&#39;, count += 1);</code></pre><p>이는 다음과 같이 Store 사용에도 굉장한 이점을 부여합니다.</p><pre><code class="js">// store.jsimport { writable } from &#39;svelte/store&#39;;export const count = writable(0); // similar to `count = 0`</code></pre><p><code>count</code>는 <code>writable()</code>에서 반환된 쓰기용 객체 데이터이기 때문에,<br><code>$</code> 접두사(<code>$count</code>)를 사용해 Store를 참조하겠다는 의미로 사용할 수 있습니다.</p><pre><code class="svelte">&lt;script&gt;  import { count } from &#39;./store.js&#39;;&lt;/script&gt;&lt;button on:click={() =&gt; $count += 1}&gt;  {$count}&lt;/button&gt;</code></pre><p><img src="/images/screenshot/svelte/svelte-twitter-with-evan-you.jpg" alt="Svelte Twitter with Evan you"></p><div class="image-caption">Svelte2는 Vue와 상당히 비슷했었죠!</div><h2><span id="f7fdc7c0-945a-431a-ba1d-93a720e0e2cf">퍼포먼스</span><a href="#f7fdc7c0-945a-431a-ba1d-93a720e0e2cf" class="header-anchor"></a></h2><p>W3C HTML5 Conf 2019에서 <a href="https://novemberde.github.io/" target="_blank" rel="noopener">변규현</a> 님의 Svelte와 React 퍼포먼스 비교 시연은 생각보다 놀라웠습니다.<br>메모리 사용량의 비교 결과를 보시면 차이가 확실히 느껴지는데, 컴파일 Output이 워낙 작기도 하고 가상 DOM Diffing이 없어서인지 훨씬 안정적으로 동작하고 있었습니다.</p><p>발표 자료는 <a href="https://novemberde.github.io/javascript/2019/10/11/Svelte-revealjs.html" target="_blank" rel="noopener">변규현 님의 블로그(Let’s start SVELTE, goodbye React &amp; Vue)</a>에서 확인하실 수 있습니다.</p><p><img src="/images/screenshot/svelte/w3c-conf-svelte-session.jpg" alt="W3C 2019 Conf Svlete"></p><div class="image-caption">W3C HTML5 Conf 2019, 변규현 님의 Svelte 세션</div><h1><span id="7c16c848-71bc-4b8f-8783-8975fa72b37e">Svelte 시작하기</span><a href="#7c16c848-71bc-4b8f-8783-8975fa72b37e" class="header-anchor"></a></h1><p>이 파트에서는 Svelte의 전반적인 내용을 비교적 가볍게 다룹니다.<br>Svelte의 특징에 대해서 빠르게 이해할 수 있습니다.</p><blockquote><p>영상 다음에 첨부된 REPL은 최종 결과로, 강의 진행과는 일부 코드가 다를 수 있습니다.</p></blockquote><h2><span id="30d39d67-2da3-4b10-bf48-30002962d058">개발환경 구성</span><a href="#30d39d67-2da3-4b10-bf48-30002962d058" class="header-anchor"></a></h2><p>Svelte는 런타임 프레임워크가 아니기 때문에, CDN을 제공하지 않습니다!</p><h3><span id="d8d48955-90bd-48dc-8f55-bc6e7b7a60e8">Svelte REPL</span><a href="#d8d48955-90bd-48dc-8f55-bc6e7b7a60e8" class="header-anchor"></a></h3><p><a href="https://svelte.dev/repl/" target="_blank" rel="noopener">Svelte REPL(레플)</a>이 준비되어 있습니다.<br>‘+’ 버튼을 눌러 파일을 추가하고 상대경로(확장자를 작성해야 합니다)로 접근할 수 있습니다.</p><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/qH4JwW9LIFc?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><iframe style="width:100%;height:600px" src="https://svelte.dev/repl/hello-world?version=3.29.4" frameborder="0" allow="allowfullscreen"></iframe><h3><span id="dcd9b9b2-5881-4c4d-b5f5-aaea4891a3d9">Svelte/template</span><a href="#dcd9b9b2-5881-4c4d-b5f5-aaea4891a3d9" class="header-anchor"></a></h3><p><a href="https://github.com/Rich-Harris/degit" target="_blank" rel="noopener">Degit</a>을 이용해 <a href="https://rollupjs.org/guide/en/" target="_blank" rel="noopener">Rollup.js</a> 기반의 새로운 프로젝트를 생성합니다.<br><a href="https://github.com/sveltejs/template" target="_blank" rel="noopener">sveltejs/template</a>에서 템플릿 구조를 확인할 수 있습니다.</p><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/ugZTg1_BvBo?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/XhNpjuG4s0w?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/Kyex5XlGzYM?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><pre><code class="bash">$ npx degit sveltejs/template PROJECT_NAME$ cd PROJECT_NAME$ npm install$ npm run dev</code></pre><p>설치 후 Svelte는 다음과 같은 구조를 가집니다.</p><p><img src="/images/screenshot/svelte/svelte-template-directory-structure.jpg" alt="Svelte template directory structure"></p><ul><li><code>/public/build</code>에는 Svelte가 수행한 컴파일 결과가 들어갑니다.</li><li><code>/src</code>는 모든 사용자 정의 Svelte 코드를 저장합니다.</li><li><code>rollup.config.js</code>은 <a href="https://github.com/rollup/rollup" target="_blank" rel="noopener">Rollup</a>이라는 <a href="https://webpack.js.org/" target="_blank" rel="noopener">Webpack</a>에 대응하는 자바스크립트용 모듈 번들러의 설정 파일입니다. 각 번들러의 차이점을 이해하고 싶다면 <a href="https://medium.com/js-imaginea/comparing-bundlers-webpack-rollup-parcel-f8f5dc609cfd" target="_blank" rel="noopener">Comparing bundlers: Webpack, Rollup &amp; Parcel</a>를 확인해 보세요.</li><li><a href="https://github.com/lukeed/sirv/tree/master/packages/sirv-cli" target="_blank" rel="noopener">sirv-cli</a> 모듈은 <code>sirv public</code>을 이용해 <a href="https://poiemaweb.com/js-spa" target="_blank" rel="noopener">SPA</a> 서버를 실행합니다.</li></ul><h4><span id="81c65259-85ba-4846-adb3-9c3be431acdf">main.js</span><a href="#81c65259-85ba-4846-adb3-9c3be431acdf" class="header-anchor"></a></h4><p><code>main.js</code>는 Svelte의 시작점입니다.<br>기본 구성을 <code>App.svelte</code>컴포넌트에서 가져오고 다음 2개 속성을 포함하는 생성자로 <code>App</code> 인스턴스를 생성합니다.</p><ul><li><code>target</code>은 <code>App.svelte</code>컴포넌트에서 생성된 HTML Output(출력)을 문서에 삽입하도록 지정합니다.</li></ul><div class="filename">src/main.js</div><pre><code class="js">import App from &#39;./App.svelte&#39;;const app = new App({  target: document.body});export default app;</code></pre><p>그리고 <code>main.js</code>를 <code>rollup.config.js</code>에서 진입점(Entry point)으로 설정합니다.</p><div class="filename">rollup.config.js</div><pre><code class="js">export default {  input: &#39;src/main.js&#39;,  // ...};</code></pre><p>Svelte에는 <a href="https://github.com/rollup/rollup-plugin-svelte" target="_blank" rel="noopener">Rollup을 위한 플러그인</a>뿐만 아니라 <a href="https://github.com/sveltejs/svelte-loader" target="_blank" rel="noopener">Webpack을 위한 Loader</a> 그리고 <a href="https://github.com/DeMoorJasper/parcel-plugin-svelte" target="_blank" rel="noopener">Parcel을 위한 플러그인</a>도 준비되어 있습니다.</p><h3><span id="867d02d4-a133-4a68-8de3-53c3fd693c5a">Snowpack template</span><a href="#867d02d4-a133-4a68-8de3-53c3fd693c5a" class="header-anchor"></a></h3><p><a href="https://github.com/ParkYoungWoong/svelte-snowpack-template" target="_blank" rel="noopener">Snowpack 기반의 Svelte 템플릿</a>을 만들었습니다.<br>별도의 구성 없이 바로 프로젝트를 시작할 수 있습니다.<br>템플릿에서 지원하는 내용은 크게 다음과 같습니다.</p><ul><li>Svelte</li><li>TypeScript</li><li>SCSS</li><li>Autoprefixer/PostCSS</li><li>Web test runner</li><li>Chai</li><li>Reset.css</li></ul><p>다음과 같이 설치합니다.</p><pre><code class="bash">## Install template$ npx degit ParkYoungWoong/svelte-snowpack-template DIR_NAME## Change directory$ cd DIR_NAME## Install dependencies$ npm i## Start dev server$ npm run dev</code></pre><p>타입스크립트를 사용하는 경우 다음과 같이 작성할 수 있습니다.</p><pre><code class="svelte">&lt;script lang=&quot;ts&quot;&gt;  let count: number = 0&lt;/script&gt;</code></pre><p>SCSS를 사용하는 경우 다음과 같이 작성할 수 있습니다.</p><pre><code class="svelte">&lt;style lang=&quot;scss&quot;&gt;  $color--primary: royalblue;  h1 {    color: $color--primary;  }&lt;/style&gt;</code></pre><h2><span id="040316bd-8413-43f9-bfd5-c926793a19bd">선언적 렌더링</span><a href="#040316bd-8413-43f9-bfd5-c926793a19bd" class="header-anchor"></a></h2><ul><li>보간법: 내용/속성/표현식 보간</li><li>반응성: 할당</li><li>클래스와 스타일: 클래스와 스타일 속성 바인딩</li><li>요소 바인딩: 입력 요소 바인딩(Properties, group) 패턴 정리</li><li>사용자 입력 핸들링: 인라인 이벤트 핸들러</li></ul><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/3a5BHWBHFGA?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p><a href="https://svelte.dev/repl/a7baafe9e79347abb4f96e185c034cc5?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let name = &#39;world&#39;  let age = 85  function assign() {    name = &#39;Heropy&#39;    age = 36  }&lt;/script&gt;&lt;h1&gt;Hello {name}!&lt;/h1&gt;&lt;h2 class={age &lt; 85 ? &#39;active&#39;: &#39;&#39;}&gt;  {age}&lt;/h2&gt;&lt;img   src=&quot;&quot;   alt={name} /&gt;&lt;input   type=&quot;text&quot;   bind:value={name} /&gt;&lt;button on:click={assign}&gt;  Assign&lt;/button&gt;&lt;style&gt;  h1 {    color: red;  }  .active {    color: blue;  }&lt;/style&gt;</code></pre><h2><span id="fe22c51d-56f6-4a22-857b-064b436ca7fe">조건문과 반복문</span><a href="#fe22c51d-56f6-4a22-857b-064b436ca7fe" class="header-anchor"></a></h2><ul><li>조건과 반복: 조건 블록 패턴 정리, 반복 블록 패턴 정리</li></ul><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/INI1x-mMDK0?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p><a href="https://svelte.dev/repl/18a4858a86f64f779f65bb4e52a03ad4?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">If.svelte</div><pre><code class="svelte">&lt;script&gt;  let name = &#39;world&#39;  let toggle = false&lt;/script&gt;&lt;button on:click={() =&gt; {toggle = !toggle}}&gt;  Toggle&lt;/button&gt;{#if toggle}  &lt;h1&gt;Hello {name}!&lt;/h1&gt;{:else}  &lt;div&gt;No name!&lt;/div&gt;{/if}</code></pre><div class="filename">Each.svelte</div><pre><code class="svelte">&lt;script&gt;  let name = &#39;Fruits&#39;  let fruits = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Cherry&#39;, &#39;Orange&#39;, &#39;Mango&#39;]  function deleteFruit() {    fruits = fruits.slice(1)  }&lt;/script&gt;&lt;h1&gt;Hello {name}!&lt;/h1&gt;&lt;ul&gt;  {#each fruits as fruit}    &lt;li&gt;{fruit}&lt;/li&gt;  {/each}&lt;/ul&gt;&lt;button on:click={deleteFruit}&gt;  Eat it!&lt;/button&gt;</code></pre><h2><span id="05af9371-b6a5-4b1f-a390-9813b4c3ff4b">이벤트 핸들링</span><a href="#05af9371-b6a5-4b1f-a390-9813b4c3ff4b" class="header-anchor"></a></h2><ul><li>사용자 입력 핸들링: 인라인 이벤트 핸들러, 다중 이벤트 핸들러</li></ul><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/7RK_Wri4qRI?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p><a href="https://svelte.dev/repl/a452ad7129a640189023590680934e13?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">EventHandling.svelte</div><pre><code class="svelte">&lt;script&gt;  let name = &#39;world&#39;  let isRed = false  function enter() {    name = &#39;enter&#39;  }  function leave() {    name = &#39;leave&#39;  }&lt;/script&gt;&lt;h1&gt;Hello {name}!&lt;/h1&gt;&lt;div  class=&quot;box&quot;  style=&quot;background-color: {isRed ? &#39;red&#39; : &#39;orange&#39;};&quot;  on:click={() =&gt; { isRed = !isRed }}  on:mouseenter={enter}  on:mouseleave={leave}&gt;  Box!&lt;/div&gt;&lt;style&gt;  .box {    width: 300px;    height: 150px;    background-color: orange;  }&lt;/style&gt;</code></pre><div class="filename">BindingInput.svelte</div><pre><code class="svelte">&lt;script&gt;  let text = &#39;&#39;&lt;/script&gt;&lt;h1&gt;  {text}&lt;/h1&gt;&lt;input   type=&quot;text&quot;   value={text}  on:input={e =&gt; {text = e.target.value}} /&gt;&lt;input   type=&quot;text&quot;  bind:value={text} /&gt;&lt;button on:click={() =&gt; {text = &#39;Heropy&#39;}}&gt;  Click&lt;/button&gt;</code></pre><h2><span id="dc0f5888-2dab-4de2-ab59-e8c283d14056">컴포넌트</span><a href="#dc0f5888-2dab-4de2-ab59-e8c283d14056" class="header-anchor"></a></h2><ul><li>반응성: 데이터의 불변성과 가변성</li><li>컴포넌트: 컴포넌트 개요(with 컴포넌트 바인딩), 부모에서 자식으로(Props)</li></ul><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/FRztfUuEvcM?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p><a href="https://svelte.dev/repl/a6bd6b5f01534d478df444f80b40ceb3?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Fruits from &#39;./Fruits.svelte&#39;  let fruits = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Cherry&#39;, &#39;Orange&#39;, &#39;Mango&#39;]&lt;/script&gt;&lt;Fruits {fruits} /&gt;&lt;Fruits {fruits} reverse /&gt;&lt;Fruits {fruits} slice=&quot;-2&quot; /&gt;&lt;Fruits {fruits} slice=&quot;0, 3&quot; /&gt;</code></pre><div class="filename">Fruits.svelte</div><pre><code class="svelte">&lt;script&gt;  // Props  export let fruits  export let reverse  export let slice  let computedFruits = []  let name = &#39;&#39;  if (reverse) {    computedFruits = [...fruits].reverse()    name = &#39;reverse&#39;  } else if (slice) {    computedFruits = fruits.slice(...slice.split(&#39;,&#39;))    name = `slice ${slice}`  } else {    computedFruits = fruits    }&lt;/script&gt;&lt;h2&gt;  Fruits {name}&lt;/h2&gt;&lt;ul&gt;  {#each computedFruits as fruit}    &lt;li&gt;{fruit}&lt;/li&gt;  {/each}&lt;/ul&gt;</code></pre><h2><span id="f977c27b-7c57-4d7b-b12e-a4618f076a8f">스토어</span><a href="#f977c27b-7c57-4d7b-b12e-a4618f076a8f" class="header-anchor"></a></h2><ul><li>스토어: 쓰기 가능 스토어(writable) &amp; 수동 구독과 자동 구독</li></ul><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/MIxp5RHEPSI?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p><a href="https://svelte.dev/repl/61d85b0d98924e31a68e304fc17982f8?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { storeName } from &#39;./store.js&#39;  import Parent from &#39;./Parent.svelte&#39;  let name = &#39;world&#39;  // let $hello = &#39;&#39; // Error!  $storeName = name  // console.log(storeName) // 스토어 객체  // console.log($storeName) // 스토어 값(데이터)&lt;/script&gt;&lt;h1&gt;Hello {name}!&lt;/h1&gt;&lt;Parent /&gt;</code></pre><div class="filename">Parent.svelte</div><pre><code class="svelte">&lt;script&gt;  import Child from &#39;./Child.svelte&#39;&lt;/script&gt;&lt;div&gt;  Parent&lt;/div&gt;&lt;Child /&gt;</code></pre><div class="filename">Child.svelte</div><pre><code class="svelte">&lt;script&gt;  import { storeName } from &#39;./store.js&#39;&lt;/script&gt;&lt;div&gt;  Child {$storeName}&lt;/div&gt;</code></pre><div class="filename">store.js</div><pre><code class="js">import { writable } from &#39;svelte/store&#39;export let storeName = writable(&#39;Heropy&#39;)</code></pre><h2><span id="7d74a6c9-b2c9-4099-bb2c-98825cb28956">Todo 예제 만들기</span><a href="#7d74a6c9-b2c9-4099-bb2c-98825cb28956" class="header-anchor"></a></h2><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/dFTu4-I0cdU?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p><a href="https://svelte.dev/repl/ef3dc9c2559847c0a8575712d25accc6?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { writable } from &#39;svelte/store&#39;  import Todo from &#39;./Todo.svelte&#39;  let title = &#39;&#39;  let todos = writable([])  let id = 0  function createTodo() {    if (!title.trim()) {      title = &#39;&#39;      return    }    $todos.push({      id,      title    })    $todos = $todos    title = &#39;&#39;    id += 1  }&lt;/script&gt;&lt;input   bind:value={title}  on:keydown={(e) =&gt; {e.key === &#39;Enter&#39; &amp;&amp; createTodo()}} /&gt;&lt;button on:click={createTodo}&gt;  Create Todo&lt;/button&gt;{#each $todos as todo}  &lt;Todo {todos} {todo} /&gt;{/each}</code></pre><div class="filename">Todo.svelte</div><pre><code class="svelte">&lt;script&gt;  export let todos // Store!  export let todo  let isEdit = false  let title = &#39;&#39;  function onEdit() {    isEdit = true    title = todo.title  }  function offEdit() {    isEdit = false  }  function updateTodo() {    todo.title = title    $todos = $todos    offEdit()  }  function deleteTodo() {    $todos = $todos.filter(t =&gt; t.id !== todo.id)  }&lt;/script&gt;{#if isEdit}  &lt;div&gt;    &lt;input        type=&quot;text&quot;        bind:value={title}       on:keydown={(e) =&gt; {e.key === &#39;Enter&#39; &amp;&amp; updateTodo()}} /&gt;      &lt;button on:click={updateTodo}&gt;OK&lt;/button&gt;    &lt;button on:click={offEdit}&gt;cancel&lt;/button&gt;  &lt;/div&gt;{:else}  &lt;div&gt;    &lt;span&gt;{todo.title}&lt;/span&gt;    &lt;button on:click={onEdit}&gt;Edit&lt;/button&gt;    &lt;button on:click={deleteTodo}&gt;Delete&lt;/button&gt;  &lt;/div&gt;{/if}</code></pre><h1><span id="6e854e14-6613-4192-a73d-7735b415fdd3">Svelte Core API</span><a href="#6e854e14-6613-4192-a73d-7735b415fdd3" class="header-anchor"></a></h1><p>앞선 ‘Svelte 시작하기’ 파트를 통해 기본적인 내용을 모두 학습하시고 다음으로 넘어가시는 것을 추천합니다.</p><h2><span id="a9827601-7a04-4609-8cfb-6cf41f5f023a">라이프 사이클</span><a href="#a9827601-7a04-4609-8cfb-6cf41f5f023a" class="header-anchor"></a></h2><p>Svelte에서는 다음과 같은 라이프 사이클을 제공합니다.</p><table><thead><tr><th>라이프 사이클</th><th>설명</th></tr></thead><tbody><tr><td>onMount</td><td>컴포넌트가 연결된 직후 콜백을 실행</td></tr><tr><td>onDestroy</td><td>컴포넌트가 연결 해제되기 직전 콜백을 실행</td></tr><tr><td>beforeUpdate</td><td>컴포넌트의 데이터가 업데이트되기 직전 콜백을 실행</td></tr><tr><td>afterUpdate</td><td>컴포넌트의 데이터가 업데이트된 직후 콜백을 실행</td></tr><tr><td>tick</td><td>변경된 데이터가 화면에 반영될 때까지 기다림</td></tr></tbody></table><h3><span id="114d1d75-2c62-46df-9050-8422de4ffea7">onMount, onDestroy, beforeUpdate, afterUpdate</span><a href="#114d1d75-2c62-46df-9050-8422de4ffea7" class="header-anchor"></a></h3><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/ZIdQ4lqKOp8?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/I5CZIdlmjoU?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/jUiSTCPzGDU?rel=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p><a href="https://svelte.dev/repl/28721203605a4ed7a398ef1955a8584d?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { beforeUpdate, afterUpdate, onMount, onDestroy } from &#39;svelte&#39;  import Something from &#39;./Something.svelte&#39;  let toggle = false  beforeUpdate(() =&gt; console.log(&#39;Before update!&#39;))  afterUpdate(() =&gt; console.log(&#39;After update!&#39;))  onMount(() =&gt; console.log(&#39;Mounted!&#39;))  onDestroy(() =&gt; console.log(&#39;Before Destroy!&#39;))&lt;/script&gt;&lt;button on:click={() =&gt; {toggle = !toggle}}&gt;  Toggle&lt;/button&gt;{#if toggle}  &lt;Something /&gt;{/if}</code></pre><div class="filename">Something.svelte</div><pre><code class="svelte">&lt;h1&gt;  Something&lt;/h1&gt;</code></pre><blockquote><p>공개 가능한 영상은 여기까지입니다.<br>더 많은 영상은 <a href="https://inf.run/6CVP" target="_blank" rel="noopener">인프런 강의 커리큘럼</a>을 확인하세요.</p></blockquote><h3><span id="5493318d-5c94-49d5-b09f-ff79527ffa0b">tick</span><a href="#5493318d-5c94-49d5-b09f-ff79527ffa0b" class="header-anchor"></a></h3><p>반응성 데이터의 변경이 곧 화면의 갱신을 의미하지는 않습니다.<br><code>tick</code>을 사용하면 데이터 변경 후 화면의 갱신까지 기다릴 수 있습니다.<br>단, 비동기로 처리해야 합니다.</p><p><a href="https://svelte.dev/repl/2a085acd47f547308fafbf463427e593?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { tick } from &#39;svelte&#39;  let name = &#39;world&#39;  async function handler() {    name = &#39;Heropy&#39;    await tick()    const h1 = document.querySelector(&#39;h1&#39;)    console.log(h1.innerText) // Hello Heropy!  }&lt;/script&gt;&lt;h1 on:click={handler}&gt;Hello {name}!&lt;/h1&gt;</code></pre><p><a href="https://svelte.dev/repl/acb036fc91794a15bb5cc70b4d3d1067?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { tick } from &#39;svelte&#39;  let isShow = false  let input  async function showInput() {    isShow = true    await tick() // Wait..    input &amp;&amp; input.focus()  }&lt;/script&gt;&lt;button on:click={showInput}&gt;Show input..&lt;/button&gt;{#if isShow}  &lt;input     bind:this={input}    type=&quot;text&quot; /&gt;{/if}</code></pre><h3><span id="88742375-8558-428c-b0ab-86e07436a87f">라이프 사이클 모듈화</span><a href="#88742375-8558-428c-b0ab-86e07436a87f" class="header-anchor"></a></h3><p>Svelte의 라이프 사이클은 컴포넌트 외부에서도 정의할 수 있기 때문에, 모듈로 만들 수 있습니다.</p><p><a href="https://svelte.dev/repl/1d3a95f322544ad6a9e2214d3b08743d?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { lifecycle, delayRender } from &#39;./lifecycle.js&#39;  import Something from &#39;./Something.svelte&#39;  let done = delayRender()  lifecycle()&lt;/script&gt;{#if $done}  &lt;h1&gt;Hello Lifecycle!&lt;/h1&gt;{/if}&lt;Something /&gt;</code></pre><div class="filename">Something.svelte</div><pre><code class="svelte">&lt;script&gt;  import { delayRender } from &#39;./lifecycle.js&#39;  let done = delayRender(1000)&lt;/script&gt;{#if $done}  &lt;h1&gt;Something...&lt;/h1&gt;{/if}</code></pre><div class="filename">lifecycle.js</div><pre><code class="js">import { onMount, onDestroy, beforeUpdate, afterUpdate } from &#39;svelte&#39;import { writable } from &#39;svelte/store&#39;export function lifecycle() {  onMount(() =&gt; {    console.log(&#39;Mounted!&#39;)  })  onDestroy(() =&gt; {    console.log(&#39;Before destroy!&#39;)  })  beforeUpdate(() =&gt; {    console.log(&#39;Before update!&#39;)  })  afterUpdate(() =&gt; {    console.log(&#39;After update!&#39;)  })}export function delayRender(delay = 3000) { // ms  let render = writable(false)  onMount(() =&gt; {    setTimeout(() =&gt; {      // $render = true      console.log(render) // set, update, subscribe      render.set(true)    }, delay)  })  return render}</code></pre><h2><span id="c6d9cf12-af57-4719-90ba-e8b9b85f3c6c">기본 보간</span><a href="#c6d9cf12-af57-4719-90ba-e8b9b85f3c6c" class="header-anchor"></a></h2><p><code>{ }</code>(중괄호)를 사용해 데이터를 속성/내용 등에 보간할 수 있습니다.</p><p><a href="https://svelte.dev/repl/9517da43874346719d77658c1d5d9f32?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let href = &#39;https://heropy.blog&#39;  let name = &#39;Heropy&#39;  let value = &#39;New input value!&#39;  let isUpperCase = false&lt;/script&gt;&lt;!-- 속성과 내용의 단방향 연결 --&gt;&lt;a {href}&gt;{name}&lt;/a&gt;&lt;!-- 입력 요소 양방향 연결 --&gt;&lt;input   {value}   on:input={e =&gt; value = e.target.value} /&gt;&lt;!-- bind 지시어로 양방향 연결 --&gt;&lt;input bind:value /&gt;&lt;!-- 표현식 --&gt;&lt;div&gt;{isUpperCase ? &#39;DIV&#39; : &#39;div&#39;}&lt;/div&gt;</code></pre><h3><span id="6e7bb879-72a4-4adf-aa22-77f94a6e6531">원시 HTML</span><a href="#6e7bb879-72a4-4adf-aa22-77f94a6e6531" class="header-anchor"></a></h3><p>기본 보간법은 데이터를 HTML이 아닌 일반 텍스트로 해석합니다.<br>실제 HTML을 출력하려면 <code>{@html}</code>를 사용해야 합니다.<br>단, 이는 <a href="https://en.wikipedia.org/wiki/Cross-site_scripting" target="_blank" rel="noopener">XSS 취약점</a>으로 이어질 수 있기 때문에, 신뢰할 수 있는 콘텐츠에서만 사용하세요!</p><p><a href="https://svelte.dev/repl/64013cecb696408bbf3d1c3b42274d4a?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let h1 = &#39;&lt;h1&gt;Hello Heropy&lt;/h1&gt;&#39;  let xss = &#39;&lt;iframe onload=&quot;alert(123)&quot;&gt;&lt;/iframe&gt;&#39;&lt;/script&gt;{@html h1}{@html xss}</code></pre><h3><span id="aee65783-0c7f-4505-b2e7-2bc1e57ea72f">디버그</span><a href="#aee65783-0c7f-4505-b2e7-2bc1e57ea72f" class="header-anchor"></a></h3><p>데이터가 변경되면 이를 감지해 로그를 작성합니다.<br>개발자 도구가 열려있는 경우엔 프로세스를 일시정시합니다.<br>HTML 구조에서 데이터 변경 감지를 작성할 때 유용하겠지만,<br>개인적으로 자주 사용하고 있진 않습니다.</p><p><a href="https://svelte.dev/repl/9b819dbcaf0e4f21b48f49a0b05c939a?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let name = &#39;Heropy&#39;  let index = 0&lt;/script&gt;{@debug index, name}&lt;h1 on:click={() =&gt; {index += 1}}&gt;  Hello {name}!&lt;/h1&gt;</code></pre><h2><span id="33287fcc-e192-46e3-816a-6555116fa659">반응성</span><a href="#33287fcc-e192-46e3-816a-6555116fa659" class="header-anchor"></a></h2><h3><span id="8a23c17a-a04c-40ef-8d77-e7722f808656">할당</span><a href="#8a23c17a-a04c-40ef-8d77-e7722f808656" class="header-anchor"></a></h3><p>Svelte에서 반응성 갱신하려면 할당 연산자(<code>=</code>)를 사용해야 합니다!<br><code>.push</code>나 <code>.splice</code> 같은 메소드 사용은 반응성을 갱신할 수 없습니다.</p><p>다음 예제에서 <code>user.numbers.push(3)</code>은 반응성이 갱신됩니다.<br>그러나 <code>assign</code> 함수에서 <code>user.name = &#39;Neo&#39;</code>과 <code>user.depth.a = &#39;c&#39;</code>를 제거하면 반응성은 갱신되지 않습니다.<br>이는 <code>user.name = &#39;Neo&#39;</code>과 <code>user.depth.a = &#39;c&#39;</code>가 동작하면서 각자 <code>user</code> 객체를 재할당하기 때문입니다.</p><blockquote><p>주석 처리된 <code>$$invalidate</code> 함수 실행을 확인하세요!<br>실제 <code>$$invalidate</code> 함수는 컴파일 결과에서 확인할 수 있습니다.</p></blockquote><p><a href="https://svelte.dev/repl/a19796ee05bc43a7b2aeb284ad01e00e?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let name = &#39;Heropy&#39;  let fruits = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Cherry&#39;]  let user = {     name: &#39;Heropy&#39;,     age: 85,     depth: {       a: &#39;b&#39;     },     numbers: [1, 2]   }  let numbers = user.numbers  let hello = &#39;world&#39;  function assign() {    name = &#39;Neo&#39;    fruits.push(&#39;Orange&#39;) // [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Cherry&#39;, &#39;Orange&#39;]    fruits = fruits    user.name = &#39;Neo&#39; // $$invalidate(2, user.name = &quot;Neo&quot;, user);    user.depth.a = &#39;c&#39; // $$invalidate(2, user.depth.a = &quot;c&quot;, user);    user.numbers.push(3)    numbers = numbers  }&lt;/script&gt;&lt;button on:click={assign}&gt;Assign!&lt;/button&gt;&lt;h1&gt;name: {name}&lt;/h1&gt;&lt;h2&gt;fruits: {fruits}&lt;/h2&gt;&lt;h2&gt;user name: {user.name} / {user.age}&lt;/h2&gt;&lt;h2&gt;user depth: {user.depth.a}&lt;/h2&gt;&lt;h2&gt;user numbers: {user.numbers}&lt;/h2&gt;&lt;h2&gt;numbers: {numbers}&lt;/h2&gt;&lt;h2&gt;{hello}&lt;/h2&gt;</code></pre><h3><span id="fb64cfe9-76f8-4f23-bdfd-1dd8634f2b64">반응성 구문</span><a href="#fb64cfe9-76f8-4f23-bdfd-1dd8634f2b64" class="header-anchor"></a></h3><p><code>$:</code>은 Label 식별자(Identifier)가 <code>$</code>인 순수한 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label" target="_blank" rel="noopener">자바스크립트 Label 구문</a>이며,<br>Svelte는 이 구문에 <strong>특별한 의미를 부여하고 반응성을 자동으로 계측</strong>합니다.<br><code>let</code> 선언을 사용하지 않는 것에 주의합시다.</p><p>데이터 변경이 아닌 반응성을 계측하는 것이기 때문에, 데이터의 변경이 즉각 반영되지 않습니다!<br>따라서 다음 예제와 같이 <code>tick</code> 라이프 사이클을 사용해 반응성을 기다릴 수 있습니다.</p><p><a href="https://svelte.dev/repl/2218611e49744987acec52df4ef3303f?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { tick } from &#39;svelte&#39;  let count = 0  $: double = count * 2  async function assign() {     count += 1    console.time(&#39;timer&#39;)    await tick() // Wait for reactivity..    console.timeEnd(&#39;timer&#39;) // 0.1~0.5ms    console.log(double)  }&lt;/script&gt;&lt;button on:click={assign}&gt;Assign!&lt;/button&gt;&lt;h2&gt;{count}&lt;/h2&gt;&lt;h2&gt;{double}&lt;/h2&gt;</code></pre><h4><span id="51d9cdf1-ea7e-4bbc-9fb0-9f74c3983792">사용 패턴</span><a href="#51d9cdf1-ea7e-4bbc-9fb0-9f74c3983792" class="header-anchor"></a></h4><p>다음 예제는 REPL에서 개발자 도구 콘솔을 꼭 확인해 보세요!</p><p><a href="https://svelte.dev/repl/f56ebc00bd2a49fbaae4943d036ed511?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;!-- 콘솔을 확인해 보세요! --&gt;&lt;script&gt;  let count = 0  // 선언  $: double = count * 2  // 블록  $: {    console.log(count)    console.log(double)  }  // 함수 실행  $: count, log()    // 즉시 실행 함수(IIFE)  $: count, (() =&gt; {     console.log(&#39;iife: Heropy&#39;)   })();  // 조건문(If)  $: if (count &gt; 0) {    console.log(&#39;if:&#39;, double)  }  // 반복문(For)  $: for (let i = 0; i &lt; 3; i += 1) {    count    console.log(&#39;for:&#39;, i)  }  // 조건문(Switch)  $: switch (count) {    case 1:      console.log(&#39;switch: 1&#39;)      break    default:      console.log(&#39;switch: default&#39;)  }  // 유효범위  $: {    function scope1() {      console.log(&#39;scope1&#39;)      function scope2() {        console.log(&#39;scope2&#39;)        function scope3() {          console.log(&#39;scope3&#39;, count)        }        scope3()      }      scope2()    }    scope1()  }  function log() {    console.log(&#39;fn: Heropy!&#39;)  }  function assign() {    count += 1  }&lt;/script&gt;&lt;button on:click={assign}&gt;Assign!&lt;/button&gt;</code></pre><h2><span id="e3fed7c9-cc4a-40ff-b11f-ee87f0649e52">클래스와 스타일</span><a href="#e3fed7c9-cc4a-40ff-b11f-ee87f0649e52" class="header-anchor"></a></h2><h3><span id="bdc250f7-7df5-48fc-94ed-2bbb8ed36173">속성 바인딩</span><a href="#bdc250f7-7df5-48fc-94ed-2bbb8ed36173" class="header-anchor"></a></h3><p>클래스와 스타일은 모두 기본 보간을 통해 데이터를 연결할 수 있습니다.<br>추가로 클래스는 <code>class</code> 지시어(Directive)를 제공하는데,<br>이를 통해 좀 더 단순한 문법으로 작성할 수 있습니다.</p><p><a href="https://svelte.dev/repl/90ae426e584a44069d9519ed7ac67ac4?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let active = false  let color = &#39;tomato&#39;  let white = &#39;white&#39;  let letterSpacing = &#39;letter-spacing: 5px;&#39;&lt;/script&gt;&lt;button on:click={() =&gt; {active = !active}}&gt;  Toggle!&lt;/button&gt;&lt;!-- &lt;div class={active ? &#39;active&#39; : &#39;&#39;}&gt; --&gt;&lt;div class:active={active}&gt;  Hello&lt;/div&gt;&lt;h2 style=&quot;  background-color: {color};   color: {white};  {letterSpacing}&quot;&gt;  Heropy!&lt;/h2&gt;&lt;style&gt;  div {    width: 120px;    height: 200px;    background: royalblue;    border-radius: 10px;    display: flex;    justify-content: center;    align-items: center;    color: white;    font-size: 20px;    transition: .4s;  }  .active {    width: 250px;    background: tomato;    }&lt;/style&gt;</code></pre><h4><span id="53f58ee6-61f9-46b5-990e-0223ab1b1441">사용 패턴</span><a href="#53f58ee6-61f9-46b5-990e-0223ab1b1441" class="header-anchor"></a></h4><p><a href="https://svelte.dev/repl/9769c0776d6945b38eb45ac8abfb715f?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let active = true  let valid = false  let camelCase = true  let color = {    white: &#39;#FFF&#39;,    red: &#39;#FF0000&#39;  }  let bold = &#39;font-weight: bold;&#39;  function multiClass() {    return &#39;active valid camel-case&#39;  }&lt;/script&gt;&lt;div class={active ? &#39;active&#39; : &#39;&#39;}&gt;  3항 연산자 보간&lt;/div&gt;&lt;div class:active={active}&gt;  Class 지시어(Directive) 바인딩&lt;/div&gt;&lt;div class:active&gt;  Class 지시어 바인딩 단축 형태&lt;/div&gt;&lt;div   class:active  class:valid  class:camelCase  class:camel-case={camelCase}&gt;  다중 Class 지시어 바인딩&lt;/div&gt;&lt;div class={multiClass()}&gt;  함수 실행&lt;/div&gt;&lt;div   class=&quot;style-binding&quot;  style=&quot;    color: {color.white};     background-color: {color.red};     {bold}&quot;&gt;  스타일 바인딩&lt;/div&gt;</code></pre><h3><span id="f0eebf0a-d21a-4c68-809b-5d7a923f6d0b">스타일 유효범위와 전역화</span><a href="#f0eebf0a-d21a-4c68-809b-5d7a923f6d0b" class="header-anchor"></a></h3><p>Svelte 컴포넌트에서 작성하는 스타일을 더 쉽고 안전하게 관리할 수 있는 편리한 방법을 제공합니다.<br><code>&lt;style&gt;</code>에 선언된 CSS는 기본적으로 해당 컴포넌트의 유효범위(Scoped)를 가집니다.<br>요소의 <code>class</code>속성에 Svelte-Hash가 추가됩니다.</p><blockquote><p>입문자 시각에선 스타일 유효범위가 더 복잡한 관리 방식으로 보일 수 있지만,<br>애플리케이션 규모가 늘어나는 경우, 전역 스타일로 인해 심각하고 복잡한 스타일 충돌이 일어날 수 있습니다!</p></blockquote><p><img src="/images/screenshot/svelte/svelte-style-class-hash-for-element.jpg" alt="Svelte style hash"></p><div class="image-caption">Class 속성에 추가된 Svelte-Hash</div><p><img src="/images/screenshot/svelte/svelte-style-class-hash-for-style.jpg" alt="Svelte style hash"></p><div class="image-caption">Svelte-Hash가 적용된 선택자</div><pre><code class="svelte">&lt;style&gt;  ul.container li.item {    width: 100px;  }&lt;/style&gt;</code></pre><p><img src="/images/screenshot/svelte/svelte-style-scoped.jpg" alt="Svelte scoped"></p><div class="image-caption">일반 선택자 사용</div><p>만약 유효범위 없이 선언하려면 <code>:global(선택자)</code> 수정자(Modifier)를 사용할 수 있습니다.</p><pre><code class="svelte">&lt;style&gt;  :global(ul.container li.item) {    width: 100px;  }&lt;/style&gt;</code></pre><p><img src="/images/screenshot/svelte/svelte-style-no-scoped.jpg" alt="Svelte no scoped"></p><div class="image-caption">:global() 사용</div><h3><span id="2b0ec369-c8f8-4c00-becf-0c15a9216283">@keyframes 전역화</span><a href="#2b0ec369-c8f8-4c00-becf-0c15a9216283" class="header-anchor"></a></h3><p>컴포넌트에서 정의한 Keyframes 규칙 또한 Svelte-Hash가 적용됩니다.<br>규칙 이름 앞에 <code>-global-</code> 수식어를 작성해 Keyframes 규칙을 전역화할 수 있습니다.</p><p><a href="https://svelte.dev/repl/88c30c43aa49483199748090dc662f31?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;style&gt;  :global(body) {    padding: 50px;    }  .box {    width: 100px;Class &amp; Style Binding: Pattern    height: 100px;    background: tomato;    animation: zoom .4s infinite alternate;  }  /* -global- */  @keyframes -global-zoom {    0% {      transform: scale(1);    }    100% {      transform: scale(1.5);    }  }&lt;/style&gt;</code></pre><h2><span id="10df2e83-5b48-477e-9414-a840f53903cf">요소 바인딩</span><a href="#10df2e83-5b48-477e-9414-a840f53903cf" class="header-anchor"></a></h2><h3><span id="3d626d56-74b6-41b1-8b67-48489b661641">일반 요소</span><a href="#3d626d56-74b6-41b1-8b67-48489b661641" class="header-anchor"></a></h3><p>DOM에서 검색하지 않아도 <code>bind:this</code>를 통해 바로 요소를 참조할 수 있습니다.</p><p>화면에 없던 요소를 데이터를 통해 출력하고 참조하기 위해,<br>데이터가 변경되고 화면이 갱신될 때까지 기다리도록 <code>tick</code> 라이플 사이클을 사용할 수 있습니다.</p><p><a href="https://svelte.dev/repl/76a65cdbd14f40a5818ab327c7b3105b?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { tick, onMount } from &#39;svelte&#39;  let isShow = false  let inputEl  async function toggle() {    isShow = !isShow    await tick()    // const inputEl = document.querySelector(&#39;input&#39;)    console.log(inputEl)    inputEl &amp;&amp; inputEl.focus()  }&lt;/script&gt;&lt;button on:click={toggle}&gt;Edit!&lt;/button&gt;{#if isShow}  &lt;input bind:this={inputEl} /&gt;{/if}</code></pre><h3><span id="3be528f0-343c-46fe-bcaf-d817ec786c73">입력 요소</span><a href="#3be528f0-343c-46fe-bcaf-d817ec786c73" class="header-anchor"></a></h3><p>입력 요소는 기본적으로 <code>value</code> 속성을 통해 데이터를 연결(바인딩)하며, 많은 경우 양방향 데이터 연결을 위해 <code>bind</code> 지시어를 사용합니다.(<code>bind:value</code>)<br>Svelte에서는 ‘checkbox’ 타입을 위해 <code>bind:checked</code>를,<br>‘radio’ 타입 등의 여러 입력 요소를 위해 <code>bind:group</code>을 제공합니다.<br>아래 예제를 통해 다양한 사용 패턴을 확인하세요!</p><p><a href="https://svelte.dev/repl/3aca814fe08e49f08a42a7db3baed19a?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let text = &#39;&#39;  let number = 3  let checked = false  let fruits = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Cherry&#39;]  let selectedFruits = []  let group = &#39;Banana&#39;  let textarea = &#39;&#39;  let select = &#39;Banana&#39;  let multipleSelect = [&#39;Banana&#39;, &#39;Cherry&#39;]&lt;/script&gt;&lt;!-- let text = &#39;&#39; --&gt;&lt;section&gt;  &lt;h2&gt;Text&lt;/h2&gt;  &lt;input type=&quot;text&quot; bind:value={text} /&gt;&lt;/section&gt;&lt;!-- let number = 3 --&gt;&lt;section&gt;  &lt;h2&gt;Number/Range&lt;/h2&gt;  &lt;div&gt;    &lt;input type=&quot;number&quot; bind:value={number} min=&quot;0&quot; max=&quot;10&quot; /&gt;  &lt;/div&gt;  &lt;div&gt;    &lt;input type=&quot;range&quot; bind:value={number} min=&quot;0&quot; max=&quot;10&quot; /&gt;  &lt;/div&gt;&lt;/section&gt;&lt;!-- let checked = false --&gt;&lt;section&gt;  &lt;h2&gt;Checkbox&lt;/h2&gt;  &lt;input type=&quot;checkbox&quot; bind:checked={checked} /&gt; Agree?  &lt;label&gt;    &lt;input type=&quot;checkbox&quot; bind:checked={checked} /&gt; Agree?(label wrapping)  &lt;/label&gt;&lt;/section&gt;&lt;!-- let fruits = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Cherry&#39;] --&gt;&lt;!-- let selectedFruits = [] --&gt;&lt;section&gt;  &lt;h2&gt;Checkbox 다중 선택&lt;/h2&gt;  &lt;strong&gt;Selected: {selectedFruits}&lt;/strong&gt;  {#each fruits as fruit}    &lt;label&gt;      &lt;input type=&quot;checkbox&quot; value={fruit} bind:group={selectedFruits} /&gt;      {fruit}    &lt;/label&gt;  {/each}&lt;/section&gt;&lt;!-- let group = &#39;Banana&#39; --&gt;&lt;section&gt;  &lt;h2&gt;Radio&lt;/h2&gt;  &lt;!--  &lt;input type=&quot;radio&quot; value=&quot;Apple&quot; name=&quot;my radio&quot; /&gt;  &lt;input type=&quot;radio&quot; value=&quot;Banana&quot; name=&quot;my radio&quot; /&gt;  &lt;input type=&quot;radio&quot; value=&quot;Cherry&quot; name=&quot;my radio&quot; /&gt;  --&gt;  &lt;strong&gt;Selected: {group}&lt;/strong&gt;  &lt;label&gt;    &lt;input type=&quot;radio&quot; value=&quot;Apple&quot; bind:group={group} /&gt; Apple  &lt;/label&gt;  &lt;label&gt;    &lt;input type=&quot;radio&quot; value=&quot;Banana&quot; bind:group={group} /&gt; Banana  &lt;/label&gt;  &lt;label&gt;    &lt;input type=&quot;radio&quot; value=&quot;Cherry&quot; bind:group={group} /&gt; Cherry  &lt;/label&gt;&lt;/section&gt;&lt;!-- let textarea = &#39;&#39; --&gt;&lt;section&gt;  &lt;h2&gt;Textarea&lt;/h2&gt;  &lt;pre&gt;{textarea}&lt;/pre&gt;  &lt;textarea bind:value={textarea} /&gt;&lt;/section&gt;&lt;!-- let select = &#39;Banana&#39; --&gt;&lt;section&gt;  &lt;h2&gt;Select 단일 선택&lt;/h2&gt;  &lt;strong&gt;Seleced: {select}&lt;/strong&gt;  &lt;div&gt;    &lt;select bind:value={select}&gt;      &lt;option disabled value=&quot;&quot;&gt;Please select one!&lt;/option&gt;      &lt;option&gt;Apple&lt;/option&gt;      &lt;option&gt;Banana&lt;/option&gt;      &lt;option&gt;Cherry&lt;/option&gt;    &lt;/select&gt;  &lt;/div&gt;&lt;/section&gt;&lt;!-- let multipleSelect = [&#39;Banana&#39;, &#39;Cherry&#39;] --&gt;&lt;section&gt;  &lt;h2&gt;Select 다중 선택(Multiple)&lt;/h2&gt;  &lt;strong&gt;Seleced: {multipleSelect}&lt;/strong&gt;  &lt;div&gt;    &lt;select multiple bind:value={multipleSelect}&gt;      &lt;option disabled value=&quot;&quot;&gt;Please select one!&lt;/option&gt;      &lt;option&gt;Apple&lt;/option&gt;      &lt;option&gt;Banana&lt;/option&gt;      &lt;option&gt;Cherry&lt;/option&gt;    &lt;/select&gt;  &lt;/div&gt;&lt;/section&gt;</code></pre><h3><span id="5b97fc02-0061-4324-884b-3eafdd466599">편집 가능 요소</span><a href="#5b97fc02-0061-4324-884b-3eafdd466599" class="header-anchor"></a></h3><p>Svelte는 <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/contenteditable" target="_blank" rel="noopener">Contenteditable(내용 수정이 가능한)</a> 요소에 연결할 수 있는, <code>innerHTML</code>과 <code>textContent</code> 속성을 제공합니다.<br>Contenteditable 요소도 하나의 입력 요소이기 때문에 <code>bind</code> 지시어를 통해서 양방향으로 데이터를 연결합니다.</p><p><a href="https://svelte.dev/repl/6a5a459c443844a3a15d16b33076396b?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let innerHTML = &#39;&#39;  let textContent = &#39;Hello world!&#39;&lt;/script&gt;&lt;div   contenteditable   bind:innerHTML  bind:textContent&gt;  Hello world!&lt;/div&gt;&lt;div&gt;{innerHTML}&lt;/div&gt;&lt;div&gt;{textContent}&lt;/div&gt;&lt;div&gt;{@html innerHTML}&lt;/div&gt;&lt;style&gt;  div {    border: 1px solid red;    margin-bottom: 10px;  }&lt;/style&gt;</code></pre><h2><span id="6b515578-38e0-4555-ab0c-ef7e854d37ec">조건 블록</span><a href="#6b515578-38e0-4555-ab0c-ef7e854d37ec" class="header-anchor"></a></h2><p>If 조건에 따라 블록을 렌더링합니다.<br>조건이 true를 반환할 때만 렌더링 됩니다.</p><p>기본 형태는 다음과 같습니다.</p><pre><code class="svelte">&lt;script&gt;  let age = 90&lt;/script&gt;{#if age &gt; 70}  &lt;div&gt;The old man!&lt;/div&gt;{/if}</code></pre><h3><span id="cdb48251-caac-4faa-baf7-0463b6fd819f">사용 패턴</span><a href="#cdb48251-caac-4faa-baf7-0463b6fd819f" class="header-anchor"></a></h3><p>시작 블록은 <code>#</code>을,<br>중간 블록은 <code>:</code>을,<br>종료 블록은 <code>/</code>를 사용합니다.</p><p><a href="https://svelte.dev/repl/7d2cffd5f49a4b279cfe720c96f51798?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let count = 0&lt;/script&gt;&lt;button on:click={() =&gt; { count += 1}}&gt;증가!&lt;/button&gt;&lt;button on:click={() =&gt; { count -= 1}}&gt;감소!&lt;/button&gt;&lt;h2&gt;{count}&lt;/h2&gt;&lt;section&gt;  &lt;h2&gt;if&lt;/h2&gt;  {#if count &gt; 3}    &lt;div&gt;count &amp;gt; 3&lt;/div&gt;  {/if}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;if else&lt;/h2&gt;  {#if count &gt; 3}     &lt;div&gt;count &amp;gt; 3&lt;/div&gt;  {:else}    &lt;div&gt;count &amp;lt;= 3&lt;/div&gt;  {/if}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;if else if&lt;/h2&gt;  {#if count &gt; 3}    &lt;div&gt;count &amp;gt; 3&lt;/div&gt;  {:else if count === 3}    &lt;div&gt;count === 3&lt;/div&gt;  {:else}    &lt;div&gt;count &amp;lt; 3&lt;/div&gt;  {/if}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;다중 블록&lt;/h2&gt;  {#if count &gt; 3}    {#if count === 5}      count === 5    {:else}      count &amp;gt; 3    {/if}  {/if}&lt;/section&gt;&lt;style&gt;  section {    border: 1px solid orange;    margin-bottom: 10px;    padding: 10px;  }  h2 {    margin: 0;    margin-bottom: 10px;  }&lt;/style&gt;</code></pre><h2><span id="6f993a12-4d52-40b6-a672-67c3e198d803">반복 블록</span><a href="#6f993a12-4d52-40b6-a672-67c3e198d803" class="header-anchor"></a></h2><p>Each 반복 블록은 배열 데이터를 기반으로 렌더링합니다.</p><p>기본 형태는 다음과 같습니다.</p><pre><code class="svelte">&lt;script&gt;  let fruits = [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Cherry&#39;]&lt;/script&gt;{#each fruits as fruit}  &lt;div&gt;{fruit}&lt;/div&gt;{/each}</code></pre><h3><span id="177a4d2c-5b18-4d61-82ad-d8feada43cbb">key</span><a href="#177a4d2c-5b18-4d61-82ad-d8feada43cbb" class="header-anchor"></a></h3><p>Svelte는 할당을 통해 반응성을 갱신하므로 반복 데이터 자체가 갱신되면 목록 전체가 다시 렌더링 됩니다.<br>이떄 Svelte가 변경되지 않은 데이터의 항목을 다시 렌더링하지 않도록 식별 가능한 고유 Key를 제공하는 것이 중요합니다.<br>Key는 고유해야 합니다!</p><blockquote><p>많은 경우 반복 데이터 각 항목의 <code>id</code> 속성을 사용합니다.</p></blockquote><p>사용할 데이터 구조가 Key로 사용할 고유한 값을 가지도록 설계하는 것이 좋습니다.<br>다음 예제의 출력 이름에 <code>Apple</code>이 중복되고 있지만,<br><code>id</code> 속성으로 식별 가능한 고유 Key를 제공했기 때문에 문제없이 동작합니다.</p><p><a href="https://svelte.dev/repl/552c012ffff549c3ba4a136f5dcc08db?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let fruits = [    { id: &#39;1&#39;, name: &#39;Apple&#39; },    { id: &#39;2&#39;, name: &#39;Banana&#39; },    { id: &#39;3&#39;, name: &#39;Cherry&#39; },    { id: &#39;4&#39;, name: &#39;Apple&#39; }  ]  function deleteFirst() {    fruits = fruits.slice(1) // [&#39;Banana&#39;, &#39;Cherry&#39;, &#39;Orange&#39;]  }&lt;/script&gt;&lt;button on:click={deleteFirst}&gt;  Delete first fruit!&lt;/button&gt;&lt;ul&gt;  {#each fruits as fruit (fruit.id)}    &lt;li&gt;{fruit.name}&lt;/li&gt;  {/each}&lt;/ul&gt;</code></pre><h3><span id="a0cf5f22-80aa-40aa-9d56-7fb8f3c48ba5">사용 패턴</span><a href="#a0cf5f22-80aa-40aa-9d56-7fb8f3c48ba5" class="header-anchor"></a></h3><p>시작 블록은 <code>#</code>을,<br>중간 블록은 <code>:</code>을,<br>종료 블록은 <code>/</code>를 사용합니다.</p><p><a href="https://svelte.dev/repl/b1f53749f8014e9c82fb8ea7d5d26825?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let fruits = [    { id: 1, name: &#39;Apple&#39; },    { id: 2, name: &#39;Banana&#39; },    { id: 3, name: &#39;Cherry&#39; },    { id: 4, name: &#39;Apple&#39; },    { id: 5, name: &#39;Orange&#39; }  ]  let todos = []  let fruits2D = [    [1, &#39;Apple&#39;],     [2, &#39;Banana&#39;],     [3, &#39;Cherry&#39;],     [4, &#39;Orange&#39;]  ]  let user = {    name: &#39;Heropy&#39;,    age: 85,    email: &#39;thesecon@gmail.com&#39;  }&lt;/script&gt;&lt;section&gt;  &lt;h2&gt;기본&lt;/h2&gt;  &lt;!-- {#each 배열 as 속성} {/each} --&gt;  {#each fruits as fruit}    &lt;div&gt;{fruit.name}&lt;/div&gt;  {/each}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;순서(index)&lt;/h2&gt;  &lt;!-- {#each 배열 as 속성, 순서} {/each} --&gt;  {#each fruits as fruit, index}    &lt;div&gt;{index} / {fruit.name}&lt;/div&gt;  {/each}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;아이템 고유화(key)&lt;/h2&gt;  &lt;!-- {#each 배열 as 속성, 순서 (키)} {/each} --&gt;  {#each fruits as fruit, index (fruit.id)}    &lt;div&gt;{index} / {fruit.name}&lt;/div&gt;  {/each}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;빈 배열 처리(else)&lt;/h2&gt;  &lt;!-- {#each} {:else} {/each} --&gt;  {#each todos as todo (todo.id)}    &lt;div&gt;{todo.name}&lt;/div&gt;  {:else}    &lt;div&gt;아이템이 없어요!&lt;/div&gt;  {/each}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;구조 분해(destructuring)&lt;/h2&gt;  &lt;!-- {#each 배열 as {id, name}} {/each} --&gt;  {#each fruits as {id, name} (id)}    &lt;div&gt;{name}&lt;/div&gt;  {/each}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;2차원 배열&lt;/h2&gt;  &lt;!-- {#each 배열 as [id, name]} {/each} --&gt;  {#each fruits2D as [id, name] (id)}    &lt;div&gt;{name}&lt;/div&gt;  {/each}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;나머지 연산자(rest)&lt;/h2&gt;  &lt;!-- {#each 배열 as {id, ...rest}} {/each} --&gt;  {#each fruits as {id, ...rest} (id)}    &lt;div&gt;{rest.name}&lt;/div&gt;  {/each}&lt;/section&gt;&lt;section&gt;  &lt;h2&gt;객체 데이터&lt;/h2&gt;  {#each Object.entries(user) as [key, value] (key)}    &lt;div&gt;{key}: {value}&lt;/div&gt;  {/each}&lt;/section&gt;&lt;style&gt;  section {    border: 1px solid orange;    margin-bottom: 10px;    padding: 10px;  }  h2 {    margin: 0;  }&lt;/style&gt;</code></pre><h2><span id="05486b13-90ab-4f2d-8c8e-4c1b45497dd5">키 블록</span><a href="#05486b13-90ab-4f2d-8c8e-4c1b45497dd5" class="header-anchor"></a></h2><p>키 블록은 연결된 데이터가 변경되면 블록 안의 내용을 화면에 다시 렌더링합니다.<br>블록 안에서 Svelte 컴포넌트를 사용하는 경우,<br>컴포넌트가 다시 초기화되고 연결(Mount)됩니다.</p><p><a href="https://svelte.dev/repl/454f0523049045e39dd647bac3c5fe05?version=3.31.0" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Count from &#39;./Count.svelte&#39;  let reset = false&lt;/script&gt;{#key reset}  &lt;Count /&gt;{/key}&lt;button on:click={() =&gt; reset = !reset}&gt;  Reset!&lt;/button&gt;</code></pre><div class="filename">Count.svelte</div><pre><code class="svelte">&lt;script&gt;  let count = 0  setInterval(() =&gt; {    count += 1  }, 1000)&lt;/script&gt;&lt;h1&gt;  {count}&lt;/h1&gt;</code></pre><h2><span id="c016884c-3bb2-4ddb-b8e5-b843862c91b9">비동기 블록</span><a href="#c016884c-3bb2-4ddb-b8e5-b843862c91b9" class="header-anchor"></a></h2><p>Await 비동기 블록은 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" rel="noopener">Promise 객체</a>를 사용해서 비동기 코드를 다음 상태로 분기할 수 있습니다.</p><ul><li>대기(pending): ‘이행’하거나 ‘거부’되지 않은 초기 상태.</li><li>이행(fulfilled): 연산이 성공적으로 완료됨.</li><li>거부(rejected): 연산이 실패함.</li></ul><p>다음의 간단한 영화 검색 예제를 테스트하기 위해 <a href="http://www.omdbapi.com/" target="_blank" rel="noopener">OMDb API</a>에서 API키를 무료로 발급받으세요.</p><p><a href="https://svelte.dev/repl/6f025fe7d28441c9a8267a6be38ea8f9?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;    // Svelte REPL에서는 npm install을 사용할 수 없어요...  // 자바스크립트 fetch 함수를 사용해도 되지만, 사용법이 일부 다릅니다.  // VS Code에서는 Axios 모듈을 다음과 같이 설치하세요!!  // npm i -D axios  // import axios from &#39;axios&#39;  import axios from &#39;https://unpkg.com/axios/dist/axios.min.js&#39;  // http://www.omdbapi.com/apikey.aspx에서 API키를 무료로 발급 받을 수 있습니다.  // 발급 받은 API키를 입력하고 테스트해 보세요!  // 무료 API는 하루 1000개 데이터 제한이 있어요.  let apikey = &#39;ENTER_YOUR_API_KEY&#39;  let title = &#39;&#39;  // let promise = new Promise(resolve =&gt; resolve([]))  let promise = Promise.resolve([])  function searchMovies() {    return new Promise(async (resolve, reject) =&gt; {      try {        const res = await axios(`https://www.omdbapi.com/?apikey=${apikey}&amp;s=${title}`)        console.log(res)        resolve(res.data.Search)      } catch (err) {        console.log(err)        reject(err)      } finally {        console.log(&#39;Done!&#39;)      }    })  }&lt;/script&gt;&lt;input bind:value={title} /&gt;&lt;button on:click={() =&gt; promise = searchMovies()}&gt;검색!&lt;/button&gt;{#await promise}  &lt;!-- pending(대기) --&gt;  &lt;p style=&quot;color: royalblue;&quot;&gt;loading...&lt;/p&gt;{:then movies}  &lt;!-- fulfilled(이행) --&gt;  &lt;ul&gt;    {#each movies as movie}      &lt;li&gt;{movie.Title}&lt;/li&gt;    {:else}      &lt;li&gt;검색된 결과가 없어요...&lt;/li&gt;    {/each}  &lt;/ul&gt;{:catch err}  &lt;!-- rejected(거부) --&gt;  &lt;p style=&quot;color: red;&quot;&gt;{err.message}&lt;/p&gt;{/await}</code></pre><h2><span id="0d35e7e5-bca6-4a62-b111-2e33ae7f713e">사용자 입력 핸들링</span><a href="#0d35e7e5-bca6-4a62-b111-2e33ae7f713e" class="header-anchor"></a></h2><p><code>on</code> 지시어를 사용해 DOM 이벤트를 작성합니다.</p><p>기본 형태는 다음과 같습니다.</p><pre><code class="svelte">&lt;script&gt;  let count = 0&lt;/script&gt;&lt;button on:click={() =&gt; count += 1}&gt;  Click me!&lt;/button&gt;&lt;h1&gt;{count}&lt;/h1&gt;</code></pre><h3><span id="2ead5099-26c1-4674-a6b6-3f2b28ff7bfc">다중 이벤트 핸들러</span><a href="#2ead5099-26c1-4674-a6b6-3f2b28ff7bfc" class="header-anchor"></a></h3><p>한 요소에 같은 이벤트를 여러 개 연결할 수 있습니다.</p><p><a href="https://svelte.dev/repl/91a053c1a3ed4aa3ac73b0b0518bf20e?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let count = 0  function increase() {    count += 1  }  function current(e) {    console.log(e.currentTarget)  }&lt;/script&gt;&lt;button  on:click={increase}  on:click={current}  on:click={() =&gt; console.log(&#39;click!&#39;)}&gt;  Click me!&lt;/button&gt;&lt;h1&gt;{count}&lt;/h1&gt;</code></pre><h3><span id="b324c25c-264c-482f-84b2-afba940f012a">이벤트 수식어</span><a href="#b324c25c-264c-482f-84b2-afba940f012a" class="header-anchor"></a></h3><p>Svelte에서는 DOM 이벤트를 위한 여러 수식어를 제공합니다.<br><code>|</code>(Vertical bar) 기호를 이용해 작성할 수 있으며, 체이닝이 가능합니다.</p><pre><code class="svelte">&lt;a   href=&quot;#&quot;   on:click|preventDefault={() =&gt; console.log(&#39;link!&#39;)}&gt;  Internal link..&lt;/a&gt;&lt;div on:click|preventDefault|capture|self|once={() =&gt; console.log(&#39;!&#39;)}&gt;  Chaining..&lt;/div&gt;</code></pre><p>다음과 같은 수식어를 사용할 수 있습니다.</p><table><thead><tr><th>수식어</th><th>설명</th></tr></thead><tbody><tr><td>preventDefault</td><td>기본 동작 방지</td></tr><tr><td>stopPropagation</td><td>이벤트 버블링 방지</td></tr><tr><td>passive</td><td>이벤트 처리를 완료하지 않고도 기본 속도로 화면을 스크롤</td></tr><tr><td>nonpassive</td><td>명시적인 <code>passive: false</code>(보통 필요하지 않음)</td></tr><tr><td>capture</td><td>캡쳐링에서 핸들러 실행</td></tr><tr><td>once</td><td>최초 실행 후 핸들러 삭제</td></tr><tr><td>self</td><td>이벤트의 <code>target</code>과 <code>currentTarget</code>이 일치하는 경우 핸들러 실행</td></tr></tbody></table><p><a href="https://svelte.dev/repl/1ff9d07bc2fc45349874b1b1b2c013e4?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  function clickHandler(event) {    // console.log(event.target)    console.log(event.currentTarget)  }  function wheelHandler(event) {    console.log(event)  }&lt;/script&gt;&lt;section&gt;  &lt;!-- 기본 동작 방지 --&gt;  &lt;!-- el.addEventListener(&#39;click&#39;, e =&gt; e.preventDefault()) --&gt;  &lt;h2&gt;preventDefault&lt;/h2&gt;  &lt;a     href=&quot;https://naver.com&quot;    target=&quot;_blank&quot;    on:click|preventDefault={clickHandler}&gt;    Naver  &lt;/a&gt;&lt;/section&gt;&lt;section&gt;  &lt;!-- 최초 실행 후 핸들러 삭제 --&gt;  &lt;h2&gt;Once&lt;/h2&gt;  &lt;a     href=&quot;https://naver.com&quot;    target=&quot;_blank&quot;    on:click|preventDefault|once={clickHandler}&gt;    Naver  &lt;/a&gt;&lt;/section&gt;&lt;section&gt;  &lt;!-- 이벤트 버블링 방지 --&gt;  &lt;!-- el.addEventListener(&#39;click&#39;, e =&gt; e.stopPropagation()) --&gt;  &lt;h2&gt;stopPropagation&lt;/h2&gt;  &lt;div     class=&quot;parent&quot;    on:click={clickHandler}&gt;    &lt;div       class=&quot;child&quot;      on:click|stopPropagation={clickHandler}&gt;&lt;/div&gt;   &lt;/div&gt;&lt;/section&gt;&lt;section&gt;  &lt;!-- 캡쳐링에서 핸들러 실행 --&gt;  &lt;!-- el.addEventListener(&#39;click&#39;, e =&gt; {}, true) --&gt;  &lt;!-- el.addEventListener(&#39;click&#39;, e =&gt; {}, {capture: true}) --&gt;  &lt;h2&gt;capture&lt;/h2&gt;  &lt;div     class=&quot;parent&quot;    on:click|capture={clickHandler}&gt;    &lt;div       class=&quot;child&quot;       on:click={clickHandler}&gt;&lt;/div&gt;   &lt;/div&gt;&lt;/section&gt;&lt;section&gt;  &lt;!-- event의 target과 currentTarget이 일치하는 경우 핸들러 실행 --&gt;  &lt;h2&gt;self&lt;/h2&gt;  &lt;div     class=&quot;parent&quot;    on:click|self={clickHandler}&gt;    &lt;div class=&quot;child&quot;&gt;&lt;/div&gt;   &lt;/div&gt;&lt;/section&gt;&lt;section&gt;  &lt;!-- 이벤트 처리를 완료하지 않고도 기본 속도로 화면을 스크롤 --&gt;  &lt;!-- el.addEventListener(&#39;wheel&#39;, e =&gt; {}, {passive: true}) --&gt;  &lt;h2&gt;passive&lt;/h2&gt;  &lt;div     class=&quot;parent wheel&quot;    on:wheel|passive={wheelHandler}&gt;    &lt;div class=&quot;child&quot;&gt;&lt;/div&gt;     &lt;/div&gt;&lt;/section&gt;&lt;style&gt;  section {    border: 1px solid orange;    padding: 10px;    margin-bottom: 10px;  }  h2 {    margin: 0;    margin-bottom: 10px;  }  .parent {    width: 160px;    height: 120px;    background: royalblue;    padding: 20px;  }  .child {    width: 100px;    height: 100px;    background: tomato;  }  .wheel.parent {    overflow: auto;    }  .wheel .child {    height: 1000px;  }&lt;/style&gt;</code></pre><h2><span id="73aedf0e-a4e7-481c-8788-9f4daa6ac893">컴포넌트</span><a href="#73aedf0e-a4e7-481c-8788-9f4daa6ac893" class="header-anchor"></a></h2><p><a href="https://svelte.dev/repl/49dba27ecdcc47e5a259b2246b939759?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { onMount } from &#39;svelte&#39;  import Heropy from &#39;./Heropy.svelte&#39;  let heropy  onMount(() =&gt; {    // Error in REPL, Try in VS Code.    // console.log(heropy)    // console.log(heropy.title)  })&lt;/script&gt;&lt;Heropy /&gt;&lt;Heropy title=&quot;Hello Heropy&quot; /&gt;&lt;Heropy   title=&quot;Hello Neo&quot;  bind:this={heropy} /&gt;</code></pre><div class="filename">Heropy.svelte</div><pre><code class="svelte">&lt;script&gt;  export let title = &#39;Default value!!&#39;  let name = &#39;Heropy&#39;  let age = 85  let email = &#39;thesecon@gmail.com&#39;&lt;/script&gt;&lt;h2&gt;{title}&lt;/h2&gt;&lt;div&gt;{name}&lt;/div&gt;&lt;div&gt;{age}&lt;/div&gt;&lt;div&gt;{email}&lt;/div&gt;</code></pre><h3><span id="f3c2ba04-705a-45b4-9b97-8228dd905be1">Props</span><a href="#f3c2ba04-705a-45b4-9b97-8228dd905be1" class="header-anchor"></a></h3><p>컴포넌트의 Props를 사용해 부모에서 자식 컴포넌트로 데이터를 전달할 수 있습니다.<br>기본적으로 단반향 연결입니다.<br>관련한 몇 가지 사용 패턴을 살펴보세요!</p><p><a href="https://svelte.dev/repl/0fdb93c27de942f5bdb88958247ec9f0?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import User from &#39;./User.svelte&#39;  let users = [    {      name: &#39;Neo&#39;,      age: 85,      email: &#39;neo@abc.com&#39;    },    {      name: &#39;Lewis&#39;,      age: 30,      email: &#39;lewis@abc.com&#39;    },    {      name: &#39;Evan&#39;,      age: 52    }  ]&lt;/script&gt;&lt;section&gt;  {#each users as user}    &lt;User       name={user.name}       age={user.age}      email={user.email} /&gt;  {/each}&lt;/section&gt;&lt;section&gt;  {#each users as {name, age, email}}    &lt;User {name} {age} {email} /&gt;  {/each}&lt;/section&gt;&lt;section&gt;  {#each users as user}    &lt;User {...user} /&gt;  {/each}&lt;/section&gt;&lt;style&gt;  section {    border: 1px solid orange;    margin-bottom: 10px;    padding: 10px;  }&lt;/style&gt;</code></pre><div class="filename">User.svelte</div><pre><code class="svelte">&lt;script&gt;  export let name  export let age  export let email = &#39;None...&#39;&lt;/script&gt;&lt;ul&gt;  &lt;li&gt;{name}&lt;/li&gt;  &lt;li&gt;{age}&lt;/li&gt;  &lt;li&gt;{email}&lt;/li&gt;&lt;/ul&gt;</code></pre><h4><span id="234195d6-c0c1-4a2d-adcb-d87be5d91b4e">양방향 바인딩</span><a href="#234195d6-c0c1-4a2d-adcb-d87be5d91b4e" class="header-anchor"></a></h4><p>자식 컴포넌트가 부모로부터 전달받은 Props를 내부에서 수정(할당)하는 경우, 부모 컴포넌트에선 반응성을 가지지 않습니다.<br>만약 자식에서 수정한 Props가 부모 컴포넌트에서도 반응성을 유지하려면 <code>bind</code> 지시어를 사용해 양방향으로 연결해야 합니다.</p><blockquote><p>편리한 방법이지만, 데이터의 유효범위 관리를 위해 이 방법을 남발하지 않는 것이 좋습니다.</p></blockquote><p><a href="https://svelte.dev/repl/5352578a0fc646929ca45a2f7f3365eb?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Todo from &#39;./Todo.svelte&#39;  let todos = [    { id: 1, title: &#39;Breakfast&#39;, done: false },    { id: 2, title: &#39;Lunch&#39;, done: false },    { id: 3, title: &#39;Dinner&#39;, done: false }  ]&lt;/script&gt;{#each todos as todo, index (todo.id)}  &lt;Todo     bind:todos     {todo}     {index} /&gt;{/each}</code></pre><div class="filename">Todo.svelte</div><pre><code class="svelte">&lt;script&gt;  export let todos  export let todo  export let index  function deleteTodo() {    todos.splice(index, 1)    todos = todos    console.log(todos)  }&lt;/script&gt;&lt;div&gt;  &lt;input type=&quot;checkbox&quot; bind:value={todo.done} /&gt;  {todo.title}  &lt;button on:click={deleteTodo}&gt;X&lt;/button&gt;&lt;/div&gt;</code></pre><h3><span id="3d80d4b0-5a8f-40d2-a042-5d853812d602">Event Dispatcher</span><a href="#3d80d4b0-5a8f-40d2-a042-5d853812d602" class="header-anchor"></a></h3><p>Props가 부모에서 자식으로 데이터를 전달하는 방법이라면,<br>Event Dispatcher는 자식에서 부모로 데이터(이벤트)를 전달하는 방법입니다.</p><p>자식 컴포넌트에서 데이터를 포함하는 이벤트를 발생시키고,<br>부모 컴포넌트에선 그 이벤트를 수신한 핸들러에서 데이터를 꺼내는 방식입니다.</p><p>자식 컴포넌트(<code>Child.svelte</code>)에서 사용하는 기본 구조는 다음과 같습니다.</p><pre><code class="svelte">&lt;script&gt;  import { createEventDispatcher } from &#39;svelte&#39;  const dispatch = createEventDispatcher()  const 전달할_데이터 = &#39;나는 데이터!&#39;  dispatch(&#39;이벤트_이름&#39;, 전달할_데이터)&lt;/script&gt;</code></pre><p>부모 컴포넌트에서 사용하는 기본 구조는 다음과 같습니다.</p><pre><code class="svelte">&lt;script&gt;  import Child from &#39;./Child.svelte&#39;&lt;/script&gt;&lt;Child on:이벤트_이름={event =&gt; {  console.log(event.detail) // &#39;나는 데이터!&#39; }} /&gt;</code></pre><p><a href="https://svelte.dev/repl/43bfe9802c88462aac11b87025b9534d?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Todo from &#39;./Todo.svelte&#39;  let todos = [    { id: 1, title: &#39;Breakfast&#39;, done: false },    { id: 2, title: &#39;Lunch&#39;, done: false },    { id: 3, title: &#39;Dinner&#39;, done: false }  ]  function deleteTodo(event) {    // event.detail =&gt; `new CustomEvent()`를 통해 이벤트를 초기화 할 때 전달 된 모든 데이터를 반환    const todo = event.detail.todo    const index = todos.findIndex(t =&gt; t.id === todo.id)    console.log(todo)    todos.splice(index, 1)    todos = todos  }&lt;/script&gt;{#each todos as todo (todo.id)}  &lt;Todo     {todo}     on:deleteMe={deleteTodo} /&gt;{/each}</code></pre><div class="filename">Todo.svelte</div><pre><code class="svelte">&lt;script&gt;  import { createEventDispatcher } from &#39;svelte&#39;  export let todo  const dispatch = createEventDispatcher()  function deleteTodo() {    // dispatch(&#39;deleteMe&#39;, todo)    dispatch(&#39;deleteMe&#39;, {      todo    })  }&lt;/script&gt;&lt;div&gt;  &lt;input     type=&quot;checkbox&quot;     bind:value={todo.done} /&gt;  {todo.title}  &lt;button on:click={deleteTodo}&gt;X&lt;/button&gt;&lt;/div&gt;</code></pre><h4><span id="1e048924-c4c4-4b5d-8b99-cb171360d4c1">Event Forwarding</span><a href="#1e048924-c4c4-4b5d-8b99-cb171360d4c1" class="header-anchor"></a></h4><p>이벤트 포워딩은 자식에서 부모 컴포넌트로 이벤트를 던져 올리는 방법으로,<br>이벤트 핸들러를 부모 컴포넌트에서 작성할 수 있습니다.</p><p>사용법은 아주 간단합니다.<br>단순히 이벤트의 핸들러를 명시하지 않으면 됩니다.</p><pre><code class="svelte">&lt;div on:click&gt;  Forwarding!&lt;/div&gt;</code></pre><p>다음은 Child 컴포넌트에서 Parent 컴포넌트를 거쳐 App 컴포넌트로 <code>myEvent</code>라는 이벤트를 전달하는 예제입니다.<br>Parent 컴포넌트의 <code>click</code> 이벤트도 잘 확인해 보세요!</p><p><a href="https://svelte.dev/repl/053c93e397ab4ccd8921d2beca238ffe?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Parent from &#39;./Parent.svelte&#39;  function handler(e) {    console.log(e.currentTarget)  }  function myEventHandler(e) {    console.log(e.detail.myName)  }&lt;/script&gt;&lt;Parent  on:click={handler}   on:myEvent={myEventHandler} /&gt;</code></pre><div class="filename">Parent.svelte</div><pre><code class="svelte">&lt;script&gt;  import Child from &#39;./Child.svelte&#39;&lt;/script&gt;&lt;h2&gt;Parent!&lt;/h2&gt;&lt;button on:click&gt;  Parent click!&lt;/button&gt;&lt;Child on:myEvent /&gt;</code></pre><div class="filename">Child.svelte</div><pre><code class="svelte">&lt;script&gt;  import { createEventDispatcher } from &#39;svelte&#39;  const dispatch = createEventDispatcher()&lt;/script&gt;&lt;h2&gt;Child!&lt;/h2&gt;&lt;button on:click={() =&gt; {  dispatch(&#39;myEvent&#39;, {    myName: &#39;Heropy!!&#39;  })}}&gt;  Child click!&lt;/button&gt;</code></pre><h3><span id="490d2aba-18da-4a29-8482-579d2ff1e300">Context API</span><a href="#490d2aba-18da-4a29-8482-579d2ff1e300" class="header-anchor"></a></h3><p>컴포넌트에서 <code>setContext</code>로 데이터를 지정하면,<br>그 컴포넌트를 포함한 모든 하위 컴포넌트에서 <code>getContext</code>로 그 정의된 데이터를 가져와 사용할 수 있습니다.</p><blockquote><p>Props와 Store의 중간 개념 정도로 이해하면 쉽습니다.</p></blockquote><p>Context API는 다음과 같이 Getter와 Setter로 구성되어 있습니다.</p><table><thead><tr><th>API</th><th>설명</th></tr></thead><tbody><tr><td>getContext</td><td>정의된 데이터를 가져옵니다.</td></tr><tr><td>setContext</td><td>자신을 포함한 하위 컴포넌트에서 사용할 데이터를 정의합니다.</td></tr></tbody></table><p>다음 예제를 통해 Context API가 동작하는 범위를 이해하세요!</p><p><a href="https://svelte.dev/repl/5f9b24be6cfa4faebe3bc549d2e0a132?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { getContext } from &#39;svelte&#39;  import Heropy from &#39;./Heropy.svelte&#39;  import Lewis from &#39;./Lewis.svelte&#39;  import Evan from &#39;./Evan.svelte&#39;  const pocketMoney = getContext(&#39;heropy&#39;) // undefined&lt;/script&gt;&lt;h1&gt;App({pocketMoney})&lt;/h1&gt;&lt;div&gt;  &lt;Heropy /&gt;  &lt;Lewis /&gt;  &lt;Evan /&gt;&lt;/div&gt;&lt;style&gt;  h1 {    font-size: 50px;  }  div {    padding-left: 50px;  }&lt;/style&gt;</code></pre><div class="filename">Heropy.svelte</div><pre><code class="svelte">&lt;script&gt;  import { getContext, setContext } from &#39;svelte&#39;  import Anderson from &#39;./Anderson.svelte&#39;  setContext(&#39;heropy&#39;, 10000)  const pocketMoney = getContext(&#39;heropy&#39;) // 10000&lt;/script&gt;&lt;h1 style=&quot;color: red&quot;&gt;  Heropy({pocketMoney})&lt;/h1&gt;&lt;ul&gt;  &lt;li&gt;    &lt;Anderson /&gt;  &lt;/li&gt;&lt;/ul&gt;</code></pre><div class="filename">Lewis.svelte</div><pre><code class="svelte">&lt;script&gt;  import { getContext } from &#39;svelte&#39;  const pocketMoney = getContext(&#39;heropy&#39;) // undefined&lt;/script&gt;&lt;h1&gt;Lewis({pocketMoney})&lt;/h1&gt;</code></pre><div class="filename">Evan.svelte</div><pre><code class="svelte">&lt;script&gt;  import { getContext } from &#39;svelte&#39;  const pocketMoney = getContext(&#39;heropy&#39;) // undefined&lt;/script&gt;&lt;h1&gt;Evan({pocketMoney})&lt;/h1&gt;</code></pre><div class="filename">Anderson.svelte</div><pre><code class="svelte">&lt;script&gt;  import { getContext } from &#39;svelte&#39;  import Neo from &#39;./Neo.svelte&#39;  import Emily from &#39;./Emily.svelte&#39;  const pocketMoney = getContext(&#39;heropy&#39;) // 10000&lt;/script&gt;&lt;h2&gt;Anderson({pocketMoney})&lt;/h2&gt;&lt;ul&gt;  &lt;li&gt;    &lt;Neo /&gt;  &lt;/li&gt;  &lt;li&gt;    &lt;Emily /&gt;  &lt;/li&gt;&lt;/ul&gt;</code></pre><div class="filename">Neo.svelte</div><pre><code class="svelte">&lt;script&gt;  import { getContext } from &#39;svelte&#39;  const pocketMoney = getContext(&#39;heropy&#39;) // 10000&lt;/script&gt;&lt;h3&gt;Neo({pocketMoney})&lt;/h3&gt;</code></pre><div class="filename">Emily.svelte</div><pre><code class="svelte">&lt;script&gt;  import { getContext } from &#39;svelte&#39;  const pocketMoney = getContext(&#39;heropy&#39;) // 10000&lt;/script&gt;&lt;h3&gt;Emily({pocketMoney})&lt;/h3&gt;</code></pre><h3><span id="d6f5eaca-6149-4b27-8e49-780bf9a4be9b">Module Context</span><a href="#d6f5eaca-6149-4b27-8e49-780bf9a4be9b" class="header-anchor"></a></h3><p>다음과 같이 <code>context=&quot;module&quot;</code> 속성/값을 가지는 SCRIPT 태그에 정의된 내용은,<br>컴포넌트를 사용하기 위해 <strong>모듈로 처음 가져오는 상황에 전역으로 한 번 실행</strong>됩니다.</p><p>이 블록 안에서 선언된 값은 해당 컴포넌트 내에서 접근 가능하지만,<br>반대로 <code>&lt;script context=&quot;module&quot;&gt;</code>가 해당 컴포넌트의 다른 값에는 접근할 수 없습니다.</p><p>흥미로운 기능이지만 <code>&lt;script context=&quot;module&quot;&gt;</code> 내에서 선언된 변수는 값을 재할당해도 반응성(DOM 업데이트)이 없다는 점에 주의해야 합니다.</p><blockquote><p>반응성이 없기 때문에 화면 갱신용이 아닌, 단순 전역 데이터처럼 사용하면 됩니다.</p></blockquote><pre><code class="svelte">&lt;script context=&quot;module&quot;&gt;  let count = 0&lt;/script&gt;</code></pre><p>컴포넌트 외부에서도 변수/함수 등을 참조할 수 있도록 <code>export</code>를 사용할 수 있습니다.</p><pre><code class="svelte">&lt;script context=&quot;module&quot;&gt;  export let count = 0&lt;/script&gt;</code></pre><p><a href="https://svelte.dev/repl/c414c6308f6e4cb2a3e6aba330a51e41?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Fruit, { count } from &#39;./Fruit.svelte&#39;  let fruits = [    &#39;Apple&#39;,     &#39;Banana&#39;,     &#39;Cherry&#39;,    &#39;Mango&#39;,    &#39;Orange&#39;  ]&lt;/script&gt;&lt;button on:click={() =&gt; console.log(count)}&gt;  Total count log!&lt;/button&gt;{#each fruits as fruit}  &lt;Fruit {fruit} /&gt;{/each}</code></pre><div class="filename">Fruit.svelte</div><pre><code class="svelte">&lt;script context=&quot;module&quot;&gt;  export let count = 0  console.log(&#39;Module context!&#39;)&lt;/script&gt;&lt;script&gt;  export let fruit  console.log(&#39;Each component init!&#39;)&lt;/script&gt;&lt;div on:click={() =&gt; {  count += 1  // console.log(count)}}&gt;  {fruit}&lt;/div&gt;</code></pre><p>다음은 공식 홈페이지의 예제를 이해하기 쉽도록 간소화한 예제입니다.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Set" target="_blank" rel="noopener">Set 생성자</a>에 대한 이해만 있으면 충분한데,<br><code>new Set()</code>으로 만들어진 인스턴스는 <code>.add()</code>, <code>.forEach()</code> 같은 메소드를 사용할 수 있는 일종의 유사 배열입니다.<br><code>.add()</code>는 <code>.push()</code>를 생각하면 쉽습니다.</p><p><code>stopAll</code> 함수가 어떤 역할을 하는지 이해하는 것이 포인트입니다.</p><p><a href="https://svelte.dev/repl/316363bd1ea047639c9ffecfde84f741?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import AudioPlayer, { stopAll } from &#39;./AudioPlayer.svelte&#39;  let audioTracks = [    &#39;https://sveltejs.github.io/assets/music/strauss.mp3&#39;,    &#39;https://sveltejs.github.io/assets/music/holst.mp3&#39;,    &#39;https://sveltejs.github.io/assets/music/satie.mp3&#39;  ]&lt;/script&gt;&lt;button on:click={stopAll}&gt;  Stop all!&lt;/button&gt;{#each audioTracks as src}  &lt;AudioPlayer {src} /&gt;{/each}</code></pre><div class="filename">AudioPlayer.svelte</div><pre><code class="svelte">&lt;script context=&quot;module&quot;&gt;  const players = new Set()  export function stopAll() {    players.forEach(p =&gt; p.pause())  }&lt;/script&gt;&lt;script&gt;  import { onMount } from &#39;svelte&#39;  export let src  let player  onMount(() =&gt; {    // Like players.push(player)    players.add(player)  })&lt;/script&gt;&lt;div&gt;  &lt;audio    bind:this={player}    {src}    controls&gt;    &lt;track kind=&quot;captions&quot; /&gt;  &lt;/audio&gt;&lt;/div&gt;</code></pre><p>다음은 <code>&lt;script context=&quot;module&quot;&gt;</code>를 사용하는 <a href="https://sapper.svelte.dev/docs#Routing" target="_blank" rel="noopener">Sapper 동적 라우팅</a> 설정 코드의 예시입니다.</p><pre><code class="svelte">&lt;!-- src/routes/blog/[slug].svelte --&gt;&lt;script context=&quot;module&quot;&gt;  // the (optional) preload function takes a  // `{ path, params, query }` object and turns it into  // the data we need to render the page  export async function preload(page, session) {    // the `slug` parameter is available because this file    // is called [slug].svelte    const { slug } = page.params;    // `this.fetch` is a wrapper around `fetch` that allows    // you to make credentialled requests on both    // server and client    const res = await this.fetch(`blog/${slug}.json`);    const article = await res.json();    return { article };  }&lt;/script&gt;&lt;script&gt;  export let article;&lt;/script&gt;</code></pre><h3><span id="baa92077-3ffc-4482-afad-86f03be70877">$$props, $$restProps</span><a href="#baa92077-3ffc-4482-afad-86f03be70877" class="header-anchor"></a></h3><p>Svelte는 컴포넌트가 전달받는 모든 Props의 정보를 가진 객체(<code>$$props</code>)를 제공합니다.<br>따라서 전달받을 Props를 모두 명시하지 않아도 사용할 수 있는 장점이 있습니다.<br>혹은 명시한 Props를 제외한 나머지만 다룰 수 있는 객체(<code>$$restProps</code>)도 제공합니다.</p><table><thead><tr><th>이름</th><th>설명</th></tr></thead><tbody><tr><td>$$props</td><td>컴포넌트에 전달된 모든 Props 정보를 가진 객체입니다.</td></tr><tr><td>$$restProps</td><td>컴포넌트에 명시된 Props를 제외한, 나머지 Props 정보를 가진 객체입니다.</td></tr></tbody></table><p>다음 예제에서 TextField 컴포넌트에 명시된 Props는 <code>value</code>와 <code>color</code>입니다.</p><p><code>value</code>와 <code>color</code>를 포함한,<br>컴포넌트에 연결된 <code>type</code>, <code>placeholder</code> 같은 모든 Props 정보가 들어있는 객체는 <code>$$props</code>이고,</p><p><code>value</code>와 <code>color</code>를 제외한,<br>컴포넌트에 연결된 <code>type</code>, <code>placeholder</code> 같은 Props 정보만 들어있는 객체가 <code>$$restProps</code>입니다.</p><p><a href="https://svelte.dev/repl/ac157035d0374176887a9e84e09fc13d?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import TextField from &#39;./TextField.svelte&#39;  let id = &#39;&#39;  let pw = &#39;&#39;  function submit() {    //   }&lt;/script&gt;&lt;TextField   bind:value={id}  color=&quot;yellowgreen&quot;  type=&quot;email&quot;  placeholder=&quot;ID!&quot;  maxlength=&quot;10&quot;  required /&gt;&lt;TextField  bind:value={pw}  color=&quot;tomato&quot;  type=&quot;password&quot;  placeholder=&quot;Password!&quot;  required /&gt;&lt;button on:click={submit}&gt;  Submit!&lt;/button&gt;&lt;!-- &lt;div&gt;{id} / {pw}&lt;/div&gt; --&gt;</code></pre><div class="filename">TextField.svelte</div><pre><code class="svelte">&lt;script&gt;  export let value  export let color&lt;/script&gt;&lt;div class=&quot;my-custom-input&quot;&gt;  &lt;input     bind:value     style=&quot;color: {color};&quot;    {...$$restProps} /&gt;    &lt;!-- {...$$props} /&gt; --&gt;&lt;/div&gt;&lt;style&gt;  .my-custom-input input {    border-radius: 100px;    padding: 10px 20px;  }&lt;/style&gt;</code></pre><h2><span id="2cf07b14-3b05-4011-beee-0e61a5078ece">슬롯(Slot)</span><a href="#2cf07b14-3b05-4011-beee-0e61a5078ece" class="header-anchor"></a></h2><p>슬롯(Slot)은 컴포넌트의 내용(Content)입니다.<br>요소(Element)의 내용(Content)과 개념이 같습니다.</p><pre><code class="svelte">&lt;script&gt;  import Hello from &#39;./Hello.svelte&#39;&lt;/script&gt;&lt;!--요소의 내용--&gt;&lt;h1&gt;Hello world!&lt;/h1&gt;&lt;!--컴포넌트의 내용--&gt;&lt;Hello&gt;Hello world!&lt;/Hello&gt;</code></pre><p>단지 컴포넌트의 내용이 어디에 삽입(출력)될 것인지만 <code>&lt;slot&gt;</code>로 지정하면 됩니다.<br>다음은 위 예제에서 사용한 Hello 컴포넌트입니다.</p><pre><code class="svelte">&lt;h2&gt;  &lt;!--내용은 &lt;slot&gt;에 삽됩니다!--&gt;  &lt;slot&gt;&lt;/slot&gt;&lt;/h2&gt;&lt;p&gt;I&#39;m &#39;Hello&#39; Component.&lt;/p&gt;</code></pre><h3><span id="224094c9-be50-42c5-8b8e-98e4445354a2">단일 슬롯과 Fallback Content</span><a href="#224094c9-be50-42c5-8b8e-98e4445354a2" class="header-anchor"></a></h3><p>슬롯으로 들어오는 내용이 없는 경우 기본 내용을 지정할 수 있습니다.<br>이를 ‘Fallback Content’라고 합니다.<br>Fallback Content는 글자(문장)만 아니라 여러 요소 및 컴포넌트를 포함할 수 있습니다.</p><pre><code class="svelte">&lt;slot&gt;Fallback content, 들어오는 내용이 없으면 이 문장을 출력합니다!&lt;/slot&gt;</code></pre><p><a href="https://svelte.dev/repl/d59c20f90ebd4985856f3fe168855674?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Btn from &#39;./Btn.svelte&#39;&lt;/script&gt;&lt;Btn&gt;&lt;/Btn&gt;&lt;Btn&gt;Submit!&lt;/Btn&gt;&lt;Btn block&gt;Submit!&lt;/Btn&gt;&lt;Btn color=&quot;royalblue&quot;&gt;Submit!&lt;/Btn&gt;&lt;Btn   block   color=&quot;red&quot;&gt;  Danger!&lt;/Btn&gt;</code></pre><div class="filename">Btn.svelte</div><pre><code class="svelte">&lt;script&gt;  export let block  export let color&lt;/script&gt;&lt;button   class:block  style=&quot;    background-color: {color};    color: {color ? &#39;white&#39; : &#39;&#39; };&quot;&gt;    &lt;slot&gt;      Default Button!    &lt;/slot&gt;&lt;/button&gt;&lt;style&gt;  button {    background: lightgray;    padding: 10px 20px;    border: none;    border-radius: 10px;    cursor: pointer;    transition: .2s;  }  button:hover {    text-decoration: underline;    }  button:active {    transform: scale(1.05);  }  button.block {    width: 100%;    display: block;  }&lt;/style&gt;</code></pre><h3><span id="a79eb398-278d-4cb3-8b09-cec1537d0ca8">이름을 가지는 슬롯</span><a href="#a79eb398-278d-4cb3-8b09-cec1537d0ca8" class="header-anchor"></a></h3><p>다음과 같이 내용에 슬롯 이름을 지정해 원하는 슬롯에 삽입할 수 있습니다.</p><pre><code class="svelte">&lt;div slot=&quot;슬롯_이름&quot;&gt;&lt;/div&gt;</code></pre><p>슬롯에 지정된 이름으로 해당 내용이 삽입됩니다.</p><pre><code class="svelte">&lt;slot name=&quot;슬롯_이름&quot;&gt;&lt;/slot&gt;</code></pre><p><a href="https://svelte.dev/repl/b0cde9c785db4dbf8f1e00203aec94ec?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Card from &#39;./Card.svelte&#39;&lt;/script&gt;&lt;Card&gt;  &lt;div slot=&quot;age&quot;&gt;85&lt;/div&gt;  &lt;h2 slot=&quot;name&quot;&gt;Heropy&lt;/h2&gt;  &lt;div slot=&quot;email&quot;&gt;thesecon@gmail.com&lt;/div&gt;&lt;/Card&gt;&lt;Card&gt;  &lt;span slot=&quot;email&quot;&gt;neo@abc.com&lt;/span&gt;  &lt;h3 slot=&quot;name&quot;&gt;Neo&lt;/h3&gt;&lt;/Card&gt;&lt;style&gt;  h2 {    font-weight: 400;  }  h3 {    color: red;  }&lt;/style&gt;</code></pre><div class="filename">Card.svelte</div><pre><code class="svelte">&lt;div class=&quot;card&quot;&gt;  &lt;slot name=&quot;name&quot;&gt;&lt;/slot&gt;  &lt;slot name=&quot;age&quot;&gt;??&lt;/slot&gt;  &lt;slot name=&quot;email&quot;&gt;&lt;/slot&gt;&lt;/div&gt;&lt;style&gt;  .card {    margin: 20px;    padding: 12px;    border: 1px solid gray;    border-radius: 10px;    box-shadow: 4px 4px 0 rgba(0,0,0,.1);  } &lt;/style&gt;</code></pre><h3><span id="2264d36d-4502-495e-8602-3ad22176bfec">범위를 가지는 슬롯</span><a href="#2264d36d-4502-495e-8602-3ad22176bfec" class="header-anchor"></a></h3><p>범위를 가지는 슬롯으로 컴포넌트 내부에서 특정한 값을 꺼내서 사용할 수 있습니다.<br>다음과 같이 슬롯에 <strong>속성과 값</strong>을 포함합니다.</p><pre><code class="svelte트">&lt;script&gt;  let message = &#39;Hello world!&#39;&lt;/script&gt;&lt;slot myMsg={message}&gt;&lt;/slot&gt;</code></pre><p>그리고 컴포넌트에서 <code>let</code> 지시어를 통해 그 속성을 정의하고 범위 내에서 변수(데이터)처럼 사용할 수 있습니다.</p><pre><code class="svelte">&lt;script&gt;  import Heropy from &#39;~/components/Heropy.svelte&#39;&lt;/script&gt;&lt;Heropy let:myMsg&gt;  &lt;h2&gt;{myMsg}&lt;/h2&gt;&lt;/Heropy&gt;</code></pre><p><a href="https://svelte.dev/repl/9078478b56b44e25af91275a6712173d?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Wrap from &#39;./Wrap.svelte&#39;  let fruits = {    apple: {      value: &#39;&#39;,      options: {        readonly: false,        disabled: false,        placeholder: &#39;placeholder A&#39;      }    },    banana: {      value: &#39;BANANA&#39;,      options: {        disabled: false,        placeholder: &#39;placeholder A&#39;      }    }  }  function add(name) {    console.log(name)  }  function update(name) {    console.log(name)  }  function remove(name) {    console.log(name)  }&lt;/script&gt;&lt;Wrap   scopeName=&quot;apple&quot;  let:_name&gt;  &lt;label    class=&quot;fruits__{_name}&quot;    name=&quot;{_name}&quot;&gt;    &lt;input      bind:value={fruits[_name].value}      readonly={fruits[_name].options.readonly}      disabled={fruits[_name].options.disabled}      placeholder={fruits[_name].options.placeholder}      on:change={() =&gt; add(_name)} /&gt;  &lt;/label&gt;&lt;/Wrap&gt;&lt;Wrap   scopeName=&quot;banana&quot;  let:_name&gt;  &lt;input    bind:value={fruits[_name].value}    disabled={fruits[_name].options.disabled}    placeholder={fruits[_name].options.placeholder}    on:click={() =&gt; update(_name)} /&gt;&lt;/Wrap&gt;&lt;Wrap  scopeName=&quot;cherry&quot;  let:_name&gt;  &lt;div    class=&quot;hello-{_name}&quot;    name=&quot;{_name}&quot;    on:click={() =&gt; remove(_name)}&gt;    {_name}  &lt;/div&gt;&lt;/Wrap&gt;</code></pre><div class="filename">Wrap.svelte</div><pre><code class="svelte">&lt;script&gt;  export let scopeName&lt;/script&gt;&lt;div&gt;  &lt;slot _name={scopeName}&gt;&lt;/slot&gt;&lt;/div&gt;</code></pre><h3><span id="a499ce4a-995b-46f6-8967-2ca68d9af87b">슬롯 포워딩</span><a href="#a499ce4a-995b-46f6-8967-2ca68d9af87b" class="header-anchor"></a></h3><p>슬롯 포워딩은 부모 컴포넌트로부터 전달(Forwarding)받은 내용(Content)을 자식 컴포넌트의 내용으로 슬롯(Slot)을 이용해 전달하는 것을 의미합니다.<br>앞서 살펴본 단일 슬롯, 이름을 가지는 슬롯, 범위를 가지는 슬롯 모두 자식 컴포넌트로 전달할 수 있습니다.</p><p><a href="https://svelte.dev/repl/df846b646a364ce689f3dabda34e54be?version=3.31.0" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Parent from &#39;./Parent.svelte&#39;&lt;/script&gt;&lt;Parent let:scoped&gt;  &lt;h2&gt;Default slot..&lt;/h2&gt;  &lt;h3 slot=&quot;named&quot;&gt;Named slot..&lt;/h3&gt;  &lt;h1 slot=&quot;scoped&quot;&gt;Scoped slot.. {scoped}&lt;/h1&gt;&lt;/Parent&gt;</code></pre><div class="filename">Parent.svelte</div><pre><code class="svelte">&lt;script&gt;  import Child from &#39;./Child.svelte&#39;&lt;/script&gt;&lt;Child let:scoped&gt;  &lt;slot&gt;&lt;/slot&gt;  &lt;slot     name=&quot;named&quot;     slot=&quot;named&quot;&gt;&lt;/slot&gt;  &lt;slot    name=&quot;scoped&quot;     slot=&quot;scoped&quot;     scoped={scoped}&gt;&lt;/slot&gt;&lt;/Child&gt;</code></pre><div class="filename">Child.svelte</div><pre><code class="svelte">&lt;script&gt;  let scoped = &#39;Scoped!!&#39;&lt;/script&gt;&lt;slot   name=&quot;scoped&quot;   scoped={scoped}&gt;&lt;/slot&gt;&lt;slot name=&quot;named&quot;&gt;&lt;/slot&gt;&lt;slot&gt;&lt;/slot&gt;</code></pre><h3><span id="3354dd9b-e3a9-4a67-9f9b-af5a73d0f964">$$slots</span><a href="#3354dd9b-e3a9-4a67-9f9b-af5a73d0f964" class="header-anchor"></a></h3><p>Svelte는 컴포넌트가 전달받은 내용(Content)의 정보를 가진 객체(<code>$$slots</code>)를 제공합니다.<br>슬롯으로 받을 내용이 존재하는지를 확인하는 데 유용합니다!</p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import UserCard from &#39;./UserCard.svelte&#39;&lt;/script&gt;&lt;UserCard&gt;  &lt;h2 slot=&quot;name&quot;&gt;HEROPY&lt;/h2&gt;  &lt;div slot=&quot;age&quot;&gt;85&lt;/div&gt;  &lt;div slot=&quot;email&quot;&gt;thesecon@gmail.com&lt;/div&gt;&lt;/UserCard&gt;&lt;UserCard&gt;  &lt;h2 slot=&quot;name&quot;&gt;Neo&lt;/h2&gt;  &lt;div slot=&quot;email&quot;&gt;neo@zillinks.com&lt;/div&gt;&lt;/UserCard&gt;&lt;UserCard&gt;  &lt;h2 slot=&quot;name&quot;&gt;Evan&lt;/h2&gt;&lt;/UserCard&gt;</code></pre><div class="filename">UserCard.svelte</div><pre><code class="svelte">&lt;script&gt;  console.log($$slots)&lt;/script&gt;&lt;div class=&quot;user-card&quot;&gt;  &lt;slot name=&quot;name&quot;&gt;&lt;/slot&gt;  {#if $$slots.age}    &lt;hr /&gt;    &lt;slot name=&quot;age&quot;&gt;&lt;/slot&gt;  {/if}  {#if $$slots.email}    &lt;hr /&gt;    &lt;slot name=&quot;email&quot;&gt;&lt;/slot&gt;  {/if}&lt;/div&gt;&lt;style&gt;  .user-card {    width: 300px;    margin: 20px 10px;    padding: 0 10px 20px;    border: 4px solid lightgray;    border-radius: 10px;    box-shadow: 8px 8px rgba(0,0,0,.03);    position: relative;  }&lt;/style&gt;</code></pre><p>위 예제의 각 <code>UserCard.svelte</code> 컴포넌트에서 출력한 콘솔을 확인하면 다음과 같습니다.</p><p><img src="/images/screenshot/svelte/svelte-slots-object.jpg" alt="Svelte $$slots object"></p><p>이름을 가지는 슬롯으로 받는 내용이 2개 이상이면 <code>default</code> 속성(단일 슬롯)이 자동으로 추가되는 것을 확인할 수 있는데,<br>이는 줄 바꿈으로 인한 공백 문자(띄어쓰기)가 내용으로 전달되면서 해석되는 것으로 확인했습니다.<br>따라서 다음과 같이 인라인으로 내용을 전달하면 <code>default</code> 속성(단일 슬롯)이 사라지게 됩니다.</p><p><img src="/images/screenshot/svelte/svelte-slots-object-inline-contents.jpg" alt="Svelte $$slots object"><br><img src="/images/screenshot/svelte/svelte-slots-object-inline-contents-console.jpg" alt="Svelte $$slots object"></p><h2><span id="fd8320ea-176e-41c8-adde-d71e8e0337ba">스토어</span><a href="#fd8320ea-176e-41c8-adde-d71e8e0337ba" class="header-anchor"></a></h2><blockquote><p>‘Svelte 시작하기 &gt; 스토어’ 파트를 먼저 학습하시면 좋습니다.</p></blockquote><p>Svelte는 자체적으로 스토어(Store)를 지원합니다.<br>내장된 <code>svelte/store</code> 모듈을 사용하면 됩니다.</p><div class="filename">store.js</div><pre><code class="js">import { readable, writable, derived, get } from &#39;svelte/store&#39;;export const r = readable(1);export const w = writable(7);export const d = derived(w, $w =&gt; $w + 1);get(r) // 1get(w) // 7get(d) // 8</code></pre><p><code>readable</code>, <code>writable</code>, <code>derived</code>로 정의된 스토어 객체는 기본적으로 <code>subscribe</code> 메소드를 포함하며,<br><code>writable</code>로 정의된 객체는 추가로 <code>set</code>과 <code>update</code> 메소드를 사용할 수 있습니다.</p><p><img src="/images/screenshot/svelte/svelte-store-objects.jpg" alt="Svelte store object"></p><p><code>subscribe</code> 메소드를 직접 사용해서 수동 구독을 만들 수 있습니다.</p><pre><code class="svelte">&lt;script&gt;  import { w } from &#39;./store.js&#39;;  const data = w.subscribe(d =&gt; data = d)  console.log(data) // 7&lt;/script&gt;</code></pre><p>Svelte 컴포넌트에서는 각 메소드를 사용할 필요 없이 <code>$</code> 접두사로 스토어를 참조할 수 있습니다.<br>이를 자동 구독(Auto-subscription)이라고 합니다.<br>자동 구독을 통해 <code>set</code>과 <code>update</code> 메소드를 대신할 수 있어 편리합니다.</p><blockquote><p>Svelte 컴포넌트에선 자동 구독(<code>$</code> 접두사) 사용을 권장합니다!</p></blockquote><pre><code class="svelte">&lt;script&gt;  import { w } from &#39;./store.js&#39;;  console.log($w) // 7  $w = 1;  // w.set(1)  $w += 1;  // w.update(v =&gt; v + 1) // .update()의 콜백에서 반환하는 값이 지정됩니다.   console.log($w) // 2&lt;/script&gt;</code></pre><p>Svelte 컴포넌트가 아니면(<code>.js</code>, <code>.ts</code> 파일 같은) 자동 구독을 사용할 수 없기 때문에,<br><code>set</code>, <code>update</code>, <code>subscribe</code> 메소드를 직접 사용해야 합니다.</p><h3><span id="e1776701-fef3-4a1a-bc18-977ae4bb622a">쓰기 가능 스토어</span><a href="#e1776701-fef3-4a1a-bc18-977ae4bb622a" class="header-anchor"></a></h3><p>값을 읽거나 쓸 수 있는 스토어를 생성합니다.</p><pre><code class="plaintext">writable(값)writable(값, 콜백)</code></pre><p>첫 번째 인수는 스토어의 값(초깃값)입니다.</p><p>두 번째 인수는 스토어 구독(자동, 수동)이 발생하면 실행될 콜백입니다.<br>콜백에서 반환하는 함수는 구독이 모두 취소되면(구독자가 모두 없어지면) 실행됩니다.</p><pre><code class="js">import { writable } from &#39;svelte/store&#39;export let store = writable(&#39;값&#39;, () =&gt; {  // 구독자가 1명 이상이 되면 실행!  return () =&gt; {    // 구독자가 0명이 되면 실행!  }})</code></pre><h4><span id="949035eb-6f51-4a25-9cf5-d03407185549">사용 패턴</span><a href="#949035eb-6f51-4a25-9cf5-d03407185549" class="header-anchor"></a></h4><p><a href="https://svelte.dev/repl/e47d56b83bea437b841a88186f8f61a5?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import WritableMethods from &#39;./WritableMethods.svelte&#39;  let toggle = true&lt;/script&gt;&lt;button on:click={() =&gt; toggle = !toggle}&gt;  Toggle&lt;/button&gt;{#if toggle}  &lt;WritableMethods /&gt;{/if}</code></pre><div class="filename">WritableMethods.svelte</div><pre><code class="svelte">&lt;script&gt;  import { onDestroy } from &#39;svelte&#39;  import { get } from &#39;svelte/store&#39;  import { name, count } from &#39;./store.js&#39;  let number  let userName  // Store 객체  // 사용 가능 메소드: set, update, subscribe  console.log(name, count)  // 구독하지 않고 Store 객체의 값만 얻기  console.log(get(name), get(count))  // count 구독자 추가!  const unsubscribeCount = count.subscribe(c =&gt; {    number = c  })  // count 구독자 추가!  const unsubscribeCount2 = count.subscribe(() =&gt; {})  // name 구독자 추가!  const unsubscribeName = name.subscribe(n =&gt; {    userName = n  })  function increase() {    count.update(c =&gt; c + 1)    try {      unsubscribeCount() // Only once!      unsubscribeCount2()    } catch (e) {}  }  function changeName() {    // name.update(() =&gt; &#39;Neo&#39;)    name.set(&#39;Neo&#39;)  }  onDestroy(() =&gt; {    unsubscribeCount()    unsubscribeCount2()    unsubscribeName()  })&lt;/script&gt;&lt;button   on:click={increase}  on:click={changeName}&gt;  Click me!&lt;/button&gt;&lt;h2&gt;{number}&lt;/h2&gt;&lt;h2&gt;{userName}&lt;/h2&gt;</code></pre><div class="filename">store.js</div><pre><code class="js">import { writable } from &#39;svelte/store&#39;export let name = writable(&#39;Heropy&#39;, () =&gt; {  console.log(&#39;name 구독자가 1명 이상일 때!&#39;)  return () =&gt; {    console.log(&#39;name 구독자가 0명일 때...&#39;)  }})export let count = writable(0, () =&gt; {  console.log(&#39;count 구독자가 1명 이상일 때!&#39;)  return () =&gt; {    console.log(&#39;count 구독자가 0명일 때...&#39;)  }})</code></pre><p>스토어 자동 구독을 통해 훨씬 간단하게 작성할 수 있습니다.</p><div class="filename">WritableMethods.svelte</div><pre><code class="svelte">&lt;script&gt;  import { name, count } from &#39;./store.js&#39;&lt;/script&gt;&lt;button on:click={() =&gt; {  $count += 1 // Increase  $name = &#39;Neo&#39; // Change name}}&gt;  Click me!&lt;/button&gt;&lt;h2&gt;{$count}&lt;/h2&gt;&lt;h2&gt;{$name}&lt;/h2&gt;</code></pre><h3><span id="e1700ff3-1c9a-4c13-bd48-2f84bd59fa00">읽기 전용 스토어</span><a href="#e1700ff3-1c9a-4c13-bd48-2f84bd59fa00" class="header-anchor"></a></h3><p>값을 읽을 수만 있는 스토어를 생성합니다.</p><pre><code class="plaintext">readable(값)readable(값, 콜백)</code></pre><p>첫 번째 인수는 스토어의 값(초깃값)입니다.</p><p>두 번째 인수는 스토어 구독(자동, 수동)이 발생하면 실행될 콜백입니다.<br>콜백에서 반환하는 함수는 구독이 모두 취소되면(구독자가 모두 없어지면) 실행됩니다.<br>읽을 수만 있는 스토어기 때문에 초깃값을 최초 한 번 수정할 수 있도록 콜백에서 <code>set</code> 함수(매개변수)를 사용할 수 있습니다.</p><pre><code class="js">import { readable } from &#39;svelte/store&#39;export let store = writable(&#39;값&#39;, set =&gt; {  // 구독자가 1명 이상이 되면 실행!  set(&#39;값&#39;)  return () =&gt; {    // 구독자가 0명이 되면 실행!  }})</code></pre><h4><span id="b9d39e0c-457b-4608-8486-22b067cab070">사용 패턴</span><a href="#b9d39e0c-457b-4608-8486-22b067cab070" class="header-anchor"></a></h4><p><a href="https://svelte.dev/repl/e4840e62287f4b44b35388276b33cea7?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Readable from &#39;./Readable.svelte&#39;  let toggle = true&lt;/script&gt;&lt;button on:click={() =&gt; toggle = !toggle}&gt;  Toggle&lt;/button&gt;{#if toggle}  &lt;Readable /&gt;{/if}</code></pre><div class="filename">Readable.svelte</div><pre><code class="svelte">&lt;script&gt;  import { user } from &#39;./store.js&#39;  // 어떤 메소드를 가지는지 Readable 스토어 출력하기!  console.log(user)  console.log($user)&lt;/script&gt;&lt;button on:click={() =&gt; {$user.name = &#39;Neo&#39;}}&gt;  Click!&lt;/button&gt;&lt;h1&gt;{$user.name}&lt;/h1&gt;</code></pre><div class="filename">store.js</div><pre><code class="js">import { readable } from &#39;svelte/store&#39;const userData = {  name: &#39;Heropy&#39;,  age: 85,  email: &#39;thesecon@gmail.com&#39;,  token: &#39;Ag1oy1hsdSDe&#39;}export let user = readable(userData, (set) =&gt; {  console.log(&#39;user 구독자가 1명 이상일 때!&#39;)  delete userData.token  set(userData)  return () =&gt; {    console.log(&#39;user 구독자가 0명일 때...&#39;)  }})</code></pre><h3><span id="58263a4e-c90c-416f-9f45-0018125b02e9">계산된 스토어</span><a href="#58263a4e-c90c-416f-9f45-0018125b02e9" class="header-anchor"></a></h3><p>쓰기 가능(<code>writable</code>)하거나 읽기 전용(<code>readable</code>) 스토어를 통해 새롭게 계산한 값을 가지는 스토어를 생성합니다.</p><blockquote><p>유독 사용 패턴이 많지만 앞선 ‘쓰기 가능’, ‘읽기 전용’ 스토어 파트를 이해했다면 특별히 어려운 건 없습니다.</p></blockquote><p>첫 번째 인수는 계산에 사용할 스토어이고,<br>계산할 스토어가 2개 이상인 경우 첫 번째 인수를 배열로 처리해야 합니다.</p><p>두 번째 인수는 스토어 구독(자동, 수동)이 발생하면 실행될 콜백입니다.<br>콜백에서 반환하는 함수는 구독이 모두 취소되면(구독자가 모두 없어지면) 실행됩니다.</p><p>세 번째 인수는 계산이 완료되기 전에(비동기 요청 등) 최초 한 번 출력할 초깃값입니다.</p><pre><code class="plaintext">derived(스토어, 콜백)derived(스토어, 콜백, 초깃값)derived([스토어1, 스토어2], 콜백)derived([스토어1, 스토어2], 콜백, 초깃값)</code></pre><p>콜백에서 스토어를 사용해 계산할 수 있습니다.</p><p>첫 번째 인수는 앞서 명시된 스토어의 값을 매개변수로 받습니다.<br>앞서 명시된 스토어가 배열로 처리되었다면, 값도 순서대로 배열로 받아야 합니다.</p><blockquote><p>따로 기능이 있는 것은 아니고 통상적으로 매개변수 앞에 <code>$</code>를 붙여서 ‘스토어의 값’이라는 의미를 부여합니다.<br>컴포넌트에서 사용하는 ‘스토어 자동 구독(Auto-subscription)’과는 관계가 없습니다.</p></blockquote><p>두 번째 인수는 <code>set</code> 매개변수입니다.<br>콜백에서 ‘계산된 값’을 반환하면 스토어(<code>derived</code>)에 반영되는데,<br><code>set</code> 매겨변수가 명시되어 있으면, 반환되는 값은 ‘구독이 모두 취소되면 실행할 함수’가 됩니다.</p><blockquote><p>‘구독이 모두 취소되면 실행할 함수’가 필요하지 않은 일반적으로는 <code>set</code> 매개변수를 명시하지 마세요.</p></blockquote><p>다음 패턴에선 이해하기 쉽게 일반 함수을 사용했지만, 보통은 화살표 함수 사용을 권장합니다.</p><pre><code class="plaintext">function ($스토어) {  // 계산..  return 계산된_값}function ([$스토어1, $스토어2]) {  // 계산..  return 계산된_값}function ($스토어, set) {  // 계산..  set(계산된_값)  return 구독이_모두_취소되면_실행할_함수}</code></pre><h4><span id="f0c13db7-bd0b-4d24-9b23-cf56c8434c4d">사용 패턴</span><a href="#f0c13db7-bd0b-4d24-9b23-cf56c8434c4d" class="header-anchor"></a></h4><p><a href="https://svelte.dev/repl/dac9457f4e2a4ad082347ccf32d860b5?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Derived from &#39;./Derived.svelte&#39;  let toggle = true&lt;/script&gt;&lt;button on:click={() =&gt; toggle = !toggle}&gt;  Toggle&lt;/button&gt;{#if toggle}  &lt;Derived /&gt;{/if}</code></pre><div class="filename">Derived.svelte</div><pre><code class="svelte">&lt;script&gt;  import { count, double, total, initialValue } from &#39;./store.js&#39;  console.log(initialValue)  console.log($count, $double)  total.subscribe($total =&gt; {    console.log($total)  })&lt;/script&gt;&lt;button on:click={() =&gt; $count += 1}&gt;  Click!&lt;/button&gt;&lt;h1&gt;total: {$total}&lt;/h1&gt;&lt;h2&gt;count: {$count}&lt;/h2&gt;&lt;h2&gt;double: {$double}&lt;/h2&gt;&lt;h2&gt;count+1(after 1s): {$initialValue}&lt;/h2&gt;</code></pre><div class="filename">store.js</div><pre><code class="js">import { writable, derived } from &#39;svelte/store&#39;export let count = writable(1)export let double = derived(count, $count =&gt; $count * 2)export let total = derived(  [count, double],  ([$count, $double], set) =&gt; {    console.log(&#39;total 구독자가 1명 이상일 때!&#39;)    set($count + $double)    return () =&gt; {      console.log(&#39;total 구독자가 0명일 때...&#39;)    }  })export let initialValue = derived(  count,  ($count, set) =&gt; {    setTimeout(() =&gt; set($count + 1), 1000)  },   &#39;최초 계산 중...&#39;)</code></pre><h3><span id="b821d1ff-969a-4ade-9476-41078a14afe5">스토어 값 얻기</span><a href="#b821d1ff-969a-4ade-9476-41078a14afe5" class="header-anchor"></a></h3><p>구독하지 않고도 스토어의 값을 얻을 수 있습니다.</p><p><a href="https://svelte.dev/repl/6e05a5d1d33642ff8ccc2d9b7af20379?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { get } from &#39;svelte/store&#39;  import { count, double, user } from &#39;./store.js&#39;  console.log(get(count))  console.log(get(double))  console.log(get(user))&lt;/script&gt;</code></pre><div class="filename">store.js</div><pre><code class="js">import { writable, readable, derived } from &#39;svelte/store&#39;export let count = writable(1)export let double = derived(count, $count =&gt; $count * 2)export let user = readable({  name: &#39;Heropy&#39;,  age: 85,  email: &#39;thesecon@gmail.com&#39;})</code></pre><h3><span id="dbf4a640-9885-4409-ae1d-d3d2a8f9cd63">커스텀 스토어</span><a href="#dbf4a640-9885-4409-ae1d-d3d2a8f9cd63" class="header-anchor"></a></h3><p>스토어 객체의 메소드(<code>set</code>, <code>update</code>, <code>subscribe</code>)가 포함된 객체를 ‘커스텀 스토어’라고 합니다.<br>다른 속성이나 메소드를 사용할 수 있는 장점이 있습니다.<br>스토어의 수동/자동 구독을 위해서 <code>subscribe</code> 메소드는 필수로 포함해야 합니다!</p><pre><code class="js">import { writable } from &#39;svelte/store&#39;const store = writable(7)export let customStore = {  subscribe: store.subscribe,  a: () =&gt; {},  b: () =&gt; {},  x: &#39;abc&#39;,  y: 123}</code></pre><pre><code class="svelte">&lt;script&gt;  import { customStore } from &#39;./store.js&#39;  // Auto-subscription  console.log($customStore) // 7&lt;/script&gt;</code></pre><p><a href="https://svelte.dev/repl/e2ac8a29dfdd4b7bb6cc782c8b91df1b?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { fruits } from &#39;./fruits.js&#39;  let value&lt;/script&gt;&lt;input bind:value /&gt;&lt;button on:click={() =&gt; fruits.setItem(value)}&gt;  Add fruit!  &lt;/button&gt;&lt;button on:click={() =&gt; console.log(fruits.getList())}&gt;  Log fruit list!&lt;/button&gt;&lt;ul&gt;  {#each $fruits as {id, name} (id)}    &lt;li&gt;{name}&lt;/li&gt;  {/each}&lt;/ul&gt;</code></pre><div class="filename">fruits.js</div><pre><code class="js">import { writable, get } from &#39;svelte/store&#39;const _fruits = writable([  { id: 1, name: &#39;Apple&#39; },  { id: 2, name: &#39;Banana&#39; },  { id: 3, name: &#39;Cherry&#39; }])export let fruits = {  ..._fruits,  getList: () =&gt; get(_fruits).map(f =&gt; f.name),  setItem: (name) =&gt; _fruits.update(f =&gt; {    f.push({      id: f.length + 1,      name    })    console.log(f)    return f  })}</code></pre><h2><span id="0a7c2b76-66ff-401e-aa19-9cf550abe248">액션</span><a href="#0a7c2b76-66ff-401e-aa19-9cf550abe248" class="header-anchor"></a></h2><p><code>use</code> 지시어를 사용해 연결된 요소가 생성될 때 호출할 함수를 지정할 수 있습니다.<br>이 함수를 ‘액션(Action)’이라고 합니다.<br>요소를 사용하는 플러그인(모듈)을 제작할 때 유용합니다.</p><pre><code class="plaintext">&lt;요소 use:함수이름&gt;&lt;/요소&gt;&lt;요소 use:함수이름={인수}&gt;&lt;/요소&gt;</code></pre><p>연결된 함수는 다음과 같은 구조를 가집니다.</p><pre><code class="plaintext">function 함수이름(요소, 인수) {  // Logic..  return {    update(인수) {}, // &#39;인수&#39;가 변경되면 실행됩니다.    destroy() {} // &#39;요소&#39;가 제거되면 실행됩니다.  }}</code></pre><p><a href="https://svelte.dev/repl/11d88c715641499c92ec70cd4813e1c2?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let toggle = true  let width = 200  function hello(node, options = {}) {    console.log(&#39;Init hello function!&#39;)    const {       width = &#39;100px&#39;,       height = &#39;100px&#39;,       color = &#39;tomato&#39;     } = options    node.style.width = width    node.style.height = height    node.style.backgroundColor = color    return {      update: (opts) =&gt; {        console.log(&#39;update!&#39;, opts)      },      destroy: () =&gt; {        console.log(&#39;destroy!&#39;)      }    }  }&lt;/script&gt;&lt;button on:click={() =&gt; toggle = !toggle}&gt;  Toggle!&lt;/button&gt;&lt;button on:click={() =&gt; width += 20}&gt;  Size up!&lt;/button&gt;&lt;div use:hello&gt;&lt;/div&gt;{#if toggle}  &lt;div use:hello={{    width: `${width}px`,    color: 'royalblue'  }}&gt;&lt;/div&gt;{/if}</code></pre><p><a href="https://svelte.dev/repl/2cf17ac3b4ea47a0ade11361c242e068?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import { zoom } from &#39;./zoom.js&#39;&lt;/script&gt;&lt;div use:zoom&gt;&lt;/div&gt;&lt;div use:zoom={0.7}&gt;&lt;/div&gt;&lt;style&gt;  div {    width: 100px;    height: 100px;    background-color: tomato;  }&lt;/style&gt;</code></pre><div class="filename">zoom.js</div><pre><code class="js">export function zoom(node, scale = 1.5) {  node.style.transition = &#39;1s&#39;  function zoomIn() {    node.style.transform = `scale(${scale})`  }  function zoomOut() {    node.style.transform = &#39;scale(1)&#39;  }  node.addEventListener(&#39;mouseenter&#39;, zoomIn)  node.addEventListener(&#39;mouseleave&#39;, zoomOut)  return {    destroy() {      node.removeEventListener(&#39;mouseenter&#39;, zoomIn)      node.removeEventListener(&#39;mouseleave&#39;, zoomOut)    }  }}</code></pre><h2><span id="cd7e6d07-d9b0-44f7-9037-9ff18f107ede">특별한 요소</span><a href="#cd7e6d07-d9b0-44f7-9037-9ff18f107ede" class="header-anchor"></a></h2><h3><span id="9e00f28a-d082-4b1b-a61b-92e9c5da653e">self</span><a href="#9e00f28a-d082-4b1b-a61b-92e9c5da653e" class="header-anchor"></a></h3><p>컴포넌트 자신을 재귀적으로 포함합니다.<br>A 컴포넌트 내부에서 다시 A 컴포넌트를 사용하는 방법입니다.</p><pre><code class="svelte">&lt;svelte:self /&gt;</code></pre><p>다음의 재귀 함수와 같은 개념입니다.<br>무한루프에 빠지지 않도록 재귀 호출이 멈출 수 있는 조건이 붙어야 합니다.</p><pre><code class="js">function self() {  self()}self()</code></pre><p>다음 예제의 <code>address</code> 데이터를 활용할 때와 같이,<br>같은 컴포넌트가 반복적으로 사용될 수 있는 구조에서 유용합니다.<br>어떤 조건으로 재귀 호출이 멈추는지 확인해 보세요!</p><p><a href="https://svelte.dev/repl/7114e7233bfd48e0b47b88f59e0b40de?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Address from &#39;./Address.svelte&#39;  let address = {    label: &#39;대한민국&#39;,    children: [      {        label: &#39;경기도&#39;,        children: [          { label: &#39;수원&#39; },          { label: &#39;성남&#39; }        ]      },      {        label: &#39;강원도&#39;,        children: [          { label: &#39;강릉&#39; },          { label: &#39;속초&#39; }        ]      }    ]  }&lt;/script&gt;&lt;Address {address} /&gt;</code></pre><div class="filename">Address.svelte</div><pre><code class="svelte">&lt;script&gt;  export let address&lt;/script&gt;&lt;ul&gt;  &lt;li&gt;    {address.label}    {#if address.children}      {#each address.children as address}        &lt;svelte:self {address} /&gt;        {/each}    {/if}  &lt;/li&gt;&lt;/ul&gt;</code></pre><h3><span id="6fd571ae-5e5b-4d29-a7c2-edc80f0896c9">component</span><a href="#6fd571ae-5e5b-4d29-a7c2-edc80f0896c9" class="header-anchor"></a></h3><p>컴포넌트를 동적으로 렌더링할 때 사용합니다.<br><code>this</code> 속성에 컴포넌트 객체를 연결해야 합니다.</p><pre><code class="svelte">&lt;script&gt;  import MyComp from &#39;./MyComp.svelte&#39;&lt;/script&gt;&lt;svelte:component this={MyComp} /&gt;</code></pre><p><a href="https://svelte.dev/repl/08f86ae2d1c54ab2bf5f9d1818695450?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Heropy from &#39;./Heropy.svelte&#39;  import Neo from &#39;./Neo.svelte&#39;  import Anderson from &#39;./Anderson.svelte&#39;  let components = [    { name: &#39;Heropy&#39;, comp: Heropy },    { name: &#39;Neo&#39;, comp: Neo },    { name: &#39;Anderson&#39;, comp: Anderson }  ]  let index = 2  let selected = components[index - 1].comp&lt;/script&gt;{#each components as {name, comp}, i (name)}  &lt;label&gt;    &lt;input       type=&quot;radio&quot;       value={comp}       bind:group={selected}      on:change={() =&gt; index = i + 1} /&gt;    {name}  &lt;/label&gt;{/each}&lt;svelte:component   this={selected}   {index} /&gt;&lt;!-- &lt;div&gt;{selected}&lt;/div&gt; --&gt;</code></pre><div class="filename">Heropy.svelte</div><pre><code class="svelte">&lt;script&gt;  export let index&lt;/script&gt;&lt;h2&gt;{index}. Heropy!&lt;/h2&gt;</code></pre><div class="filename">Neo.svelte</div><pre><code class="svelte">&lt;script&gt;  export let index&lt;/script&gt;&lt;h2&gt;{index}. Neo?&lt;/h2&gt;</code></pre><div class="filename">Anderson.svelte</div><pre><code class="svelte">&lt;h2&gt;  Anderson~&lt;/h2&gt;</code></pre><h3><span id="a996236f-03c1-498d-940f-726b290cd8bc">window</span><a href="#a996236f-03c1-498d-940f-726b290cd8bc" class="header-anchor"></a></h3><p>컴포넌트가 파괴(제거)될 때 같이 제거할 Window 이벤트를 추가하거나,<br>SSR에서 window 객체의 존재를 확인하지 않고도 이벤트를 추가할 때 사용합니다.<br><code>bind</code> 지시어를 사용해 아래 명시된 속성들과 연결할 수 있습니다.</p><pre><code class="svelte">&lt;svelte:window   on:이벤트={핸들러}  bind:속성={데이터} /&gt;</code></pre><table><thead><tr><th>속성</th><th>특성</th><th>설명</th></tr></thead><tbody><tr><td>innerWidth</td><td>읽기 전용</td><td>뷰포트의 가로 너비</td></tr><tr><td>innerHeight</td><td>읽기 전용</td><td>뷰포트의 가로 너비</td></tr><tr><td>outerWidth</td><td>읽기 전용</td><td>브라우저 가로 너비</td></tr><tr><td>outerHeight</td><td>읽기 전용</td><td>브라우저 가로 너비</td></tr><tr><td>online</td><td>읽기 전용</td><td>네트워크 상태</td></tr><tr><td>scrollX</td><td>쓰기 가능</td><td>스크롤 X좌표</td></tr><tr><td>scrollY</td><td>쓰기 가능</td><td>스크롤 Y좌표</td></tr></tbody></table><p><a href="https://svelte.dev/repl/aa24e0b95bad4d06a5c8d0d051b521b4?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  let key = &#39;&#39;  let innerWidth  let innerHeight  let outerWidth  let outerHeight  let online  let scrollX  let scrollY  // window.addEventListener(&#39;keydown&#39;, event =&gt; {  //   key += event.key  // })&lt;/script&gt;&lt;svelte:window   on:keydown={e =&gt; key = e.key}  bind:innerWidth={innerWidth}  bind:innerHeight  bind:outerWidth  bind:outerHeight  bind:online  bind:scrollX  bind:scrollY /&gt;&lt;h1&gt;{key}&lt;/h1&gt;&lt;div&gt;innerWidth: {innerWidth}&lt;/div&gt;&lt;div&gt;innerHeight: {innerHeight}&lt;/div&gt;&lt;div&gt;outerWidth: {outerWidth}&lt;/div&gt;&lt;div&gt;outerHeight: {outerHeight}&lt;/div&gt;&lt;div&gt;online: {online}&lt;/div&gt;&lt;div class=&quot;fixed&quot;&gt;  &lt;input type=&quot;number&quot; bind:value={scrollX} /&gt;  &lt;input type=&quot;number&quot; bind:value={scrollY} /&gt;  &lt;/div&gt;&lt;div class=&quot;for-scroll&quot;&gt;&lt;/div&gt;&lt;style&gt;  .fixed {    position: fixed;    top: 10px;    right: 10px;  }  .for-scroll {    width: 2000px;    height: 2000px;  }&lt;/style&gt;</code></pre><h3><span id="ba491066-8add-4c89-b255-b0ae81b03806">head, body</span><a href="#ba491066-8add-4c89-b255-b0ae81b03806" class="header-anchor"></a></h3><p><code>document.head</code>를 통해 정보 요소(META, LINK..)를 삽입하거나,<br><code>document.body</code>에 이벤트를 추가할 수 있습니다.<br>해당 컴포넌트가 파괴(제거)될 때 같이 제거됩니다.</p><pre><code class="svelte">&lt;svelte:head&gt;&lt;/svelte:head&gt;&lt;svelte:body /&gt;</code></pre><p><a href="https://svelte.dev/repl/e02ef1d7e75d4657aa2bd62f9481f775?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Heropy from &#39;./Heropy.svelte&#39;  let toggle = false&lt;/script&gt;&lt;button on:click={() =&gt; toggle = !toggle}&gt;  Toggle!&lt;/button&gt;{#if toggle}  &lt;Heropy /&gt;{/if}</code></pre><div class="filename">Heropy.svelte</div><pre><code class="svelte">&lt;svelte:head&gt;  &lt;link rel=&quot;stylesheet&quot; href=&quot;./main.css&quot; /&gt;  &lt;style&gt;    body {      background-color: orange;    }  &lt;/style&gt;&lt;/svelte:head&gt;&lt;svelte:body on:mousemove={e =&gt; console.log(e.clientX, e.clientY)} /&gt;&lt;h1&gt;Heropy!&lt;/h1&gt;</code></pre><h3><span id="19f4a07e-b858-4edf-beb5-44ff9bb19d61">options</span><a href="#19f4a07e-b858-4edf-beb5-44ff9bb19d61" class="header-anchor"></a></h3><pre><code class="svelte">&lt;svelte:options 속성={값} /&gt;</code></pre><h4><span id="84460d43-8c73-43ab-aa54-bd4fef9366d3">불변성 선언(immutable)</span><a href="#84460d43-8c73-43ab-aa54-bd4fef9366d3" class="header-anchor"></a></h4><p>가변성(같은 메모리 주소를 참조)을 가지는 객체 타입(object, array, function..)의 특성으로 인해,<br>Svelte의 할당은 불필요한 반응성(Reactive, DOM 업데이트)을 가질 수 있습니다.<br>컴포넌트가 전달받은 Props의 데이터 불변성(Immutable)을 선언합니다.</p><pre><code class="svelte">&lt;svelte:options immutable={true} /&gt;&lt;svelte:options immutable /&gt;</code></pre><p>해당 Props의 불변성이 확인되면 Svelte는 기존 Props와 새로운 Props를 동등 연산자로 비교하고,<br>그 결과가 <code>false</code>가 되면 반응성을 갱신합니다.</p><pre><code class="plaintext">기존_Props === 새로운_Props</code></pre><p>Fruit 컴포넌트의 옵션을 활성화하고 테스트해보세요!</p><p><a href="https://svelte.dev/repl/6392d60f17c44329b0d8087a651e4b0b?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Fruit from &#39;./Fruit.svelte&#39;  let fruits = [    { id: 1, name: &#39;Apple&#39; },    { id: 2, name: &#39;Banana&#39; },    { id: 3, name: &#39;Cherry&#39; },    { id: 5, name: &#39;Mango&#39; },    { id: 4, name: &#39;Orange&#39; }  ]&lt;/script&gt;&lt;button on:click={() =&gt; {  fruits[0] = { id: 1, name: &#39;Apple&#39; }  fruits = fruits}}&gt;  Update!&lt;/button&gt;{#each fruits as fruit (fruit.id)}  &lt;Fruit {fruit} /&gt;{/each}</code></pre><div class="filename">Fruit.svelte</div><pre><code class="svelte">&lt;script&gt;  import { afterUpdate } from &#39;svelte&#39;  export let fruit  // 기존 fruit === 새로운 fruit  let updateCount = 0  afterUpdate(() =&gt; {    updateCount += 1  })&lt;/script&gt;&lt;!-- &lt;svelte:options immutable /&gt; --&gt;&lt;div&gt;{fruit.name}({updateCount})&lt;/div&gt;</code></pre><h4><span id="f9237e30-b709-4b30-85be-c7624b7d081d">접근 허용(accessors)</span><a href="#f9237e30-b709-4b30-85be-c7624b7d081d" class="header-anchor"></a></h4><p>외부에서 컴포넌트의 데이터 혹은 함수에 접근을 허용할 때 사용합니다.<br>단, 허용할 데이터나 함수에 <code>export</code>를 사용해야 합니다.</p><pre><code class="svelte">&lt;svelte:options accessors={true} /&gt;&lt;svelte:options accessors /&gt;</code></pre><p>Heropy 컴포넌트의 옵션을 비활성화하고 테스트해보세요!</p><p><a href="https://svelte.dev/repl/811a7e65d9524b3ea20d5c5fe4d48649?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><div class="filename">App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Heropy from &#39;./Heropy.svelte&#39;  let heropy  function handler() {    console.log(heropy)    console.log(heropy.name)    console.log(heropy.getAge())  }&lt;/script&gt;&lt;button on:click={handler}&gt;  Toggle!&lt;/button&gt;&lt;Heropy bind:this={heropy} /&gt;</code></pre><div class="filename">Heropy.svelte</div><pre><code class="svelte">&lt;svelte:options accessors /&gt;&lt;script&gt;  let age = 85  export let name = &#39;Heropy&#39;  export function getAge() {    console.log(age)  }&lt;/script&gt;&lt;h1 on:click={getAge}&gt;{name}!&lt;/h1&gt;</code></pre><h1><span id="2d4adc77-e425-4432-b26f-422bd53be1f7">Svelte Animation API</span><a href="#2d4adc77-e425-4432-b26f-422bd53be1f7" class="header-anchor"></a></h1><h2><span id="bb797741-0cac-4bf4-be03-279c5b5c7a51">모션</span><a href="#bb797741-0cac-4bf4-be03-279c5b5c7a51" class="header-anchor"></a></h2><h3><span id="39e9885b-5b9e-489d-af47-ff7b0d8d36bd">tweened</span><a href="#39e9885b-5b9e-489d-af47-ff7b0d8d36bd" class="header-anchor"></a></h3><p><code>tweened</code>는 지정된 값을 정해진 시간 동안 업데이트하는 재미있는 기능으로 스토어 객체를 반환하는 것에 주의해야 합니다.<br>다음과 같이 작성할 수 있습니다.</p><pre><code class="js">import { tweened } from &#39;svelte/motion&#39;const store = tweened(1, { // 첫 번째 인수로 기본값 설정  delay: 0, // 값을 업데이트 하기 전에 대기 시간  duration: 400, // 값을 업데이트 하는 시간  easing: t =&gt; t, // Same as `linear`, 타이밍 함수  interpolate: (a, b) =&gt; t =&gt; value // 보간 함수})</code></pre><p><code>tweened</code>가 스토어 객체를 반환하기 때문에 <code>number</code>을 업데이트하기 위해 <code>$</code>(<code>$number += 1</code>)를 사용합니다.<br><code>number.update(n =&gt; n + 1)</code>과 같이 <code>update</code> 메소드를 사용할 수도 있습니다.</p><pre><code class="svelte">&lt;script&gt;  import { tweened } from &#39;svelte/motion&#39;  import { linear } from &#39;svelte/easing&#39;  const number = tweened(1, {    duration: 1000,    // easing: linear  // Default value!  })  $: fixedNumber = $number.toFixed(2)&lt;/script&gt;&lt;h1&gt;  {fixedNumber}&lt;/h1&gt;&lt;button on:click={() =&gt; $number += 1}&gt;  Increase!&lt;/button&gt;</code></pre><p><img src="/images/screenshot/svelte/svelte-tweened-example.gif" alt="Svelte tweened store example"></p><div class="image-caption">Tweened Store 예제 결과</div><h1><span id="9fe1ff14-536a-450e-b631-36a617611bc8">Router</span><a href="#9fe1ff14-536a-450e-b631-36a617611bc8" class="header-anchor"></a></h1><h2><span id="fa732cba-b87d-442c-89f2-101a87c0f433">svelte-spa-router</span><a href="#fa732cba-b87d-442c-89f2-101a87c0f433" class="header-anchor"></a></h2><p><a href="https://github.com/ItalyPaleAle/svelte-spa-router" target="_blank" rel="noopener">svelte-spa-router</a>는 <strong>SPA(Single Page Application)</strong>을 위한 라우터 모듈입니다.<br>Svelte REPL에서도 사용할 수 있습니다.</p><p><a href="https://svelte.dev/repl/c35ccfc5a3914983b386929b0e3c82fd?version=3.29.4" target="_blank" rel="noopener">REPL에서 예제 보기 &gt;</a></p><pre><code class="bash">$ npm i -D svelte-spa-router</code></pre><p><code>routes.js</code>를 생성해 다음과 같이 Route로 정의할 컴포넌트를 연결합니다.<br>이 컴포넌트들은 <code>/routes</code> 디렉터리에 생성합니다.</p><div class="filename">src/routes.js</div><pre><code class="js">import Home from &#39;./routes/Home.svelte&#39;import About from &#39;./routes/About.svelte&#39;import Blog from &#39;./routes/Blog.svelte&#39;const routes = {  &#39;/&#39;: Home,  &#39;/about&#39;: About,  &#39;/blog&#39;: Blog}export default routes</code></pre><p>위 설정 파일을 <code>App.svelte</code>에서 가져와 다음과 같이 연결합니다.<br>각 경로로 이동할 수 있게 <code>Header.svelte</code> 컴포넌트도 같이 추가합니다.</p><p><img src="/images/screenshot/svelte/svelte-spa-router-default-directory-structure.jpg" alt="Svelte SPA Router default directory structure"></p><div class="filename">src/App.svelte</div><pre><code class="svelte">&lt;script&gt;  import Router from &#39;svelte-spa-router&#39;  import routes from &#39;./routes&#39;  import Header from &#39;./components/Header.svelte&#39;&lt;/script&gt;&lt;Header /&gt;&lt;Router {routes} /&gt;</code></pre><div class="filename">src/components/Header.svelte</div><pre><code class="svelte">&lt;script&gt;  import { link } from &#39;svelte-spa-router&#39;  import active from &#39;svelte-spa-router/active&#39;&lt;/script&gt;&lt;header&gt;  &lt;a     href=&quot;/&quot;    use:link    use:active&gt;    Home  &lt;/a&gt;  &lt;a     href=&quot;/about&quot;    use:link    use:active&gt;    About  &lt;/a&gt;  &lt;a     href=&quot;/blog&quot;    use:link    use:active&gt;    Blog  &lt;/a&gt;&lt;/header&gt;&lt;style&gt;  :global(header a.active) {    font-weight: bold;    text-decoration: underline;  }&lt;/style&gt;</code></pre><p><img src="/images/screenshot/svelte/svelte-spa-router-default-example.gif" alt="Svelte SPA Router Example"></p><h1><span id="54744aff-c44e-487e-b539-5bc336d47d51">기능</span><a href="#54744aff-c44e-487e-b539-5bc336d47d51" class="header-anchor"></a></h1><h2><span id="91732805-1c75-49d7-9cef-8d74d17dda73">전/후 처리기(svelte-preprocess)</span><a href="#91732805-1c75-49d7-9cef-8d74d17dda73" class="header-anchor"></a></h2><p><a href="https://github.com/kaisermann/svelte-preprocess" target="_blank" rel="noopener">svelte-preprocess</a>는 다음과 같은 전/후 처리기들을 지원합니다.<br><a href="https://github.com/sveltejs/svelte-preprocess/tree/master/docs" target="_blank" rel="noopener">Svelte Preprocess Documentation</a></p><ul><li>Babel</li><li>TypeScript</li><li>CoffeeScript</li><li>Pug</li><li>PostCSS / SugarSS</li><li>Sass(SCSS)</li><li>Less</li><li>Stylus</li><li>globalStyle</li><li>replace</li></ul><h3><span id="fc3d4ec7-f7d5-46ad-b353-24f6c604146d">자동 전처리 모드(Auto Preprocessing mode)</span><a href="#fc3d4ec7-f7d5-46ad-b353-24f6c604146d" class="header-anchor"></a></h3><p>svelte-preprocess는 요소의 <code>src</code>, <code>lang</code>, <code>type</code> 속성을 기반으로 각각의 전처리기를 자동으로 사용합니다.</p><pre><code class="svelte">&lt;template lang=&quot;pug&quot;&gt;&lt;/template&gt;&lt;script lang=&quot;ts&quot;&gt;&lt;/script&gt;&lt;script lang=&quot;coffee&quot;&gt;&lt;/script&gt;&lt;style type=&quot;text/less&quot;&gt;&lt;/style&gt;&lt;style lang=&quot;scss&quot;&gt;&lt;/style&gt;&lt;style src=&quot;./my-style.styl&quot;&gt;&lt;/style&gt;</code></pre><h3><span id="2836c4ad-f9ac-4260-ac8c-1d148b4eea1c">구성</span><a href="#2836c4ad-f9ac-4260-ac8c-1d148b4eea1c" class="header-anchor"></a></h3><h4><span id="21a30f69-e792-416c-a50a-07222de66c50">Rollup</span><a href="#21a30f69-e792-416c-a50a-07222de66c50" class="header-anchor"></a></h4><pre><code class="bash">$ npm i -D rollup-plugin-svelte svelte-preprocess</code></pre><div class="filename">rollup.config.js</div><pre><code class="js">import svelte from &#39;rollup-plugin-svelte&#39;;import sveltePreprocess from &#39;svelte-preprocess&#39;;export default {  plugins: [    svelte({      preprocess: sveltePreprocess({        // 옵션..      })    })  ]};</code></pre><h4><span id="7d5e9e2d-aaf7-469c-b720-f18efbde2fdf">Snowpack</span><a href="#7d5e9e2d-aaf7-469c-b720-f18efbde2fdf" class="header-anchor"></a></h4><p><strong>@snowpack/plugin-svelte</strong>를 설치하면 <strong>svelte-preprocess</strong>이 같이 설치됩니다.</p><pre><code class="bash">$ npm i -D @snowpack/plugin-svelte</code></pre><div class="filename">snowpack.config.js</div><pre><code class="js">import sveltePreprocess from &#39;svelte-preprocess&#39;;module.exports = {  plugins: [    [&#39;@snowpack/plugin-svelte&#39;, {      preprocess: sveltePreprocess({        // 옵션..      })    }]  ]};</code></pre><h2><span id="689d04d6-14ab-4228-a76c-3200be05b527">경로 별칭</span><a href="#689d04d6-14ab-4228-a76c-3200be05b527" class="header-anchor"></a></h2><pre><code class="svelte">&lt;script&gt;  // 상대 경로인 경우..  import MyComponent from &#39;../../../components/MyComponent&#39;&lt;/script&gt;</code></pre><p>이하 각 빌드 구성을 마치면,<br>경로에서 다음과 같이 <code>~</code> 혹은 <code>@</code>를 별칭으로 사용할 수 있습니다.</p><pre><code class="svelte">&lt;script&gt;  import MyComponent from &#39;~/components/MyComponent&#39;;&lt;/script&gt;</code></pre><h3><span id="49f30b17-1254-4dc2-bf40-3148970a135a">Rollup</span><a href="#49f30b17-1254-4dc2-bf40-3148970a135a" class="header-anchor"></a></h3><p>다음과 같이 <a href="https://github.com/rollup/plugins/tree/master/packages/alias" target="_blank" rel="noopener">@rollup/plugin-alias</a>를 설치하고 <code>rollup.config.js</code>를 설정합니다.</p><pre><code class="bash">$ npm i -D @rollup/plugin-alias</code></pre><div class="filename">rollup.config.js</div><pre><code class="js">import path from &#39;path&#39;;import alias from &#39;@rollup/plugin-alias&#39;;export default {  plugins: [    alias({      entries: [        { find: &#39;~&#39;, replacement: path.resolve(__dirname, &#39;src/&#39;) },        { find: &#39;@&#39;, replacement: path.resolve(__dirname, &#39;src/&#39;) }      ]    })  ]};</code></pre><h3><span id="1c30f1af-10db-4a53-8b8d-696d06e8bdbe">Snowpack</span><a href="#1c30f1af-10db-4a53-8b8d-696d06e8bdbe" class="header-anchor"></a></h3><p>Snowpack에는 경로 별칭 기능이 내장되어 있습니다.<br>다음과 같이 구성합니다.</p><div class="filename">snowpack.config.js</div><pre><code class="js">module.exports = {  alias: {    &#39;~&#39;: &#39;./src&#39;,    &#39;@&#39;: &#39;./src&#39;  }};</code></pre><h1><span id="1dbad537-f3ec-4046-ba24-cb2e7e41e6e4">Unit Test</span><a href="#1dbad537-f3ec-4046-ba24-cb2e7e41e6e4" class="header-anchor"></a></h1><h2><span id="7ca6b33d-2565-4ce8-b954-290005878d6b">Jest</span><a href="#7ca6b33d-2565-4ce8-b954-290005878d6b" class="header-anchor"></a></h2><p><a href="https://jestjs.io/docs/en/getting-started" target="_blank" rel="noopener">Jest</a>를 사용해 Test 환경을 구성합니다.</p><blockquote><p>Jest 24버전부터 Babel 6버전의 지원이 중단되었습니다. Jest 23버전의 설치와는 방법이 조금 다릅니다.</p></blockquote><pre><code class="bash">$ npm i -D jest @babel/core @babel/preset-env babel-core@7.0.0-bridge.0 babel-jest @testing-library/svelte jest-transform-svelte</code></pre><ul><li><code>babel-jest</code>: Jest를 위한 Babel을 구성.</li><li><code>babel-core@7.0.0-bridge.0</code>: <code>babel-jest</code>를 <code>@babel/*</code>패키지에서 정상적으로 동작시키기 위해 사용.</li><li><code>jest-transform-svelte</code>: Jest를 사용해 Svelte 컴포넌트를 정상적으로 동작시키기 위해 사용.</li></ul><p>Jest의 설정을 위해 <code>jest.config.js</code>을 생성합니다.<br>혹은 <code>package.json</code>에 <code>&quot;jest&quot;</code>블록을 선언할 수도 있습니다.</p><div class="filename">jest.config.js</div><pre><code class="js">const sveltePreprocess = require(&#39;svelte-preprocess&#39;); // If you use Svelte preprocess like SCSS or Autoprefixermodule.exports = {  transform: {    &#39;^.+\\.js$&#39;: &#39;babel-jest&#39;,    &#39;^.+\\.svelte$&#39;: [      &#39;jest-transform-svelte&#39;,      {        preprocess: sveltePreprocess(),        debug: false,        noStyles: true,        compilerOptions: {}      }    ]  },  moduleFileExtensions: [&#39;js&#39;, &#39;svelte&#39;],  moduleNameMapper: {    &#39;~(.*)$&#39;: &#39;&lt;rootDir&gt;/src/$1&#39;, // If you use Import path alias `~`  },  coverageReporters: [&#39;html&#39;],  bail: false,  verbose: false};</code></pre><blockquote><p>우리는 Babel 7버전을 설치했습니다.</p></blockquote><div class="filename">babel.config.js</div><pre><code class="js">module.exports = {  &#39;env&#39;: {    &#39;test&#39;: {      &#39;presets&#39;: [[&#39;@babel/preset-env&#39;, { &#39;targets&#39;: { &#39;node&#39;: &#39;current&#39; } }]]    }  }};</code></pre><p>간단한 설정이 끝났습니다.<br>빠른 테스트 실행을 위해 Watch 모드로 스크립트를 등록합니다.</p><div class="filename">package.json</div><pre><code class="json">{  &quot;_comment&quot;: &quot;package.json&quot;,  &quot;scripts&quot;: {    &quot;test&quot;: &quot;jest --watchAll&quot;  }}</code></pre><p>이제 다음과 같이 실행할 수 있습니다.</p><pre><code class="bash">$ npm test$ npm t # Alias</code></pre><p>정상적으로 동작하는지 테스트하기 위해,<br><code>__tests__</code> 디텍터리를 생성하고 테스트를 진행할 파일과 동일한 이름으로 <code>App.test.js</code>을 생성합니다.</p><pre><code class="bash">├─ /src│   ├─ /__tests__│   │   └─ App.test.js│   └─ App.svelte</code></pre><p>설정한 테스트 환경이 정상적으로 동작하는지 확인하기 위해 최소한의 코드만 작성합니다.</p><div class="filename">&lowbar;&lowbar;tests&lowbar;&lowbar;/App.test.js</div><pre><code class="js">import App from &#39;../App.svelte&#39;import { render, cleanup } from &#39;@testing-library/svelte&#39;beforeEach(cleanup) // Required!describe(&#39;App&#39;, () =&gt; {  test(&#39;정상적으로 동작해야 합니다&#39;, () =&gt; {    const { container } = render(App)    expect(container.nodeName).toBe(&#39;BODY&#39;)  })})</code></pre><p><img src="/images/screenshot/svelte/svelte-with-jest-success.jpg" alt="Svelte test with Jest"></p><p>다음은 <a href="https://github.com/testing-library/svelte-testing-library/blob/master/src/index.js" target="_blank" rel="noopener">svelte-testing-library</a>의 일부분으로 <code>render</code>의 반환 값을 확인할 수 있습니다.<br><code>getQueriesForElement</code>는 <a href="https://testing-library.com/docs/dom-testing-library/api-queries" target="_blank" rel="noopener">DOM Testing library Queries</a>에서 확인할 수 있습니다.</p><script src="https://gist.github.com/ParkYoungWoong/c6d1a152faa665df824f20ef0b5d23ca.js"></script><h2><span id="e23e0db3-6050-491e-9c36-67d056a9e639">Web Test Runner</span><a href="#e23e0db3-6050-491e-9c36-67d056a9e639" class="header-anchor"></a></h2><p><a href="https://modern-web.dev/docs/test-runner/overview/" target="_blank" rel="noopener">@web/test-runner</a>는 <strong>Snowpack 프로젝트에 권장되는 테스트 러너</strong> 입니다.<br>Jest보다 더 빠르고 실제 브라우저와 더 근접하게 일치하는 테스트 환경을 제공합니다.</p><p><a href="https://github.com/ParkYoungWoong/svelte-snowpack-template" target="_blank" rel="noopener">Svelte &amp; Snowpack 템플릿</a>로 쉽게 시작하고 테스트하세요!</p><div class="filename">web-test-runner.config.js</div><pre><code class="js">// NODE_ENV=test - Needed by &quot;@snowpack/web-test-runner-plugin&quot;process.env.NODE_ENV = &#39;test&#39;;module.exports = {  plugins: [require(&#39;@snowpack/web-test-runner-plugin&#39;)()],};</code></pre><h1><span id="1652873e-c74c-4baf-ae73-60756d778b11">Tools</span><a href="#1652873e-c74c-4baf-ae73-60756d778b11" class="header-anchor"></a></h1><h2><span id="68347524-f433-4e41-a67e-2c72811ac535">WebStorm plugin</span><a href="#68347524-f433-4e41-a67e-2c72811ac535" class="header-anchor"></a></h2><p><a href="https://plugins.jetbrains.com/plugin/12375-svelte/" target="_blank" rel="noopener">https://plugins.jetbrains.com/plugin/12375-svelte/</a></p><p>WebStorm을 위한 Svelte 플러그인이 있네요.<br>WebStorm 버전에 따라 플러그인 버전도 차이가 있을 수 있습니다.</p><p><img src="/images/screenshot/svelte/svelte-plugin-for-webstorm.jpg" alt="Svelte for Webstorm"></p><h2><span id="e84e057b-2dc6-468a-8207-590d28b97241">VS Code plugin</span><a href="#e84e057b-2dc6-468a-8207-590d28b97241" class="header-anchor"></a></h2><p><a href="https://marketplace.visualstudio.com/items?itemName=JamesBirtles.svelte-vscode" target="_blank" rel="noopener">https://marketplace.visualstudio.com/items?itemName=JamesBirtles.svelte-vscode</a></p><p>VS Code를 위한 플러그인도 있으니 확인해 보세요.</p><blockquote><p>‘Svelte for VS Code’ 확장 프로그램은, 기존 ‘Svelte’ 확장 프로그램(by James Birtles)과 통합된 버전입니다.</p></blockquote><p><img src="/images/screenshot/svelte/svelte-plugin-for-vscode.jpg" alt="Svelte for VS Code"></p><h3><span id="c0a1dc27-bd57-41d6-ac65-cff2d69195cb">Sass(SCSS) 에러 해결</span><a href="#c0a1dc27-bd57-41d6-ac65-cff2d69195cb" class="header-anchor"></a></h3><p>Svelte <code>&lt;style&gt;</code>에서 SCSS를 사용할 때,<br><br><code>node-sass</code>를 설치해도 VS Code에서 다음과 같은 에러가 발생할 수 있습니다.</p><pre><code class="error">Cannot find any of modules: sass,node-sass ...</code></pre><p><img src="/images/screenshot/svelte/issue1-cannot-find-module-node-sass.jpg" alt="Cannot find node-sass module"></p><p>확장 프로그램 <strong>Svelte for VS Code</strong>의 환경설정에서,<br><br><code>Svelte &gt; Language-server: Runtime</code> 옵션에 NodeJS 설치 경로를 입력하세요.<br><br>NodeJS 설치 경로는 터미널에서 다음과 같이 입력해 확인할 수 있습니다.</p><pre><code class="bash"># for Mac$ which node# for Windows$ where node</code></pre><p><img src="/images/screenshot/svelte/issue1-svelte-for-vs-code-extension-settings.jpg" alt="Svelte for VS Code extension settings"></p><p><img src="/images/screenshot/svelte/issue1-language-server-runtime.jpg" alt="Svelte language server: runtime"></p><p>설정 완료 후 VS Code를 재부팅하세요!</p><h2><span id="fb00a760-7e94-4223-8667-2bb52e411a6b">Svelte Devtools</span><a href="#fb00a760-7e94-4223-8667-2bb52e411a6b" class="header-anchor"></a></h2><p>For Chrome<br><a href="https://chrome.google.com/webstore/detail/svelte-devtools/ckolcbmkjpjmangdbmnkpjigpkddpogn" target="_blank" rel="noopener">https://chrome.google.com/webstore/detail/svelte-devtools/ckolcbmkjpjmangdbmnkpjigpkddpogn</a></p><p>For FireFox<br><a href="https://addons.mozilla.org/en-US/firefox/addon/svelte-devtools/" target="_blank" rel="noopener">https://addons.mozilla.org/en-US/firefox/addon/svelte-devtools/</a></p><p>Svelte 애플리케이션 디버깅을 위한 브라우저용 확장 프로그램입니다.<br>크롬과 파이어폭스 브라우저를 지원합니다.</p><p><img src="/images/screenshot/svelte/svelte-dev-tool.jpg" alt="Svelte Devtools"></p><div class="image-caption">구성이 단순하네요</div><h1><span id="189e5563-e77f-425a-8cd5-c05327aa44a9">참고 자료(References)</span><a href="#189e5563-e77f-425a-8cd5-c05327aa44a9" class="header-anchor"></a></h1><p><a href="https://svelte.dev" target="_blank" rel="noopener">https://svelte.dev</a></p>]]></content>
    
    <summary type="html">
    
      Svelte는 Rich Harris가 제작한 새로운 접근 방식을 가지는 프론트엔드 프레임워크입니다. Svelte는 자신을 &#39;프레임워크가 없는 프레임워크&#39; 혹은 &#39;컴파일러&#39;라고 소개합니다. 이는 Virtual(가상) DOM이 없고, Runtime(런타임)에 로드할 프레임워크가 없음을 의미합니다.
    
    </summary>
    
    
      <category term="sapper" scheme="https://heropy.blog/tags/sapper/"/>
    
      <category term="svelte.js" scheme="https://heropy.blog/tags/svelte-js/"/>
    
      <category term="routify" scheme="https://heropy.blog/tags/routify/"/>
    
  </entry>
  
  <entry>
    <title>CSS Grid 완벽 가이드</title>
    <link href="https://heropy.blog/2019/08/17/css-grid/"/>
    <id>https://heropy.blog/2019/08/17/css-grid/</id>
    <published>2019-08-17T14:59:59.000Z</published>
    <updated>2019-08-25T05:23:33.000Z</updated>
    
    <content type="html"><![CDATA[<p>CSS Grid(그리드)는 2차원(행과 열)의 레이아웃 시스템을 제공합니다.<br>Flexible Box도 훌륭하지만 비교적 단순한 1차원 레이아웃을 위하며, 좀 더 복잡한 레이아웃을 위해 우리는 CSS Grid를 사용할 수 있습니다.</p><blockquote><p>CSS Grid는 예전부터 핵(Hack)으로 불린 다양한 레이아웃 대체 방식들을 해결하기 위해 만들어진 특별한 CSS 모듈입니다.</p></blockquote><div class="toc"><ul><li><a href="c05aee8e-2f2a-4458-b9d7-4888f74936eb">CSS Grid</a></li><li><a href="e56a4287-494f-4c15-8405-de228e7992ee">Grid Properties</a><ul><li><a href="beb5e866-83a7-4d5e-bf8c-2aabea2d275e">Grid Container Properties</a></li><li><a href="64a2402a-6741-41fa-b293-c816fffe1a0d">Grid Item Properties</a></li></ul></li><li><a href="d48fae17-c3f1-404b-b0ea-a12b5e87bfb8">Grid Containers</a><ul><li><a href="6bc489a3-3e0e-44d0-b866-ee66496ca424">display</a></li><li><a href="0f59d6c5-f7c0-41ef-9a64-71f43f0f0ee8">grid-template-rows</a></li><li><a href="2afd3c7f-e0ae-4aff-9a88-f4fb2e172b65">grid-template-columns</a></li><li><a href="6cf262c9-4048-4d9d-94de-71f049bf41e7">grid-template-areas</a></li><li><a href="1a5cfae4-12db-4fa0-832a-692c9e6a3721">grid-template</a></li><li><a href="94488701-242a-4696-bda0-e5d62743beb9">row-gap(grid-row-gap)</a></li><li><a href="1409f591-689e-4dca-a157-4847e5a1d20e">column-gap(grid-column-gap)</a></li><li><a href="12c08d4c-89e4-4a5e-891f-1bf7fc8ad957">gap(grid-gap)</a></li><li><a href="4bc22f6b-9912-447b-aba6-c221500e66ef">grid-auto-rows</a></li><li><a href="aa307b14-60aa-431b-8e6c-1c007b0cdac6">grid-auto-columns</a></li><li><a href="c8a525d4-d4bf-47c1-be48-babae9897aa4">grid-auto-flow</a></li><li><a href="c632acad-f20d-461e-b26f-0616deef9f06">grid</a></li><li><a href="ec2ba5a7-a787-4299-a202-e7301e3a254b">align-content</a></li><li><a href="4b2d58b8-d1bf-496d-a069-9e7853bef2cb">justify-content</a></li><li><a href="e3cbe0f3-9fb5-4004-a683-23fba7a5e4e5">place-content</a></li><li><a href="67df11f1-36f8-4ceb-955c-32e5c56414d6">align-items</a></li><li><a href="cb6a9751-5ed7-4df6-9eb6-8f948981db62">justify-items</a></li><li><a href="f416b6c9-32a5-41ac-8536-80b1e7557a4a">place-items</a></li></ul></li><li><a href="1577c77c-8c25-43ea-b122-f005207e5e23">Grid Items</a><ul><li><a href="80e446b9-1cfa-4ac5-9624-dc25871c39bd">grid-row-start, grid-row-end, grid-column-start, grid-column-end</a></li><li><a href="e8078220-e1c2-41d2-83b5-f42331b78a87">grid-row</a></li><li><a href="16dd767b-1dee-4622-8ef1-8c68fd91e568">grid-column</a></li><li><a href="eb828793-910b-4f66-8dc4-6072f325c9fe">grid-area</a></li><li><a href="c8fd6c74-1553-4861-886b-0a2e2f924539">align-self</a></li><li><a href="c1cd9100-64ee-4667-8582-7d86ca660c1a">justify-self</a></li><li><a href="f2a864ce-de12-40de-8e48-60b52f6e0b57">place-self</a></li><li><a href="92cc973d-0f5c-42f9-820e-da777f64ebe7">order</a></li><li><a href="59df6afe-ac36-49bf-bfde-50569f27cc96">z-index</a></li></ul></li><li><a href="e00d4c09-f1cc-4d61-a71e-1ba6efd03c7e">Grid Functions</a><ul><li><a href="1ede60b9-0f8a-41d6-9fa1-dc54c5ee8c5f">repeat</a></li><li><a href="e30962de-d6b7-45f1-abe4-0a4724cef8e6">minmax</a></li><li><a href="4fab992e-c6cc-4e7f-9419-54234de25cd9">fit-content</a></li></ul></li><li><a href="28564ace-7241-4e86-a226-dfcfd984c8cc">Grid Units</a><ul><li><a href="d579b5e0-b74d-4230-9dd0-56458d0625ce">fr</a></li><li><a href="79441915-7467-442b-a426-2f1494df4a97">min-content</a></li><li><a href="609017f2-e357-4127-9b44-0abec87ff3d2">max-content</a></li><li><a href="bdaa38f9-6c13-4615-a081-02a82c10ad98">auto-fill, auto-fit</a><ul><li><a href="2404db24-2314-4eaa-bb2f-b7490d5402a0">auto-fill과 auto-fit의 차이</a></li></ul></li></ul></li><li><a href="507cc860-d8db-415d-8a4c-320d1bdb09ab">주요 용어 정리</a><ul><li><a href="7d8c8117-fe56-4c7f-a242-ba7fc60aa72d">Track</a></li><li><a href="0d8d961a-eb03-4f88-97de-8ce615ca1a36">Line</a></li><li><a href="92021c1a-5d46-40e6-84a9-72f89eb45425">Cell</a></li><li><a href="f4532d47-7a76-4527-9e4a-5a4665c3135b">Area</a></li></ul></li><li><a href="7ac567db-da05-4e3a-8851-7f5d524cff08">브라우저 지원</a></li><li><a href="598ede6f-6801-48cf-b67d-9efd1e41c1ce">참고 자료(References)</a></li></ul></div><h1><span id="c05aee8e-2f2a-4458-b9d7-4888f74936eb">CSS Grid</span><a href="#c05aee8e-2f2a-4458-b9d7-4888f74936eb" class="header-anchor"></a></h1><p>CSS Grid의 효율적인 학습을 위해서 <a href="https://www.mozilla.org/ko/firefox/new/" target="_blank" rel="noopener">파이어폭스 브라우저</a>를 사용해 테스트할 것을 추천합니다.<br>개발자 도구를 열고 요소를 검색해 표시된 <code>grid</code> 버튼을 선택합니다.</p><p><img src="/images/screenshot/css-grid/use-to-firefox.jpg" alt="CSS Grid"></p><p>CSS Grid를 처음 시작하시는 분들을 위해 간단한 테스트 영상을 준비했습니다.</p><div style="position:relative;padding-bottom:56.25%;height:0;margin-bottom:50px"><br><iframe style="position:absolute;top:0;left:0;width:100%;height:100%" src="https://www.youtube.com/embed/b0aSTppYUFE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br></div><p>다음은 위 영상과 이하 예제들에서 사용한 샘플 코드입니다.<br><a href="https://heropy.blog/2018/01/31/sass/">SCSS</a>로 작성되어 있지만, CSS 문법을 포함할 수 있음으로 <code>// Test here!</code> 이하에 <code>.container</code>와 <code>.item</code>을 정의해 보세요.<br>혹은 새로운 환경에서 Grid를 테스트해 보세요.</p><iframe height="585" style="width:100%" scrolling="no" title="Heropy CSS Grid Sample" src="//codepen.io/heropark/embed/ExYKWrR/?height=585&theme-id=0&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen><br>See the Pen <a href="https://codepen.io/heropark/pen/ExYKWrR/" target="_blank" rel="noopener">Heropy CSS Grid Sample</a> by park young woong<br>(<a href="https://codepen.io/heropark" target="_blank" rel="noopener">@heropark</a>) on <a href="https://codepen.io" target="_blank" rel="noopener">CodePen</a>.<br></iframe><h1><span id="e56a4287-494f-4c15-8405-de228e7992ee">Grid Properties</span><a href="#e56a4287-494f-4c15-8405-de228e7992ee" class="header-anchor"></a></h1><p>CSS Grid는 <a href="https://heropy.blog/2018/11/24/css-flexible-box/">CSS Flex</a>와 같이 Container(컨테이너)와 Item(아이템)이라는 두 가지 개념으로 구분되어 있습니다.<br>Container는 Items를 감싸는 부모 요소이며, 그 안에서 각 Item을 배치할 수 있습니다.</p><h2><span id="beb5e866-83a7-4d5e-bf8c-2aabea2d275e">Grid Container Properties</span><a href="#beb5e866-83a7-4d5e-bf8c-2aabea2d275e" class="header-anchor"></a></h2><p>Grid Container를 위한 속성들은 다음과 같습니다.</p><table><thead><tr><th>속성</th><th>의미</th></tr></thead><tbody><tr><td>display</td><td>그리드 컨테이너(Container)를 정의</td></tr><tr><td>grid-template-rows</td><td>명시적 행(Track)의 크기를 정의</td></tr><tr><td>grid-template-columns</td><td>명시적 열(Track)의 크기를 정의</td></tr><tr><td>grid-template-areas</td><td>영역(Area) 이름을 참조해 템플릿 생성</td></tr><tr><td>grid-template</td><td><code>grid-template-xxx</code>의 단축 속성</td></tr><tr><td>row-gap(grid-row-gap)</td><td>행과 행 사이의 간격(Line)을 정의</td></tr><tr><td>column-gap(grid-column-gap)</td><td>열과 열 사이의 간격(Line)을 정의</td></tr><tr><td>gap(grid-gap)</td><td><code>xxx-gap</code>의 단축 속성</td></tr><tr><td>grid-auto-rows</td><td>암시적인 행(Track)의 크기를 정의</td></tr><tr><td>grid-auto-columns</td><td>암시적인 열(Track)의 크기를 정의</td></tr><tr><td>grid-auto-flow</td><td>자동 배치 알고리즘 방식을 정의</td></tr><tr><td>grid</td><td><code>grid-template-xxx</code>과 <code>grid-auto-xxx</code>의 단축 속성</td></tr><tr><td>align-content</td><td>그리드 콘텐츠(Grid Contents)를 수직(열 축) 정렬</td></tr><tr><td>justify-content</td><td>그리드 콘텐츠를 수평(행 축) 정렬</td></tr><tr><td>place-content</td><td><code>align-content</code>와 <code>justify-content</code>의 단축 속성</td></tr><tr><td>align-items</td><td>그리드 아이템(Items)들을 수직(열 축) 정렬</td></tr><tr><td>justify-items</td><td>그리드 아이템들을 수평(행 축) 정렬</td></tr><tr><td>place-items</td><td><code>align-items</code>와 <code>justify-items</code>의 단축 속성</td></tr></tbody></table><h2><span id="64a2402a-6741-41fa-b293-c816fffe1a0d">Grid Item Properties</span><a href="#64a2402a-6741-41fa-b293-c816fffe1a0d" class="header-anchor"></a></h2><p>Grid Item을 위한 속성들은 다음과 같습니다.</p><table><thead><tr><th>속성</th><th>의미</th></tr></thead><tbody><tr><td>grid-row-start</td><td>그리드 아이템(Item)의 행 시작 위치 지정</td></tr><tr><td>grid-row-end</td><td>그리드 아이템의 행 끝 위치 지정</td></tr><tr><td>grid-row</td><td><code>grid-row-xxx</code>의 단축 속성(행 시작/끝 위치)</td></tr><tr><td>grid-column-start</td><td>그리드 아이템의 열 시작 위치 지정</td></tr><tr><td>grid-column-end</td><td>그리드 아이템의 열 끝 위치 지정</td></tr><tr><td>grid-column</td><td><code>grid-column-xxx</code>의 단축 속성(열 시작/끝 위치)</td></tr><tr><td>grid-area</td><td>영역(Area) 이름을 설정하거나, <code>grid-row</code>와 <code>grid-column</code>의 단축 속성</td></tr><tr><td>align-self</td><td>단일 그리드 아이템을 수직(열 축) 정렬</td></tr><tr><td>justify-self</td><td>단일 그리드 아이템을 수평(행 축) 정렬</td></tr><tr><td>place-self</td><td><code>align-self</code>와 <code>justify-self</code>의 단축 속성</td></tr><tr><td>order</td><td>그리드 아이템의 배치 순서를 지정</td></tr><tr><td>z-index</td><td>그리드 아이템의 쌓이는 순서를 지정</td></tr></tbody></table><h1><span id="d48fae17-c3f1-404b-b0ea-a12b5e87bfb8">Grid Containers</span><a href="#d48fae17-c3f1-404b-b0ea-a12b5e87bfb8" class="header-anchor"></a></h1><h2><span id="6bc489a3-3e0e-44d0-b866-ee66496ca424">display</span><a href="#6bc489a3-3e0e-44d0-b866-ee66496ca424" class="header-anchor"></a></h2><p>Grid Container(컨테이너)를 정의합니다.<br>정의된 컨테이너의 자식 요소들은 자동으로 Grid Items(아이템)로 정의됩니다.</p><blockquote><p>그리드를 사용하기 위해 컨테이너에 필수로 작성합니다!</p></blockquote><table><thead><tr><th>값</th><th>의미</th></tr></thead><tbody><tr><td><code>grid</code></td><td>Block 특성의 Grid Container를 정의</td></tr><tr><td><code>inline-grid</code></td><td>Inline 특성의 Grid Container를 정의</td></tr></tbody></table><pre><code class="css">.container {  display: grid;}</code></pre><h2><span id="0f59d6c5-f7c0-41ef-9a64-71f43f0f0ee8">grid-template-rows</span><a href="#0f59d6c5-f7c0-41ef-9a64-71f43f0f0ee8" class="header-anchor"></a></h2><p>명시적 행(Track)의 크기를 정의합니다.<br>동시에 라인(Line)의 이름도 정의할 수 있습니다.<br><code>fr</code>(fraction, 공간 비율) 단위를 사용할 수 있습니다.<br><code>repeat()</code> 함수를 사용할 수 있습니다.</p><blockquote><p>사용 방법은 <code>grid-template-columns</code>와 같습니다.</p></blockquote><pre><code class="css">.container {  display: grid;  grid-template-rows: 1행크기 2행크기 ...;  grid-template-rows: [선이름] 1행크기 [선이름] 2행크기 [선이름] ...;}</code></pre><pre><code class="css">/* 각 행의 크기를 정의합니다. */.container {  grid-template-rows: 100px 200px;}/* 동시에 각 라인의 이름도 정의할 수 있습니다. */.container {  grid-template-rows: [first] 100px [second] 200px [third];}/* 라인에 중복된 이름을 지정할 수 있습니다. */.container {  grid-template-rows: [row1-start] 100px [row1-end row2-start] 200px [row2-end];}</code></pre><p>각 라인은 행(Row, Track)과 열(Column, Track)의 개수대로 숫자(양수/음수) 라인 이름이 자동으로 지정되어 있어서, 꼭 필요한 경우가 아니면 라인 이름을 정의할 필요가 없습니다.</p><pre><code class="css">.container {  grid-template-rows: 100px 200px;  /* grid-template-rows: [1 -3] 100px [2 -2] 200px [3 -1]; */}</code></pre><pre><code class="css">.container {  width: 400px;  display: grid;  grid-template-rows: repeat(3, 100px);  grid-template-columns: repeat(3, 1fr);}</code></pre><p><img src="/images/screenshot/css-grid/grid-template-rows-1.jpg" alt="CSS Grid"></p><h2><span id="2afd3c7f-e0ae-4aff-9a88-f4fb2e172b65">grid-template-columns</span><a href="#2afd3c7f-e0ae-4aff-9a88-f4fb2e172b65" class="header-anchor"></a></h2><p>명시적 열(Track)의 크기를 정의합니다.<br>동시에 라인(Line)의 이름도 정의할 수 있습니다.<br><code>fr</code>(fraction, 공간 비율) 단위를 사용할 수 있습니다.<br><code>repeat()</code> 함수를 사용할 수 있습니다.</p><blockquote><p>사용 방법은 <code>grid-template-rows</code>와 같습니다.</p></blockquote><pre><code class="css">.container {  display: grid;  grid-template-columns: 1열크기 2열크기 ...;  grid-template-columns: [선이름] 1열크기 [선이름] 2열크기 [선이름] ...;}</code></pre><pre><code class="css">/* 각 열의 크기를 정의합니다. */.container {  grid-template-columns: 100px 200px;}/* 동시에 각 라인의 이름도 정의할 수 있습니다. */.container {  grid-template-columns: [first] 100px [second] 200px [third];}/* 라인에 중복된 이름을 지정할 수 있습니다. */.container {  grid-template-columns: [col1-start] 100px [col1-end col2-start] 200px [col2-end];}</code></pre><p>만약 <code>1200px</code> 너비의 ‘12컬럼 그리드 템플릿’을 정의한다면 다음과 작성할 수 있습니다.</p><pre><code class="css">.container {  width: 1200px;  grid-template-columns: 100px 100px 100px 100px 100px 100px 100px 100px 100px 100px 100px 100px;}</code></pre><p>12개의 열(컬럼) 크기를 하나씩 지정했습니다만, 이 방법은 당연히 입력도 관리도 힘들겠죠!<br><code>repeat()</code> 함수를 사용하면 위 내용을 다음과 같이 간소화할 수 있습니다.</p><pre><code class="css">.container {  width: 1200px;  grid-template-columns: repeat(12, 100px);}</code></pre><p>컬럼을 크기를 <code>fr</code> 단위를 사용해 다음과 같이 비율로 지정할 수도 있습니다.<br>각 컬럼은 비율에 맞게 출력되기 때문에 컨테이너의 너비가 가변해도 열 크기를 수정할 필요가 없습니다.</p><blockquote><p><code>fr</code> 단위에 대한 좀 더 자세한 내용은 본 포스트 하단에 있는 ‘Grid Units / fr’ 파트에서 확인할 수 있습니다.</p></blockquote><pre><code class="css">.container {  width: 80%;  grid-template-columns: repeat(12, 1fr);}</code></pre><p><code>repeat()</code> 함수는 2번째 인수를 반복하기 때문에 다음과 같이 활용할 수 있습니다.</p><pre><code class="css">.container {  grid-template-columns: repeat(4, 100px 200px 50px);  /* grid-template-columns: 100px 200px 50px 100px 200px 50px 100px 200px 50px 100px 200px 50px; */}.container {  grid-template-columns: repeat(4, 1fr 2fr 3fr);  /* grid-template-columns: 1fr 2fr 3fr 1fr 2fr 3fr 1fr 2fr 3fr 1fr 2fr 3fr; */}</code></pre><h2><span id="6cf262c9-4048-4d9d-94de-71f049bf41e7">grid-template-areas</span><a href="#6cf262c9-4048-4d9d-94de-71f049bf41e7" class="header-anchor"></a></h2><p>지정된 그리드 영역 이름(<code>grid-area</code>)을 참조해 그리드 템플릿을 생성합니다.</p><blockquote><p><code>grid-area</code>는 Grid Container가 아닌 Grid Item에 적용하는 속성입니다.</p></blockquote><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(3, 100px);  grid-template-columns: repeat(3, 1fr);  grid-template-areas:    &quot;header header header&quot;    &quot;main main aside&quot;    &quot;footer footer footer&quot;;}header { grid-area: header; }main   { grid-area: main;   }aside  { grid-area: aside;  }footer { grid-area: footer; }</code></pre><p><img src="/images/screenshot/css-grid/grid-template-areas-1.jpg" alt="CSS Grid"></p><p><code>.</code>(마침표)를 사용하거나 명시적으로 <code>none</code>을 입력해 빈 영역을 정의할 수 있습니다.</p><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(4, 100px);  grid-template-columns: repeat(3, 1fr);  grid-template-areas:    &quot;header header header&quot;    &quot;main . .&quot;    &quot;main . aside&quot;    &quot;footer footer footer&quot;;}header { grid-area: header; }main   { grid-area: main;   }aside  { grid-area: aside;  }footer { grid-area: footer; }</code></pre><p><img src="/images/screenshot/css-grid/grid-template-areas-2.jpg" alt="CSS Grid"></p><h2><span id="1a5cfae4-12db-4fa0-832a-692c9e6a3721">grid-template</span><a href="#1a5cfae4-12db-4fa0-832a-692c9e6a3721" class="header-anchor"></a></h2><p><code>grid-template-rows</code>, <code>grid-template-columns</code> 그리고 <code>grid-template-areas</code>의 단축 속성입니다.</p><pre><code class="css">.container {  grid-template: &lt;grid-template-rows&gt; / &lt;grid-template-columns&gt;;  grid-template: &lt;grid-template-areas&gt;;}</code></pre><p>다음과 같이 작성할 수도 있습니다.</p><pre><code class="css">.container {  grid-template:    [1행시작선이름] &quot;AREAS&quot; 행너비 [1행끝선이름]    [2행시작선이름] &quot;AREAS&quot; 행너비 [2행끝선이름]    / &lt;grid-template-columns&gt;;}</code></pre><pre><code class="css">.container {  display: grid;  grid-template:    &quot;header header header&quot; 80px    &quot;main main aside&quot; 350px    &quot;footer footer footer&quot; 130px    / 2fr 100px 1fr;}header { grid-area: header; }main   { grid-area: main; }aside  { grid-area: aside; }footer { grid-area: footer; }</code></pre><p>위 예제의 컨테이너는 다음과 같이 해석할 수 있습니다.</p><pre><code class="css">.container {  display: grid;  grid-template-rows: 80px 350px 130px;  grid-template-columns: 2fr 100px 1fr;  grid-template-areas:    &quot;header header header&quot;    &quot;main main aside&quot;    &quot;footer footer footer&quot;;}</code></pre><h2><span id="94488701-242a-4696-bda0-e5d62743beb9">row-gap(grid-row-gap)</span><a href="#94488701-242a-4696-bda0-e5d62743beb9" class="header-anchor"></a></h2><p>각 행과 행 사이의 간격(Gutter)을 지정합니다.</p><blockquote><p>더 명확하게는 그리드 선(Grid Line)의 크기를 지정한다고 표현할 수 있습니다.</p></blockquote><pre><code class="css">.container {  row-gap: 크기;}</code></pre><h2><span id="1409f591-689e-4dca-a157-4847e5a1d20e">column-gap(grid-column-gap)</span><a href="#1409f591-689e-4dca-a157-4847e5a1d20e" class="header-anchor"></a></h2><p>각 열과 열 사이의 간격(Gutter)을 지정합니다.</p><pre><code class="css">.container {  column-gap: 크기;}</code></pre><h2><span id="12c08d4c-89e4-4a5e-891f-1bf7fc8ad957">gap(grid-gap)</span><a href="#12c08d4c-89e4-4a5e-891f-1bf7fc8ad957" class="header-anchor"></a></h2><p>각 행과 행, 열과 열 사이의 간격(Gutter)을 지정합니다.</p><pre><code class="css">.container {  gap: &lt;grid-row-gap&gt; &lt;grid-column-gap&gt;;}</code></pre><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(2, 150px);  grid-template-columns: repeat(3, 1fr);  gap: 20px 10px;}/* 하나의 값으로 통일할 수 있습니다. */.container {  gap: 10px;  /* row-gap: 10px; + column-gap: 10px; */}/* 하나의 값만 적용하고자 한다면 다음과 같이 사용할 수 있습니다. */.container {  gap: 10px 0; /* row-gap */  gap: 0 10px; /* column-gap */}</code></pre><p><img src="/images/screenshot/css-grid/gap-1.jpg" alt="CSS Grid"></p><p><code>grid-gap</code>(<code>grid-row-gap</code>, <code>grid-column-gap</code>)의 접두사 <code>grid-</code>는 더 이상 사용되지 않으며(Deprecated), <code>gap</code>(<code>row-gap</code>, <code>column-gap</code>)로 교체되었습니다.<br>하지만 일부 버전의 브라우저 지원을 위해 <code>grid-</code> 접두사의 사용을 고려할 수 있습니다.</p><ul><li><a href="https://drafts.csswg.org/css-grid/#change-2016-grid-gap" target="_blank" rel="noopener">&lsqb;css-grid&rsqb;&lsqb;css-multicol&rsqb;&lsqb;css-flexbox&rsqb;&lsqb;css-tables&rsqb; Proposal for Various Gap Issues.</a></li><li><a href="https://github.com/postcss/autoprefixer/issues/1046" target="_blank" rel="noopener">&lsqb;css-grid&rsqb; grid-gap is deprecated.</a></li><li><a href="https://drafts.csswg.org/css-grid/#change-2016-grid-gap" target="_blank" rel="noopener">https://drafts.csswg.org/css-grid/#change-2016-grid-gap</a></li></ul><h2><span id="4bc22f6b-9912-447b-aba6-c221500e66ef">grid-auto-rows</span><a href="#4bc22f6b-9912-447b-aba6-c221500e66ef" class="header-anchor"></a></h2><p>암시적 행(Track)의 크기를 정의합니다.<br>아이템(Item)이 <code>grid-template-rows</code>로 정의한 명시적 행 외부에 배치되는 경우 암시적 행의 크기가 적용됩니다.</p><pre><code class="html">&lt;div class=&quot;container&quot;&gt;  &lt;div class=&quot;item&quot;&gt;1&lt;/div&gt;  &lt;div class=&quot;item&quot;&gt;2&lt;/div&gt;  &lt;div class=&quot;item&quot;&gt;3&lt;/div&gt;&lt;/div&gt;</code></pre><pre><code class="css">.container {  width: 300px;  height: 200px;  display: grid;  grid-template-rows: 100px 100px; /* 명시적 2개 행 정의 */  grid-template-columns: 150px 150px; /* 명시적 2개 열 정의 */  grid-auto-rows: 100px; /* 그 외(암시적) 행의 크기 정의 */}.item:nth-child(3) {  grid-row: 3 / 4;}</code></pre><p><img src="/images/screenshot/css-grid/grid-auto-rows-1.jpg" alt="CSS Grid"></p><h2><span id="aa307b14-60aa-431b-8e6c-1c007b0cdac6">grid-auto-columns</span><a href="#aa307b14-60aa-431b-8e6c-1c007b0cdac6" class="header-anchor"></a></h2><p>암시적 열(Track)의 크기를 정의합니다.<br>아이템(Item)이 <code>grid-template-columns</code>로 정의한 명시적 열 외부에 배치되는 경우 암시적 열의 크기가 적용됩니다.</p><pre><code class="css">.container {  width: 300px;  height: 200px;  display: grid;  grid-template-rows: 100px 100px;  grid-template-columns: 150px 150px;  grid-auto-rows: 100px;  grid-auto-columns: 100px;}.item:nth-child(3) {  grid-row: 3 / 4;  grid-column: 3 / 4;}</code></pre><p><img src="/images/screenshot/css-grid/grid-auto-columns-1.jpg" alt="CSS Grid"></p><p>다음과 같이 아이템이 배치되는 위치에 맞게 암시적 행과 열의 개수가 생성됩니다.<br>암시적 크기가 적용된 행과 열은 양수 라인 번호만 사용할 수 있습니다.(음수 사용 불가)</p><p><img src="/images/screenshot/css-grid/grid-auto-columns-2.jpg" alt="CSS Grid"></p><h2><span id="c8a525d4-d4bf-47c1-be48-babae9897aa4">grid-auto-flow</span><a href="#c8a525d4-d4bf-47c1-be48-babae9897aa4" class="header-anchor"></a></h2><p>배치하지 않은 아이템(Item)을 어떤 방식의 ‘자동 배치 알고리즘’으로 처리할지 정의합니다.</p><blockquote><p>배치한 아이템은 <code>grid-area</code>(이하 개별 속성 포함)를 사용한 아이템을 의미합니다.</p></blockquote><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>row</td><td>각 행 축을 따라 차례로 배치</td><td><code>row</code></td></tr><tr><td>column</td><td>각 열 축을 따라 차례로 배치</td><td></td></tr><tr><td>row dense(dense)</td><td>각 행 축을 따라 차례로 배치, 빈 영역 메움!</td><td></td></tr><tr><td>column dense</td><td>각 열 축을 따라 차례로 배치, 빈 영역 메움!</td><td></td></tr></tbody></table><p>다음은 <code>row</code>와 <code>row dense</code>에 대한 예제입니다.</p><pre><code class="css">/* For row &amp; row dense */.container {  display: grid;  grid-template-rows: repeat(3, 1fr);  grid-template-columns: repeat(3, 1fr);  grid-auto-flow: row || row dense || dense;}.item:nth-child(2) {  grid-column: span 3;}</code></pre><p><img src="/images/screenshot/css-grid/grid-auto-flow-1.jpg" alt="CSS Grid"></p><p>다음은 <code>column</code>과 <code>column dense</code>에 대한 예제입니다.</p><pre><code class="css">/* For column &amp; column dense */.container {  display: grid;  grid-template-rows: repeat(3, 1fr);  grid-template-columns: repeat(3, 1fr);  grid-auto-flow: column || column dense;}.item:nth-child(1) {  grid-column: 2 / span 2;}.item:nth-child(2) {  grid-column: span 2;}</code></pre><p><img src="/images/screenshot/css-grid/grid-auto-flow-2.jpg" alt="CSS Grid"></p><h2><span id="c632acad-f20d-461e-b26f-0616deef9f06">grid</span><a href="#c632acad-f20d-461e-b26f-0616deef9f06" class="header-anchor"></a></h2><p><code>grid-template-xxx</code>과 <code>grid-auto-xxx</code>의 단축 속성입니다.</p><pre><code class="css">.container {  grid: &lt;grid-template&gt;;  grid: &lt;grid-template-rows&gt; / &lt;grid-auto-flow&gt; &lt;grid-auto-columns&gt;;  grid: &lt;grid-auto-flow&gt; &lt;grid-auto-rows&gt; / &lt;grid-template-columns&gt;;}</code></pre><p>각 코드 블록 내 컨테이너(<code>.container</code>)들은 모두 같은 의미입니다.</p><pre><code class="css">.container {  grid: &lt;grid-template-rows&gt; / &lt;grid-template-columns&gt;;}.container {  grid: 100px 200px / 1fr 2fr;}.container {  grid-template-rows: 100px 200px;  grid-template-columns: 1fr 2fr;}</code></pre><pre><code class="css">.container {  grid: &lt;grid-template&gt;;}.container {  grid:    &quot;header header header&quot; 80px    &quot;main main aside&quot; 350px    &quot;footer footer footer&quot; 130px    / 2fr 100px 1fr;}.container {  grid-template:    &quot;header header header&quot; 80px    &quot;main main aside&quot; 350px    &quot;footer footer footer&quot; 130px    / 2fr 100px 1fr;}</code></pre><p><code>grid-auto-flow</code>를 작성할 때는 <code>auto-flow</code> 키워드를 사용합니다.<br><code>/</code>로 구분해 작성하는 위치가 곧 <code>row</code>, <code>column</code> 값을 의미합니다.<br>따라서, <code>row</code>, <code>column</code> 값은 작성하지 마세요.<br><code>dense</code> 값은 <code>auto-flow</code> 뒤에 붙여줍니다.</p><pre><code class="css">.container {  grid: &lt;grid-template-rows&gt; / &lt;grid-auto-flow&gt; &lt;grid-auto-columns&gt;;}.container {  grid: 100px 100px / auto-flow 150px;}.container {  grid-template-row: 100px 100px;  grid-auto-flow: column;  grid-auto-columns: 150px;}</code></pre><pre><code class="css">.container {  grid: &lt;grid-auto-flow&gt; &lt;grid-auto-rows&gt; / &lt;grid-template-columns&gt;;}.container {  grid: auto-flow 150px / 100px 100px;}.container {  grid-template-columns: 100px 100px;  grid-auto-flow: row;  grid-auto-rows: 150px;}</code></pre><pre><code class="css">.container {  grid: auto-flow dense 150px / 100px 100px;}.container {  grid-template-columns: 100px 100px;  grid-auto-flow: row dense;  grid-auto-rows: 150px;}</code></pre><h2><span id="ec2ba5a7-a787-4299-a202-e7301e3a254b">align-content</span><a href="#ec2ba5a7-a787-4299-a202-e7301e3a254b" class="header-anchor"></a></h2><p>그리드 콘텐츠(Contents)를 수직(열 축) 정렬합니다.<br>그리드 콘텐츠의 세로 너비가 그리드 컨테이너(Container)보다 작아야 합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>normal</td><td><code>stretch</code>와 같습니다.</td><td><code>normal</code></td></tr><tr><td>start</td><td>시작점(위쪽) 정렬</td><td></td></tr><tr><td>center</td><td>수직 가운데 정렬</td><td></td></tr><tr><td>end</td><td>끝점(아래쪽) 정렬</td><td></td></tr><tr><td>space-around</td><td>각 행 위아래에 여백을 고르게 정렬</td><td></td></tr><tr><td>space-between</td><td>첫 행은 시작점에, 끝 행은 끝점에 정렬되고 나머지 여백으로 고르게 정렬</td><td></td></tr><tr><td>space-evenly</td><td>모든 여백을 고르게 정렬</td><td></td></tr><tr><td>stretch</td><td>열 축을 채우기 위해 그리드 콘텐츠를 늘림</td><td></td></tr></tbody></table><pre><code class="css">.container {  width: 450px;  height: 450px;  display: grid;  grid-template-rows: repeat(3, 100px);  grid-template-columns: repeat(3, 100px);  align-content: &lt;align-content&gt;;}</code></pre><p><img src="/images/screenshot/css-grid/align-content-1.jpg" alt="CSS Grid"></p><h2><span id="4b2d58b8-d1bf-496d-a069-9e7853bef2cb">justify-content</span><a href="#4b2d58b8-d1bf-496d-a069-9e7853bef2cb" class="header-anchor"></a></h2><p>그리드 콘텐츠(Contents)를 수평(행 축) 정렬합니다.<br>그리드 콘텐츠의 가로 너비가 그리드 컨테이너(Container)보다 작아야 합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>normal</td><td><code>stretch</code>와 같습니다.</td><td><code>normal</code></td></tr><tr><td>start</td><td>시작점(왼쪽) 정렬</td><td></td></tr><tr><td>center</td><td>수평 가운데 정렬</td><td></td></tr><tr><td>end</td><td>끝점(오른쪽) 정렬</td><td></td></tr><tr><td>space-around</td><td>각 열 좌우에 여백을 고르게 정렬</td><td></td></tr><tr><td>space-between</td><td>첫 열은 시작점에, 끝 열은 끝점에 정렬되고 나머지 여백으로 고르게 정렬</td><td></td></tr><tr><td>space-evenly</td><td>모든 여백을 고르게 정렬</td><td></td></tr><tr><td>stretch</td><td>행 축을 채우기 위해 그리드 콘텐츠를 늘림</td><td></td></tr></tbody></table><pre><code class="css">.container {  width: 450px;  height: 450px;  display: grid;  grid-template-rows: repeat(3, 100px);  grid-template-columns: repeat(3, 100px);  justify-content: &lt;justify-content&gt;;}</code></pre><p><img src="/images/screenshot/css-grid/justify-content-1.jpg" alt="CSS Grid"></p><h2><span id="e3cbe0f3-9fb5-4004-a683-23fba7a5e4e5">place-content</span><a href="#e3cbe0f3-9fb5-4004-a683-23fba7a5e4e5" class="header-anchor"></a></h2><p><code>align-content</code>와 <code>justify-content</code>의 단축 속성입니다.<br>하나의 값만 입력하면 두 속성에 모두 적용됩니다.</p><blockquote><p>Edge(IE) 브라우저에서 지원하지 않는 속성입니다.</p></blockquote><pre><code class="css">.container {  place-content: &lt;align-content&gt; &lt;justify-content&gt;;}</code></pre><p>각 코드 블록 내 컨테이너(<code>.container</code>)들은 모두 같은 의미입니다.</p><pre><code class="css">.container {  place-content: center space-evenly;}.container {  align-content: center;  justify-content: space-evenly;}</code></pre><pre><code class="css">.container {  place-content: end;}.container {  align-content: end;  justify-content: end;}</code></pre><h2><span id="67df11f1-36f8-4ceb-955c-32e5c56414d6">align-items</span><a href="#67df11f1-36f8-4ceb-955c-32e5c56414d6" class="header-anchor"></a></h2><p>그리드 아이템(Items)들을 수직(열 축) 정렬합니다.<br>그리드 아이템의 세로 너비가 자신이 속한 그리드 행(Track)의 크기보다 작아야 합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>normal</td><td><code>stretch</code>와 같습니다.</td><td><code>normal</code></td></tr><tr><td>start</td><td>시작점(위쪽) 정렬</td><td></td></tr><tr><td>center</td><td>수직 가운데 정렬</td><td></td></tr><tr><td>end</td><td>끝점(아래쪽) 정렬</td><td></td></tr><tr><td>stretch</td><td>열 축을 채우기 위해 그리드 아이템을 늘림</td><td></td></tr></tbody></table><pre><code class="css">.container {  width: 450px;  height: 450px;  display: grid;  grid-template-rows: repeat(3, 1fr);  grid-template-columns: repeat(3, 1fr);  align-items: &lt;align-items&gt;;}</code></pre><p><img src="/images/screenshot/css-grid/align-items-1.jpg" alt="CSS Grid"></p><h2><span id="cb6a9751-5ed7-4df6-9eb6-8f948981db62">justify-items</span><a href="#cb6a9751-5ed7-4df6-9eb6-8f948981db62" class="header-anchor"></a></h2><p>그리드 아이템(Items)들을 수평(행 축) 정렬합니다.<br>그리드 아이템의 가로 너비가 자신이 속한 그리드 열(Track)의 크기보다 작아야 합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>normal</td><td><code>stretch</code>와 같습니다.</td><td><code>normal</code></td></tr><tr><td>start</td><td>시작점(왼쪽) 정렬</td><td></td></tr><tr><td>center</td><td>수평 가운데 정렬</td><td></td></tr><tr><td>end</td><td>끝점(오른쪽) 정렬</td><td></td></tr><tr><td>stretch</td><td>행 축을 채우기 위해 그리드 아이템을 늘림</td><td></td></tr></tbody></table><pre><code class="css">.container {  width: 450px;  height: 450px;  display: grid;  grid-template-rows: repeat(3, 1fr);  grid-template-columns: repeat(3, 1fr);  justify-items: &lt;justify-items&gt;;}</code></pre><p><img src="/images/screenshot/css-grid/justify-items-1.jpg" alt="CSS Grid"></p><h2><span id="f416b6c9-32a5-41ac-8536-80b1e7557a4a">place-items</span><a href="#f416b6c9-32a5-41ac-8536-80b1e7557a4a" class="header-anchor"></a></h2><p><code>align-items</code>와 <code>justify-items</code>의 단축 속성입니다.<br>하나의 값만 입력하면 두 속성에 모두 적용됩니다.</p><blockquote><p>Edge(IE) 브라우저에서 지원하지 않는 속성입니다.</p></blockquote><pre><code class="css">.container {  place-items: &lt;align-items&gt; &lt;justify-items&gt;;}</code></pre><p>각 코드 블록 내 컨테이너(<code>.container</code>)들은 모두 같은 의미입니다.</p><pre><code class="css">.container {  place-items: start stretch;}.container {  align-items: start;  justify-items: stretch;}</code></pre><pre><code class="css">.container {  place-items: center;}.container {  align-items: center;  justify-items: center;}</code></pre><h1><span id="1577c77c-8c25-43ea-b122-f005207e5e23">Grid Items</span><a href="#1577c77c-8c25-43ea-b122-f005207e5e23" class="header-anchor"></a></h1><p>정의된 컨테이너의 자식 요소들은 자동으로 Grid Items(아이템)로 정의됩니다.</p><h2><span id="80e446b9-1cfa-4ac5-9624-dc25871c39bd">grid-row-start, grid-row-end, grid-column-start, grid-column-end</span><a href="#80e446b9-1cfa-4ac5-9624-dc25871c39bd" class="header-anchor"></a></h2><p>그리드 아이템(Item)을 배치하기 위해 그리드 선(Line)의 ‘시작 위치’와 ‘끝 위치’를 지정합니다.<br>‘숫자’를 지정하거나, ‘선 이름’을 지정하거나, <code>span</code> 키워드를 사용합니다.</p><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(2, 1fr);  grid-template-columns: repeat(3, 1fr);}.item:nth-child(1) {  grid-row-start: 1;  grid-row-end: 3;  grid-column-start: 2;  grid-column-end: 4;}</code></pre><p><img src="/images/screenshot/css-grid/grid-area-1.jpg" alt="CSS Grid"></p><p>선의 이름을 지정할 수도 있습니다.</p><pre><code class="css">.container {  display: grid;  grid-template-rows: [row-1st] 1fr [row-2nd] 1fr [row-3rd];  grid-template-columns: [col-1st] 1fr [col-2nd] 1fr [col-3rd] 1fr [col-4th];}.item:nth-child(1) {  grid-row-start: row-2nd;  grid-row-end: row-3rd;  grid-column-start: col-2nd;  grid-column-end: col-4th;}</code></pre><p><img src="/images/screenshot/css-grid/grid-area-2.jpg" alt="CSS Grid"></p><p><code>span</code> 키워드를 사용하면 좀 더 쉽게 배치할 수 있습니다.<br><code>span</code> 키워드와 ‘숫자’를 조합하면 ‘숫자’만큼 라인을 확장하는(<code>+</code>) 개념입니다.<br>명시하지 않으면 <code>span 1</code>이 기본값입니다.</p><pre><code class="css">.item:nth-child(1) {  /* Row 1번에서 3번(1+2=3)까지 */  grid-row-start: 1;  grid-row-end: span 2;  /* Column 2번에서 3번(2+1=3)까지 */  grid-column-start: 2;  /* grid-column-end: span 1; (생략) */}</code></pre><p><img src="/images/screenshot/css-grid/grid-area-3.jpg" alt="CSS Grid"></p><p><code>span</code> 키워드를 ‘시작 위치’에 작성하고, ‘끝 위치’를 명시해서 확장할(<code>-</code>) 수도 있습니다.</p><pre><code class="css">.item:nth-child(1) {  /* Column 3번에서 2번(3-1=2)까지 */  /* grid-row-start: span 1; (생략) */  grid-row-end: 3;  /* Column 4번에서 2번(4-2=2)까지 */  grid-column-start: span 2;  grid-column-end: 4;}</code></pre><p><img src="/images/screenshot/css-grid/grid-area-4.jpg" alt="CSS Grid"></p><h2><span id="e8078220-e1c2-41d2-83b5-f42331b78a87">grid-row</span><a href="#e8078220-e1c2-41d2-83b5-f42331b78a87" class="header-anchor"></a></h2><p><code>grid-row-start</code>과 <code>grid-row-end</code>의 단축 속성입니다.<br>각 속성을 <code>/</code>로 구분하는 것에 주의하세요.</p><pre><code class="css">.item {  grid-row: &lt;grid-row-start&gt; / &lt;grid-row-end&gt;;}</code></pre><p>각 코드 블록 내 아이템(<code>.item</code>)들은 모두 같은 의미입니다.</p><pre><code class="css">.item {  grid-row-start: 1;  grid-row-end: 2;}.item {  grid-row: 1 / 2;}</code></pre><pre><code class="css">.item {  grid-row-start: 2;  grid-row-end: span 3;}.item {  grid-row: 2 / span 3;}.item {  grid-row: 2 / 5;}</code></pre><pre><code class="css">.item {  grid-row-start: span 3;  grid-row-end: 4;}.item {  grid-row: span 3 / 4;}.item {  grid-row: 1 / 4;}</code></pre><h2><span id="16dd767b-1dee-4622-8ef1-8c68fd91e568">grid-column</span><a href="#16dd767b-1dee-4622-8ef1-8c68fd91e568" class="header-anchor"></a></h2><p><code>grid-column-start</code>과 <code>grid-column-end</code>의 단축 속성입니다.<br>각 속성을 <code>/</code>로 구분하는 것에 주의하세요.</p><pre><code class="css">.item {  grid-column: &lt;grid-column-start&gt; / &lt;grid-column-end&gt;;}</code></pre><p>각 코드 블록 내 아이템(<code>.item</code>)들은 모두 같은 의미입니다.<br>음수 결과를 위해 <code>span</code> 키워드를 ‘시작 위치’에 작성함에 주의하세요!</p><pre><code class="css">.item {  grid-column-start: -1;  grid-column-end: -3;}.item {  grid-column: -1 / -3;}.item {  /* Column -1번에서 -3번(-1-2=-3)까지 */  grid-column: span 2 / -1;}</code></pre><pre><code class="css">.item {  grid-column-start: 2;  grid-column-end: -1;}.item {  /* Column 2번에서 끝(-1번)까지 */  grid-column: 2 / -1;}</code></pre><h2><span id="eb828793-910b-4f66-8dc4-6072f325c9fe">grid-area</span><a href="#eb828793-910b-4f66-8dc4-6072f325c9fe" class="header-anchor"></a></h2><p><code>grid-row-start</code>, <code>grid-column-start</code>, <code>grid-row-end</code> 그리고 <code>grid-column-end</code>의 단축 속성입니다.<br>혹은 <code>grid-template-areas</code>가 참조할 영역(Area) 이름을 설정할 수도 있습니다.<br>영역 이름을 설정할 경우 <code>grid-row</code>와 <code>grid-column</code> 개념은 무시됩니다.</p><pre><code class="css">.item {  grid-area: &lt;grid-row-start&gt; / &lt;grid-column-start&gt; / &lt;grid-row-end&gt; / &lt;grid-column-end&gt;;  grid-area: 영역이름;}</code></pre><p>각 코드 블록 내 아이템(<code>.item</code>)들은 모두 같은 의미입니다.</p><pre><code class="css">.item {  grid-row: 2 / 3;  grid-column: span 2 / -1;}.item {  /* &#39;시작 / 시작 / 끝 / 끝&#39;임에 주의합시다! */  grid-area: 2 / span 2 / 3 / -1;}</code></pre><p>다음과 같이 영역 이름을 지정해 <code>grid-template-areas</code>에서 참조할 수 있습니다.</p><pre><code class="html">&lt;div class=&quot;container&quot;&gt;  &lt;header class=&quot;item&quot;&gt;HEADER&lt;/header&gt;  &lt;main class=&quot;item&quot;&gt;MAIN&lt;/main&gt;  &lt;aside class=&quot;item&quot;&gt;ASIDE&lt;/aside&gt;  &lt;footer class=&quot;item&quot;&gt;FOOTER&lt;/footer&gt;&lt;/div&gt;</code></pre><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(4, 90px);  grid-template-columns: repeat(3, 1fr);  grid-template-areas:    &quot;header header header&quot;    &quot;main main aside&quot;    &quot;main main .&quot;    &quot;footer footer footer&quot;;}header.item { grid-area: header; }main.item   { grid-area: main;   }aside.item  { grid-area: aside;  }footer.item { grid-area: footer; }</code></pre><p><img src="/images/screenshot/css-grid/grid-area-6.jpg" alt="CSS Grid"></p><h2><span id="c8fd6c74-1553-4861-886b-0a2e2f924539">align-self</span><a href="#c8fd6c74-1553-4861-886b-0a2e2f924539" class="header-anchor"></a></h2><p>단일 그리드 아이템(Item)을 수직(열 축) 정렬합니다.<br>그리드 아이템의 세로 너비가 자신이 속한 그리드 행(Track)의 크기보다 작아야 합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>normal</td><td><code>stretch</code>와 같습니다.</td><td><code>normal</code></td></tr><tr><td>start</td><td>시작점(위쪽) 정렬</td><td></td></tr><tr><td>center</td><td>수직 가운데 정렬</td><td></td></tr><tr><td>end</td><td>끝점(아래쪽) 정렬</td><td></td></tr><tr><td>stretch</td><td>열 축을 채우기 위해 그리드 아이템을 늘림</td><td></td></tr></tbody></table><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(2, 1fr);  grid-template-columns: repeat(2, 1fr);}.item:nth-child(1) { align-self: start; }.item:nth-child(2) { align-self: center; }.item:nth-child(3) { align-self: end; }.item:nth-child(4) { align-self: stretch; }</code></pre><p><img src="/images/screenshot/css-grid/align-self-1.jpg" alt="CSS Grid"></p><h2><span id="c1cd9100-64ee-4667-8582-7d86ca660c1a">justify-self</span><a href="#c1cd9100-64ee-4667-8582-7d86ca660c1a" class="header-anchor"></a></h2><p>단일 그리드 아이템(Item)을 수평(행 축) 정렬합니다.<br>그리드 아이템의 가로 너비가 자신이 속한 그리드 열(Track)의 크기보다 작아야 합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>normal</td><td><code>stretch</code>와 같습니다.</td><td><code>normal</code></td></tr><tr><td>start</td><td>시작점(왼쪽) 정렬</td><td></td></tr><tr><td>center</td><td>수평 가운데 정렬</td><td></td></tr><tr><td>end</td><td>끝점(오른쪽) 정렬</td><td></td></tr><tr><td>stretch</td><td>행 축을 채우기 위해 그리드 아이템을 늘림</td><td></td></tr></tbody></table><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(2, 1fr);  grid-template-columns: repeat(2, 1fr);}.item:nth-child(1) { justify-self: start; }.item:nth-child(2) { justify-self: center; }.item:nth-child(3) { justify-self: end; }.item:nth-child(4) { justify-self: stretch; }</code></pre><p><img src="/images/screenshot/css-grid/justify-self-1.jpg" alt="CSS Grid"></p><h2><span id="f2a864ce-de12-40de-8e48-60b52f6e0b57">place-self</span><a href="#f2a864ce-de12-40de-8e48-60b52f6e0b57" class="header-anchor"></a></h2><p><code>align-self</code>와 <code>justify-self</code>의 단축 속성입니다.<br>하나의 값만 입력하면 두 속성에 모두 적용됩니다.</p><blockquote><p>Edge(IE) 브라우저에서 지원하지 않는 속성입니다.</p></blockquote><pre><code class="css">.item {  place-self: &lt;align-self&gt; &lt;justify-self&gt;;}</code></pre><p>각 코드 블록 내 아이템(<code>.item</code>)들은 모두 같은 의미입니다.</p><pre><code class="css">.item {  place-self: start end;}.item {  align-self: start;  justify-self: end;}</code></pre><pre><code class="css">.item {  place-self: center;}.item {  align-self: center;  justify-self: center;}</code></pre><h2><span id="92cc973d-0f5c-42f9-820e-da777f64ebe7">order</span><a href="#92cc973d-0f5c-42f9-820e-da777f64ebe7" class="header-anchor"></a></h2><p>그리드 아이템이 자동 배치되는 순서를 변경할 수 있습니다.<br>숫자가 작을수록 앞서 배치됩니다.</p><pre><code class="css">.container {  display: grid;  grid-template-rows: repeat(2, 1fr);  grid-template-columns: repeat(3, 1fr);}.item:nth-child(1) { order: 1; }.item:nth-child(3) { order: 5; }.item:nth-child(5) { order: -1; }</code></pre><p><img src="/images/screenshot/css-grid/order-1.jpg" alt="CSS Grid"></p><h2><span id="59df6afe-ac36-49bf-bfde-50569f27cc96">z-index</span><a href="#59df6afe-ac36-49bf-bfde-50569f27cc96" class="header-anchor"></a></h2><p><code>z-index</code> 속성을 이용해 아이템이 쌓이는 순서를 변경할 수 있습니다.</p><pre><code class="css">.item:nth-child(1) {  grid-area: 1 / 1 / 2 / 3;}.item:nth-child(2) {  grid-area: 1 / 2 / 3 / 3;  z-index: 1;}.item:nth-child(3) {  grid-area: 2 / 2 / 3 / 4;}</code></pre><p><img src="/images/screenshot/css-grid/z-index-1.jpg" alt="CSS Grid"></p><h1><span id="e00d4c09-f1cc-4d61-a71e-1ba6efd03c7e">Grid Functions</span><a href="#e00d4c09-f1cc-4d61-a71e-1ba6efd03c7e" class="header-anchor"></a></h1><p>그리드에서 사용하는 주요 함수들에 대해서 알아봅시다.</p><h2><span id="1ede60b9-0f8a-41d6-9fa1-dc54c5ee8c5f">repeat</span><a href="#1ede60b9-0f8a-41d6-9fa1-dc54c5ee8c5f" class="header-anchor"></a></h2><p><code>repeat()</code> 함수는 행/열(Track)의 크기 정의를 반복합니다.<br>‘반복되는 횟수’와 ‘행/열의 크기 정의’를 인수로 사용합니다.<br><code>grid-template-rows</code>와 <code>grid-template-columns</code>에서 사용합니다.</p><p>각 코드 블록 내 컨테이너(<code>.container</code>)들은 모두 같은 의미입니다.</p><pre><code class="css">/* 9컬럼 그리드 */.container {  grid-template-columns: 100px 100px 100px 100px 100px 100px 100px 100px 100px;}.container {  grid-template-columns: repeat(9, 100px);}</code></pre><pre><code class="css">.container {  grid-template-rows: [row-start] 200px [row-end row-start] 200px [row-end];  grid-template-columns: [col-start] 100px [col-end col-start] 100px [col-end col-start] 100px [col-end];}.container {  grid-template-rows: repeat(2, [row-start] 200px [row-end]);  grid-template-columns: repeat(3, [col-start] 100px [col-end]);}.container {  grid-template: repeat(2, [row-start] 200px [row-end]) / repeat(3, [col-start] 100px [col-end]);}</code></pre><pre><code class="css">.container {  /* 12컬럼 그리드 */  grid-template-columns: 1fr 2fr 1fr 2fr 1fr 2fr 1fr 2fr 1fr 2fr 1fr 2fr;}.container {  grid-template-columns: repeat(6, 1fr 2fr);}</code></pre><h2><span id="e30962de-d6b7-45f1-abe4-0a4724cef8e6">minmax</span><a href="#e30962de-d6b7-45f1-abe4-0a4724cef8e6" class="header-anchor"></a></h2><p><code>minmax()</code> 함수는 행/열(Track)의 ‘최소/최대 크기’를 정의합니다.<br>첫 번째 인수는 ‘최솟값’이고 두 번째 인수는 ‘최댓값’입니다.<br><code>grid-template-rows</code>, <code>grid-template-columns</code>, <code>grid-auto-rows</code> 그리고 <code>grid-auto-columns</code>에서 사용합니다.</p><p>일반 요소에 <code>min-width</code>와 <code>max-width</code> 속성을 동시 지정하는 것과 유사합니다.</p><pre><code class="css">.container {  grid-template-columns: minmax(100px, 1fr) minmax(200px, 1fr);}</code></pre><p><img src="/images/screenshot/css-grid/minmax-1.jpg" alt="CSS Grid"></p><p><code>minmax()</code>를 통해 암시적 행/열(Track) 크기를 좀 더 유연하게 사용할 수 있습니다.<br>다음 예제는 암시적 ‘행/열’의 크기를 최소 ‘200px/300px’으로 지정하지만 <code>auto</code>를 통해 그리드 아이템의 크기에 따라 확장될 수 있습니다.</p><pre><code class="css">.container {  grid-auto-rows: minmax(200px, auto);  grid-auto-columns: minmax(300px, auto);}</code></pre><h2><span id="4fab992e-c6cc-4e7f-9419-54234de25cd9">fit-content</span><a href="#4fab992e-c6cc-4e7f-9419-54234de25cd9" class="header-anchor"></a></h2><p><code>fit-content()</code> 함수는 행/열(Track)의 크기를 그리드 아이템(Item)이 포함하는 내용(Contents) 크기에 맞춥니다.<br>‘내용의 최대 크기’를 인수로 사용합니다.<br><code>minmax(auto, max-content)</code>와 유사합니다.</p><pre><code class="css">.container {  grid-template-columns: fit-content(300px) fit-content(300px);}</code></pre><p><img src="/images/screenshot/css-grid/fit-content-1.jpg" alt="CSS Grid"></p><h1><span id="28564ace-7241-4e86-a226-dfcfd984c8cc">Grid Units</span><a href="#28564ace-7241-4e86-a226-dfcfd984c8cc" class="header-anchor"></a></h1><p>그리드에서 사용하는 주요 단위들에 대해서 알아봅시다.</p><h2><span id="d579b5e0-b74d-4230-9dd0-56458d0625ce">fr</span><a href="#d579b5e0-b74d-4230-9dd0-56458d0625ce" class="header-anchor"></a></h2><p><code>fr</code>(fractional unit)은 <strong>사용 가능한 공간에 대한 비율</strong>을 의미합니다.</p><p>다음 예제는 그리드 컨테이너의 3번째 컬럼에 <code>100px</code>, 4번째 컬럼에 <code>25%</code>를 사용하고 남은 공간을 1번째 컬럼에 ‘1/3’, 2번째 컬럼에 ‘2/3’ 만큼 사용합니다.</p><pre><code class="css">.container {  grid-template-columns: 1fr 2fr 100px 25%;}</code></pre><p><img src="/images/screenshot/css-grid/fr-1.jpg" alt="CSS Grid"></p><h2><span id="79441915-7467-442b-a426-2f1494df4a97">min-content</span><a href="#79441915-7467-442b-a426-2f1494df4a97" class="header-anchor"></a></h2><p>그리드 아이템이 포함하는 내용(Contents)의 최소 크기를 의미합니다.</p><pre><code class="html">&lt;div class=&quot;container&quot;&gt;  &lt;div class=&quot;item&quot;&gt;Hello HEROPY~&lt;/div&gt;  &lt;!-- ... --&gt;&lt;/div&gt;</code></pre><pre><code class="css">.container {  grid-template-columns: min-content 1fr;}</code></pre><p><img src="/images/screenshot/css-grid/min-content-1.jpg" alt="CSS Grid"></p><pre><code class="html">&lt;div class=&quot;container&quot;&gt;  &lt;div class=&quot;item&quot;&gt;내용의 최소 크기&lt;/div&gt;  &lt;!-- ... --&gt;&lt;/div&gt;</code></pre><p>한글을 사용하는 경우 <code>word-break: keep-all;</code>를 설정하면 정상적으로 동작합니다.</p><p><img src="/images/screenshot/css-grid/min-content-2.jpg" alt="CSS Grid"></p><h2><span id="609017f2-e357-4127-9b44-0abec87ff3d2">max-content</span><a href="#609017f2-e357-4127-9b44-0abec87ff3d2" class="header-anchor"></a></h2><p>그리드 아이템이 포함하는 내용(Contents)의 최대 크기를 의미합니다.</p><pre><code class="html">&lt;div class=&quot;container&quot;&gt;  &lt;div class=&quot;item&quot;&gt;Hello HEROPY~&lt;/div&gt;  &lt;!-- ... --&gt;&lt;/div&gt;</code></pre><pre><code class="css">.container {  grid-template-columns: max-content 1fr;}</code></pre><p><img src="/images/screenshot/css-grid/max-content-1.jpg" alt="CSS Grid"></p><p>그리드 함수들과 같이 더 유용하게 활용할 수 있습니다.<br>다음 예제는 총 4컬럼 그리드를 생성하며 각 열(Track)은 최대 <code>1fr</code> 크기를 가지지만, <code>max-content</code>를 통해 포함된 그리드 아이템의 내용보다 작아질 수 없습니다.</p><pre><code class="css">.container {  grid-template-columns: repeat(4, minmax(max-content, 1fr));}</code></pre><h2><span id="bdaa38f9-6c13-4615-a081-02a82c10ad98">auto-fill, auto-fit</span><a href="#bdaa38f9-6c13-4615-a081-02a82c10ad98" class="header-anchor"></a></h2><p>행/열(Track)의 개수를 그리드 컨테이너(Container) 및 행/열 크기에 맞게 자동으로(암시적) 조정합니다.<br><code>repeat()</code> 함수와 같이 사용하며, 행/열과 아이템(Item) 개수가 명확할 필요가 없거나 명확하지 않은 경우 유용합니다.(반응형 그리드)<br><code>auto-fill</code>과 <code>auto-fit</code>은 <strong>간단한 차이점을 제외하면 동일하게 동작합니다.</strong></p><p>다음 4컬럼 그리드 예제에서 컨테이너의 크기가 아이템들을 수용하기 충분하지 않은 경우 아이템은 넘치기 시작합니다.(아이템의 최소 크기가 <code>120px</code>입니다.)</p><pre><code class="css">.container {  grid-template-columns: repeat(4, minmax(120px, 1fr));}</code></pre><p><img src="/images/screenshot/css-grid/auto-fill-1.jpg" alt="CSS Grid"></p><p>만약 4컬럼 그리드를 고집할 필요가 없다면, 다음과 같이 ‘반복횟수’(<code>repeat()</code> 함수의 첫 번째 인수)를 <code>auto-fill</code>이나 <code>auto-fit</code>으로 수정할 수 있습니다.<br>이는 컨테이너의 크기가 아이템들을 수용하기 충분하지 않을 경우 아이템을 자동으로 줄 바꿈 처리하며, 그에 맞게 암시적 행/열도 자동으로 수정합니다.</p><pre><code class="css">.container {  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));}</code></pre><p><img src="/images/screenshot/css-grid/auto-fill-2.jpg" alt="CSS Grid"></p><h3><span id="2404db24-2314-4eaa-bb2f-b7490d5402a0">auto-fill과 auto-fit의 차이</span><a href="#2404db24-2314-4eaa-bb2f-b7490d5402a0" class="header-anchor"></a></h3><p><code>auto-fill</code>과 <code>auto-fit</code>은 차이점은 그리드 컨테이너가 하나의 행/열(Track)에 모든 아이템을 수용하고 <strong>남는 공간이 있을 때</strong> 발생합니다.<br>다음과 같이 <code>auto-fill</code>은 남는 공간(빈 트랙)을 그대로 유지하고, <code>auto-fit</code>은 남는 공간을 축소합니다.</p><pre><code class="css">.container.auto-fill {  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));}.container.auto-fit {  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));}</code></pre><p><img src="/images/screenshot/css-grid/auto-fill-3.jpg" alt="CSS Grid"></p><h1><span id="507cc860-d8db-415d-8a4c-320d1bdb09ab">주요 용어 정리</span><a href="#507cc860-d8db-415d-8a4c-320d1bdb09ab" class="header-anchor"></a></h1><h2><span id="7d8c8117-fe56-4c7f-a242-ba7fc60aa72d">Track</span><a href="#7d8c8117-fe56-4c7f-a242-ba7fc60aa72d" class="header-anchor"></a></h2><p>트랙(Track)은 하나의 행(Row) 혹은 열(Column)을 의미합니다.</p><p><img src="/images/screenshot/css-grid/track-1.jpg" alt="CSS Grid"></p><h2><span id="0d8d961a-eb03-4f88-97de-8ce615ca1a36">Line</span><a href="#0d8d961a-eb03-4f88-97de-8ce615ca1a36" class="header-anchor"></a></h2><p>선(Line)은 일반적으로 거터(Gutter)라고 하는 트랙과 트랙 사이의 간격을 의미합니다.</p><p><img src="/images/screenshot/css-grid/line-1.jpg" alt="CSS Grid"></p><h2><span id="92021c1a-5d46-40e6-84a9-72f89eb45425">Cell</span><a href="#92021c1a-5d46-40e6-84a9-72f89eb45425" class="header-anchor"></a></h2><p>셀(Cell)은 아이템(Item)이 배치되는 최소 단위의 영역(Area)입니다.</p><p><img src="/images/screenshot/css-grid/cell-1.jpg" alt="CSS Grid"></p><h2><span id="f4532d47-7a76-4527-9e4a-5a4665c3135b">Area</span><a href="#f4532d47-7a76-4527-9e4a-5a4665c3135b" class="header-anchor"></a></h2><p>영역(Area)은 아이템이 배치되는, 하나 이상의 셀(Cell)로 이루어진 영역입니다.</p><p><img src="/images/screenshot/css-grid/area-1.jpg" alt="CSS Grid"></p><h1><span id="7ac567db-da05-4e3a-8851-7f5d524cff08">브라우저 지원</span><a href="#7ac567db-da05-4e3a-8851-7f5d524cff08" class="header-anchor"></a></h1><p><a href="https://caniuse.com/#search=grid" target="_blank" rel="noopener">https://caniuse.com/#search=grid</a></p><p><img src="/images/screenshot/css-grid/grid-browser-support.jpg" alt="CSS Grid browser support"></p><p>IE11에서는 <code>-ms-</code> 접두사를 이용해 일부 Grid 기능을 지원합니다.<br>다음에 <a href="https://github.com/postcss/autoprefixer" target="_blank" rel="noopener">Autoprefixer</a>에서 지원 가능한 속성만 정리했습니다.</p><table><thead><tr><th>속성</th><th>IE 속성</th></tr></thead><tbody><tr><td><code>display: grid;</code></td><td><code>display: -ms-grid;</code></td></tr><tr><td>grid-template-rows</td><td>-ms-grid-rows</td></tr><tr><td>grid-template-columns</td><td>-ms-grid-columns</td></tr><tr><td>grid-template-areas</td><td>-</td></tr><tr><td>grid-template</td><td>-</td></tr><tr><td>row-gap(grid-row-gap)</td><td>-</td></tr><tr><td>column-gap(grid-column-gap)</td><td>-</td></tr><tr><td>gap(grid-gap)</td><td>-</td></tr><tr><td>grid-row-start</td><td>-ms-grid-row</td></tr><tr><td>grid-row-end</td><td>-</td></tr><tr><td>grid-row</td><td>-</td></tr><tr><td>grid-column-start</td><td>-ms-grid-column</td></tr><tr><td>grid-column-end</td><td>-</td></tr><tr><td>grid-column</td><td>-</td></tr><tr><td>align-self</td><td>-ms-grid-row-align</td></tr><tr><td>justify-self</td><td>-ms-grid-column-align</td></tr><tr><td>-</td><td>-ms-grid-row-span</td></tr><tr><td>-</td><td>-ms-grid-column-span</td></tr></tbody></table><h1><span id="598ede6f-6801-48cf-b67d-9efd1e41c1ce">참고 자료(References)</span><a href="#598ede6f-6801-48cf-b67d-9efd1e41c1ce" class="header-anchor"></a></h1><p><a href="https://developer.mozilla.org/ko/docs/Web/CSS/grid" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/CSS/grid</a><br><a href="https://css-tricks.com/snippets/css/complete-guide-grid/" target="_blank" rel="noopener">https://css-tricks.com/snippets/css/complete-guide-grid/</a><br><a href="https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/" target="_blank" rel="noopener">https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/</a><br><a href="https://rachelandrew.co.uk/archives/2016/11/26/should-i-try-to-use-the-ie-implementation-of-css-grid-layout/" target="_blank" rel="noopener">https://rachelandrew.co.uk/archives/2016/11/26/should-i-try-to-use-the-ie-implementation-of-css-grid-layout/</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;CSS Grid(그리드)는 2차원(행과 열)의 레이아웃 시스템을 제공합니다.&lt;br&gt;Flexible Box도 훌륭하지만 비교적 단순한 1차원 레이아웃을 위하며, 좀 더 복잡한 레이아웃을 위해 우리는 CSS Grid를 사용할 수 있습니다.&lt;/p&gt;&lt;b
      
    
    </summary>
    
    
      <category term="css3" scheme="https://heropy.blog/tags/css3/"/>
    
      <category term="grid" scheme="https://heropy.blog/tags/grid/"/>
    
  </entry>
  
  <entry>
    <title>AWS Lambda@edge로 실시간 이미지 리사이징(updated)</title>
    <link href="https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/"/>
    <id>https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/</id>
    <published>2019-07-21T06:00:00.000Z</published>
    <updated>2020-10-30T06:01:57.941Z</updated>
    
    <content type="html"><![CDATA[<h1 id="변경사항"><a href="#변경사항" class="headerlink" title="변경사항"></a>변경사항</h1><h2 id="2019년-12월"><a href="#2019년-12월" class="headerlink" title="2019년 12월"></a>2019년 12월</h2><ul><li>CloudWatch 로그의 리전 확인에 대한 내용을 추가했습니다.</li></ul><h2 id="2019년-8월"><a href="#2019년-8월" class="headerlink" title="2019년 8월"></a>2019년 8월</h2><ul><li>Lambda@Edge에 대한 제한사항을 추가했습니다.</li><li><code>response.body</code>가 1MB 이상일 경우에 대한 예외처리 코드를 추가했습니다.</li><li>Sharp 라이브러리의 GIF 변환 이슈와 관련해 예외처리 방법을 추가했습니다.</li><li>좀 더 간편한 Lambda 새 버전 게시 방법을 추가했습니다.</li><li>CloudFront Cache에서 객체(파일)를 무효화하는 방법을 추가했습니다.</li></ul><div class="toc"><ul><li><a href="c19fb5a6-226c-4bf4-80dd-92566fd31017">AWS Lambda@edge로 실시간 이미지 리사이징</a></li><li><a href="5bc56b2f-720c-47f8-837d-7b56aba98246">IAM 정책 및 역할 생성</a><ul><li><a href="20d4c191-54cf-4da2-95fc-12fff8d2fb84">정책 생성</a><ul><li><a href="dc609a0b-060a-45be-acea-30cce43eecec">IAM / 정책</a></li></ul></li><li><a href="592930b5-a1d9-4c3d-acba-d71ece0f8b25">역할 생성</a><ul><li><a href="a421b8aa-49b3-4162-b24b-693a849ea6cc">IAM / 역할</a><ul><li><a href="e2808b45-04fd-4e11-8a98-f32e4dba7118">역할의 신뢰 관계(정책) 수정</a></li></ul></li></ul></li></ul></li><li><a href="e947a906-d754-46e1-9246-e18fb5578585">S3 설정 확인</a></li><li><a href="a0733f8e-0cbc-455d-85b4-0ba32c3b5883">Lambda 함수 생성</a></li><li><a href="4613aec2-22e1-4944-bce8-e89693477436">Cloud 9을 이용한 Lambda 함수 작성</a><ul><li><a href="01e1dd80-5828-46b3-9f35-d21605f87961">Cloud 9 / Your environments</a></li></ul></li><li><a href="16d069bd-d0c5-4889-8eee-ce6f09132ae1">Lambda 새 버전 게시</a><ul><li><a href="b585baed-4097-4934-9692-9653df920b5d">Lambda / 함수</a></li></ul></li><li><a href="4cee4800-e240-40ed-b7e9-bb97186dd442">CloudFront</a><ul><li><a href="ffccbf56-37ac-4719-8b49-c6ea1999d34e">CloudFront / Distributions</a></li></ul></li><li><a href="030d9439-2c10-497b-94bf-bb731f71e1bf">확인</a></li><li><a href="367b2d80-f4b3-45c9-8150-58033df50861">CloudWatch 로그 확인</a></li><li><a href="7a271edc-ba8c-472b-9f04-e180750bc827">Cache miss vs Cache hit</a><ul><li><a href="e807ab0e-c3ce-4967-b46d-2753d059f87d">CloudFront 엣지 캐시에서 파일 무효화</a></li></ul></li><li><a href="099b4c44-eb37-4a3c-b103-dffee4517c01">참고자료</a></li></ul></div><h1><span id="c19fb5a6-226c-4bf4-80dd-92566fd31017">AWS Lambda@edge로 실시간 이미지 리사이징</span><a href="#c19fb5a6-226c-4bf4-80dd-92566fd31017" class="header-anchor"></a></h1><p>Lambda@Edge는 Amazon CloudFront의 기능 중 하나로서, 서버를 <a href="https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D" target="_blank" rel="noopener">프로비저닝</a>하고, 코드(Node.js)를 AWS Lambda에 업로드하고 요청에 대한 응답으로 함수가 사용자에게 좀 더 가까운 위치(리전)에서 트리거 되도록 구성할 수 있습니다.</p><p>Lambda@Edge를 이용해 다음과 같이 쿼리스트링을 옵션으로 요청에 따라 실시간으로 이미지의 크기(w, h), 품질(q), 파일 형식(포맷, f)을 변경할 수 있도록 구성하고자 합니다.</p><pre><code class="plaintext">https://heropy.blog/heropy.png?w=150&amp;q=70&amp;f=webp</code></pre><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/Lambda@Edge for Image Resizing.jpg" alt="CloudCraft Lambda@Edge for Image Resizing"></p><h1><span id="5bc56b2f-720c-47f8-837d-7b56aba98246">IAM 정책 및 역할 생성</span><a href="#5bc56b2f-720c-47f8-837d-7b56aba98246" class="header-anchor"></a></h1><h2><span id="20d4c191-54cf-4da2-95fc-12fff8d2fb84">정책 생성</span><a href="#20d4c191-54cf-4da2-95fc-12fff8d2fb84" class="header-anchor"></a></h2><p>람다(Lambda)를 CloudFront 배포(Deploy)와 연결하기 위한 IAM 권한을 설정합니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/iam-policy.jpg" alt="IAM 정책"></p><ul><li><code>s3:PutObject</code> 권한은 필요치 않습니다.</li><li><code>cloudfront:CreateDistribution</code> 또는 <code>cloudfront:UpdateDistribution</code> 중 하나만 설정합니다.</li><li>CloudWatch에서 로그 데이터를 처리하기 위해 <code>logs:xxx</code> 권한들을 설정합니다.</li></ul><h3><span id="dc609a0b-060a-45be-acea-30cce43eecec">IAM / 정책</span><a href="#dc609a0b-060a-45be-acea-30cce43eecec" class="header-anchor"></a></h3><ol><li><code>정책 생성</code>을 선택합니다.</li><li><code>JSON</code>을 선택해 아래 정책을 입력합니다.</li><li>정책의 이름(<code>ResizingImages</code>)과 설명을 작성합니다.</li><li><code>정책 생성</code>!</li></ol><pre><code class="json">{    &quot;Version&quot;: &quot;2012-10-17&quot;,    &quot;Statement&quot;: [        {            &quot;Sid&quot;: &quot;VisualEditor0&quot;,            &quot;Effect&quot;: &quot;Allow&quot;,            &quot;Action&quot;: [                &quot;iam:CreateServiceLinkedRole&quot;,                &quot;lambda:GetFunction&quot;,                &quot;lambda:EnableReplication&quot;,                &quot;cloudfront:UpdateDistribution&quot;,                &quot;s3:GetObject&quot;,                &quot;logs:CreateLogGroup&quot;,                &quot;logs:CreateLogStream&quot;,                &quot;logs:PutLogEvents&quot;,                &quot;logs:DescribeLogStreams&quot;            ],            &quot;Resource&quot;: &quot;*&quot;        }    ]}</code></pre><h2><span id="592930b5-a1d9-4c3d-acba-d71ece0f8b25">역할 생성</span><a href="#592930b5-a1d9-4c3d-acba-d71ece0f8b25" class="header-anchor"></a></h2><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/iam-role.jpg" alt="IAM 역할"></p><h3><span id="a421b8aa-49b3-4162-b24b-693a849ea6cc">IAM / 역할</span><a href="#a421b8aa-49b3-4162-b24b-693a849ea6cc" class="header-anchor"></a></h3><ul><li><code>역할 만들기</code>를 선택해 다음과 같이 진행합니다.<ol><li>‘이 역할을 사용할 서비스’로 <code>Lambda</code>를 선택합니다.</li><li>목록 중 위에서 생성한 정책 <code>ResizingImages</code>를 선택합니다.</li><li>역할의 이름(<code>ResizingImages</code>)과 설명을 작성합니다.</li><li><code>역할 만들기</code>!</li></ol></li></ul><h4><span id="e2808b45-04fd-4e11-8a98-f32e4dba7118">역할의 신뢰 관계(정책) 수정</span><a href="#e2808b45-04fd-4e11-8a98-f32e4dba7118" class="header-anchor"></a></h4><p>서비스 보안 주체 <code>lambda.amazonaws.com</code> 및 <code>edgelambda.amazonaws.com</code>에 권한을 위임하기 위해 IAM 역할을 수정합니다.</p><ol><li>나의 역할 목록에서 방금 생성한 <code>ResizingImages</code> 선택합니다.</li><li><code>신뢰 관계</code> 탭의 <code>신뢰 관계 편집</code> 선택을 선택합니다.</li><li>아래의 신뢰 관계 정보를 입력합니다.</li><li><code>신뢰 정책 업데이트</code>!</li></ol><pre><code class="json">{  &quot;Version&quot;: &quot;2012-10-17&quot;,  &quot;Statement&quot;: [    {      &quot;Effect&quot;: &quot;Allow&quot;,      &quot;Principal&quot;: {        &quot;Service&quot;: [          &quot;edgelambda.amazonaws.com&quot;,          &quot;lambda.amazonaws.com&quot;        ]      },      &quot;Action&quot;: &quot;sts:AssumeRole&quot;    }  ]}</code></pre><h1><span id="e947a906-d754-46e1-9246-e18fb5578585">S3 설정 확인</span><a href="#e947a906-d754-46e1-9246-e18fb5578585" class="header-anchor"></a></h1><ul><li>버킷(Bucket)의 리전(Region)은 상관없습니다.</li><li>추후 CloudFront 설정 후 버킷 ‘권한/버킷정책’의 <code>CloudFront Origin Access Identity</code>가 연결한 CloudFront의 Origin Access Identity인지 확인하시면 됩니다.</li><li>테스트를 위한 버킷의 <code>퍼블릭</code> 액세스 권한이 필요할 수 있습니다.</li><li>버킷을 소유한 AWS 계정이 객체도 소유해야 합니다.</li><li>요청된 객체가 버킷에 존재해야 합니다.</li><li>버킷 Root 경로에 <code>favicon.ico</code> 파일을 업로드하세요. S3 Favicon error가 발생할 수 있습니다.</li></ul><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/s3-favicon-error.jpg" alt="S3 Favicon error"></p><h1><span id="a0733f8e-0cbc-455d-85b4-0ba32c3b5883">Lambda 함수 생성</span><a href="#a0733f8e-0cbc-455d-85b4-0ba32c3b5883" class="header-anchor"></a></h1><p>다음 Lambda@Edge에 대한 제한을 주의합니다.</p><table><thead><tr><th>엔터티</th><th>오리진 요청 및<br>응답 이벤트 제한</th><th>최종 사용자 요청 및<br>응답 이벤트 제한</th></tr></thead><tbody><tr><td>함수 리소스 할당</td><td>Lambda 제한과 동일</td><td>128MB</td></tr><tr><td>함수 제한 시간</td><td>30초</td><td>5초</td></tr><tr><td>헤더 및 본문을 포함하여 Lambda 함수에서 생성되는 응답의 크기</td><td>1MB</td><td>40KB</td></tr><tr><td>Lambda 함수 및 포함된 라이브러리의 최대 압축 크기</td><td>50MB</td><td>1MB</td></tr></tbody></table><ol><li>Lambda@Edge의 리전은 <code>미국 동부(버지니아 북부)</code>(us-east-1)만 허용됩니다.</li><li><code>함수 생성</code>을 선택해 다음과 같이 설정합니다.<ol><li>함수 이름(<code>ResizingImages</code>)을 입력합니다.</li><li>런타임(<code>Node.js 10.x</code>)을 선택합니다.</li><li><code>실행 역할 선택 또는 생성</code><ul><li>실행 역할: <code>기존 역할 사용</code></li><li>기존 역할: <code>ResizingImages</code></li></ul></li></ol></li><li><code>함수 생성</code>!</li><li>‘제한 시간’을 <code>3초</code>로 설정하면 최초 요청(Cache miss)에 연산이 많아지면 리사이징되지 않을 수 있습니다. <code>10초</code>로 설정합니다.</li><li><code>저장</code>!(화면 오른쪽 위)</li></ol><p>리전을 <code>미국 동부(버지니아 북부)</code>이 아닌 <code>아시아 태평양(서울)</code>로 설정했더니 다음과 같이 에러 메시지가 두둥!</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/lambda-function-region-error.jpg" alt="람다 리전 에러"></p><h1><span id="4613aec2-22e1-4944-bce8-e89693477436">Cloud 9을 이용한 Lambda 함수 작성</span><a href="#4613aec2-22e1-4944-bce8-e89693477436" class="header-anchor"></a></h1><p>로컬(macOS)에서 람다 코드를 세팅하고 zip파일로 업로드하니,</p><pre><code class="error">&quot;&#39;darwin-x64&#39; binaries cannot be used on the &#39;linux-x64&#39; platform. Please remove the &#39;node_modules/sharp/vendor&#39; directory and run &#39;npm install&#39;.&quot;</code></pre><p>라는 에러 메시지가 출력되네요.<br><code>node_modules</code> 디렉터리에 있는 Sharp 바이너리는 <code>linux-x64</code> 플랫폼용으로 설정되어야 하기 때문에,<br><a href="https://github.com/lambci/docker-lambda" target="_blank" rel="noopener">docker-lambda</a>로 람다 런타임과 일치하는 환경을 복제해 사용할 수 있습니다.<br>하지만 저는 다른 방법으로 파일 업로드(zip)를 하지 않고 작성하고 싶어 Cloud 9을 사용했습니다.</p><p>모듈은 <a href="https://sharp.pixelplumbing.com/en/stable/" target="_blank" rel="noopener">Sharp</a>만 설치하시면 됩니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/cloud9-details.jpg" alt="Cloud 9 Details"></p><h3><span id="01e1dd80-5828-46b3-9f35-d21605f87961">Cloud 9 / Your environments</span><a href="#01e1dd80-5828-46b3-9f35-d21605f87961" class="header-anchor"></a></h3><p>람다 코드를 작성하기 위해 새로운 Cloud 9 환경을 설정합니다.</p><blockquote><p>아직 서울 리전은 지원되지 않습니다.</p></blockquote><ol><li><code>Create environment</code>를 선택합니다.</li><li>Name environment의 Name(<code>ResizingImages</code>)과 Description을 입력합니다.</li><li>다음과 같이 Configure settings를 설정했습니다.<ul><li>Create a new instance for environment (EC2)</li><li>t2.micro (1 GiB RAM + 1 vCPU)</li><li>Amazon Linux</li><li>After 30 minutes (default)</li></ul></li><li><code>Create environment</code>!</li></ol><p>위에서 생성했던 람다 함수를 Cloud 9 환경으로 불러옵니다.</p><ol><li>화면 오른쪽 위 메뉴 중 <code>AWS Resources</code>를 선택합니다.</li><li>Lambda(us-east-1)/<code>Remote Functions</code> 목록의 <code>ResizingImages</code>를 <code>Import</code>합니다.</li><li>불러온 람다 함수로 접근하기 위해 터미널(Terminal)을 이용합니다.(<code>package.json</code>을 생성하고 Sharp 모듈을 설치합니다.)<ol><li><code>$ cd ResizingImages</code></li><li><code>$ npm init -y</code></li><li><code>$ npm i sharp</code></li></ol></li><li><code>index.js</code>을 아래 코드와 같이 수정합니다.</li><li>작성한 람다 함수를 <code>$LATEST</code> 버전으로 배포합니다.<ul><li>Lambda(us-east-1)/<code>Local Functions</code> 목록의 <code>ResizingImages</code>를 <code>Deploy</code>합니다.</li></ul></li></ol><pre><code class="js">&#39;use strict&#39;;const querystring = require(&#39;querystring&#39;); // Don&#39;t install.const AWS = require(&#39;aws-sdk&#39;); // Don&#39;t install.const Sharp = require(&#39;sharp&#39;);const S3 = new AWS.S3({  region: &#39;&lt;YOUR_BUCKET_REGION&gt;&#39;});const BUCKET = &#39;&lt;YOUR_BUCKET_NAME&gt;&#39;;exports.handler = async (event, context, callback) =&gt; {  const { request, response } = event.Records[0].cf;  // Parameters are w, h, f, q and indicate width, height, format and quality.  const params = querystring.parse(request.querystring);  // Required width or height value.  if (!params.w &amp;&amp; !params.h) {    return callback(null, response);  }  // Extract name and format.  const { uri } = request;  const [, imageName, extension] = uri.match(/\/?(.*)\.(.*)/);  // Init variables  let width;  let height;  let format;  let quality; // Sharp는 이미지 포맷에 따라서 품질(quality)의 기본값이 다릅니다.  let s3Object;  let resizedImage;  // Init sizes.  width = parseInt(params.w, 10) ? parseInt(params.w, 10) : null;  height = parseInt(params.h, 10) ? parseInt(params.h, 10) : null;  // Init quality.  if (parseInt(params.q, 10)) {    quality = parseInt(params.q, 10);  }  // Init format.  format = params.f ? params.f : extension;  format = format === &#39;jpg&#39; ? &#39;jpeg&#39; : format;  // For AWS CloudWatch.  console.log(`parmas: ${JSON.stringify(params)}`); // Cannot convert object to primitive value.  console.log(`name: ${imageName}.${extension}`); // Favicon error, if name is `favicon.ico`.  try {    s3Object = await S3.getObject({      Bucket: BUCKET,      Key: decodeURI(imageName + &#39;.&#39; + extension)    }).promise();  } catch (error) {    console.log(&#39;S3.getObject: &#39;, error);    return callback(error);  }  try {    resizedImage = await Sharp(s3Object.Body)      .resize(width, height)      .toFormat(format, {        quality      })      .toBuffer();  } catch (error) {    console.log(&#39;Sharp: &#39;, error);    return callback(error);  }  const resizedImageByteLength = Buffer.byteLength(resizedImage, &#39;base64&#39;);  console.log(&#39;byteLength: &#39;, resizedImageByteLength);  // `response.body`가 변경된 경우 1MB까지만 허용됩니다.  if (resizedImageByteLength &gt;= 1 * 1024 * 1024) {    return callback(null, response);  }  response.status = 200;  response.body = resizedImage.toString(&#39;base64&#39;);  response.bodyEncoding = &#39;base64&#39;;  response.headers[&#39;content-type&#39;] = [    {      key: &#39;Content-Type&#39;,      value: `image/${format}`    }  ];  return callback(null, response);};</code></pre><p>만약 GIF 포맷을 사용하는 경우 원본 그대로 반환하도록 예외처리할 수 있습니다.</p><blockquote><p>Sharp 라이브러리에서 GIF 포맷을 다시 GIF로 리사이징하는데 문제를 확인했습니다.<br>관련 이슈는 <a href="https://github.com/lovell/sharp/issues/1372에서" target="_blank" rel="noopener">https://github.com/lovell/sharp/issues/1372에서</a> 확인할 수 있습니다.</p></blockquote><pre><code class="js">// Exception &#39;.gif&#39; image.if (extension === &#39;gif&#39;) {  console.log(&#39;GIF image requested!&#39;);  return callback(null, response);}</code></pre><p>다음과 같이 변환할 포맷이 없는 경우만 처리할 수도 있습니다.</p><pre><code class="js">if (extension === &#39;gif&#39; &amp;&amp; !params.f) {  console.log(&#39;GIF image requested!&#39;);  return callback(null, response);}</code></pre><p>혹은 CloudFront에서 예외처리할 수도 있습니다.</p><blockquote><p>캐시 동작 순서를 주의합니다!</p></blockquote><ol><li>CloudFront로 이동해 해당 Distribution을 선택합니다.</li><li><code>Behaviors</code> 탭에서 <code>Create Behavior</code>를 선택합니다.</li><li>다음과 같이 수정 후 <code>Create</code>!<ul><li>Path Pattern: <code>*.gif</code></li><li>Compress Objects Automatically: <code>Yes</code></li></ul></li></ol><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/cloudfront-create-behavior-gif.jpg" alt="CloudFront Create Behavior"></p><h1><span id="16d069bd-d0c5-4889-8eee-ce6f09132ae1">Lambda 새 버전 게시</span><a href="#16d069bd-d0c5-4889-8eee-ce6f09132ae1" class="header-anchor"></a></h1><p>$LATEST 버전으론 Lambda@Edge를 사용할 수 없음으로 새로운 버전을 게시하여 CloudFront에 연결합니다.<br>내용을 수정했다면 수정된 버전을 게시하여 CloudFront에 연결해야 합니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/lambda-create-new-version.jpg" alt="Lambda 새 버전 게시"></p><h3><span id="b585baed-4097-4934-9692-9653df920b5d">Lambda / 함수</span><a href="#b585baed-4097-4934-9692-9653df920b5d" class="header-anchor"></a></h3><ol><li>함수 목록에서 <code>ResizingImages</code>를 선택합니다.</li><li>오른쪽 위 메뉴 중 ‘작업’의 <code>새 버전 게시</code>를 선택합니다.</li><li>‘$LATEST의 새 버전 게시’의 ‘버전 설명’을 입력하고 새로운 버전을 <code>게시</code>합니다.</li><li>게시된 새 버전의 ARN 복사(<code>ARN - arn:aws:lambda~~~:ResizingImages:1</code>, 화면 오른쪽 위)하여 CloudFront에 연결해야 합니다.</li></ol><p>만약 람다를 수정했다면 다음과 같이 새 버전을 게시하고 CloudFront와 연결합니다.</p><ol><li>게시된 새 버전의 ARN 복사(<code>ARN - arn:aws:lambda~~~:ResizingImages:2</code>, 화면 오른쪽 위)합니다.</li><li>CloudFront로 이동해 해당 Distribution을 선택합니다.</li><li><code>Behaviors</code> 탭에서 기본 Behavior를 체크하고 <code>Edit</code>를 선택합니다.</li><li>다음과 같이 수정 후 <code>Yes, Edit</code>!<ul><li>Lambda Function Associations:<ul><li>Lambda Function ARN: Lambda 함수에서 복사한 ARN 입력(<code>arn:aws:lambda~~~:ResizingImages:2</code>)</li></ul></li></ul></li></ol><p>만약 람다를 수정했다면 좀 더 쉽게 새 버전을 게시하고 CloudFront와 연결할 수 있습니다.</p><blockquote><p>CloudFront 설정이 미리 필요합니다.</p></blockquote><ol><li>수정한 $LATEST 버전에서 ‘작업’의 <code>Lambda@Edge 배포</code>를 선택합니다.</li><li><code>&#39;이 함수에 기존 CloudFront 트리거 사용</code>을 선택하고 내용을 확인한 뒤 <code>배포</code>합니다.</li></ol><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/lambda-cloudfront-use-trigger-existing.jpg" alt="기존 트리거 사용"></p><p>새로운 트리거를 구성할 경우 다음과 같이 설정할 수 있습니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/lambda-cloudfront-use-trigger-new.jpg" alt="새로운 트리거 구성"></p><h1><span id="4cee4800-e240-40ed-b7e9-bb97186dd442">CloudFront</span><a href="#4cee4800-e240-40ed-b7e9-bb97186dd442" class="header-anchor"></a></h1><h3><span id="ffccbf56-37ac-4719-8b49-c6ea1999d34e">CloudFront / Distributions</span><a href="#ffccbf56-37ac-4719-8b49-c6ea1999d34e" class="header-anchor"></a></h3><ol><li><code>Create Distribution</code>을 선택합니다.</li><li>Web에서 <code>Get Started</code>를 선택하고 다음과 같이 설정합니다.<ul><li>Origin Domain Name: 웹 콘텐츠를 가져올 AWS S3 Bucket</li><li>Restrict Bucket Access: <code>Yes</code>(항상 CloudFront URL로 S3 액세스하도록 요구)</li><li>Origin Access Identity: <code>Create a New Identity</code></li><li>Grant Read Permissions on Bucket: <code>Yes, Update Bucket Policy</code>(CloudFront가 S3의 버킷 정책에 액세스하여 업데이트)</li><li>Query String Forwarding and Caching: <code>Forward all, cache based on whitelist</code></li><li>Query String Whitelist: <code>w</code>, <code>h</code>, <code>f</code>, <code>q</code>(CloudFront가 사용할(캐싱 기반) 쿼리스트링 매개 변수 정의)</li><li>Compress Objects Automatically: <code>Yes</code>(<code>Accept-Encoding: gzip</code> 요청에 대한 콘텐츠 압축 여부)</li><li>Lambda Function Associations:<ul><li>CloudFront Event: <code>Origin Response</code></li><li>Lambda Function ARN: Lambda 함수에서 복사한 ARN 입력(<code>arn:aws:lambda~~~:ResizingImages:1</code>)</li></ul></li><li>Comment: <code>ResizingImages</code>(Distribution 구분을 위한 간략한 이름/설명)</li></ul></li><li><code>Create Distribution</code>!</li></ol><p>Distribution의 ‘Status’를 확인하세요. <code>In Progress</code>에서 <code>Deployed</code>로 변경되는데 10~15분 정도 소요됩니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/cloudfront-status-in-progress.jpg" alt="CloudFront 배포 중"></p><p>CloudFront 설정에서 대체 도메인 이름(CNAME)을 사용하면 CloudFront에서 배포용 도메인 이름 대신에 고유의 도메인 이름(예: www.heropy.blog)이 사용됩니다.<br>이 포스트에선 대체 도메인을 지정하지 않습니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/cloudfront-cname.jpg" alt="CloudFront CNAME"></p><h1><span id="030d9439-2c10-497b-94bf-bb731f71e1bf">확인</span><a href="#030d9439-2c10-497b-94bf-bb731f71e1bf" class="header-anchor"></a></h1><p>CloudFront Distribution가 배포 완료되면, CloudFront Domain Name으로 다음 예제와 같이 이미지에 접근할 수 있습니다.</p><pre><code class="plaintext">d2d73zr4p2zskr.cloudfront.net/heropy.png?w=120&amp;f=webp&amp;q=90</code></pre><blockquote><p>쿼리스트링의 순서가 바뀌지 않도록 주의합니다!</p></blockquote><h1><span id="367b2d80-f4b3-45c9-8150-58033df50861">CloudWatch 로그 확인</span><a href="#367b2d80-f4b3-45c9-8150-58033df50861" class="header-anchor"></a></h1><p>CloudWatch를 통해 람다 함수에서 발생하는 로그를 확인할 수 있습니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/cloudwatch-logs.jpg" alt="CloudWatch Logs"></p><p>Lambda@Edge를 배포하면 그 함수를 전 세계의 AWS 리전에 복제하기 때문에 요청이 들어온 리전에서 해당 람다의 로그를 확인해야 합니다.<br>따라서 일반적인 CloudWatch 로그는 <strong>서울 리전에서 확인</strong>하시면 됩니다.</p><p><a href="https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-how-it-works.html" target="_blank" rel="noopener">Lambda@Edge 함수 생성 및 사용 시작하기</a><br><img src="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/images/lambda-creation-workflow-aws-location.png" alt="Lambda@Edge 함수 생성 및 사용 시작하기"></p><h1><span id="7a271edc-ba8c-472b-9f04-e180750bc827">Cache miss vs Cache hit</span><a href="#7a271edc-ba8c-472b-9f04-e180750bc827" class="header-anchor"></a></h1><p>한 번의 테스트에서 PNG 포맷의 원본 이미지(500x500px)를 555px 크기의 WEBP 포맷으로 변경하는데,<br>‘Cache miss’ 경우 <code>5.26s</code>가, ‘Cache hit’ 경우 <code>31ms</code>가 소요되었습니다.</p><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/cache-miss-vs-cache-hitrequest-time.jpg" alt="cache miss vs cache hit"></p><h2><span id="e807ab0e-c3ce-4967-b46d-2753d059f87d">CloudFront 엣지 캐시에서 파일 무효화</span><a href="#e807ab0e-c3ce-4967-b46d-2753d059f87d" class="header-anchor"></a></h2><p>CloudFront 엣지 캐시에서 파일이 만료되기 전에 파일을 제거해야 할 경우, 다음과 같이 설정합니다.</p><ol><li>CloudFront로 이동해 해당 Distribution을 선택합니다.</li><li><code>Invalidations</code> 탭에서 <code>Create Invalidation</code>을 선택합니다.</li><li>Object Paths에 경로를 입력합니다.<ul><li>E.g. <code>/heropy.png</code>, <code>/*</code>, <code>/images/*</code></li></ul></li><li><code>Invalidate</code>!</li></ol><p><img src="/images/screenshot/resizing-images-cloudfrount-lambda/cloudfront-invalidate-file.jpg" alt="Invalidate file"></p><h1><span id="099b4c44-eb37-4a3c-b103-dffee4517c01">참고자료</span><a href="#099b4c44-eb37-4a3c-b103-dffee4517c01" class="header-anchor"></a></h1><p><a href="https://engineering.huiseoul.com/lambda-%ED%95%9C%EA%B0%9C%EB%A1%9C-%EB%A7%8C%EB%93%9C%EB%8A%94-on-demand-image-resizing-d48167cc1c31" target="_blank" rel="noopener">https://engineering.huiseoul.com/lambda-%ED%95%9C%EA%B0%9C%EB%A1%9C-%EB%A7%8C%EB%93%9C%EB%8A%94-on-demand-image-resizing-d48167cc1c31</a><br><a href="https://ikso2000.tistory.com/106" target="_blank" rel="noopener">https://ikso2000.tistory.com/106</a><br><a href="https://medium.com/daangn/aws-lambda%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EC%83%9D%EC%84%B1-%EA%B0%9C%EB%B0%9C-%ED%9B%84%EA%B8%B0-acc278d49980" target="_blank" rel="noopener">https://medium.com/daangn/aws-lambda%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EC%83%9D%EC%84%B1-%EA%B0%9C%EB%B0%9C-%ED%9B%84%EA%B8%B0-acc278d49980</a><br><a href="https://medium.com/daangn/lambda-edge%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-on-the-fly-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%A7%95-f4e5052d49f3" target="_blank" rel="noopener">https://medium.com/daangn/lambda-edge%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-on-the-fly-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%A7%95-f4e5052d49f3</a><br><a href="https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html</a><br><a href="https://jasonbla.tistory.com/5" target="_blank" rel="noopener">https://jasonbla.tistory.com/5</a><br><a href="https://github.com/lovell/sharp/issues/1372" target="_blank" rel="noopener">https://github.com/lovell/sharp/issues/1372</a><br><a href="https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge" target="_blank" rel="noopener">https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge</a></p>]]></content>
    
    <summary type="html">
    
      AWS Lambda@edge(CloudFront)로 실시간 이미지 리사이징 기능을 구현합니다. Cloud 9으로 람다 함수를 작성하고 CloudWatch로 로그를 확인합니다.
    
    </summary>
    
    
      <category term="aws" scheme="https://heropy.blog/tags/aws/"/>
    
      <category term="lambda@edge" scheme="https://heropy.blog/tags/lambda-edge/"/>
    
      <category term="cloudfront" scheme="https://heropy.blog/tags/cloudfront/"/>
    
      <category term="s3" scheme="https://heropy.blog/tags/s3/"/>
    
      <category term="cloudwatch" scheme="https://heropy.blog/tags/cloudwatch/"/>
    
      <category term="cloud 9" scheme="https://heropy.blog/tags/cloud-9/"/>
    
  </entry>
  
  <entry>
    <title>HTML IMG의 srcset과 sizes 속성(updated)</title>
    <link href="https://heropy.blog/2019/06/16/html-img-srcset-and-sizes/"/>
    <id>https://heropy.blog/2019/06/16/html-img-srcset-and-sizes/</id>
    <published>2019-06-15T19:38:00.000Z</published>
    <updated>2020-10-04T04:46:23.733Z</updated>
    
    <content type="html"><![CDATA[<h1 id="드리는-말씀"><a href="#드리는-말씀" class="headerlink" title="드리는 말씀"></a>드리는 말씀</h1><p>이 포스트 내용과 관련해 비슷한 질문을 자주 받는 이유로 몇 가지 저의 생각을 정리하고자 합니다.<br>최대한 쉬운 용어와 표현으로 정리해 보겠습니다.<br>도움이 되셨으면 좋겠습니다.</p><p>첫 번째, <code>srcset</code> 속성은 ‘이미지 소스의 세트’라는 의미입니다.<br><strong>같은 비율의 다양한 크기를 가지는 동일 이미지들</strong>을 최소 2개 이상 명시하는 속성이죠.<br>만약 1개 이미지 소스만 명시하려면 <code>src</code> 속성을 사용하시면 됩니다.</p><p>두 번째, 제가 본문과 댓글에서 지속해서 강조하고 있는데,<br><code>srcset</code> 속성은 <strong>브라우저에 이미지 선택권을 위임하는 속성</strong>으로,<br>개발자는 어느 환경에서 어떤 크기의 이미지가 선택될 것인지 알 필요가 없으며, 알더라도 그냥 모르는 척하는 것이 정신 건강에 좋습니다.<br>수많은 사용자 환경의 각 브라우저는 사용자에게 최적 크기의 이미지를 보여주기 위해 항상 노력하고 있습니다.<br>그러다 보니 대표적으로 브라우저가 큰 이미지를 한 번 불러왔으면, 오히려 해상도가 떨어지는 작은 이미지를 또 불러올 필요를 느끼지 못해 뷰포트 크기가 변경되어도 이미지 갱신이 일어나지 않는다고 보시면 됩니다.<br>이미지를 불러오는 행위는 네트워크, 메모리, 성능, 시간 등의 여러 비용을 지불해야 하기 때문입니다.<br>그런 이유에서 앞서 ‘같은 비율의 다양한 크기를 가지는 동일 이미지들’이란 표현을 사용했고요.</p><p>세 번째, 다른 비율의 다양한 크기의 다른 이미지들을 사용하고 싶다면,<br>IMG 요소의 <code>srcset</code> 속성이 아닌 CSS의 <code>@media</code>(미디어쿼리 혹은 미디어 규칙) 사용을 권장합니다.<br>개발자가 원하는 해상도에 맞게 출력할 결과를 정확히 명시할 수 있기 때문이죠.<br>기타 이유는 ‘두 번째’ 설명과 같습니다.</p><p>네 번째, <code>x</code>와 <code>w</code> 디스크립터는 개발자가 브라우저에 이미지의 크기를 알려주는 용도의 단위입니다.<br>‘두 번째’ 설명에서 말씀드린대로 이미지 로드는 지불할 비용이 크기 때문에,<br>브라우저가 비용 지불 없이도 이미지의 크기를 알 수 있도록 개발자가 <code>x</code> 혹은 <code>w</code> 디스크립터로 이미지 크기를 명시하는 것입니다.<br>따라서 이미지의 정확한 크기를 확인하고 입력하는 것이 중요합니다.</p><p>다섯 번째, Mac에서는 많은 경우 디스플레이 해상도로 ‘스케일링’을 사용합니다.<br>쉽게 말씀드리면, 디스플레이 확대/축소 비율이 100%가 아니고 120%, 150% 같은 확대된 해상도라는 겁니다.<br>Windows도 같은 기능이 있습니다만 기본 옵션이 아닌 경우가 많아 말씀드리고자 하는 현상이 덜합니다.<br>모두 OS 디스플레이 옵션에서 해당 내용을 확인할 수 있는데요.<br>특히 Mac의 경우 정확한 해상도 수치로 옵션 선택이 안되다 보니 문제 해결과 이해에 어려움을 겪는 경우가 많은 듯합니다.<br>그래서 Mac에서는 RDM(<a href="https://github.com/avibrazil/RDM" target="_blank" rel="noopener">https://github.com/avibrazil/RDM</a>) 같은 프로그램을 사용하시는 것도 좋습니다.<br>아무튼 디스플레이가 확대/축소된 경우, 이미지도 그에 맞게 확대/축소된 크기로 렌더링을 해야 하는데요.<br>그러다 보면 확대/축소 일부 구간에서 이미지가 흐려지는 현상이 발생합니다.<br>대부분의 이미지는 비트맵 방식(jpg, png, gif 등)이고 픽셀 단위로 렌더링 되다 보니 발생하는 현상입니다.<br>그런데 일부 브라우저는 이 현상을 해결하기 위해 이미지 비율을 임의로 조정합니다.<br>예를 들어, 141% 확대된 이미지라면 150%로 출력한다는 것이죠.<br>그래서 해당 뷰포트 크기에서 원하는 이미지가 나오지 않을 수 있는 겁니다.<br>그 외 ‘두 번째’ 내용도 같이 발생할 수 있고요.</p><p>여섯 번째, <code>x</code>와 <code>w</code> 디스크립터는 필요에 맞게 사용하는 것이지, 더 좋고 나쁨의 단위는 아닙니다.<br>제가 아는 바로는 <code>w</code> 디스크립터가 비교적 더 최신 기술이고, 그 전에 많이 사용된 <code>x</code> 디스크립터에 관련된 정보가 훨씬 많습니다.<br>아이폰이 3에서 4로 넘어가면서 3과 4를 동시에 지원해야 하니 iOS 개발에 필요한 이미지를 특정 배수 크기로 요구하기 시작했고,<br>(예를 들어 <code>image.png</code>와 <code>image@2x.png</code>를 동시에 관리해야 했죠)<br>이후 수많은 모바일 개발에서 배수 방식을 사용하면서 <code>x</code> 디스크립터가 유용해졌죠.<br>그리고 그것을 API로 요구하는 경우도 있고요.<br>그래서 <code>x</code>와 <code>w</code> 디스크립터 사용에 대한 입장이 일부 나뉘는 듯한데요, 저의 생각은 이렇습니다.<br><strong>‘일반적인 웹 개발 경우에 꼭 배수(<code>x</code>)로 작성할 필요 없이 그냥 <code>w</code> 디스크립터로 다양한 이미지를 제공하면, 브라우저가 이미지를 선택할 권한이 있으니 알아서 필요한 이미지를 사용하지 않을까?! 단, 개발 요구사항이 <code>x</code> 디스크립터라면 필수로 작성해야지!’</strong><br>수많은 사용자 환경이 동일한 배수 이미지를 요구하는 것이 아니기 때문에 개발자는 모든 환경에 대응한 이미지를 준비할 수 없고 그만큼 <code>w</code> 디스크립터가 중요해졌다고 생각합니다.<br>그리고 <code>w</code>는 ‘Width’를 의미하는데요, 그러면 ‘Height’를 의미하는 <code>h</code> 디스크립터는 없느냐?<br>실제 <code>h</code> 디스크립터 도입을 논의하긴 했지만, 여러 문제로 폐기되었는데요, 그 문제 중 하나가 바로 사용과 인지의 복잡성입니다.<br>결론적으로 개발자는 <code>w</code> 디스크립터를 사용해 이미지의 가로 너비만 명확하게 명시하면, 수많은 사용자 환경에 대한 대응은 브라우저가 알아서 할 것이니 고민을 덜 해도 된다는 겁니다.</p><p>여기까지가 댓글과 이메일을 통해 자주 받는 질문에 대한 저의 생각입니다.<br>여러 정보를 바탕으로 지극히 주관적으로 풀어낸 답변이니, 무조건적인 신뢰는 금물입니다.<br>만약 위와 다른 의견이나 정보가 있으시다면 댓글이나 이메일 부탁드립니다.<br>감사합니다.</p><h1 id="변경사항"><a href="#변경사항" class="headerlink" title="변경사항"></a>변경사항</h1><h2 id="2020년-4월"><a href="#2020년-4월" class="headerlink" title="2020년 4월"></a>2020년 4월</h2><ul><li><code>px</code>단위와 혼동되지 않게, <code>x</code> 및 <code>w</code> descriptor의 ‘단위’ 단어를 ‘디스크립터’로 변경했습니다.</li><li>X descriptor 파트에 ‘내 디바이스 화면 측정’ 링크를 추가했습니다.</li><li>일부 내용과 오타를 수정했습니다.</li></ul><div class="toc"><ul><li><a href="ed486bee-ff8d-4a8a-b6c1-c1c868e6cb56">개요</a></li><li><a href="a2503fcf-bcfc-43b3-bce4-3b8803c1e938">테스트에 앞서..</a></li><li><a href="b78cf4d4-c5a3-4792-8498-17a4df63285a">간단한 예제</a></li><li><a href="7238110c-c2e5-4905-ae6d-8ec7bee7e02c">srcset</a><ul><li><a href="3b134bf3-7197-4237-9fb5-8a5c657fac7e">W descriptor</a></li><li><a href="18252fe9-e71e-4ccb-891b-9606820ee298">X descriptor</a></li></ul></li><li><a href="933a4a96-59b8-4b40-829d-d906bc20100b">sizes</a></li><li><a href="492b67c9-a45b-4c44-8b60-4ca5f4cc62af">브라우저 지원</a><ul><li><a href="e3ef580b-860c-4797-ba91-8511e3abac8e">Polyfill</a></li></ul></li><li><a href="9575a735-a6dd-4817-8d48-968cd7bdebe1">Sample images</a></li><li><a href="f94a9d29-7631-4708-bf65-409938569dbd">참고 자료(References)</a></li></ul></div><h1><span id="ed486bee-ff8d-4a8a-b6c1-c1c868e6cb56">개요</span><a href="#ed486bee-ff8d-4a8a-b6c1-c1c868e6cb56" class="header-anchor"></a></h1><p>일반적으로 반응형 웹에서 이미지를 지원하기 위해, ‘미디어쿼리’라고 부르는 CSS Media Rule(<code>@media</code>)에서 <code>background-image</code> 속성을 많이 사용하는데, 반응형 이미지를 처리하기 위해 뷰포트(Viewport)의 크기부터 사용자 화면의 해상도 등 많은 환경을 고려해야 합니다.<br>하지만 우리는 HTML IMG의 <code>srcset</code>과 <code>sizes</code>를 통해 쉽게는 이미지의 크기를 설정하는 것만으로 대부분의 고려 사항을 사용자 브라우저(User agent)에 떠넘길 수 있습니다.</p><h1><span id="a2503fcf-bcfc-43b3-bce4-3b8803c1e938">테스트에 앞서..</span><a href="#a2503fcf-bcfc-43b3-bce4-3b8803c1e938" class="header-anchor"></a></h1><p>테스트를 위해 우선 확인할 부분이 있습니다.<br>이미지가 캐싱 되어 새로고침 되지 않는 경우가 발생할 수 있기 때문에, (크롬 브라우저를 사용한다면) 개발자 도구(<code>F12</code>)에서 <code>disable cache</code>를 체크하세요!</p><p><img src="/images/screenshot/html-img-srcset-and-sizes/chrome_network_disable_cache.jpg" alt="disable cache"></p><p>그리고 예제에 사용한 이미지들은 포스트 하단에 첨부했습니다.</p><h1><span id="b78cf4d4-c5a3-4792-8498-17a4df63285a">간단한 예제</span><a href="#b78cf4d4-c5a3-4792-8498-17a4df63285a" class="header-anchor"></a></h1><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 400w,          images/heropy_medium.png 700w,          images/heropy_large.png 1000w&quot;       sizes=&quot;(max-width: 500px) 444px,         (max-width: 800px) 777px,         1222px&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><p>위 예제는 다음과 같이 이해할 수 있습니다.<br><code>srcset</code> 속성은 쉼표(<code>,</code>)로 구분된 사용할 이미지들의 경로와 해당 이미지의 원본 크기를 지정하고,<br><code>sizes</code> 속성은 쉼표(<code>,</code>)로 구분된 미디어조건(선택적)과 그에 따라 최적화되어 출력될 이미지 크기를 지정합니다.</p><pre><code class="html">&lt;img  srcset=&quot;경로 원본크기,          경로 원본크기,          경로 원본크기&quot;  sizes=&quot;(미디어조건) 최적화출력크기,         (미디어조건) 최적화출력크기,         기본출력크기&quot;  src=&quot;경로&quot;  alt=&quot;대체텍스트&quot; /&gt;</code></pre><p>일반적인 IMG 작성 방식이 아니기 때문에 조금 생소합니다.<br>하지만 어렵게 생각할 필요가 없습니다!<br>차근차근 알아봅시다.</p><blockquote><p><code>src</code> 속성은 <code>srcset</code>을 사용할 수 없는 환경에서 동작합니다!</p></blockquote><h1><span id="7238110c-c2e5-4905-ae6d-8ec7bee7e02c">srcset</span><a href="#7238110c-c2e5-4905-ae6d-8ec7bee7e02c" class="header-anchor"></a></h1><p><code>srcset</code>은 브라우저에 제시할(사용할) 이미지들과 그 이미지들의 원본 크기를 지정합니다.</p><p>사용 방법은 간단합니다.</p><p>사용할 이미지를 사이즈별로 2장 이상 준비하여 <code>srcset</code> 속성에 작성합니다.<br>단, 주의사항은 이미지의 크기로 <code>px</code>단위가 아닌 <code>w</code> 디스크립터 혹은 <code>x</code> 디스크립터를 입력해야 하며, <strong>작은 크기 이미지부터 순서대로 입력</strong>합니다.</p><h2><span id="3b134bf3-7197-4237-9fb5-8a5c657fac7e">W descriptor</span><a href="#3b134bf3-7197-4237-9fb5-8a5c657fac7e" class="header-anchor"></a></h2><p><code>w</code> 디스크립터(Width descriptor)는 이미지의 원본 크기(가로 너비)를 의미합니다.<br>예를 들어 <code>400x300</code>(px) 크기 이미지의 <code>w</code> 값은 <code>400w</code>입니다.</p><blockquote><p>브라우저(User agent)는 지정된 <code>w</code> 디스크립터를 통해 각 이미지의 최적화된 픽셀 밀도를 계산합니다.</p></blockquote><p>다음 예제를 살펴봅시다.</p><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 400w,          images/heropy_medium.png 700w,          images/heropy_large.png 1000w&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><p>위 예제의 결과로,<br>뷰포트 너비가 400px 이하일 때 <code>heropy_small.png</code>(400px)가 사용됩니다.<br>뷰포트 너비가 401~700px 일 때 <code>heropy_medium.png</code>(700px)가 사용됩니다.<br>뷰포트 너비가 701px 이상일 때 <code>heropy_large.png</code>(1000px)가 사용됩니다.</p><blockquote><p>이하 모든 예제의 뷰포트 너비는 뷰포트의 가로 너비를 의미합니다.</p></blockquote><p><img src="/images/screenshot/html-img-srcset-and-sizes/example_1.jpg" alt="srcset example 1"></p><p>우리는 단지 3장의 이미지와 그 크기만 <code>srcset</code>에 입력했을 뿐인데 브라우저는 각 이미지 중 현재 뷰포트 너비에 최적화된 이미지를 선택해 출력합니다.<br>마치 다음의 CSS 미디어조건과 비슷합니다.</p><pre><code class="css">.some-image {  width: 400px;  height: 400px;  background-image: url(&quot;images/heropy_small.png&quot;);     background-repeat: no-repeat;}@media (min-width: 401px) {  .some-image {    width: 700px;    height: 700px;    background-image: url(&quot;images/heropy_medium.png&quot;);     }}@media (min-width: 701px) {  .some-image {    width: 1000px;    height: 1000px;    background-image: url(&quot;images/heropy_large.png&quot;);     }}</code></pre><p>고정된 이미지 크기를 유지하려면 <code>width</code> 속성을 추가할 수 있습니다.<br>(<code>sizes</code> 속성과는 다른 개념입니다!)</p><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 400w,          images/heropy_medium.png 700w,          images/heropy_large.png 1000w&quot;  width=&quot;400&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><p><img src="/images/screenshot/html-img-srcset-and-sizes/example_2.jpg" alt="srcset example 2"></p><p>이는 다음과 비슷합니다.</p><pre><code class="css">.some-image {  width: 400px;  height: 400px;  background-image: url(&quot;images/heropy_small.png&quot;);     background-repeat: no-repeat;  background-size: cover;}@media (min-width: 401px) {  .some-image {    background-image: url(&quot;images/heropy_medium.png&quot;);     }}@media (min-width: 701px) {  .some-image {    background-image: url(&quot;images/heropy_large.png&quot;);     }}</code></pre><h2><span id="18252fe9-e71e-4ccb-891b-9606820ee298">X descriptor</span><a href="#18252fe9-e71e-4ccb-891b-9606820ee298" class="header-anchor"></a></h2><p><code>x</code> 디스크립터(Device pixel ratio descriptor)는 이미지의 비율 의도를 의미합니다.<br>위 <code>w</code> 디스크립터에서 사용했던 예제를 다음과 같이 수정할 수 있습니다.</p><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 1x,          images/heropy_medium.png 1.75x,          images/heropy_large.png 2.5x&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><p><code>x</code> 디스크립터는 디바이스의 픽셀 비율(Device pixel ratio)과 일치하는 값으로 최적화 선택됩니다.<br><a href="https://www.mydevice.io/#tab1" target="_blank" rel="noopener">mydevice.io</a>에서 현재 화면의 측정 값을 확인할 수 있습니다.</p><p>일반적으로 정수(integer) 값으로 제공하는 것이 좋습니다.</p><blockquote><p><code>w</code> 디스크립터를 사용하면 <code>x</code> 디스크립터를 사용하지 않아도 됩니다.<br>많은 경우 <code>w</code> 디스크립터의 사용을 추천합니다.</p></blockquote><h1><span id="933a4a96-59b8-4b40-829d-d906bc20100b">sizes</span><a href="#933a4a96-59b8-4b40-829d-d906bc20100b" class="header-anchor"></a></h1><p><code>sizes</code>는 미디어조건과 그 조건에 해당하는 이미지의 ‘최적화 출력 크기’를 지정합니다.</p><p>다음 예제를 살펴봅시다.</p><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 400w,          images/heropy_medium.png 700w,          images/heropy_large.png 1000w&quot;  sizes=&quot;(min-width: 1000px) 700px&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><p>위 예제의 결과로,<br>뷰포트 너비가 400px 이하일 때 <code>heropy_small.png</code>(400px)가 사용됩니다.<br>뷰포트 너비가 401~700px 일 때 <code>heropy_medium.png</code>(700px)가 사용됩니다.<br>뷰포트 너비가 701~999px 일 때 <code>heropy_large.png</code>(1000px)가 사용됩니다.<br>뷰포트 너비가 1000px 이상일 때 <code>heropy_medium.png</code>(700px)가 사용됩니다.</p><p><img src="/images/screenshot/html-img-srcset-and-sizes/example_3.jpg" alt="srcset example 3"></p><p><code>sizes=&quot;(min-width: 1000px) 700px&quot;</code>에서 <code>(min-width: 1000px)</code>은 ‘뷰포트 너비(가로)가 1000px 이상일 때’를 의미하며, 이어나오는 <code>700px</code>은 그 조건일 때 이미지를 ‘700px로 최적화 출력하겠다’를 의미합니다.<br>그렇다면 700px로 이미지를 출력하기 위해 <code>srcset</code> 목록에서 사용될 최적의 이미지는 <code>heropy_medium.png</code>이며, 결과로 뷰포트 너비(가로)가 1000px 이상일 때 <code>heropy_medium.png</code>가 사용되었습니다.</p><p>다음 예제도 살펴봅시다.<br>이번엔 미디어조건을 생략했네요.</p><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 400w,          images/heropy_medium.png 700w,          images/heropy_large.png 1000w&quot;  sizes=&quot;500px&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><p>위 예제의 결과로,<br>뷰포트 너비와 상관없이(‘헛 상관이 없다고?!’) <code>heropy_medium.png</code>만 사용됩니다.<br>또한 <code>heropy_medium.png</code>는 <code>500px</code>의 크기를 가집니다.(원래는 700px 크기의 이미지입니다)</p><p><img src="/images/screenshot/html-img-srcset-and-sizes/example_4.jpg" alt="srcset example 4"></p><p>왜 뷰포트 너비와 상관없이 하나의 이미지만 사용되는지 아직 확실치 않지만, 다음 예제와 비교해 봅시다.<br>이번엔 <code>sizes</code>는 없고 <code>width</code>가 있네요.</p><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 400w,          images/heropy_medium.png 700w,          images/heropy_large.png 1000w&quot;  width=&quot;500&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><p>위 예제의 결과로,<br>뷰포트 너비가 400px 이하일 때 <code>heropy_small.png</code>가 사용됩니다.<br>뷰포트 너비가 401~700px 일 때 <code>heropy_medium.png</code>가 사용됩니다.<br>뷰포트 너비가 701px 이상일 때 <code>heropy_large.png</code>가 사용됩니다.<br>뷰포트 너비에 따라 사용되는 이미지가 달라지지만 크기는 <code>500px</code>로 고정되었습니다.</p><p><img src="/images/screenshot/html-img-srcset-and-sizes/example_5.jpg" alt="srcset example 5"></p><p>그럼 이해가 되시나요?!<br><code>width</code>는 이미지의 ‘출력 크기’만 지정하는 데 반해, <code>sizes</code>는 이미지의 ‘출력 크기’ + ‘최적 크기’도 함께 지정하는 개념입니다.<br>따라서 <code>sizes=&quot;500px&quot;</code>이 지정된 첫번째 예제는 <code>500px</code>에 최적화된 이미지로 <code>heropy_medium.png</code>를 사용했고 이미지 크기도 <code>500px</code>로 설정한 것이죠.</p><blockquote><p><code>sizes</code>와 <code>width</code>를 같이 작성할 경우 <code>width</code>가 우선합니다.</p></blockquote><p>개념이 이해되었다면, 다음과 같이 여러 조건을 작성할 수도 있습니다.<br>쉼표(<code>,</code>)를 사용해 구분한다는 것을 주의하세요.</p><pre><code class="html">&lt;img  srcset=&quot;images/heropy_small.png 400w,          images/heropy_medium.png 700w,          images/heropy_large.png 1000w&quot;  sizes=&quot;(min-width: 701px) 1000px,         (min-width: 401px) 700px,         400px&quot;  src=&quot;images/heropy.png&quot;  alt=&quot;HEROPY&quot; /&gt;</code></pre><h1><span id="492b67c9-a45b-4c44-8b60-4ca5f4cc62af">브라우저 지원</span><a href="#492b67c9-a45b-4c44-8b60-4ca5f4cc62af" class="header-anchor"></a></h1><p><code>srcset</code>과 <code>sizes</code>는 IE를 지원하지 않습니다.</p><p><a href="https://caniuse.com/#feat=srcset" target="_blank" rel="noopener">https://caniuse.com/#feat=srcset</a></p><p><img src="/images/screenshot/html-img-srcset-and-sizes/browser_support.jpg" alt="brower support for srcset and sizes attributes"></p><h2><span id="e3ef580b-860c-4797-ba91-8511e3abac8e">Polyfill</span><a href="#e3ef580b-860c-4797-ba91-8511e3abac8e" class="header-anchor"></a></h2><p><a href="https://github.com/aFarkas/respimage" target="_blank" rel="noopener">https://github.com/aFarkas/respimage</a></p><h1><span id="9575a735-a6dd-4817-8d48-968cd7bdebe1">Sample images</span><a href="#9575a735-a6dd-4817-8d48-968cd7bdebe1" class="header-anchor"></a></h1><p><img src="/images/screenshot/html-img-srcset-and-sizes/heropy.png" alt="HEROPY"><br><img src="/images/screenshot/html-img-srcset-and-sizes/heropy_small.png" alt="HEROPY"><br><img src="/images/screenshot/html-img-srcset-and-sizes/heropy_medium.png" alt="HEROPY"><br><img src="/images/screenshot/html-img-srcset-and-sizes/heropy_large.png" alt="HEROPY"></p><h1><span id="f94a9d29-7631-4708-bf65-409938569dbd">참고 자료(References)</span><a href="#f94a9d29-7631-4708-bf65-409938569dbd" class="header-anchor"></a></h1><p><a href="https://developer.mozilla.org/ko/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images</a><br><a href="https://stackoverflow.com/questions/40890825/explain-w-in-srcset-of-image" target="_blank" rel="noopener">https://stackoverflow.com/questions/40890825/explain-w-in-srcset-of-image</a><br><a href="https://github.com/ResponsiveImagesCG/picture-element/issues/85" target="_blank" rel="noopener">https://github.com/ResponsiveImagesCG/picture-element/issues/85</a><br><a href="https://stackoverflow.com/questions/26928828/html5-srcset-mixing-x-and-w-syntax" target="_blank" rel="noopener">https://stackoverflow.com/questions/26928828/html5-srcset-mixing-x-and-w-syntax</a></p>]]></content>
    
    <summary type="html">
    
      HTML IMG의 srcset과 sizes를 통해 반응형 이미지를 제공하는 방법에 대해서 알아봅시다!
    
    </summary>
    
    
      <category term="html" scheme="https://heropy.blog/tags/html/"/>
    
      <category term="html5" scheme="https://heropy.blog/tags/html5/"/>
    
      <category term="img" scheme="https://heropy.blog/tags/img/"/>
    
      <category term="srcset" scheme="https://heropy.blog/tags/srcset/"/>
    
      <category term="sizes" scheme="https://heropy.blog/tags/sizes/"/>
    
  </entry>
  
  <entry>
    <title>한눈에 보는 HTML 요소(Elements &amp; Attributes) 총정리</title>
    <link href="https://heropy.blog/2019/05/26/html-elements/"/>
    <id>https://heropy.blog/2019/05/26/html-elements/</id>
    <published>2019-05-26T10:47:54.000Z</published>
    <updated>2020-12-28T23:09:26.482Z</updated>
    
    <content type="html"><![CDATA[<h1 id="드리는-말씀"><a href="#드리는-말씀" class="headerlink" title="드리는 말씀"></a>드리는 말씀</h1><p>이 포스트는 HTML 요소(태그)와 속성에 대해 최대한 객관적이고 핵심적이며 파편화된 많은 정보를 한자리에서 다루기 위해 고민한 결과물이지만,<br>HTML의 모든 내용을 포함하지 않으며, 일부 주관이 포함되어 있습니다.<br>가장 정확하게는 W3C 문서가 있지만, 지나치게 기술적으로 서술되어 있어서 이해하기 쉽지 않습니다.<br>따라서 대부분의 경우, 집단 지성으로 작성되는 최신 버전의 MDN 문서를 확인하는 것이 객관성과 이해도를 동시에 확보할 수 있는 가장 좋은 방법입니다.<br>만약 이 포스트가 MDN 문서와 다른 부분이 있다면, 당연히 MDN 문서를 더 신뢰하시면 됩니다.<br>(물론 MDN 문서도 극히 일부 잘못된 내용이 있습니다)</p><p>그리고 웹 개발 입문자분들은 당장, 이 HTML 포스트 내용을 정독하기보단 속독을 통해 전반적인 내용을 빠르게 파악하고 필요에 따라 기억을 더듬어 해당 내용을 찾아 사용하시는 방법을 추천합니다.<br>물론 정독할 수 있는 시간과 환경이 있다면 머릿속에 차곡차곡 꼼꼼하게 정보를 쌓는 방법이 더 좋겠지요.<br>하지만, 사용 빈도가 압도적으로 높은 상위 몇 개 요소가 있다 보니 더욱이 HTML 정독법은 효율이 떨어진다고 생각합니다.<br>특히 HTML 학습이 그렇습니다, CSS나 JS 학습은 또 다른 입장입니다.<br>어디까지나 학습에 도움이 될만한 주관적인 생각이니 참고만 하세요.</p><p>항상 공부하시고 노력하시는 모든 분을 존경하며 응원합니다.<br>감사합니다.</p><h1 id="변경사항"><a href="#변경사항" class="headerlink" title="변경사항"></a>변경사항</h1><h2 id="2019년-7월"><a href="#2019년-7월" class="headerlink" title="2019년 7월"></a>2019년 7월</h2><p>더욱 핵심적인 내용들만 요약 전달하기 위해 일부 내용을 삭제했습니다.<br>(온라인 강의와 함께 보시는 분들은 현재 포스트에 없는 내용이 있다면 건너뛰세요)</p><div class="toc"><ul><li><a href="aa71823f-fac6-49b2-8254-5e65ae6e586f">개요</a></li><li><a href="25752d52-216d-457a-89e7-a2fb448ba403">주요 범위</a><ul><li><a href="45811b6b-0968-469f-8688-ce4d85d38df1">&lt;html&gt;</a></li><li><a href="3f2d8c46-79f5-48ae-86b7-fcd642498d71">&lt;head&gt;</a></li><li><a href="7d99a7af-4abe-4bac-8e0d-b0a5776320a5">&lt;body&gt;</a></li></ul></li><li><a href="b6611d2a-432d-4239-9ec7-e5ec7b250d7f">메타데이터</a><ul><li><a href="6c2ed0f7-2653-4abf-b7a1-781793f2affb">&lt;title&gt;</a></li><li><a href="6ca14da8-95c1-4a0d-9307-b82634c8fd2f">&lt;base /&gt;</a></li><li><a href="cccbf64d-95ed-427c-9c50-3f4847966408">&lt;link /&gt;</a></li><li><a href="12d95334-bd25-4eeb-b72d-3d79f7cdd440">&lt;meta /&gt;</a></li><li><a href="d1d7dd84-def8-47cc-b6d2-ce5062f880c4">&lt;style&gt;</a></li></ul></li><li><a href="2f45a072-063e-4500-9531-b87fee280788">콘텐츠 구분</a><ul><li><a href="b8d623bb-1a35-466f-bd61-520d5349753e">&lt;h1&gt;, &lt;h2&gt;, &lt;h3&gt;, &lt;h4&gt;, &lt;h5&gt;, &lt;h6&gt;</a></li><li><a href="59b03c07-c458-498d-9224-6db66c2b0968">&lt;header&gt;</a></li><li><a href="92330193-42a3-400d-a850-be2b78172d5d">&lt;footer&gt;</a></li><li><a href="3b361892-f826-4417-9d1d-835baabfd426">&lt;main&gt;</a></li><li><a href="0fa94456-90bc-491f-af9a-e5b8f6e1aef2">&lt;article&gt;</a></li><li><a href="e2a48d5f-c94b-4c12-927c-c6c31f23763d">&lt;section&gt;</a></li><li><a href="7bca1191-6d62-49e5-a91d-be2c65bc74c4">&lt;aside&gt;</a></li><li><a href="c5dd2ad0-8172-423d-bdb0-8b7290758f6c">&lt;nav&gt;</a></li><li><a href="c835867a-a209-45b2-8eb6-e864c7ea92a6">&lt;address&gt;</a></li><li><a href="3cb613f1-2769-43b3-a9d1-d8709eddf10d">&lt;div&gt;</a></li></ul></li><li><a href="9bc29bc6-1b24-46aa-9034-1977d91c74ef">문자 콘텐츠</a><ul><li><a href="8986a450-5537-4188-a67f-df87398c130d">&lt;ol&gt;, &lt;ul&gt;, &lt;li&gt;</a><ul><li><a href="b187273e-1e0f-44fe-b847-443e700d79d4">&lt;ol&gt;</a></li><li><a href="d8bd9787-c760-4be1-bec0-f249ebf8b886">&lt;li&gt;</a></li></ul></li><li><a href="0f67db3f-f583-4e53-9bbf-de937528c6c0">&lt;dl&gt;, &lt;dt&gt;, &lt;dd&gt;</a></li><li><a href="0ffa6252-5ee1-471a-8c79-3405356371b1">&lt;p&gt;</a></li><li><a href="394916ab-648e-4d46-92e7-85537a71ed79">&lt;hr /&gt;</a></li><li><a href="6ddb4c26-017f-4821-8493-c7ac55ce7a83">&lt;pre&gt;</a></li><li><a href="e7f5d00b-b6c3-43c7-9a32-fb0e0b42d950">&lt;blockquote&gt;</a></li></ul></li><li><a href="39ae7c66-9347-4e90-9edc-931f4eada85a">인라인 텍스트</a><ul><li><a href="03cce895-61c1-43dd-85fa-fc922d77c14d">&lt;a&gt;</a></li><li><a href="74373305-dccf-43a7-b935-9b32a0f5c05d">&lt;abbr&gt;</a></li><li><a href="c986cea0-f1d1-4d20-8f41-603aeaae26c0">&lt;b&gt;</a></li><li><a href="83467755-1d89-4cc3-94af-8b52a4ec7e0e">&lt;mark&gt;</a></li><li><a href="123296f0-5bef-4547-ab55-c9b2586e319e">&lt;em&gt;</a></li><li><a href="753f76e5-0964-4238-afd8-0ef7e2417951">&lt;strong&gt;</a></li><li><a href="cddfc53e-aa59-4cdf-9bab-cd7eb7af91fc">&lt;i&gt;</a></li><li><a href="08f2e355-7a38-45b5-9ab8-e8f1fb7c7326">&lt;dfn&gt;</a></li><li><a href="9c3a0680-4efe-48b6-84a8-3eca843a28b0">&lt;cite&gt;</a></li><li><a href="df78cde4-c560-4039-b35d-aa3c9123c2be">&lt;q&gt;</a></li><li><a href="e8fac5f7-f846-4382-8f82-fb6f3ff85ae1">&lt;u&gt;</a></li><li><a href="07fa9642-61f3-4be0-8561-10aec48a9923">&lt;code&gt;</a></li><li><a href="67b9c979-7b63-496e-8805-a8a6334b69e7">&lt;kbd&gt;</a></li><li><a href="8ca38d28-947d-4049-af19-78a31b8ab719">&lt;sup&gt;, &lt;sub&gt;</a></li><li><a href="e078da66-bf47-416a-892a-ac1719ac0ba8">&lt;time&gt;</a></li><li><a href="f91672f3-ba5c-4938-b67e-bda7689054e1">&lt;span&gt;</a></li><li><a href="1a8745f7-1836-4337-b681-9ce6bb3dcf75">&lt;br /&gt;</a></li></ul></li><li><a href="90fdd4bd-9d74-47c1-8227-bea83b5ae503">수정</a><ul><li><a href="0f148419-66a2-44f4-a501-f9d739949340">&lt;del&gt;</a></li><li><a href="2375685c-1ee7-4284-96ac-92a434c0f608">&lt;ins&gt;</a></li></ul></li><li><a href="217fe3f5-38d1-46fb-bab9-2b2d5e17ca28">멀티미디어</a><ul><li><a href="d7b83f8c-61fb-47c4-9642-a3d72152551f">&lt;img /&gt;</a></li><li><a href="616a00af-9116-4e8a-abdf-9b267091cbe7">&lt;audio&gt;</a></li><li><a href="54e71d3b-1ab2-4d91-a25e-11bedc0b136b">&lt;video&gt;</a></li><li><a href="89b1e7be-696b-4d3a-b0a7-579316f8d782">&lt;figure&gt;, &lt;figcaption&gt;</a></li></ul></li><li><a href="0c8e3844-4877-45a0-bf56-ce5b702d4583">내장 콘텐츠</a><ul><li><a href="19fa9a9a-6423-415c-affe-3877426aeeff">&lt;iframe&gt;</a></li><li><a href="54655002-d81f-4f46-b0db-eca7c183140e">&lt;canvas&gt;</a></li></ul></li><li><a href="fbf5ddaa-c426-4044-a383-38f46d0012a5">스크립트</a><ul><li><a href="67bc3177-6646-4a7b-9bdc-dd39621d8194">&lt;script&gt;</a></li><li><a href="237fce2b-06a1-4fdc-86e1-a0ad2c00a2be">&lt;noscript&gt;</a></li></ul></li><li><a href="89b2a12c-83b2-4373-b330-4750834e915c">표 콘텐츠</a><ul><li><a href="14de74f0-88c7-49d6-9be3-7511c58f3747">&lt;table&gt;, &lt;tr&gt;, &lt;th&gt;, &lt;td&gt;</a><ul><li><a href="b80f2f23-64ae-40ef-9cdb-786361cdc9e8">&lt;th&gt;</a></li><li><a href="363f73dd-b19f-4aeb-9bf2-593d9ab9c17d">&lt;td&gt;</a></li></ul></li><li><a href="f11ec000-fc7c-4ebf-9d7d-98d677ed4064">&lt;caption&gt;</a></li><li><a href="e666f74b-7f47-464d-a101-b1efc0611d81">&lt;colgroup&gt;, &lt;col /&gt;</a></li><li><a href="7444f48e-17c1-4ba2-8aab-a2fe833eec77">&lt;thead&gt;, &lt;tbody&gt;, &lt;tfoot&gt;</a></li></ul></li><li><a href="e07a7dea-6a6c-46de-8dd1-096e345161e6">양식</a><ul><li><a href="e2b00faf-9a8c-4fe4-8293-a4f01b1bc6d1">&lt;form&gt;</a></li><li><a href="3e917c79-793f-43b2-9e6a-970e8a1ce58b">&lt;input /&gt;</a><ul><li><a href="67dc6645-9698-4063-8d2f-2c9c72c4ed7f">데이터 종류(Type)의 값(Values)</a></li></ul></li><li><a href="cec47681-09da-4de1-84dd-d37702de6971">&lt;label&gt;</a></li><li><a href="d00633a1-c4b3-4c64-a945-b37bb0c5152f">&lt;button&gt;</a></li><li><a href="549c8e3d-9508-4ee9-94ff-85c870ad83c9">&lt;textarea&gt;</a></li><li><a href="74d12042-a762-4e3d-9b3a-bad7263eac21">&lt;fieldset&gt;, &lt;legend&gt;</a><ul><li><a href="629f9dce-c0cc-4a9f-994e-ee23105310cc">&lt;fieldset&gt;</a></li></ul></li><li><a href="773489eb-9da5-4aa0-ae5c-d2ae8f1572f8">&lt;select&gt;, &lt;datalist&gt;, &lt;optgroup&gt;, &lt;option&gt;</a><ul><li><a href="560d3793-369e-42d2-b649-45280c5925bc">&lt;select&gt;</a></li><li><a href="b87fb1da-ae45-42bd-b5d3-97f52b74ef8a">&lt;datalist&gt;</a></li><li><a href="7cc1911e-a62e-4433-a398-6afde2adefb4">&lt;optgroup&gt;</a></li><li><a href="0ab01517-39c7-4aca-bd76-f8b42e754b44">&lt;option&gt;</a></li></ul></li><li><a href="b1c9aa2a-430d-4754-9ee0-c3b84177b760">&lt;progress&gt;</a></li></ul></li><li><a href="0e87bf98-d71c-4c03-9133-0ae8eb75c2cc">전역 속성(Global Attributes)</a><ul><li><a href="d273b1d8-2acb-4d3b-9927-45227ad513e3">class</a></li><li><a href="8dbdb52b-a7cd-475e-b088-9b2ae6c93cd5">id</a></li><li><a href="2379f98f-9449-488e-be15-7a8c25ed8c38">style</a></li><li><a href="2a0bc56c-fb44-4ff9-9141-c0922ef7263a">title</a></li><li><a href="9ebb643f-ff0e-47a6-b5ca-439f41c963c5">lang</a></li><li><a href="fb54e2f4-ca48-4d7e-b27d-19d39d5708ea">data-*</a></li><li><a href="13bdb5fc-f782-4438-9775-ed0a91b8a709">draggable</a></li><li><a href="23a25bbb-3f45-426f-a0ce-a924b887c330">hidden</a></li><li><a href="f8af5d0c-ca82-4eee-9a74-a4b91e8f9fc1">tabindex</a></li></ul></li><li><a href="8ab92377-4ce1-4dcb-8536-1ce6377e6623">생략한 요소</a><ul><li><a href="0cb3cff8-db6c-44bc-bc8f-34acae140146">&lt;template&gt;</a></li><li><a href="04d5dea7-aa17-4af7-a29c-4d6ae39b2fc8">&lt;map&gt;, &lt;area&gt;</a></li><li><a href="94f60512-4f8a-49ce-ae34-de48f6f45947">&lt;picture&gt;</a></li><li><a href="f7b5af7f-04ec-4dfa-9910-525061615b7e">&lt;source&gt;</a></li><li><a href="5ab2845c-b2ee-4b77-a703-b9aa41b0edae">&lt;track&gt;</a></li><li><a href="bab77345-59b2-4f21-8842-6ed08a56c8b9">&lt;embed&gt;</a></li><li><a href="28d95f66-84f7-41cf-aa7b-a7bffe46a8c5">&lt;object&gt;</a></li><li><a href="b60f941e-704e-49ef-a147-ca8f5dd09e1c">&lt;param&gt;</a></li></ul></li><li><a href="cdec39d1-ad81-4411-ac35-43760d667f59">생략한 속성</a></li><li><a href="5051ee88-0645-4c67-ab2d-1307fd76fee0">생략한 전역 속성</a><ul><li><a href="96fbbd1d-744b-47d3-afdb-49ae85b92d27">accesskey</a></li><li><a href="d5a886c2-5901-4653-98bb-262bb92b6dc2">contenteditable</a></li></ul></li><li><a href="b50be8c2-53f9-4072-88b9-207c44a9bab6">참고자료</a></li></ul></div><h1><span id="aa71823f-fac6-49b2-8254-5e65ae6e586f">개요</span><a href="#aa71823f-fac6-49b2-8254-5e65ae6e586f" class="header-anchor"></a></h1><ul><li>HTML5 기준으로 작성합니다.</li><li>모든 브라우저에서 사용할 수 있어야 합니다.(IE 지원 불가는 별도 표시)</li><li>Deprecated(더 이상 사용되지 않는) 요소나 속성은 제외합니다.</li><li>의미론적(Semantic)인 내용 위주로 작성합니다.</li><li>표시적 의미(화면에 표시되는 방식)로 사용되지 않음을 전제하므로 그에 대한 내용은 생략합니다.</li><li>빈 태그(Empty Tags)는 <code>&lt;TAG /&gt;</code>와 같이 <code>/</code>를 포함하여 표시합니다.</li><li>해당 요소에 필수적으로 사용되어야 하는 속성(Required Attributes)은 설명에 <code>(필수)</code>를 표시합니다.(그 외는 모두 선택 속성)</li><li>개인적 경험에 의해 사용 빈도, 중요도, 난이도가 높은 요소는 강조/첨언 합니다.</li><li>개인적 경험에 의해 사용 빈도, 중요도, 난이도가 낮은 요소는 생략/간소화 합니다.</li></ul><h1><span id="25752d52-216d-457a-89e7-a2fb448ba403">주요 범위</span><a href="#25752d52-216d-457a-89e7-a2fb448ba403" class="header-anchor"></a></h1><h2><span id="45811b6b-0968-469f-8688-ce4d85d38df1">&lt;html&gt;</span><a href="#45811b6b-0968-469f-8688-ce4d85d38df1" class="header-anchor"></a></h2><p>HTML 문서의 범위를 설정.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>lang</td><td>문서의 언어(<a href="https://ko.wikipedia.org/wiki/ISO_639-1_%EC%BD%94%EB%93%9C_%EB%AA%A9%EB%A1%9D" target="_blank" rel="noopener">ISO 639-1</a>)</td><td><code>ko</code>, <code>en</code>…</td></tr></tbody></table><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/html" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_html.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="3f2d8c46-79f5-48ae-86b7-fcd642498d71">&lt;head&gt;</span><a href="#3f2d8c46-79f5-48ae-86b7-fcd642498d71" class="header-anchor"></a></h2><p>HTML 문서의 정보를 설정.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/head" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_head.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="7d99a7af-4abe-4bac-8e0d-b0a5776320a5">&lt;body&gt;</span><a href="#7d99a7af-4abe-4bac-8e0d-b0a5776320a5" class="header-anchor"></a></h2><p>HTML 문서의 구조를 설정.</p><pre><code class="css">body { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/body" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_body.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="b6611d2a-432d-4239-9ec7-e5ec7b250d7f">메타데이터</span><a href="#b6611d2a-432d-4239-9ec7-e5ec7b250d7f" class="header-anchor"></a></h1><h2><span id="6c2ed0f7-2653-4abf-b7a1-781793f2affb">&lt;title&gt;</span><a href="#6c2ed0f7-2653-4abf-b7a1-781793f2affb" class="header-anchor"></a></h2><p>브라우저의 제목 표시줄이나 페이지 탭에 보여지는 문서의 제목을 설정.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/title" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_title.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="6ca14da8-95c1-4a0d-9307-b82634c8fd2f">&lt;base /&gt;</span><a href="#6ca14da8-95c1-4a0d-9307-b82634c8fd2f" class="header-anchor"></a></h2><p>HTML 문서에 포함된 모든 상대 URL들의 기준 URL를 설정.</p><ul><li>한 문서에 하나의 <code>&lt;base /&gt;</code> 요소만 포함 가능.</li></ul><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>href</td><td>기준 URL</td><td>URL</td><td></td></tr><tr><td>target</td><td>A 요소처럼 target 속성을 사용하는 요소의 기본값</td><td><code>_self</code>, <code>_blank</code></td><td><code>_self</code></td></tr></tbody></table><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/base" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_base.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="cccbf64d-95ed-427c-9c50-3f4847966408">&lt;link /&gt;</span><a href="#cccbf64d-95ed-427c-9c50-3f4847966408" class="header-anchor"></a></h2><p>외부 리소스의 연결 및 현재 문서와의 관계를 명시.<br>(HTML, <strong>CSS</strong>, ICON 등 가져오기)</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>rel</td><td>(필수)현재 문서와 외부 리소스와의 관계(<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types" target="_blank" rel="noopener">Link Types</a>)</td><td><code>stylesheet</code>, <code>icon</code>…</td><td></td></tr><tr><td>href</td><td>외부 리소스의 URL</td><td>URL</td><td></td></tr><tr><td>type</td><td>외부 리소스의 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank" rel="noopener">MIME 타입</a></td><td><code>text/css</code>, <code>image/x-icon</code>…</td><td></td></tr></tbody></table><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/link" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_link.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="12d95334-bd25-4eeb-b72d-3d79f7cdd440">&lt;meta /&gt;</span><a href="#12d95334-bd25-4eeb-b72d-3d79f7cdd440" class="header-anchor"></a></h2><p>기타 메타데이터 요소(<code>&lt;link /&gt;</code>, <code>&lt;style&gt;</code> 같은)로 나타낼 수 없는 메타데이터를 나타내기 위해 설정.<br>(검색엔진이나 브라우저에 정보 제공)</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>charset</td><td><a href="https://www.iana.org/assignments/character-sets/character-sets.xhtml" target="_blank" rel="noopener">문자인코딩 방식</a></td><td><code>UTF-8</code>, <code>EUC-KR</code>…</td></tr><tr><td>name</td><td>메타 데이터의 이름(<a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/meta#attr-name" target="_blank" rel="noopener">정보의 종류</a>)</td><td><code>author</code>, <code>description</code>…</td></tr><tr><td>http-equiv</td><td>서버/사용자 에이전트의 작동방식 변경에 대한 <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/meta#attr-http-equiv" target="_blank" rel="noopener">지시</a>(HTTP 응답 헤더 제공)</td><td><code>refresh</code>, <code>X-UA-Compatible</code>…</td></tr><tr><td>content</td><td><code>name</code>, <code>http-equiv</code>의 값</td><td></td></tr></tbody></table><pre><code class="html">&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1, minimum-scale=1&quot; /&gt;&lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot; /&gt;</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/meta" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_meta.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="d1d7dd84-def8-47cc-b6d2-ce5062f880c4">&lt;style&gt;</span><a href="#d1d7dd84-def8-47cc-b6d2-ce5062f880c4" class="header-anchor"></a></h2><p>스타일 정보(CSS)를 설정.</p><table><thead><tr><th>속성</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>type</td><td><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank" rel="noopener">MIME 타입</a></td><td><code>text/css</code></td></tr></tbody></table><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/style" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_style.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="2f45a072-063e-4500-9531-b87fee280788">콘텐츠 구분</span><a href="#2f45a072-063e-4500-9531-b87fee280788" class="header-anchor"></a></h1><h2><span id="b8d623bb-1a35-466f-bd61-520d5349753e">&lt;h1&gt;, &lt;h2&gt;, &lt;h3&gt;, &lt;h4&gt;, &lt;h5&gt;, &lt;h6&gt;</span><a href="#b8d623bb-1a35-466f-bd61-520d5349753e" class="header-anchor"></a></h2><p>문서의 정보 계층을 구조화.<br>(Heading, 문서나 구분된 영역의 제목을 설정, 문서의 목차)</p><ul><li>숫자가 낮을수록 높은 단계(중요한)의 제목.</li></ul><pre><code class="css">h1, h2, h3, h4, h5, h6 { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/Heading_Elements" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_hn.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="59b03c07-c458-498d-9224-6db66c2b0968">&lt;header&gt;</span><a href="#59b03c07-c458-498d-9224-6db66c2b0968" class="header-anchor"></a></h2><p>문서의 헤더를 설정.<br>(보통 로고, 제목, 검색 등을 포함)</p><pre><code class="css">header { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/header" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_header.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="92330193-42a3-400d-a850-be2b78172d5d">&lt;footer&gt;</span><a href="#92330193-42a3-400d-a850-be2b78172d5d" class="header-anchor"></a></h2><p>문서의 푸터를 설정.<br>(보통 작성자, 저작권, 관련 문서 등을 포함)</p><pre><code class="css">footer { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/footer" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_footer.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="3b361892-f826-4417-9d1d-835baabfd426">&lt;main&gt;</span><a href="#3b361892-f826-4417-9d1d-835baabfd426" class="header-anchor"></a></h2><p>문서의 주요 콘텐츠를 설정.</p><ul><li>IE 지원 불가</li><li>한 문서에 하나의 <code>&lt;main&gt;</code> 요소만 포함 가능.</li></ul><pre><code class="css">main { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/main" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_main.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="0fa94456-90bc-491f-af9a-e5b8f6e1aef2">&lt;article&gt;</span><a href="#0fa94456-90bc-491f-af9a-e5b8f6e1aef2" class="header-anchor"></a></h2><p>독립적으로 구분되거나 재사용 가능한 영역을 설정.<br>(매거진/신문 기사, 블로그 등)</p><ul><li>일반적으로 <code>&lt;h1&gt;</code>~<code>&lt;h6&gt;</code>를 포함하여 식별.</li><li>작성일자와 시간을 <code>&lt;time&gt;</code>의 <code>datetime</code> 속성으로 작성.</li></ul><pre><code class="css">article { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/article" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_article.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="e2a48d5f-c94b-4c12-927c-c6c31f23763d">&lt;section&gt;</span><a href="#e2a48d5f-c94b-4c12-927c-c6c31f23763d" class="header-anchor"></a></h2><p>문서의 일반적인 영역을 설정.</p><ul><li>일반적으로 <code>&lt;h1&gt;</code>~<code>&lt;h6&gt;</code>를 포함하여 식별.</li></ul><pre><code class="css">section { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/section" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_section.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="7bca1191-6d62-49e5-a91d-be2c65bc74c4">&lt;aside&gt;</span><a href="#7bca1191-6d62-49e5-a91d-be2c65bc74c4" class="header-anchor"></a></h2><p>문서의 별도 콘텐츠를 설정.<br>(보통 광고나 기타 링크 등의 사이드바(Side bar)를 설정)</p><pre><code class="css">aside { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/aside" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_aside.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="c5dd2ad0-8172-423d-bdb0-8b7290758f6c">&lt;nav&gt;</span><a href="#c5dd2ad0-8172-423d-bdb0-8b7290758f6c" class="header-anchor"></a></h2><p>다른 페이지 링크를 제공하는 영역을 설정.<br>(Navigation, 보통 메뉴(Home, About, Contact), 목차, 색인 등을 설정)</p><pre><code class="css">nav { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/nav" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_nav.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="c835867a-a209-45b2-8eb6-e864c7ea92a6">&lt;address&gt;</span><a href="#c835867a-a209-45b2-8eb6-e864c7ea92a6" class="header-anchor"></a></h2><p><code>&lt;body&gt;</code>, <code>&lt;article&gt;</code>, <code>&lt;footer&gt;</code> 등에서 연락처 정보를 제공하기 위해 포함하여 사용.</p><pre><code class="css">address { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/address" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_address.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="3cb613f1-2769-43b3-a9d1-d8709eddf10d">&lt;div&gt;</span><a href="#3cb613f1-2769-43b3-a9d1-d8709eddf10d" class="header-anchor"></a></h2><p>본질적으로 아무것도 나타내지 않는 콘텐츠 영역을 설정.<br>(Division, 꾸미는 목적으로 사용)</p><pre><code class="css">div { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/div" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_div.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="9bc29bc6-1b24-46aa-9034-1977d91c74ef">문자 콘텐츠</span><a href="#9bc29bc6-1b24-46aa-9034-1977d91c74ef" class="header-anchor"></a></h1><h2><span id="8986a450-5537-4188-a67f-df87398c130d">&lt;ol&gt;, &lt;ul&gt;, &lt;li&gt;</span><a href="#8986a450-5537-4188-a67f-df87398c130d" class="header-anchor"></a></h2><p>각 항목(<code>&lt;li&gt;</code>)의 정렬된 목록(<code>&lt;ol&gt;</code>)이나 정렬되지 않은 목록(<code>&lt;ul&gt;</code>)을 설정.<br>(Ordered List, Unordered List, List Item, 순서가 필요하거나(<code>&lt;ol&gt;</code>) 순서가 필요하지 않은(<code>&lt;ul&gt;</code>) 목록을 정의)</p><ul><li><code>&lt;ol&gt;</code>과 <code>&lt;ul&gt;</code>은 자식으로 <code>&lt;li&gt;</code>만 포함 가능.</li><li><code>&lt;li&gt;</code>는 단독으로 사용할 수 없으며, <code>&lt;ol&gt;</code>이나 <code>&lt;ul&gt;</code>에 자식으로 포함되어야 함.</li><li>정렬된 목록(<code>&lt;ol&gt;</code>)의 항목 순서는 중요도를 의미할 수 있음.</li></ul><pre><code class="css">ol, ul { display: block; }li { display: list-item; }</code></pre><p>OL: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/ol" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_ol.asp" target="_blank" rel="noopener">W3Schools</a><br>UL: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/ul" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_ul.asp" target="_blank" rel="noopener">W3Schools</a><br>LI: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/li" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_li.asp" target="_blank" rel="noopener">W3Schools</a></p><h3><span id="b187273e-1e0f-44fe-b847-443e700d79d4">&lt;ol&gt;</span><a href="#b187273e-1e0f-44fe-b847-443e700d79d4" class="header-anchor"></a></h3><p>정렬된 목록을 설정.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>특징</th></tr></thead><tbody><tr><td>start</td><td>항목에 매겨지는 번호의 시작 값</td><td>숫자(Number)</td><td></td></tr><tr><td>type</td><td>항목에 매겨지는 번호의 유형</td><td><code>a</code>, <code>A</code>, <code>i</code>, <code>I</code>, <code>1</code></td><td></td></tr></tbody></table><h3><span id="d8bd9787-c760-4be1-bec0-f249ebf8b886">&lt;li&gt;</span><a href="#d8bd9787-c760-4be1-bec0-f249ebf8b886" class="header-anchor"></a></h3><p>항목을 설정.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>특징</th></tr></thead><tbody><tr><td>value</td><td>항목의 순서를 설정</td><td>숫자(Number)</td><td>이하 항목들의 순서가 다시 지정됨</td></tr></tbody></table><h2><span id="0f67db3f-f583-4e53-9bbf-de937528c6c0">&lt;dl&gt;, &lt;dt&gt;, &lt;dd&gt;</span><a href="#0f67db3f-f583-4e53-9bbf-de937528c6c0" class="header-anchor"></a></h2><p>용어(<code>&lt;dt&gt;</code>)와 정의(<code>&lt;dd&gt;</code>) 쌍들의 영역(<code>&lt;dl&gt;</code>)을 설정.<br>(Description List, Definition Details, Definition Term)</p><ul><li><code>&lt;dl&gt;</code>은 <code>&lt;dd&gt;</code>, <code>&lt;dt&gt;</code>만을 포함해야 함.</li><li>키(key)/값(value) 형태를 표시할 때 유용.</li></ul><pre><code class="html">&lt;dl&gt;  &lt;dt&gt;Coffee&lt;/dt&gt;  &lt;dd&gt;Coffee is a brewed drink prepared from roasted coffee beans, the seeds of berries from certain Coffea species.&lt;/dd&gt;  &lt;dt&gt;Milk&lt;/dt&gt;  &lt;dd&gt;Milk is a nutrient-rich, white liquid food produced by the mammary glands of mammals.&lt;/dd&gt;&lt;/dl&gt;</code></pre><pre><code class="css">dl, dt, dd { display: block; }</code></pre><p>DL: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/dl" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_dl.asp" target="_blank" rel="noopener">W3Schools</a><br>DT: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/dt" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_dt.asp" target="_blank" rel="noopener">W3Schools</a><br>DD: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/dd" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_dd.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="0ffa6252-5ee1-471a-8c79-3405356371b1">&lt;p&gt;</span><a href="#0ffa6252-5ee1-471a-8c79-3405356371b1" class="header-anchor"></a></h2><p>하나의 문단을 설정.<br>(Paragraph)</p><ul><li>일반적으로 정보통신보조기기 등은 다음 문단(<code>&lt;p&gt;</code>)으로 넘어갈 수 있는 단축키를 제공함.</li></ul><pre><code class="css">p { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/p" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_p.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="394916ab-648e-4d46-92e7-85537a71ed79">&lt;hr /&gt;</span><a href="#394916ab-648e-4d46-92e7-85537a71ed79" class="header-anchor"></a></h2><p>문단의 분리(주제에 의한)를 위해 설정.<br>(Horizontal Rule)</p><ul><li>대부분의 경우 수평선(<code>border</code>)으로 표시(표현적 관점)되나 의미적 관점으로만 사용해야 함.</li></ul><pre><code class="css">hr { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/hr" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_hr.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="6ddb4c26-017f-4821-8493-c7ac55ce7a83">&lt;pre&gt;</span><a href="#6ddb4c26-017f-4821-8493-c7ac55ce7a83" class="header-anchor"></a></h2><p>서식이 미리 지정된 텍스트를 설정.<br>(Preformatted Text)</p><ul><li>텍스트의 공백과 줄바꿈을 유지하여 표시할 수 있음.</li><li>기본적으로 Monospace 글꼴 계열로 표시됨.</li></ul><pre><code class="css">pre { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/pre" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_pre.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="e7f5d00b-b6c3-43c7-9a32-fb0e0b42d950">&lt;blockquote&gt;</span><a href="#e7f5d00b-b6c3-43c7-9a32-fb0e0b42d950" class="header-anchor"></a></h2><p>일반적인 인용문을 설정.<br>(Block Quotation)</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>cite</td><td>인용된 정보의 URL</td><td>URL</td></tr></tbody></table><pre><code class="css">blockquote { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/blockquote" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_blockquote.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="39ae7c66-9347-4e90-9edc-931f4eada85a">인라인 텍스트</span><a href="#39ae7c66-9347-4e90-9edc-931f4eada85a" class="header-anchor"></a></h1><h2><span id="03cce895-61c1-43dd-85fa-fc922d77c14d">&lt;a&gt;</span><a href="#03cce895-61c1-43dd-85fa-fc922d77c14d" class="header-anchor"></a></h2><p>다른 페이지, 같은 페이지 위치(<code>#</code>, 해시 태그), 파일, 이메일 주소, 전화번호 등 다른 URL로 연결할 수 있는 하이퍼링크를 설정.<br>(Anchor, 외부로 내보내기)</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th><th>특징</th></tr></thead><tbody><tr><td>download</td><td>이 요소가 리소스를 다운로드하는 용도로 사용됨을 의미</td><td>불린(Boolean)</td><td></td></tr><tr><td>href</td><td>링크 URL</td><td>URL</td><td></td><td>생략 가능</td></tr><tr><td>rel</td><td>현재 문서와 링크 URL의 관계(<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types" target="_blank" rel="noopener">Link Types</a>)</td><td><code>license</code>, <code>prev</code>, <code>next</code>…</td><td></td><td></td></tr><tr><td>target</td><td>링크 URL의 표시(브라우저 탭) 위치</td><td><code>_self</code>, <code>_blank</code></td><td><code>_self</code></td><td></td></tr><tr><td>type</td><td>링크 URL의 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank" rel="noopener">MIME 타입</a></td><td><code>text/html</code>…</td><td></td><td></td></tr></tbody></table><pre><code class="css">a { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/a" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_a.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="74373305-dccf-43a7-b935-9b32a0f5c05d">&lt;abbr&gt;</span><a href="#74373305-dccf-43a7-b935-9b32a0f5c05d" class="header-anchor"></a></h2><p>약어를 지정.<br>(Abbreviation, 보통 <code>title</code> 속성을 사용하여 전체 글자나 설명을 제공)</p><pre><code class="html">Using &lt;abbr title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/abbr&gt; is fun and easy!</code></pre><pre><code class="css">abbr { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/abbr" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_abbr.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="c986cea0-f1d1-4d20-8f41-603aeaae26c0">&lt;b&gt;</span><a href="#c986cea0-f1d1-4d20-8f41-603aeaae26c0" class="header-anchor"></a></h2><p>문체가 다른 글자의 범위를 설정.<br>(Bring Attention)</p><ul><li>특별한 의미를 가지지 않음.</li><li>읽기 흐름에 도움을 주는 용도로 사용.</li><li>다른 태그가 적합하지 않은 경우 마지막 수단으로 사용.</li><li>기본적으로 글자가 두껍게(Bold) 표시됨.</li></ul><pre><code class="css">b { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/b" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_b.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="83467755-1d89-4cc3-94af-8b52a4ec7e0e">&lt;mark&gt;</span><a href="#83467755-1d89-4cc3-94af-8b52a4ec7e0e" class="header-anchor"></a></h2><p>사용자의 관심을 끌기 위해 하이라이팅할 때 사용.<br>(Mark Text, 형광펜을 사용하여 관심있는 부분을 표시하는 것과 같은 의미)</p><ul><li>기본적으로 형광펜을 사용한 것처럼 글자 배경이 노란색(Yellow)으로 표시됨.</li></ul><pre><code class="css">mark { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/mark" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_mark.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="123296f0-5bef-4547-ab55-c9b2586e319e">&lt;em&gt;</span><a href="#123296f0-5bef-4547-ab55-c9b2586e319e" class="header-anchor"></a></h2><p>단순한 의미 강조를 표시.<br>(Emphasis)</p><ul><li>중첩이 가능.</li><li>중첩될수록 강조 의미가 강해짐.</li><li>정보통신보조기기 등에서 구두 강조로 발음됨.</li><li>기본적으로 이탤릭체(Italic type)로 표시됨.</li></ul><pre><code class="css">em { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/em" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_em.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="753f76e5-0964-4238-afd8-0ef7e2417951">&lt;strong&gt;</span><a href="#753f76e5-0964-4238-afd8-0ef7e2417951" class="header-anchor"></a></h2><p>의미의 중요성을 나타내기 위해 사용.<br>(Strong Importance)</p><ul><li>기본적으로 글자가 두껍게(Bold) 표시됨.</li></ul><pre><code class="css">strong { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/strong" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_strong.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="cddfc53e-aa59-4cdf-9bab-cd7eb7af91fc">&lt;i&gt;</span><a href="#cddfc53e-aa59-4cdf-9bab-cd7eb7af91fc" class="header-anchor"></a></h2><p><code>&lt;em&gt;</code>, <code>&lt;strong&gt;</code> <code>&lt;mark&gt;</code> <code>&lt;cite&gt;</code> <code>&lt;dfn&gt;</code> 등에서 표현할 수 있는 적합한 의미가 아닌 경우 사용.<br>(평범한 글자와 구분(아이콘이나 특수기호 같은)하기 위해 사용)</p><ul><li>기본적으로 이탤릭체(Italic type)로 표시됨.</li></ul><pre><code class="css">i { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/i" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_i.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="08f2e355-7a38-45b5-9ab8-e8f1fb7c7326">&lt;dfn&gt;</span><a href="#08f2e355-7a38-45b5-9ab8-e8f1fb7c7326" class="header-anchor"></a></h2><p>용어를 정의할 때 사용.<br>(Definition)</p><pre><code class="css">dfn { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/dfn" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_dfn.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="9c3a0680-4efe-48b6-84a8-3eca843a28b0">&lt;cite&gt;</span><a href="#9c3a0680-4efe-48b6-84a8-3eca843a28b0" class="header-anchor"></a></h2><p>창작물에 대한 참조를 설정.<br>(책, 논문, 영화, TV 프로그램, 노래, 게임 등의 제목)</p><ul><li>기본적으로 이탤릭체(Italic type)로 표시됨.</li></ul><pre><code class="html">&lt;cite&gt;The Scream&lt;/cite&gt; by Edward Munch. Painted in 1893.</code></pre><pre><code class="css">cite { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/cite" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_cite.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="df78cde4-c560-4039-b35d-aa3c9123c2be">&lt;q&gt;</span><a href="#df78cde4-c560-4039-b35d-aa3c9123c2be" class="header-anchor"></a></h2><p>짦은 인용문을 설정.<br>(Inline Quotation)</p><ul><li>긴 인용문을 설정할 경우 <code>&lt;blockquote&gt;</code>를 사용.</li></ul><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>cite</td><td>인용된 정보의 URL</td><td>URL</td></tr></tbody></table><pre><code class="css">q { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/q" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_q.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="e8fac5f7-f846-4382-8f82-fb6f3ff85ae1">&lt;u&gt;</span><a href="#e8fac5f7-f846-4382-8f82-fb6f3ff85ae1" class="header-anchor"></a></h2><p>밑줄이 있는 글자를 설정.<br>(Underline)</p><ul><li>순수하게 꾸미는 용도의 요소로 사용.</li><li><code>&lt;a&gt;</code>와 헷갈릴 수 있는 위치에서 사용하지 않도록 주의.</li><li><code>&lt;span style=&quot;text-decoration: underline;&quot;&gt;</code>을 활용할 수 있을 경우에는 사용을 권장하지 않음.</li></ul><pre><code class="css">u { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/u" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_u.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="07fa9642-61f3-4be0-8561-10aec48a9923">&lt;code&gt;</span><a href="#07fa9642-61f3-4be0-8561-10aec48a9923" class="header-anchor"></a></h2><p>컴퓨터 코드 범위를 설정.<br>(Inline Code)</p><p><code>&lt;code&gt;document.getElementById(&#39;id-value&#39;)&lt;/code&gt; is a piece of computer code.</code></p><ul><li>기본적으로 고정폭(Monospace) 글꼴 계열로 표시됨.</li></ul><pre><code class="css">code { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/code" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_code.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="67b9c979-7b63-496e-8805-a8a6334b69e7">&lt;kbd&gt;</span><a href="#67b9c979-7b63-496e-8805-a8a6334b69e7" class="header-anchor"></a></h2><p>텍스트 입력 장치(키보드)에서 사용자 입력을 나타내는 텍스트 범위를 설정.<br>(Keyboard Input)</p><pre><code class="html">&lt;p&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;K&lt;/kbd&gt;&lt;/p&gt;, &lt;kbd&gt;ESC&lt;/kbd&gt;</code></pre><pre><code class="css">kbd { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/kbd" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_kbd.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="8ca38d28-947d-4049-af19-78a31b8ab719">&lt;sup&gt;, &lt;sub&gt;</span><a href="#8ca38d28-947d-4049-af19-78a31b8ab719" class="header-anchor"></a></h2><p>위 첨자(<code>&lt;sup&gt;</code>)와 아래 첨자(<code>&lt;sub&gt;</code>를 설정.<br>(Superscripted text, Subscript text)</p><pre><code class="html">X&lt;sup&gt;4&lt;/sup&gt; + Y&lt;sup&gt;2&lt;/sup&gt;, H&lt;sub&gt;2&lt;/sub&gt;O</code></pre><pre><code class="css">sup, sub { display: inline; }</code></pre><p>SUP: <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_sup.asp" target="_blank" rel="noopener">W3Schools</a><br>SUB: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/sub" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_sub.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="e078da66-bf47-416a-892a-ac1719ac0ba8">&lt;time&gt;</span><a href="#e078da66-bf47-416a-892a-ac1719ac0ba8" class="header-anchor"></a></h2><p>날짜나 시간을 나타내기 위한 목적으로 사용.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>datetime</td><td><a href="https://www.w3.org/TR/html51/infrastructure.html#dates-and-times" target="_blank" rel="noopener">유효한 날짜 문자</a></td><td>Date</td></tr></tbody></table><ul><li>IE 지원 불가</li></ul><pre><code class="html">&lt;p&gt;The Cure will be celebrating their 40th anniversary on &lt;time datetime=&quot;2018-07-07&quot;&gt;July 7&lt;/time&gt; in London&#39;s Hyde Park.&lt;/p&gt;</code></pre><pre><code class="css">time { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/time" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_time.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="f91672f3-ba5c-4938-b67e-bda7689054e1">&lt;span&gt;</span><a href="#f91672f3-ba5c-4938-b67e-bda7689054e1" class="header-anchor"></a></h2><p>본질적으로 아무것도 나타내지 않는 콘텐츠 영역을 설정.</p><pre><code class="css">span { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/span" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_span.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="1a8745f7-1836-4337-b681-9ce6bb3dcf75">&lt;br /&gt;</span><a href="#1a8745f7-1836-4337-b681-9ce6bb3dcf75" class="header-anchor"></a></h2><p>줄바꿈을 설정.</p><pre><code class="css">br { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/br" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_br.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="90fdd4bd-9d74-47c1-8227-bea83b5ae503">수정</span><a href="#90fdd4bd-9d74-47c1-8227-bea83b5ae503" class="header-anchor"></a></h1><h2><span id="0f148419-66a2-44f4-a501-f9d739949340">&lt;del&gt;</span><a href="#0f148419-66a2-44f4-a501-f9d739949340" class="header-anchor"></a></h2><p>삭제된(변경된) 텍스트의 범위를 지정.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>cite</td><td>변경을 설명하는 리소스의 URI</td><td>URI</td></tr><tr><td>datetime</td><td>변경이 일어난 <a href="https://www.w3.org/TR/html51/infrastructure.html#dates-and-times" target="_blank" rel="noopener">유요한 날짜 문자</a></td><td>Date</td></tr></tbody></table><pre><code class="css">del { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/del" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_del.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="2375685c-1ee7-4284-96ac-92a434c0f608">&lt;ins&gt;</span><a href="#2375685c-1ee7-4284-96ac-92a434c0f608" class="header-anchor"></a></h2><p>새로 추가된(변경된) 텍스트의 범위를 지정.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>cite</td><td>변경을 설명하는 리소스의 URI</td><td>URI</td></tr><tr><td>datetime</td><td>변경이 일어난 <a href="https://www.w3.org/TR/html51/infrastructure.html#dates-and-times" target="_blank" rel="noopener">유요한 날짜 문자</a></td><td>Date</td></tr></tbody></table><pre><code class="css">ins { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/ins" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_ins.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="217fe3f5-38d1-46fb-bab9-2b2d5e17ca28">멀티미디어</span><a href="#217fe3f5-38d1-46fb-bab9-2b2d5e17ca28" class="header-anchor"></a></h1><h2><span id="d7b83f8c-61fb-47c4-9642-a3d72152551f">&lt;img /&gt;</span><a href="#d7b83f8c-61fb-47c4-9642-a3d72152551f" class="header-anchor"></a></h2><p>이미지를 삽입.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>src</td><td>(필수)이미지 URL</td><td>URL</td><td></td></tr><tr><td>alt</td><td>(필수)이미지의 대체텍스트</td><td></td></tr><tr><td>width</td><td>이미지의 가로 너비</td><td></td></tr><tr><td>height</td><td>이미지의 세로 너비</td><td></td></tr><tr><td>srcset</td><td>브라우저에게 제시할 이미지 URL과 원본 크기의 목록을 정의</td><td><code>w</code>, <code>x</code></td></tr><tr><td>sizes</td><td>미디어 조건과 해당 조건일 때 이미지 최적화 크기의 목록을 정의</td><td></td></tr></tbody></table><pre><code class="html">&lt;!-- srcset, sizes --&gt;&lt;!-- 다양한 디스플레이 해상도에 맞는 최적의 이미지를 브라우저가 선택해서 사용 --&gt;&lt;img srcset=&quot;./small.jpg 320w,             ./medium.jpg 640w,             ./large.jpg 1024w&quot;     sizes=&quot;(max-width: 480px) 300px,            (max-width: 800px) 600px,            900px&quot;     src=&quot;./small.jpg&quot;     alt=&quot;The image&quot; /&gt;&lt;img srcset=&quot;./image.jpg,             ./image-1.5x.jpg 1.5x,             ./image-2x.jpg 2x&quot;     src=&quot;./image.jpg&quot;     alt=&quot;The image&quot; /&gt;</code></pre><pre><code class="css">img { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/img" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_img.asp" target="_blank" rel="noopener">W3Schools</a></p><ul><li><a href="https://heropy.blog/2019/06/16/html-img-srcset-and-sizes/">HTML IMG의 srcset과 sizes 속성</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" target="_blank" rel="noopener">Responsive images for <code>srcset</code> and <code>sizes</code></a></li></ul><h2><span id="616a00af-9116-4e8a-abdf-9b267091cbe7">&lt;audio&gt;</span><a href="#616a00af-9116-4e8a-abdf-9b267091cbe7" class="header-anchor"></a></h2><p>소리 콘텐츠(MP3)를 삽입.</p><ul><li><code>autoplay</code>가 지정된 경우, <code>preload</code>는 무시됨.</li></ul><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>autoplay</td><td>준비되면 바로 재생</td><td>불린(Boolean)</td><td></td></tr><tr><td>controls</td><td>제어 메뉴를 표시</td><td>불린(Boolean)</td><td></td></tr><tr><td>loop</td><td>재생이 끝나면 다시 처음부터 재생</td><td>불린(Boolean)</td><td></td></tr><tr><td>preload</td><td>페이지가 로드될 때 파일을 로드할지의 지정(힌트 제공)</td><td><code>none</code>: 로드하지 않음,<br><code>metadata</code>: 메타데이터만 로드,<br><code>auto</code>: 전체 파일 로드</td><td><code>metadata</code></td></tr><tr><td>src</td><td>콘텐츠 URL</td><td>URL</td><td></td></tr><tr><td>muted</td><td>음소거 여부</td><td>불린(Boolean)</td><td></td></tr></tbody></table><pre><code class="css">audio { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/audio" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_audio.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="54e71d3b-1ab2-4d91-a25e-11bedc0b136b">&lt;video&gt;</span><a href="#54e71d3b-1ab2-4d91-a25e-11bedc0b136b" class="header-anchor"></a></h2><p>동영상 콘텐츠(MP4)를 삽입.</p><ul><li><code>autoplay</code>가 지정된 경우, <code>preload</code>는 무시됨.</li></ul><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>autoplay</td><td>준비되면 바로 재생</td><td>불린(Boolean)</td><td></td></tr><tr><td>controls</td><td>제어 메뉴를 표시</td><td>불린(Boolean)</td><td></td></tr><tr><td>loop</td><td>재생이 끝나면 다시 처음부터 재생</td><td>불린(Boolean)</td><td></td></tr><tr><td>muted</td><td>음소거 여부</td><td>불린(Boolean)</td><td></td></tr><tr><td>poster</td><td>동영상 썸네일 이미지 URL</td><td>URL</td><td></td></tr><tr><td>preload</td><td>페이지가 로드될 때 파일을 로드할지의 지정(힌트 제공)</td><td><code>none</code>: 로드하지 않음,<br><code>metadata</code>: 메타데이터만 로드,<br><code>auto</code>: 전체 파일 로드</td><td><code>metadata</code></td></tr><tr><td>src</td><td>콘텐츠 URL</td><td>URL</td><td></td></tr><tr><td>width</td><td>동영상 가로 너비</td><td></td><td></td></tr><tr><td>height</td><td>동영상 세로 너비</td><td></td><td></td></tr></tbody></table><pre><code class="css">video { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/Video" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_video.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="89b1e7be-696b-4d3a-b0a7-579316f8d782">&lt;figure&gt;, &lt;figcaption&gt;</span><a href="#89b1e7be-696b-4d3a-b0a7-579316f8d782" class="header-anchor"></a></h2><p><code>&lt;figure&gt;</code>는 이미지나 삽화, 도표 등의 영역을 설정.<br><code>&lt;figcaption&gt;</code>는 <code>&lt;figure&gt;</code>에 포함되어 이미지나 삽화 등의 설명을 표시.(Figure Caption)</p><pre><code class="html">&lt;figure&gt;  &lt;img src=&quot;milk.jpg&quot; alt=&quot;A milk&quot;&gt;  &lt;figcaption&gt;Milk is a nutrient-rich, white liquid food produced by the mammary glands of mammals.&lt;/figcaption&gt;&lt;/figure&gt;</code></pre><pre><code class="css">figure { display: block; }figcation { display: inline; }</code></pre><p>FIGURE: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/figure" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_figure.asp" target="_blank" rel="noopener">W3Schools</a><br>FIGCAPTION: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/figcaption" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_figcaption.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="0c8e3844-4877-45a0-bf56-ce5b702d4583">내장 콘텐츠</span><a href="#0c8e3844-4877-45a0-bf56-ce5b702d4583" class="header-anchor"></a></h1><h2><span id="19fa9a9a-6423-415c-affe-3877426aeeff">&lt;iframe&gt;</span><a href="#19fa9a9a-6423-415c-affe-3877426aeeff" class="header-anchor"></a></h2><p>다른 HTML 페이지를 현재 페이지에 삽입.<br>(중첩된 브라우저 컨텍스트(프레임)를 표시)</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>name</td><td>프레임의 이름</td><td></td><td></td></tr><tr><td>src</td><td>포함할 문서의 URL</td><td>URL</td><td></td></tr><tr><td>width</td><td>프레임의 가로 너비</td><td></td><td></td></tr><tr><td>height</td><td>프레임의 세로 너비</td><td></td><td></td></tr><tr><td>allowfullscreen</td><td>전체 화면 모드 사용 여부</td><td>불린(Boolean)</td><td></td></tr><tr><td>frameborder</td><td>프레임 테두리 사용 여부</td><td><code>0</code>, <code>1</code></td><td><code>1</code></td></tr><tr><td>sandbox</td><td>보안을 위한 읽기 전용으로 삽입</td><td>불린(Boolean) or<br><code>allow-form</code>: 양식 제출 가능,<br><code>allow-scripts</code>: 스크립트 실행 가능 ,<br><code>allow-same-origin</code>: 같은 출처(도메인)의 리소스 사용 가능</td><td></td></tr></tbody></table><pre><code class="html">&lt;iframe width=&quot;1280&quot; height=&quot;720&quot; src=&quot;https://www.youtube.com/embed/Q9yn1DpZkHQ&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;</code></pre><pre><code class="css">iframe { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/iframe" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_iframe.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="54655002-d81f-4f46-b0db-eca7c183140e">&lt;canvas&gt;</span><a href="#54655002-d81f-4f46-b0db-eca7c183140e" class="header-anchor"></a></h2><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Canvas" target="_blank" rel="noopener">Canvas API</a>이나 <a href="https://developer.mozilla.org/ko/docs/Web/API/WebGL_API" target="_blank" rel="noopener">WebGL API</a>를 사용하여 그래픽이나 애니메이션을 랜더링.</p><table><thead><tr><th>속성</th><th>의미</th></tr></thead><tbody><tr><td>width</td><td>캔버스의 가로 너비</td></tr><tr><td>height</td><td>캔버스의 세로 너비</td></tr></tbody></table><pre><code class="css">canvas { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Canvas/Tutorial/Basic_usage" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/html/html5_canvas.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="fbf5ddaa-c426-4044-a383-38f46d0012a5">스크립트</span><a href="#fbf5ddaa-c426-4044-a383-38f46d0012a5" class="header-anchor"></a></h1><h2><span id="67bc3177-6646-4a7b-9bdc-dd39621d8194">&lt;script&gt;</span><a href="#67bc3177-6646-4a7b-9bdc-dd39621d8194" class="header-anchor"></a></h2><p>스크립트 코드를 문서에 포함하거나 참조(외부 스크립트).</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>특징</th></tr></thead><tbody><tr><td>async</td><td>스크립트의 비동기적(Asynchronously) 실행 여부</td><td>불린(Boolean)</td><td><code>src</code> 속성 필수</td></tr><tr><td>defer</td><td>문서 파싱(구문 분석) 후 작동 여부</td><td>불린(Boolean)</td><td><code>src</code> 속성 필수</td></tr><tr><td>src</td><td>참조할 외부 스크립트 URL</td><td>URL</td><td>포함된 스크립트 코드는 무시됨</td></tr><tr><td>type</td><td><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank" rel="noopener">MIME 타입</a></td><td><code>text/javascript</code>(기본값)</td><td></td></tr></tbody></table><pre><code class="css">script { display: none; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/script" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_script.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="237fce2b-06a1-4fdc-86e1-a0ad2c00a2be">&lt;noscript&gt;</span><a href="#237fce2b-06a1-4fdc-86e1-a0ad2c00a2be" class="header-anchor"></a></h2><p>스크립트를 지원하지 않는 경우에 삽입할 HTML을 정의.</p><pre><code class="html">&lt;noscript&gt;  &lt;p&gt;Your browser does not support JavaScript!&lt;/p&gt;&lt;/noscript&gt;</code></pre><pre><code class="css">noscript { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/noscript" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_noscript.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="89b2a12c-83b2-4373-b330-4750834e915c">표 콘텐츠</span><a href="#89b2a12c-83b2-4373-b330-4750834e915c" class="header-anchor"></a></h1><pre><code class="html">&lt;table&gt;  &lt;caption&gt;Fruits&lt;/caption&gt;  &lt;colgroup&gt;    &lt;col span=&quot;2&quot; style=&quot;background-color: yellowgreen;&quot;&gt;    &lt;col style=&quot;background-color: tomato;&quot;&gt;  &lt;/colgroup&gt;  &lt;thead&gt;    &lt;tr&gt;      &lt;th&gt;ID&lt;/th&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Price&lt;/th&gt;    &lt;/tr&gt;  &lt;/thead&gt;  &lt;tbody&gt;    &lt;tr&gt;      &lt;td&gt;F123A&lt;/td&gt;      &lt;td&gt;Apple&lt;/td&gt;      &lt;td&gt;$22&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;F098B&lt;/td&gt;      &lt;td&gt;Banana&lt;/td&gt;      &lt;td&gt;$19&lt;/td&gt;    &lt;/tr&gt;  &lt;/tbody&gt;&lt;/table&gt;</code></pre><h2><span id="14de74f0-88c7-49d6-9be3-7511c58f3747">&lt;table&gt;, &lt;tr&gt;, &lt;th&gt;, &lt;td&gt;</span><a href="#14de74f0-88c7-49d6-9be3-7511c58f3747" class="header-anchor"></a></h2><p>데이터 표(<code>&lt;table&gt;</code>)의 행(줄 / <code>&lt;tr&gt;</code>)과 열(칸, 셀(Cell) / <code>&lt;th&gt;</code>, <code>&lt;td&gt;</code>)을 생성.<br>(Table Row, Table Header, Table Data)</p><pre><code class="css">table { display: table; }tr { display: table-row; }th, td { display: table-cell; }</code></pre><p>TABLE: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/table" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_table.asp" target="_blank" rel="noopener">W3Schools</a><br>TR: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/tr" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_tr.asp" target="_blank" rel="noopener">W3Schools</a><br>TH: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/th" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_th.asp" target="_blank" rel="noopener">W3Schools</a><br>TD: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/td" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_td.asp" target="_blank" rel="noopener">W3Schools</a></p><h3><span id="b80f2f23-64ae-40ef-9cdb-786361cdc9e8">&lt;th&gt;</span><a href="#b80f2f23-64ae-40ef-9cdb-786361cdc9e8" class="header-anchor"></a></h3><p>‘머리글 칸’을 지정</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>abbr</td><td>열에 대한 간단한 설명</td><td></td><td></td></tr><tr><td>headers</td><td>관련된 하나 이상의 다른 머리글 칸 <code>id</code> 속성 값</td><td></td><td></td></tr><tr><td>colspan</td><td>확장하려는(병합) 열의 수</td><td></td><td><code>1</code></td></tr><tr><td>rowspan</td><td>확장하려는(병합) 행의 수</td><td></td><td><code>1</code></td></tr><tr><td>scope</td><td>자신이 누구의 ‘머리글 칸’인지 명시</td><td><code>col</code>: 자신의 열<br><code>colgroup</code>: 모든 열<br><code>row</code>: 자신의 행<br><code>rowgroup</code>: 모든 행<br><code>auto</code></td><td><code>auto</code></td></tr></tbody></table><h3><span id="363f73dd-b19f-4aeb-9bf2-593d9ab9c17d">&lt;td&gt;</span><a href="#363f73dd-b19f-4aeb-9bf2-593d9ab9c17d" class="header-anchor"></a></h3><p>‘일반 칸’을 지정</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>headers</td><td>관련된 하나 이상의 다른 머리글 칸 <code>id</code> 속성 값</td><td></td><td></td></tr><tr><td>colspan</td><td>확장하려는(병합) 열의 수</td><td></td><td><code>1</code></td></tr><tr><td>rowspan</td><td>확장하려는(병합) 행의 수</td><td></td><td><code>1</code></td></tr></tbody></table><h2><span id="f11ec000-fc7c-4ebf-9d7d-98d677ed4064">&lt;caption&gt;</span><a href="#f11ec000-fc7c-4ebf-9d7d-98d677ed4064" class="header-anchor"></a></h2><p>표의 제목을 설정.</p><ul><li>열리는 TABLE 태그 바로 다음에 작성해야 함.</li><li>&lt;table&gt; 당 하나의 &lt;caption&gt;만 사용 가능.</li></ul><pre><code class="css">caption { display: table-caption; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/caption" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_caption.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="e666f74b-7f47-464d-a101-b1efc0611d81">&lt;colgroup&gt;, &lt;col /&gt;</span><a href="#e666f74b-7f47-464d-a101-b1efc0611d81" class="header-anchor"></a></h2><p>표의 열들을 공통적으로 정의하는 컬럼(<code>&lt;col&gt;</code>)과 그의 집합(<code>&lt;colgroup&gt;</code>).<br>(Column, Column Group)</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>span</td><td>연속되는 열 수</td><td>숫자(Number)</td><td><code>1</code></td></tr></tbody></table><pre><code class="css">colgroup { display: table-column-group; }col { display: table-column; }</code></pre><p>COLGROUP: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/colgroup" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_colgroup.asp" target="_blank" rel="noopener">W3Schools</a><br>COL: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/col" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_col.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="7444f48e-17c1-4ba2-8aab-a2fe833eec77">&lt;thead&gt;, &lt;tbody&gt;, &lt;tfoot&gt;</span><a href="#7444f48e-17c1-4ba2-8aab-a2fe833eec77" class="header-anchor"></a></h2><p>표의 머리글(<code>&lt;thead&gt;</code>), 본문(<code>&lt;tbody&gt;</code>), 바닥글(<code>&lt;tfoot&gt;</code>)을 지정.</p><ul><li>기본적으로 테이블의 레이아웃에 영향을 주지 않음.</li></ul><pre><code class="css">thead { display: table-header-group; }tbody { display: table-row-group; }tfoot { display: table-footer-group; }</code></pre><p>THEAD: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/thead" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_thead.asp" target="_blank" rel="noopener">W3Schools</a><br>TBODY: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/tbody" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_tbody.asp" target="_blank" rel="noopener">W3Schools</a><br>TFOOT: <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_tfoot.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="e07a7dea-6a6c-46de-8dd1-096e345161e6">양식</span><a href="#e07a7dea-6a6c-46de-8dd1-096e345161e6" class="header-anchor"></a></h1><h2><span id="e2b00faf-9a8c-4fe4-8293-a4f01b1bc6d1">&lt;form&gt;</span><a href="#e2b00faf-9a8c-4fe4-8293-a4f01b1bc6d1" class="header-anchor"></a></h2><p>웹 서버에 정보를 제출하기 위한 양식 범위를 정의.</p><ul><li><code>&lt;form&gt;</code>이 다른 <code>&lt;form&gt;</code>을 자식 요소로 포함할 수 없음.</li></ul><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>action</td><td>전송한 정보를 처리할 웹페이지의 URL</td><td>URL</td><td></td></tr><tr><td>autocomplete</td><td>사용자가 이전에 입력한 값으로 자동 완성 기능을 사용할 것인지 여부</td><td><code>on</code>, <code>off</code></td><td><code>on</code></td></tr><tr><td>method</td><td>서버로 전송할 <a href="https://www.w3.org/Protocols/rfc2616/rfc2616.html" target="_blank" rel="noopener">HTTP</a> 방식</td><td><code>GET</code>, <code>POST</code></td><td><code>GET</code></td></tr><tr><td>name</td><td>고유한 양식의 이름</td><td></td><td></td></tr><tr><td>novalidate</td><td>서버로 전송시 양식 데이터의 유효성을 검사하지 않도록 지정</td><td></td><td></td></tr><tr><td>target</td><td>서버로 전송 후 응답받을 방식을 지정</td><td><code>_self</code>, <code>_blank</code></td><td><code>_self</code></td></tr></tbody></table><pre><code class="css">form { display: block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/form" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_form.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="3e917c79-793f-43b2-9e6a-970e8a1ce58b">&lt;input /&gt;</span><a href="#3e917c79-793f-43b2-9e6a-970e8a1ce58b" class="header-anchor"></a></h2><p>사용자에게 입력 받을 데이터 양식.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th><th>특징</th></tr></thead><tbody><tr><td>autocomplete</td><td>사용자가 이전에 입력한 값으로 자동 완성 기능을 사용할 것인지 여부</td><td><code>on</code>, <code>off</code></td><td><code>on</code></td><td></td></tr><tr><td>autofocus</td><td>페이지가 로드될 때 자동으로 포커스</td><td>불린(Boolean)</td><td></td><td>문서 내 고유해야 함</td></tr><tr><td>checked</td><td>양식이 선택되었음을 표시</td><td>불린(Boolean)</td><td></td><td><code>type</code> 속성 값이 <code>radio</code>, <code>checkbox</code>일 경우만</td></tr><tr><td>disabled</td><td>양식을 비활성화</td><td>불린(Boolean)</td><td></td><td></td></tr><tr><td>form</td><td><code>&lt;form&gt;</code>의 <code>id</code> 속성 값</td><td></td><td></td><td>해당 <code>&lt;form&gt;</code>의 후손이 아닐 경우만</td></tr><tr><td>list</td><td>참조할 <code>&lt;datalist&gt;</code>의 <code>id</code> 속성 값</td><td></td><td></td><td></td></tr><tr><td>max</td><td>지정 가능한 최대 값</td><td>숫자(Number)</td><td></td><td><code>type</code> 속성 값이 <code>number</code>일 경우만,<br><code>min</code>속성보다 큰 값만 허용</td></tr><tr><td>min</td><td>지정 가능한 최소 값</td><td>숫자(Number)</td><td></td><td><code>type</code> 속성 값이 <code>number</code>일 경우만,<br><code>max</code>속성보다 작은 값만 허용</td></tr><tr><td>maxlength</td><td>입력 가능한 최대 문자 수</td><td>숫자(Number)</td><td><code>524288</code></td><td><code>type</code> 속성 값이 <code>text</code>, <code>email</code>, <code>password</code>, <code>tel</code>, <code>url</code>일 경우만</td></tr><tr><td>multiple</td><td>둘 이상의 값을 입력 할 수 있는지 여부</td><td>불린(Boolean)</td><td></td><td><code>type</code> 속성 값이 <code>email</code>, <code>file</code>일 경우만,<br><code>email</code>일 경우 <code>,</code>로 구분</td></tr><tr><td>name</td><td>양식의 이름</td><td></td><td></td><td></td></tr><tr><td>placeholder</td><td>사용자가 입력할 값의 힌트</td><td></td><td></td><td><code>type</code> 속성 값이 <code>text</code>, <code>search</code>, <code>tel</code>, <code>url</code>, <code>email</code>일 경우만</td></tr><tr><td>readonly</td><td>수정 불가한 읽기 전용</td><td>불린(Boolean)</td><td></td><td></td></tr><tr><td>step</td><td>유효한 증감 숫자의 간격</td><td>숫자(Number)</td><td><code>1</code></td><td><code>type</code> 속성 값이 <code>number</code>, <code>range</code>일 경우만</td></tr><tr><td>src</td><td>이미지의 URL</td><td>URL</td><td></td><td><code>type</code> 속성 값이 <code>image</code>일 경우만</td></tr><tr><td>alt</td><td>이미지의 대체 텍스트</td><td></td><td></td><td><code>type</code> 속성 값이 <code>image</code>일 경우만</td></tr><tr><td>type</td><td>입력 받을 데이터의 종류</td><td>별도 정리</td><td><code>text</code></td></tr><tr><td>value</td><td>양식의 초기 값</td><td></td><td></td><td></td></tr></tbody></table><h3><span id="67dc6645-9698-4063-8d2f-2c9c72c4ed7f">데이터 종류(Type)의 값(Values)</span><a href="#67dc6645-9698-4063-8d2f-2c9c72c4ed7f" class="header-anchor"></a></h3><p><code>type</code>속성에 입력할 수 있는 값의 목록.</p><pre><code class="html">&lt;input type=&quot;button&quot; /&gt;&lt;input type=&quot;checkbox&quot; /&gt;&lt;input type=&quot;file&quot; /&gt;&lt;input type=&quot;text&quot; /&gt;</code></pre><table><thead><tr><th>값</th><th>데이터 종류</th><th>특징</th></tr></thead><tbody><tr><td>button</td><td>일반 버튼</td><td><code>&lt;button&gt;</code>처럼 사용</td></tr><tr><td>checkbox</td><td>체크박스</td><td></td></tr><tr><td>color</td><td>색상</td><td>IE 지원 불가</td></tr><tr><td>email</td><td>이메일</td><td></td></tr><tr><td>file</td><td>파일</td><td></td></tr><tr><td>hidden</td><td>보이지 않지만 전송할 양식</td><td><code>value</code> 속성으로 값을 지정</td></tr><tr><td>image</td><td>이미지 제출 버튼</td><td><code>&lt;img /&gt;</code>처럼 사용</td></tr><tr><td>number</td><td>숫자</td><td></td></tr><tr><td>password</td><td>비밀</td><td>가려지는 양식</td></tr><tr><td>radio</td><td>라디오 버튼</td><td>같은 <code>name</code> 속성 그룹 내 하나만 선택 가능</td></tr><tr><td>range</td><td>범위 컨트롤</td><td><code>min</code>, <code>max</code>, <code>step</code>, <code>value</code>(기본값) 속성 사용</td></tr><tr><td>reset</td><td>초기화</td><td>해당 <code>&lt;form&gt;</code> 범위 내 모든 양식</td></tr><tr><td>search</td><td>검색</td><td></td></tr><tr><td>submit</td><td>제출 버튼</td><td>해당 <code>&lt;form&gt;</code> 범위 내 고유한 양식</td></tr><tr><td>tel</td><td>전화번호</td><td></td></tr><tr><td>text</td><td>일반 텍스트</td><td></td></tr><tr><td>url</td><td>절대 URL</td><td></td></tr></tbody></table><pre><code class="css">input { display: inline-block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/Input" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_input.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="cec47681-09da-4de1-84dd-d37702de6971">&lt;label&gt;</span><a href="#cec47681-09da-4de1-84dd-d37702de6971" class="header-anchor"></a></h2><p>라벨 가능 요소(<a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable" target="_blank" rel="noopener">labelable</a>)의 제목(Caption).</p><ul><li><code>for</code> 속성으로 라벨 가능 요소를 참조하거나 콘텐츠로 포함.</li><li>라벨 가능 요소: <code>&lt;button&gt;</code>, <code>&lt;input&gt;</code>, <code>&lt;progress&gt;</code>, <code>&lt;select&gt;</code>, <code>&lt;textarea&gt;</code></li></ul><table><thead><tr><th>속성</th><th>의미</th></tr></thead><tbody><tr><td>for</td><td>참조할 라벨 가능 요소의 <code>id</code> 속성 값</td></tr></tbody></table><pre><code class="html">&lt;!-- 라벨 가능 요소를 참조 --&gt;&lt;input type=&quot;checkbox&quot; id=&quot;user-agreement&quot; /&gt;&lt;label for=&quot;user-agreement&quot;&gt;동의하십니까?&lt;/label&gt;&lt;!-- 라벨 가능 요소를 포함 --&gt;&lt;label&gt;&lt;input type=&quot;checkbox&quot; /&gt;동의하십니까?&lt;/label&gt;</code></pre><pre><code class="css">label { display: inline; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/label" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_label.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="d00633a1-c4b3-4c64-a945-b37bb0c5152f">&lt;button&gt;</span><a href="#d00633a1-c4b3-4c64-a945-b37bb0c5152f" class="header-anchor"></a></h2><p>선택 가능한 버튼을 지정.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>특징</th></tr></thead><tbody><tr><td>autofocus</td><td>페이지가 로드될 때 자동으로 포커스</td><td>불린(Boolean)</td><td>문서 내 고유해야 함</td></tr><tr><td>disabled</td><td>버튼을 비활성화</td><td>불린(Boolean)</td><td></td></tr><tr><td>form</td><td><code>&lt;form&gt;</code>의 <code>id</code> 속성 값</td><td></td><td>해당 <code>&lt;form&gt;</code>의 후손이 아닐 경우만</td></tr><tr><td>name</td><td>폼 데이터와 함께 전송되는 버튼의 이름</td><td></td><td></td></tr><tr><td>type</td><td>버튼의 타입</td><td><code>button</code>, <code>reset</code>, <code>submit</code></td><td></td></tr></tbody></table><pre><code class="css">button { display: inline-block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/button" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_button.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="549c8e3d-9508-4ee9-94ff-85c870ad83c9">&lt;textarea&gt;</span><a href="#549c8e3d-9508-4ee9-94ff-85c870ad83c9" class="header-anchor"></a></h2><p>여러 줄의 일반 텍스트 양식.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th><th>특징</th></tr></thead><tbody><tr><td>autocomplete</td><td>사용자가 이전에 입력한 값으로 자동 완성 기능을 사용할 것인지 여부</td><td><code>on</code>, <code>off</code></td><td><code>on</code></td><td></td></tr><tr><td>autofocus</td><td>페이지가 로드될 때 자동으로 포커스</td><td>불린(Boolean)</td><td></td><td>문서 내 고유해야 함</td></tr><tr><td>disabled</td><td>양식을 비활성화</td><td>불린(Boolean)</td><td></td><td></td></tr><tr><td>form</td><td><code>&lt;form&gt;</code>의 <code>id</code> 속성 값</td><td></td><td></td><td>해당 <code>&lt;form&gt;</code>의 후손이 아닐 경우만</td></tr><tr><td>maxlength</td><td>입력 가능한 최대 문자 수</td><td>숫자(Number)</td><td>무한</td><td></td></tr><tr><td>name</td><td>양식의 이름</td><td></td><td></td><td></td></tr><tr><td>placeholder</td><td>사용자가 입력할 값의 힌트</td><td></td><td></td><td></td></tr><tr><td>readonly</td><td>수정 불가한 읽기 전용</td><td>불린(Boolean)</td><td></td><td></td></tr><tr><td>rows</td><td>양식의 줄 수</td><td>숫자(Number)</td><td><code>2</code></td><td></td></tr></tbody></table><pre><code class="css">textarea { display: inline-block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/textarea" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_textarea.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="74d12042-a762-4e3d-9b3a-bad7263eac21">&lt;fieldset&gt;, &lt;legend&gt;</span><a href="#74d12042-a762-4e3d-9b3a-bad7263eac21" class="header-anchor"></a></h2><p>같은 목적의 양식을 그룹화(<code>&lt;fieldset&gt;</code>)하여 제목(<code>&lt;legend&gt;</code>)을 지정.</p><pre><code class="html">&lt;form&gt;  &lt;fieldset&gt;    &lt;legend&gt;Coffee Size&lt;/legend&gt;    &lt;label&gt;        &lt;input type=&quot;radio&quot; name=&quot;size&quot; value=&quot;tall&quot; /&gt;        Tall    &lt;/label&gt;    &lt;label&gt;        &lt;input type=&quot;radio&quot; name=&quot;size&quot; value=&quot;grande&quot; /&gt;        Grande    &lt;/label&gt;    &lt;label&gt;        &lt;input type=&quot;radio&quot; name=&quot;size&quot; value=&quot;venti&quot; /&gt;        Venti    &lt;/label&gt;  &lt;/fieldset&gt;&lt;/form&gt;</code></pre><pre><code class="css">fieldset, legend { display: block; }</code></pre><p>FIELDSET: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/fieldset" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_fieldset.asp" target="_blank" rel="noopener">W3Schools</a><br>LEGEND: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/legend" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_legend.asp" target="_blank" rel="noopener">W3Schools</a></p><h3><span id="629f9dce-c0cc-4a9f-994e-ee23105310cc">&lt;fieldset&gt;</span><a href="#629f9dce-c0cc-4a9f-994e-ee23105310cc" class="header-anchor"></a></h3><p>같은 목적의 양식을 그룹화.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>disabled</td><td>그룹 내 모든 양식 요소를 비활성화</td><td>불린(Boolean)</td><td></td></tr><tr><td>form</td><td>그룹이 속할 하나 이상의 <code>&lt;form&gt;</code>의 <code>id</code> 속성 값</td><td></td></tr><tr><td>name</td><td>그룹의 이름</td><td></td></tr></tbody></table><h2><span id="773489eb-9da5-4aa0-ae5c-d2ae8f1572f8">&lt;select&gt;, &lt;datalist&gt;, &lt;optgroup&gt;, &lt;option&gt;</span><a href="#773489eb-9da5-4aa0-ae5c-d2ae8f1572f8" class="header-anchor"></a></h2><p>옵션(<code>&lt;option&gt;</code>, <code>&lt;optgroup&gt;</code>)의 선택 메뉴(<code>&lt;select&gt;</code>)나 자동완성(<code>&lt;datalist&gt;</code>)을 제공.</p><pre><code class="html">&lt;select&gt;  &lt;optgroup label=&quot;Coffee&quot;&gt;    &lt;option&gt;Americano&lt;/option&gt;    &lt;option&gt;Caffe Mocha&lt;/option&gt;    &lt;option label=&quot;Cappuccino&quot; value=&quot;Cappuccino&quot;&gt;&lt;/option&gt;  &lt;/optgroup&gt;  &lt;optgroup label=&quot;Latte&quot; disabled&gt;    &lt;option&gt;Caffe Latte&lt;/option&gt;    &lt;option&gt;Vanilla Latte&lt;/option&gt;  &lt;/optgroup&gt;  &lt;optgroup label=&quot;Smoothie&quot;&gt;    &lt;option&gt;Plain&lt;/option&gt;    &lt;option&gt;Strawberry&lt;/option&gt;    &lt;option&gt;Banana&lt;/option&gt;    &lt;option&gt;Mango&lt;/option&gt;  &lt;/optgroup&gt;&lt;/select&gt;</code></pre><pre><code class="css">select { display: inline-block; }datalist { display: none; }optgroup, option { display: block; }</code></pre><p>SELECT: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/select" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_select.asp" target="_blank" rel="noopener">W3Schools</a><br>DATALIST: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/datalist" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_datalist.asp" target="_blank" rel="noopener">W3Schools</a><br>OPTGROUP: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/optgroup" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_optgroup.asp" target="_blank" rel="noopener">W3Schools</a><br>OPTION: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/option" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_option.asp" target="_blank" rel="noopener">W3Schools</a></p><h3><span id="560d3793-369e-42d2-b649-45280c5925bc">&lt;select&gt;</span><a href="#560d3793-369e-42d2-b649-45280c5925bc" class="header-anchor"></a></h3><p>옵션을 선택하는 메뉴.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>기본값</th></tr></thead><tbody><tr><td>autocomplete</td><td>사용자가 이전에 입력한 값으로 자동 완성 기능을 사용할 것인지 여부</td><td><code>on</code>, <code>off</code></td><td><code>on</code></td><td></td></tr><tr><td>disabled</td><td>선택 메뉴를 비활성화</td><td>불린(Boolean)</td><td></td></tr><tr><td>form</td><td>선택 메뉴가 속할 하나 이상의 <code>&lt;form&gt;</code>의 <code>id</code> 속성 값</td><td></td><td></td></tr><tr><td>multiple</td><td>다중 선택 여부</td><td>불린(Boolean)</td><td></td></tr><tr><td>name</td><td>선택 메뉴의 이름</td><td></td><td></td></tr><tr><td>size</td><td>한 번에 볼 수 있는 행의 개수</td><td>숫자(Number)</td><td><code>0</code>(<code>1</code>과 같음)</td></tr></tbody></table><h3><span id="b87fb1da-ae45-42bd-b5d3-97f52b74ef8a">&lt;datalist&gt;</span><a href="#b87fb1da-ae45-42bd-b5d3-97f52b74ef8a" class="header-anchor"></a></h3><p><code>&lt;input&gt;</code>에 미리 정의된 옵션을 지정하여 자동완성(Autocomplete) 기능을 제공하는 데 사용.</p><ul><li><code>&lt;input&gt;</code>의 <code>list</code> 속성 바인딩.</li><li><code>&lt;option&gt;</code>을 포함하여 정의된 옵션을 지정.</li></ul><pre><code class="html">&lt;input type=&quot;text&quot; list=&quot;fruits&quot;&gt;&lt;datalist id=&quot;fruits&quot;&gt;  &lt;option&gt;Apple&lt;/option&gt;  &lt;option&gt;Orange&lt;/option&gt;  &lt;option&gt;Banana&lt;/option&gt;  &lt;option&gt;Mango&lt;/option&gt;  &lt;option&gt;Fineapple&lt;/option&gt;&lt;/datalist&gt;</code></pre><h3><span id="7cc1911e-a62e-4433-a398-6afde2adefb4">&lt;optgroup&gt;</span><a href="#7cc1911e-a62e-4433-a398-6afde2adefb4" class="header-anchor"></a></h3><p><code>&lt;option&gt;</code>을 그룹화.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td>label</td><td>(필수)옵션 그룹의 이름</td><td></td></tr><tr><td>disabled</td><td>옵션 그룹을 비활성화</td><td>불린(Boolean)</td></tr></tbody></table><h3><span id="0ab01517-39c7-4aca-bd76-f8b42e754b44">&lt;option&gt;</span><a href="#0ab01517-39c7-4aca-bd76-f8b42e754b44" class="header-anchor"></a></h3><p>선택 메뉴(<code>&lt;select&gt;</code>)나 자동완성(<code>&lt;datalist&gt;</code>)에서 사용될 옵션.</p><ul><li>선택적 빈(Empty) 태그로 사용 가능.</li></ul><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>특성</th></tr></thead><tbody><tr><td>disabled</td><td>옵션을 비활성화</td><td>불린(Boolean)</td><td></td></tr><tr><td>label</td><td>표시될 옵션의 제목</td><td></td><td>생략되면 포함된 텍스트를 표시</td></tr><tr><td>selected</td><td>옵션이 선택되었음을 표시</td><td>불린(Boolean)</td><td></td></tr><tr><td>value</td><td>양식으로 제출될 값</td><td></td><td>생략되면 포함된 텍스트를 값으로 사용</td></tr></tbody></table><h2><span id="b1c9aa2a-430d-4754-9ee0-c3b84177b760">&lt;progress&gt;</span><a href="#b1c9aa2a-430d-4754-9ee0-c3b84177b760" class="header-anchor"></a></h2><p>작업의 완료 진행률을 표시.</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th><th>특징</th></tr></thead><tbody><tr><td>max</td><td>작업의 총량</td><td>숫자(Number)</td><td></td></tr><tr><td>value</td><td>작업의 진행량</td><td>숫자(Number)</td><td><code>max</code> 속성을 생략할 경우 <code>0</code>~<code>1</code> 사이의 숫자여야 함</td></tr></tbody></table><pre><code class="html">&lt;progress value=&quot;70&quot; max=&quot;100&quot;&gt;70 %&lt;/progress&gt;</code></pre><pre><code class="css">progress { display: inline-block; }</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/progress" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_progress.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="0e87bf98-d71c-4c03-9133-0ae8eb75c2cc">전역 속성(Global Attributes)</span><a href="#0e87bf98-d71c-4c03-9133-0ae8eb75c2cc" class="header-anchor"></a></h1><p>모든 HTML 요소에서 공통적으로 사용 가능한 속성.</p><h2><span id="d273b1d8-2acb-4d3b-9927-45227ad513e3">class</span><a href="#d273b1d8-2acb-4d3b-9927-45227ad513e3" class="header-anchor"></a></h2><p>공백으로 구분된 요소의 별칭을 지정.<br>CSS 혹은 JavaScript의 요소 선택기(<a href="https://developer.mozilla.org/ko/docs/Web/CSS/CSS_%EC%84%A0%ED%83%9D%EC%9E%90" target="_blank" rel="noopener">CSS 선택자</a>나 <a href="https://developer.mozilla.org/ko/docs/Web/API/Document/getElementsByClassName" target="_blank" rel="noopener">GetElementsByClassName</a>, <a href="https://developer.mozilla.org/ko/docs/Web/API/Document/querySelectorAll" target="_blank" rel="noopener">QuerySelectorAll</a> 같은)를 통해서 요소를 선택하거나 접근.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/%ED%81%B4%EB%9E%98%EC%8A%A4" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_class.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="8dbdb52b-a7cd-475e-b088-9b2ae6c93cd5">id</span><a href="#8dbdb52b-a7cd-475e-b088-9b2ae6c93cd5" class="header-anchor"></a></h2><p>문서에서 고유한 식별자(idenifier, ID)를 정의.<br>CSS 혹은 JavaScript의 요소 선택기(<a href="https://developer.mozilla.org/ko/docs/Web/CSS/CSS_%EC%84%A0%ED%83%9D%EC%9E%90" target="_blank" rel="noopener">CSS 선택자</a>나 <a href="https://developer.mozilla.org/ko/docs/Web/API/Document/getElementsByClassName" target="_blank" rel="noopener">GetElementsByClassName</a>, <a href="https://developer.mozilla.org/ko/docs/Web/API/Document/querySelectorAll" target="_blank" rel="noopener">QuerySelectorAll</a> 같은)를 통해서 요소를 선택하거나 접근.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/id" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_id.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="2379f98f-9449-488e-be15-7a8c25ed8c38">style</span><a href="#2379f98f-9449-488e-be15-7a8c25ed8c38" class="header-anchor"></a></h2><p>요소에 적용할 CSS를 선언.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/style" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_style.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="2a0bc56c-fb44-4ff9-9141-c0922ef7263a">title</span><a href="#2a0bc56c-fb44-4ff9-9141-c0922ef7263a" class="header-anchor"></a></h2><p>요소의 정보(설명)을 지정.</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_title.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="9ebb643f-ff0e-47a6-b5ca-439f41c963c5">lang</span><a href="#9ebb643f-ff0e-47a6-b5ca-439f41c963c5" class="header-anchor"></a></h2><p>요소의 언어(<a href="https://ko.wikipedia.org/wiki/ISO_639-1_%EC%BD%94%EB%93%9C_%EB%AA%A9%EB%A1%9D" target="_blank" rel="noopener">ISO 639-1</a>)를 지정.</p><pre><code class="html">&lt;p lang=&quot;en&quot;&gt;This paragraph is English&lt;/p&gt;&lt;p lang=&quot;ko&quot;&gt;이 단락은 한글입니다.&lt;/p&gt;&lt;p lang=&quot;fr&quot;&gt;Ce paragraphe est défini en français.&lt;/p&gt;</code></pre><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_lang.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="fb54e2f4-ca48-4d7e-b27d-19d39d5708ea">data-*</span><a href="#fb54e2f4-ca48-4d7e-b27d-19d39d5708ea" class="header-anchor"></a></h2><p>사용자 정의 데이터 속성을 지정.<br>HTML에 JavaScript에서 이용할 수 있는 데이터(정보)를 저장하는 용도로 사용.</p><pre><code class="html">&lt;!-- data-custom-data-attributes --&gt;&lt;div id=&quot;me&quot; data-my-name=&quot;Heropy&quot; data-my-age=&quot;851&quot;&gt;Heropy&lt;/div&gt;</code></pre><pre><code class="js">// dataset.customDataAttributesconst $me = document.getElementById(&#39;me&#39;);console.log($me.dataset.myName); // &quot;Heropy&quot;console.log($me.dataset.myAge); // &quot;851&quot;</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/data-*" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_data.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="13bdb5fc-f782-4438-9775-ed0a91b8a709">draggable</span><a href="#13bdb5fc-f782-4438-9775-ed0a91b8a709" class="header-anchor"></a></h2><p>요소가 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API" target="_blank" rel="noopener">Drag and Drop API</a>를 사용 가능한지 여부를 지정.</p><pre><code class="html">&lt;div draggable=&quot;true&quot;&gt;The element to drag.&lt;/div&gt;</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/draggable" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_draggable.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="23a25bbb-3f45-426f-a0ce-a924b887c330">hidden</span><a href="#23a25bbb-3f45-426f-a0ce-a924b887c330" class="header-anchor"></a></h2><p>요소를 숨김.</p><pre><code class="html">&lt;form id=&quot;hidden-form&quot; action=&quot;/form-action&quot; hidden&gt;  &lt;!-- 숨겨진 양식들.. --&gt;&lt;/form&gt;&lt;button form=&quot;hidden-form&quot; type=&quot;submit&quot;&gt;전송&lt;/button&gt;</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/hidden" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_hidden.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="f8af5d0c-ca82-4eee-9a74-a4b91e8f9fc1">tabindex</span><a href="#f8af5d0c-ca82-4eee-9a74-a4b91e8f9fc1" class="header-anchor"></a></h2><p><code>Tab</code>키를 이용해 요소를 순차적으로 포커스 탐색할 순서를 지정.</p><ul><li><a href="https://developer.mozilla.org/ko/docs/Web/Guide/HTML/Content_categories#%EB%8C%80%ED%99%94%ED%98%95_%EC%BD%98%ED%85%90%EC%B8%A0" target="_blank" rel="noopener">대화형 콘텐츠(Interactive Content)</a>는 기본적으로 코드 순서대로 탭 순서가 지정됨.</li><li>비대화형 콘텐츠에 <code>tabindex=&quot;0&quot;</code>을 지정하여 대화형 콘텐츠와 같이 탭 순서를 사용.</li><li><code>tabindex=&quot;-1&quot;</code>을 통해 포커스는 가능하지만 탭 순서에서 제외 가능.</li><li><code>tabindex=&quot;1&quot;</code> 이상의 양수 값은 논리적 흐름을 방해하기 때문에 사용을 추천하지 않음.</li></ul><pre><code class="html">&lt;h1 tabindex=&quot;0&quot;&gt;Sign In&lt;/h1&gt;&lt;label&gt;Username: &lt;input type=&quot;text&quot;&gt;&lt;/label&gt;&lt;label&gt;Password: &lt;input type=&quot;password&quot;&gt;&lt;/label&gt;&lt;label&gt;PS: &lt;input type=&quot;text&quot; tabindex=&quot;-1&quot;&gt;&lt;/label&gt;&lt;input type=&quot;submit&quot; value=&quot;Sign In&quot;&gt;</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/tabindex" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_tabindex.asp" target="_blank" rel="noopener">W3Schools</a></p><p><a href="https://developer.paciellogroup.com/blog/2014/08/using-the-tabindex-attribute/" target="_blank" rel="noopener">Using the tabindex attribute</a></p><h1><span id="8ab92377-4ce1-4dcb-8536-1ce6377e6623">생략한 요소</span><a href="#8ab92377-4ce1-4dcb-8536-1ce6377e6623" class="header-anchor"></a></h1><h2><span id="0cb3cff8-db6c-44bc-bc8f-34acae140146">&lt;template&gt;</span><a href="#0cb3cff8-db6c-44bc-bc8f-34acae140146" class="header-anchor"></a></h2><p>렌더링되지 않는 콘텐츠를 보유.</p><ul><li>JavaScript를 사용해 렌더링.</li><li>반복적으로 사용하는 콘텐츠에 활용.</li><li>IE 지원 불가</li></ul><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/template" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_template.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="04d5dea7-aa17-4af7-a29c-4d6ae39b2fc8">&lt;map&gt;, &lt;area&gt;</span><a href="#04d5dea7-aa17-4af7-a29c-4d6ae39b2fc8" class="header-anchor"></a></h2><p>클라이언트 측 이미지 맵(<code>&lt;map&gt;</code>)과 클릭 가능한 영역(<code>&lt;area&gt;</code>)을 정의.<br>(<code>&lt;img /&gt;</code>와 연결해 사용)</p><p>MAP: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/map" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_map.asp" target="_blank" rel="noopener">W3Schools</a><br>AREA: <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/area" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_area.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="94f60512-4f8a-49ce-ae34-de48f6f45947">&lt;picture&gt;</span><a href="#94f60512-4f8a-49ce-ae34-de48f6f45947" class="header-anchor"></a></h2><p>이미지를 삽입.<br>(<code>&lt;img /&gt;</code>의 <code>srcset</code>, <code>sizes</code>로 대체 가능)</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_picture.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="f7b5af7f-04ec-4dfa-9910-525061615b7e">&lt;source&gt;</span><a href="#f7b5af7f-04ec-4dfa-9910-525061615b7e" class="header-anchor"></a></h2><p>브라우저가 선택 가능한 <code>&lt;audio&gt;</code>, <code>&lt;video&gt;</code>, <code>&lt;picture&gt;</code>등의 다중 미디어 리소스를 지정.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/source" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_source.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="5ab2845c-b2ee-4b77-a703-b9aa41b0edae">&lt;track&gt;</span><a href="#5ab2845c-b2ee-4b77-a703-b9aa41b0edae" class="header-anchor"></a></h2><p><code>&lt;audio&gt;</code>, <code>&lt;video&gt;</code> 같은 미디어가 재생 중일 때 표시될 자막, 캡션 파일 등을 지정.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/track" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_track.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="bab77345-59b2-4f21-8842-6ed08a56c8b9">&lt;embed&gt;</span><a href="#bab77345-59b2-4f21-8842-6ed08a56c8b9" class="header-anchor"></a></h2><p>외부 응용 프로그램이나 대화형 플러그인을 삽입.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/embed" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_embed.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="28d95f66-84f7-41cf-aa7b-a7bffe46a8c5">&lt;object&gt;</span><a href="#28d95f66-84f7-41cf-aa7b-a7bffe46a8c5" class="header-anchor"></a></h2><p>멀티미디어, 중첩된 브라우저 컨텍스트(프레임), 플러그인 등을 삽입.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/object" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_object.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="b60f941e-704e-49ef-a147-ca8f5dd09e1c">&lt;param&gt;</span><a href="#b60f941e-704e-49ef-a147-ca8f5dd09e1c" class="header-anchor"></a></h2><p><code>&lt;object&gt;</code>의 매개 변수를 정의.</p><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/param" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/tag_param.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="cdec39d1-ad81-4411-ac35-43760d667f59">생략한 속성</span><a href="#cdec39d1-ad81-4411-ac35-43760d667f59" class="header-anchor"></a></h1><table><thead><tr><th>사용 태그</th><th>속성</th><th>의미</th><th>값</th><th>특징</th></tr></thead><tbody><tr><td><code>&lt;link /&gt;</code>,<br><code>&lt;a&gt;</code></td><td>hreflang</td><td>현재 페이지의 대체 언어(<a href="https://ko.wikipedia.org/wiki/ISO_639-1_%EC%BD%94%EB%93%9C_%EB%AA%A9%EB%A1%9D" target="_blank" rel="noopener">ISO 639-1</a>)</td><td><code>ko</code>, <code>en</code>…</td><td><a href="https://moz.com/learn/seo/hreflang-tag" target="_blank" rel="noopener">다른 언어 또는 지역별 여러 버전의 페이지가 있는 경우</a></td></tr><tr><td><code>&lt;ol&gt;</code></td><td>reversed</td><td>항목을 역순으로 설정</td><td></td><td>IE 지원 불가</td></tr><tr><td><code>&lt;link&gt;</code>,<br><code>&lt;img /&gt;</code>,<br><code>&lt;video&gt;</code>,<br><code>&lt;script&gt;</code></td><td>crossorigin</td><td>가져 오기가 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS" target="_blank" rel="noopener">CORS</a>를 사용하여 수행되어야하는지 여부</td><td><code>anonymous</code>,<br><code>use-credentials</code></td><td></td></tr><tr><td><code>&lt;img /&gt;</code></td><td>ismap</td><td>서버 측 이미지 맵으로 지정해 클릭하여 좌표를 <a href="https://en.wikipedia.org/wiki/Query_string" target="_blank" rel="noopener">쿼리스트링</a>으로 서버에 전송할지 여부</td><td>불린(Boolean)</td><td><code>&lt;img /&gt;</code>가 유효한 <code>href</code> 속성을 가진 <code>&lt;a&gt;</code>의 하위 요소인 경우에만 허용</td></tr><tr><td><code>&lt;img /&gt;</code></td><td>usemap</td><td>클라이언트 측 이미지 맵으로 지정</td><td><code>&lt;map&gt;</code>의 <code>#</code> + <code>name</code> 속성 값</td><td><code>&lt;a&gt;</code>, <code>&lt;button&gt;</code>의 하위 요소인 경우 사용 불가</td></tr><tr><td><code>&lt;form&gt;</code></td><td>accept-charset</td><td>서버가 받을 <a href="https://www.iana.org/assignments/character-sets/character-sets.xhtml" target="_blank" rel="noopener">문자인코딩 방식</a></td><td><code>UTF-8</code>, <code>EUC-KR</code>…</td><td><code>UNKNOWN</code></td></tr><tr><td><code>&lt;form&gt;</code></td><td>enctype</td><td><code>method</code>속성이 <code>POST</code>일 경우, 서버로 전송하는 콘텐츠의 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank" rel="noopener">MIME 타입</a></td><td></td><td></td></tr><tr><td><code>&lt;input /&gt;</code></td><td>accept</td><td>서버가 받을 파일 종류</td><td>파일 확장자(<code>.jpg</code>, <code>.png</code>..),<br><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank" rel="noopener">MIME 타입</a>,<br><code>audio/*</code>,<br><code>video/*</code>,<br><code>image/*</code></td><td><code>type=&quot;file&quot;</code></td></tr><tr><td><code>&lt;input /&gt;</code></td><td>width</td><td>이미지의 가로 너비</td><td>숫자(Number)</td><td><code>type=&quot;image&quot;</code></td></tr><tr><td><code>&lt;input /&gt;</code></td><td>height</td><td>이미지의 가로 너비</td><td>숫자(Number)</td><td><code>type=&quot;image&quot;</code></td></tr><tr><td><code>&lt;input /&gt;</code>,<br><code>&lt;button&gt;</code></td><td>formaction</td><td>양식을 제출할 때 양식 데이터를 보낼 위치</td><td>URL</td><td><code>type=&quot;submit&quot;</code>,<br><code>type=&quot;image&quot;</code>,<br><code>form</code>의 속성보다 우선</td></tr><tr><td><code>&lt;input /&gt;</code>,<br><code>&lt;button&gt;</code></td><td>formenctype</td><td>양식 데이터를 서버로 보내기 전에 인코딩 할 방법을 지정</td><td>-</td><td><code>type=&quot;submit&quot;</code>,<br><code>type=&quot;image&quot;</code>,<br><code>form</code>의 속성보다 우선</td></tr><tr><td><code>&lt;input /&gt;</code>,<br><code>&lt;button&gt;</code></td><td>formmethod</td><td>양식 데이터를 보내는 방법</td><td><code>GET</code>, <code>POST</code></td><td><code>type=&quot;submit&quot;</code>,<br><code>type=&quot;image&quot;</code>,<br><code>form</code>의 속성보다 우선</td></tr><tr><td><code>&lt;input /&gt;</code>,<br><code>&lt;button&gt;</code></td><td>formnovalidate</td><td>양식 데이터의 유효성을 검사하지 않도록 지정</td><td>불린(Boolean)</td><td><code>type=&quot;submit&quot;</code>,<br><code>type=&quot;image&quot;</code>,<br><code>form</code>의 속성보다 우선</td></tr><tr><td><code>&lt;input /&gt;</code>,<br><code>&lt;button&gt;</code></td><td>formtarget</td><td></td><td><code>_self</code>, <code>_blank</code></td><td><code>type=&quot;submit&quot;</code>,<br><code>type=&quot;image&quot;</code>,<br><code>form</code>의 속성보다 우선</td></tr><tr><td><code>&lt;input /&gt;,</code><br><code>&lt;textarea&gt;</code></td><td>minlength</td><td>입력 가능한 최소 문자 수</td><td>숫자(Number)</td><td><code>type=&quot;text&quot;</code>,<br><code>type=&quot;email&quot;</code>,<br><code>type=&quot;password&quot;</code>,<br><code>type=&quot;tel&quot;</code>,<br><code>type=&quot;url&quot;</code></td></tr><tr><td><code>&lt;input /&gt;</code></td><td>pattern</td><td>양식의 값을 검사하는 정규표현식</td><td>정규표현식(RegExp)</td><td><code>type=&quot;text&quot;</code>,<br><code>type=&quot;search&quot;</code>,<br><code>type=&quot;tel&quot;</code>,<br><code>type=&quot;url&quot;</code>,<br><code>type=&quot;email&quot;</code></td></tr><tr><td><code>&lt;input /&gt;</code>,<br><code>&lt;textarea&gt;</code>,<br><code>&lt;select&gt;</code></td><td>required</td><td>필수 여부</td><td>불린(Boolean)</td><td></td></tr><tr><td><code>&lt;input /&gt;</code></td><td>size</td><td>양식의 가로 너비</td><td>숫자(Number, <code>20</code>)</td><td>평균 문자 너비를 기준</td></tr><tr><td><code>&lt;input /&gt;</code>,<br><code>&lt;textarea&gt;</code></td><td>spellcheck</td><td>맞춤법 및 문법 검사의 필요 여부</td><td><code>true</code>, <code>false</code></td><td></td></tr><tr><td><code>&lt;button&gt;</code></td><td>value</td><td>폼 데이터와 함께 전송되는 버튼의 초기값</td><td></td><td>IE 지원 불가</td></tr><tr><td><code>&lt;textarea&gt;</code></td><td>cols</td><td>양식의 가로 너비</td><td>숫자(Number, <code>20</code>)</td><td>평균 문자 너비를 기준</td></tr></tbody></table><h1><span id="5051ee88-0645-4c67-ab2d-1307fd76fee0">생략한 전역 속성</span><a href="#5051ee88-0645-4c67-ab2d-1307fd76fee0" class="header-anchor"></a></h1><h2><span id="96fbbd1d-744b-47d3-afdb-49ae85b92d27">accesskey</span><a href="#96fbbd1d-744b-47d3-afdb-49ae85b92d27" class="header-anchor"></a></h2><p>요소의 키보드 단축키 힌트를 제공.</p><ul><li>다음의 이유 등으로 일반 목적의 웹사이트에서는 사용을 권장하지 않음.<ul><li>브라우저 키보드 단축키 혹은 보조기기의 기능과 충돌</li><li>특정 키보드의 존재하지 않는 키 사용</li><li>숫자 같은 논리적인 관계가 없는 키 지정</li><li><code>accesskey</code>의 존재를 모르는 사용자의 실수</li></ul></li></ul><pre><code class="html">&lt;a href=&quot;https://google.com&quot; accesskey=&quot;G&quot;&gt;Press &#39;Alt&#39; + &#39;G&#39; key on Chrome!&lt;/a&gt;</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/accesskey" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_accesskey.asp" target="_blank" rel="noopener">W3Schools</a></p><h2><span id="d5a886c2-5901-4653-98bb-262bb92b6dc2">contenteditable</span><a href="#d5a886c2-5901-4653-98bb-262bb92b6dc2" class="header-anchor"></a></h2><p>요소의 사용자 편집 여부를 지정.</p><pre><code class="html">&lt;style&gt;  p::before { content: &quot;[&quot;; }  p::after { content: &quot;]&quot;; }&lt;/style&gt;&lt;blockquote contenteditable=&quot;true&quot;&gt;  &lt;p&gt;Edit this content to add your own bracket.&lt;/p&gt;&lt;/blockquote&gt;</code></pre><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Global_attributes/contenteditable" target="_blank" rel="noopener">MDN</a> / <a href="https://www.w3schools.com/tags/att_global_contenteditable.asp" target="_blank" rel="noopener">W3Schools</a></p><h1><span id="b50be8c2-53f9-4072-88b9-207c44a9bab6">참고자료</span><a href="#b50be8c2-53f9-4072-88b9-207c44a9bab6" class="header-anchor"></a></h1><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/HTML/Element</a><br><a href="https://webclub.tistory.com/523" target="_blank" rel="noopener">https://webclub.tistory.com/523</a></p>]]></content>
    
    <summary type="html">
    
      인터넷에 검색 가능한 많은 HTML 문서들의 내용을 요소(Elements), 속성(Attributes)의 개념으로 핵심적인 내용들만 요약해서 정리했습니다. 각 요소들의 자세한 설명은 패스트캠퍼스 온라인 강의(online.fastcampus.co.kr)에서 확인할 수 있습니다.
    
    </summary>
    
    
      <category term="html" scheme="https://heropy.blog/tags/html/"/>
    
      <category term="html5" scheme="https://heropy.blog/tags/html5/"/>
    
  </entry>
  
  <entry>
    <title>입문자에게 추천하는 HTML, CSS 첫걸음</title>
    <link href="https://heropy.blog/2019/04/24/html-css-starter/"/>
    <id>https://heropy.blog/2019/04/24/html-css-starter/</id>
    <published>2019-04-23T15:00:00.000Z</published>
    <updated>2020-08-25T08:04:26.644Z</updated>
    
    <content type="html"><![CDATA[<div class="toc"><ul><li><a href="e564ab1b-f4ff-457e-99a5-8bcce51c6e8f">개요</a><ul><li><a href="fc5f06b4-b989-499c-b3a2-4e85c0bc9c9b">HTML, CSS 그리고 JavaScript</a><ul><li><a href="e466c074-4055-4eb4-be1b-ed4b40a63ab6">웹 표준</a></li><li><a href="1307a872-2c94-4394-8f20-7732760a25c8">크로스 브라우징</a></li><li><a href="208187c8-1686-4470-900f-4c4891a7676d">웹 접근성</a><ul><li><a href="a15fcb50-a44d-4a6e-af1d-00219a6efed3">정보 통신 보조기기</a></li><li><a href="9d25c279-5f0c-4967-8eed-94d7ee751abe">웹 접근성 품질인증 마크</a></li></ul></li></ul></li><li><a href="a02dfdbf-6068-4abb-b52a-7f206aee88aa">웹 개발을 위한 에디터</a><ul><li><a href="8a1e96b6-082f-4cd4-aba4-370360d36ee4">Sublime Text</a></li><li><a href="fbd514ae-40a1-43bd-a926-b4f273239601">Atom</a></li><li><a href="1c8cfcf0-6a7c-4534-b8cd-cb083d822f3d">Brackets</a></li><li><a href="e58f8aa7-a7a9-4f0c-8646-9a3914829fe0">VS Code</a></li><li><a href="469461a1-1b80-43be-b3b0-5e850bc84f6b">WebStorm</a></li></ul></li><li><a href="44732bda-8014-47b4-8218-dc74f9dc07e7">VS Code 설치 및 설정</a><ul><li><a href="d4b10d29-759c-4cf4-b3f1-8240e1d42899">설치</a><ul><li><a href="a578441d-96e8-420a-b2c6-80af92e019e6">인터페이스</a></li></ul></li><li><a href="b5af4acf-d4de-42bc-b33b-025410bfffdb">확장 기능(Extensions)</a><ul><li><a href="04eeabf3-c0fb-4cd2-ad9f-68729745240b">Korean Language Pack for Visual Studio Code</a></li><li><a href="6e4d6fbf-5143-4482-9d44-1d6c123e3b16">Beautify</a></li><li><a href="a887475b-23ec-431c-a91d-62cf0b5c8901">Live Server</a></li><li><a href="0251fe34-1a48-4369-973f-51425f972d62">Auto Rename Tag</a></li><li><a href="4c23822f-a1f4-429e-af68-82e97504d210">그 외 추천</a></li></ul></li><li><a href="80bb44df-4790-4e87-8056-41d6ccdb595a">알아두면 좋은 단축키</a><ul><li><a href="54650e6b-c42e-4256-b192-e82da4fdbc38">약어로 랩핑(Wrap)</a></li></ul></li></ul></li><li><a href="00466c29-e659-47ca-b4d2-de962f56a448">웹에서 사용하는 이미지</a><ul><li><a href="27f07baf-95c9-42d7-86d8-2e8d59c8c744">비트맵과 벡터 이미지</a></li><li><a href="dc4a69f2-2a10-4ed5-8f78-8328208e47b1">JPG(JPEG)</a></li><li><a href="d9860e3c-4c44-49e3-a373-10061e2fa1ab">PNG</a></li><li><a href="8db32921-c3b4-40e9-b317-a54d88d90a84">GIF</a></li><li><a href="f4fcce54-c03b-466b-bf8b-b9684b70f2bd">WEBP</a></li><li><a href="52a43cec-6ba2-4a42-83e7-dca125e12d20">SVG</a></li></ul></li><li><a href="80cd6bd2-9072-4e63-bf51-04c65a8c3fa0">특수 문자 용어 정리</a></li><li><a href="f904dd32-192c-4630-90ed-8e6276c3c4d1">오픈소스 라이선스</a><ul><li><a href="d6503867-d7f8-4667-97c2-49d167c79e39">Apache License</a></li><li><a href="b7d98b4c-8d2a-4a4e-a817-9429a686d846">MIT License</a></li><li><a href="20968c07-7fc8-434d-a375-3c839556279e">BSD License</a></li><li><a href="3951fa5b-d35f-48e0-a7dd-3a34c1d456f6">Beerware</a></li></ul></li></ul></li><li><a href="e1e7400b-2763-4c61-b270-8d7c0089e65b">HTML</a><ul><li><a href="8d3965dd-746b-4330-8693-feb0471948d5">정말 쉬운 HTML 문법</a><ul><li><a href="cfd0dd5f-c106-475e-a60a-25b700e312ad">기본 형태</a></li><li><a href="8c09a7d0-7731-45d5-b997-5183cb44a963">속성(Attributes)과 값(Value)</a></li><li><a href="2e99705d-b76f-46fa-9098-40325c527cee">부모와 자식 요소</a></li><li><a href="71ac46c5-337f-489e-ad87-854618a581d5">빈 태그</a></li></ul></li><li><a href="08893d9c-e2de-4e86-b272-71bed4442309">HTML 문서의 범위</a><ul><li><a href="d16a5dcf-94da-4b59-8e7a-3f406915edd6">HTML(전체 범위)</a></li><li><a href="2fd41702-593e-4c3e-80cb-185974e74401">HEAD(정보 범위)</a></li><li><a href="b2671ea4-1c65-4def-b9a8-45ca39b510c7">BODY(구조 범위)</a></li><li><a href="fc44e1ae-256d-4771-a55d-e6228c9bff5a">DOCTYPE(DTD, 버전 지정)</a></li></ul></li><li><a href="75cce07a-57fc-4c25-a47c-4e1d15f5bf17">HTML 문서의 정보</a><ul><li><a href="cbe6c400-085f-4997-9188-c2d47521e27a">TITLE(웹 페이지의 제목)</a></li><li><a href="987bfc15-2511-4d33-b6bd-c449505da36e">META(웹 페이지의 정보)</a></li><li><a href="407d8f47-0b5c-44fa-84cd-de26d395d8e2">LINK(CSS 불러오기)</a></li><li><a href="2cb2a710-ecf8-468f-a176-bb77e7ca62be">STYLE(CSS 작성하기)</a></li><li><a href="477315b9-97c3-481e-94bd-aaee24cf53fa">SCRIPT(JS 불러오거나 작성하기)</a></li></ul></li><li><a href="f4e01c6d-2979-4690-9829-4ab9413a39d1">HTML 문서의 구조</a><ul><li><a href="4a21c344-c30b-4465-8c99-24074dad76a0">DIV(막 쓰는 태그)</a></li><li><a href="64b90b49-b111-4d26-8004-e55ac12db074">IMG(이미지 넣는 태그)</a></li></ul></li><li><a href="e37d0c03-0719-4b6a-a1fa-a1a27a56edad">웹 표준 검사하기</a></li><li><a href="1ec5e5bb-3003-4c2f-bb31-7363130528b1">HTML 예제</a></li></ul></li><li><a href="8a4eee59-b59d-4106-99e5-ec773a72ea34">CSS</a><ul><li><a href="84dac196-1ca9-4b88-ab1f-d1d8b626c5cd">이보다 쉬울 수 없는 CSS 문법</a><ul><li><a href="671a8fe8-c44a-4862-a2c6-edea467269b2">선택자의 역할</a></li><li><a href="28bbaf8d-40b8-4676-8981-8875586fe77b">속성(Properties)과 값(Value)</a></li></ul></li><li><a href="8815aa0b-26c8-4a6e-939b-e28476c74947">CSS 선언 방식</a><ul><li><a href="ab178391-d9bb-478e-81bb-3d0801393e94">태그에 직접 작성하기(인라인)</a></li><li><a href="09a05d3e-1841-49a1-aa36-55e0370aa9a1">HTML에 포함하기(내장)</a></li><li><a href="abd2a66f-682f-407a-9e67-94594ba17f3e">HTML 외부에서 불러오기</a></li></ul></li><li><a href="6e3778a3-f1b0-4b4d-ae64-b1a156cc9bb9">선택자</a><ul><li><a href="3f8849ed-5db3-4914-b833-2a55ae09bdb6">태그로 찾기</a></li><li><a href="8c51ffdf-3ca0-447b-a196-d2cc0348d803">클래스로 찾기</a></li></ul></li><li><a href="64f1af3b-2249-41d9-99ae-6c9fbcd0fa3a">속성</a><ul><li><a href="1fb7594d-3ce8-4aed-97fd-d668f8bf1b36">크기</a><ul><li><a href="152f7052-7ce2-4331-8aa6-00dc2050847e">width(가로 너비)</a></li><li><a href="a8bd2c6c-a1fa-437b-b433-c9c2330c053d">height(세로 너비)</a></li><li><a href="bb842bdb-748d-4203-aefe-41f5e846c5ca">font-size(글자 크기)</a></li></ul></li><li><a href="d1271262-c3d8-4eba-b98b-c23fe43e3c55">여백</a><ul><li><a href="ec7d0c38-e0c5-4956-bbcd-d94bfa360f35">margin(요소의 바깥 여백)</a></li><li><a href="711c1395-aecd-4e88-ac82-bc32fc782f7d">padding(요소의 내부 여백)</a></li></ul></li><li><a href="b88d836e-1e16-4d93-a2b5-2f7457875747">색상</a><ul><li><a href="5d6a2c7f-3343-4966-be9e-9d7d30510474">color(글자 색상)</a></li><li><a href="3e3a2dd9-5886-471f-afa3-e550400eb275">background(요소 색상)</a></li></ul></li></ul></li><li><a href="41b93c77-4d7f-46c0-9a8c-ff4ecdab4ba4">CSS 예제</a></li></ul></li><li><a href="963eec84-b2ac-453b-9ee4-f2ee79a81d69">앞으로..</a><ul><li><a href="301da5be-3e99-44af-a51a-2ea9238ebd23">패스트캠퍼스 온라인 강의[FO]</a><ul><li><a href="c3a9e27c-5af4-484a-aab4-c0c22c0a4179">[FO] HTML/CSS 입문</a></li><li><a href="6c783534-5577-4682-8287-4f0ba73c8345">[FO] HTML</a></li><li><a href="3e2e656a-c6f9-417f-aebd-3f2c4e56acfe">[FO] CSS</a></li><li><a href="2c2608d0-1d5d-4ae2-9a88-392a8a5a5b32">[FO] SCSS</a></li><li><a href="c8c15a8f-6c86-47d1-aed0-1a19e2d8cd40">[FO] 마크다운(Markdown)</a></li><li><a href="48d76ae1-878e-419a-91e1-bbd9246f1b1f">[FO] VueJS</a></li></ul></li></ul></li><li><a href="8ab17cf8-bcde-4c72-82bf-4bb74328a7dd">참고자료</a></li></ul></div><h1><span id="e564ab1b-f4ff-457e-99a5-8bcce51c6e8f">개요</span><a href="#e564ab1b-f4ff-457e-99a5-8bcce51c6e8f" class="header-anchor"></a></h1><p>웹(Web)을 구성하는 HTML, CSS, JS의 이해와 학습은 단순히 기술을 배우는 것으로 그치지 않고 웹과 모바일 그리고 IT 전반의 과거, 현재, 미래를 이해하는 데 많은 도움을 줍니다.<br>그리고 실무적으로 Product를 보다 구조적인 관점에서 볼 수 있는 능력을 키울 수 있습니다.<br>특히 Product 개발과 관련해 밀접하게 협업하는 기획, 디자인, 기술 영업 등의 직군에서 많은 도움이 될 수 있습니다.<br>이런 이유로 여러 기업에서 비전공자에게 개발 경험을 요구하는 경우가 상당히 많아졌습니다.</p><p>HTML, CSS를 학습하기 전에 필요한 지식을 최대한 정리했습니다.</p><blockquote><p>조금 어렵다고 판단되는 내용은 건너뛰어도 좋습니다!</p></blockquote><h2><span id="fc5f06b4-b989-499c-b3a2-4e85c0bc9c9b">HTML, CSS 그리고 JavaScript</span><a href="#fc5f06b4-b989-499c-b3a2-4e85c0bc9c9b" class="header-anchor"></a></h2><p><img src="/css/images/vendor_icons/html5.png" alt="HTML"></p><p>HTML(Hyper Text Markup Language)은 페이지에 제목, 문단, 표, 이미지, 동영상 등을 정의하고 그 구조와 의미를 부여하는 정적 언어로 웹의 구조를 담당합니다.<br>HTML으로 화면을 예쁘게 만들려고 시도하지 마세요!(그렇게 할 수도 없지만..)<br>온전히 튼튼한 구조(Semantic)를 만드는 것에만 집중해야 합니다.</p><p><img src="/css/images/vendor_icons/css3.png" alt="CSS"></p><p>CSS(Cascading Style Sheets)는 마크업 언어(HTML, XML 등)가 실제 표시되는 방법(색상, 크기, 폰트, 레이아웃 등)을 지정하여 콘텐츠 구조를 꾸며주는 정적 언어로 웹의 시각적인 표현을 담당합니다.<br>예쁘게 만드는 것만 집중할 수 있습니다.<br>적당한 크기와 아름다운 색상, 원하는 위치를 지정하는데 집중할 수 있습니다.</p><p><img src="/css/images/vendor_icons/javascript.png" alt="JavaScript"></p><p>JS(JavaScript)는 콘텐츠를 바꾸고 움직이는 등 페이지를 동적으로 꾸며주는 역할을 하는 프로그래밍 언어로 웹의 동적 처리를 담당합니다.<br>JS는 HTML과 CSS를 동원해서 그들의 업무(구조, 시각적 표현 등)도 담당할 수 있지만, 그들만큼 잘하진 못하기 때문에(성능적으로) 되도록 동적 처리에만 집중해야 합니다.</p><p>집을 지을 때 골조 전문, 미장 전문, 인테리어 전문 등 효율적인 작업을 위해 각 분야가 나뉘듯 웹 페이지를 제작할 때 각 언어의 역할을 다른 언어가 대체하지 않도록 주의해야 하며, 각 역할이 제대로 수행되도록 구조적, 기술적으로 언어(코드)를 최적화시킬 필요가 있습니다.<br>(많은 입문자가 실전 과정으로 넘어갈 때 이 3가지의 단순한 역할을 도대체 왜 하나하나 파일과 폴더별로 구분하고 더 복잡하게 작성하는지 의아해하지만, 이는 언급한 것처럼 더욱 규모가 크고 복잡한 웹 페이지를 만들 때 구조적, 기술적으로 최적화하는 과정이며 유지/보수, 확장성, 생산성 등을 위해서 꼭 필요합니다. 이런 과정 자체를 이해하고 익숙해지는 것이 학습할 때 매우 중요합니다. <strong>단, 아직은 너무 어렵게 생각할 필요가 없습니다!</strong>)</p><p>다음은 HTML, CSS, JS가 담당하는 역할을 시각적으로 표현한 사이트입니다.<br>각 언어의 역할을 이해하는 데 도움이 될 것입니다.</p><p><img src="/images/screenshot/html-css-starter/html-css-js-site-screenshot.jpg" alt="HTML-CSS-JS 스크린샷"><br><a href="https://html-css-js.com/" target="_blank" rel="noopener">HTML-CSS-JS</a></p><h3><span id="e466c074-4055-4eb4-be1b-ed4b40a63ab6">웹 표준</span><a href="#e466c074-4055-4eb4-be1b-ed4b40a63ab6" class="header-anchor"></a></h3><p>웹 표준(Web Standard)이란 ‘웹에서 사용되는 표준 기술이나 규칙’을 의미하며 W3C의 권고안에서 나온 기술들이 여기에 해당합니다.<br>이 표준 기술들을 기준으로 웹 브라우저들(크롬, IE, 사파리 등)이 만들어지는데, 브라우저를 만드는 업체(구글, MS, 애플 같은)에서 표준 기술을 해석하는 차이, 새로운 기술 삽입(<a href="https://wit.nts-corp.com/2013/10/16/280" target="_blank" rel="noopener">표준화 제정 단계</a>에 따른) 등으로 조금은 다르게 구동되는 브라우저가 생기게 됩니다.<br>지금은 거의 사라졌지만 ActiveX, Flash 같은 기술은 표준이 아닙니다.</p><blockquote><p>대부분의 경우 표준화 제정 단계의 ‘권고안(REC)’에 해당하는 기술을 표준이라고 생각하시면 쉽습니다.</p></blockquote><h3><span id="1307a872-2c94-4394-8f20-7732760a25c8">크로스 브라우징</span><a href="#1307a872-2c94-4394-8f20-7732760a25c8" class="header-anchor"></a></h3><p><img src="/images/screenshot/html-css-starter/browsers.jpg" alt="웹 브라우저들"></p><blockquote><p>오른쪽부터 구글 크롬, MS 엣지, 모질라 파이어폭스, 오페라, 이스트소프트 스윙, 네이버 웨일, MS IE, 애플 사파리</p></blockquote><p>크로스 브라우징(Cross Browsing)은 위에서 설명한 것처럼 조금은 다르게 구동되는 여러 브라우저에서 동일한 사용자 경험(같은 화면, 같은 동작 등)을 줄 수 있도록 제작하는 기술, 방법 등을 말합니다.<br>대부분의 브라우저는 최대한 웹 표준을 준수해서 제작되기 때문에 문제 되는 경우가 적지만 MSIE(마이크로소프트 인터넷 익스플로어) 브라우저는 <del>웹 표준 따위.., 제멋대로 만들어져 있기 때문에</del> 여러 의미에서 표준화하기 쉽지 않은 브라우저입니다.<br>그래서 IE에서도 동작하게 하는 것을 크로스 브라우징이라고 부르기도 합니다.(대부분의 경우 IE에서 문제가 없으면 다른 브라우저는 OK!)</p><blockquote><p>2016년 봄에 네이버에서 <a href="https://campaign.naver.com/goodbye_ie10/" target="_blank" rel="noopener">‘내 PC의 보안을 위협하는 구버전 익스플로러 이제 그만 헤어지자!’</a>라는 캠페인을 했었습니다.</p></blockquote><h3><span id="208187c8-1686-4470-900f-4c4891a7676d">웹 접근성</span><a href="#208187c8-1686-4470-900f-4c4891a7676d" class="header-anchor"></a></h3><p>웹 접근성이란 정상적인 웹 콘텐츠 사용이 가능한 일반 사용자부터 고령자, 장애인 같은 신체적, 환경적 조건에 제한이 있는 사용자를 포함해 모든 사용자들이 동등하게 접근할 수 있는 웹 콘텐츠를 제작하는 방법을 말합니다.<br>청각 장애인을 위해 영상에 자막을 넣거나, 마우스를 사용할 수 없는 사람들을 위해 키보드를 통해서도 웹을 이용할 수 있게 하거나(혹은 그 반대), 이미지에 대체 텍스트를 제공하는 간단한 방법들까지 모두 웹 접근성에 해당합니다.<br>아래 링크에 접근성에 대한 지침이나 제작기법 등의 문서가 상세하게 정리되어 있으니 참고하세요.</p><p><a href="https://www.wah.or.kr:444/Accessibility/define.asp" target="_blank" rel="noopener">웹 접근성 연구소</a><br><a href="https://www.wah.or.kr:444/Participation/guide.asp" target="_blank" rel="noopener">한국형 웹 콘텐츠 접근성 지침 2.1</a><br><a href="https://www.wah.or.kr:444/Participation/technique.asp" target="_blank" rel="noopener">웹 콘텐츠 제작기법</a></p><blockquote><p>웹의 힘은 그것의 보편성에 있다. 장애에 구애 없이 모든 사람이 접근할 수 있는 것이 필수적인 요소이다.<br><strong>팀 버너스 리 경 - 웹의 창시자</strong></p></blockquote><h4><span id="a15fcb50-a44d-4a6e-af1d-00219a6efed3">정보 통신 보조기기</span><a href="#a15fcb50-a44d-4a6e-af1d-00219a6efed3" class="header-anchor"></a></h4><p>장애인의 경우 다음과 같은 정보 통신 보조기기를 통해 웹 콘텐츠에 접근할 수 있어야 합니다.<br>이런 보조기기는 개인 구매 혹은 정부 기관이나 장애인 복지시설에서 임대하여 사용할 수 있습니다.</p><p><img src="/images/screenshot/html-css-starter/assistiv_technology.jpg" alt="정보 통신 보조 기기들"></p><ul><li>한손 사용자용 키보드: 편마비 장애인을 위해 한손으로 타이핑하게 디자인되어있는 키보드</li><li>큰 키 키보드: 키보드의 입력 버튼을 크게 만들어 손 떨림이나 상지 기능에 장애가 있는 분에게 적합하며, 키가드가 있어 오타를 방지할 수 있는 특수 키보드</li><li>n-Abler 조이스틱: 팔의 움직임이 원활하지 못해 일반 마우스를 사용하기 어려운 분들을 위한 조이스틱 마우스</li><li>SmartNav: 상지 및 하지를 사용하기 어려운 분들이 머리의 움직임을 통해(적외선 및 난반사 스티커 활용) 컨트롤할 수 있는 특수 마우스</li><li>소리 알리미: 소리 정보를 불빛과 진동으로 알려주는 청각장애인용 무선신호기</li></ul><h4><span id="9d25c279-5f0c-4967-8eed-94d7ee751abe">웹 접근성 품질인증 마크</span><a href="#9d25c279-5f0c-4967-8eed-94d7ee751abe" class="header-anchor"></a></h4><p><img src="/images/screenshot/html-css-starter/web_access_mark.jpg" alt="웹 접근성 품질인증 마크"></p><p>장애인 및 고령자가 웹 사이트 이용에 불편이 없도록 웹 접근성 표준을 준수한 우수 사이트에 대해 품질을 인증하고 마크를 부여하는 제도로써 「국가정보화기본법」제32조의2 및 동법 시행규칙 제3조의3에 의거 과학기술정보통신부가 지정한 웹 접근성 품질인증 기관을 통해 심사 및 인증을 받을 수 있습니다.<br>서면 심사와 기술 심사 등을 거쳐 페이지 수에 따라 기본 100만원 이상의 심사 비용을 지불해야 합니다.</p><blockquote><p>현실적으론 개인이나 중소기업 사이트에서 획득하기는 쉽지 않습니다.</p></blockquote><h2><span id="a02dfdbf-6068-4abb-b52a-7f206aee88aa">웹 개발을 위한 에디터</span><a href="#a02dfdbf-6068-4abb-b52a-7f206aee88aa" class="header-anchor"></a></h2><p>포토샵, 스케치3 같은 한정된 툴에 종속되는 웹앱 디자인 등과는 다르게 웹 개발은 툴에 대한 종속성이 거의 없습니다.<br>심지어 메모장을 사용해도 상관없지만, 개발 퍼포먼스를 생각해서 좋은 에디터를 고르는 것을 추천합니다.<br>HTML, CSS, JS를 위한 웹 개발(프론트엔드, Node.js) 에디터 중 실무에서 많이 사용되는 크로스 플랫폼(Windows, macOS) 에디터들을 간단하게 소개합니다.</p><blockquote><p>다음은 모두 좋은 에디터들입니다.<br>주관적인 관점에서 작성했으니 참고만 하시고 모두 한 번씩 써본 후 자신에게 맞는 에디터를 선택하시길 바랍니다.</p></blockquote><h3><span id="8a1e96b6-082f-4cd4-aba4-370360d36ee4">Sublime Text</span><a href="#8a1e96b6-082f-4cd4-aba4-370360d36ee4" class="header-anchor"></a></h3><p><img src="/images/screenshot/html-css-starter/logo_sublime_text.jpg" alt="Sublime Text Icon"></p><p><a href="https://www.sublimetext.com/" target="_blank" rel="noopener">https://www.sublimetext.com/</a></p><p>상대적으로 가볍고 성능 저하가 적은 편이라고 합니다.<br>많이 써보지 않아서 평가는 생략합니다.<br>무료입니다.</p><h3><span id="fbd514ae-40a1-43bd-a926-b4f273239601">Atom</span><a href="#fbd514ae-40a1-43bd-a926-b4f273239601" class="header-anchor"></a></h3><p><img src="/images/screenshot/html-css-starter/logo_atom.jpg" alt="Atom Icon"></p><p><a href="https://atom.io/" target="_blank" rel="noopener">https://atom.io/</a></p><p>깃헙(GitHub)에서 만든 텍스트 에디터입니다.<br>확장 기능도 충분하고 외국에선 인기가 많다고 합니다.<br>(2018년 깃헙이 MS에 인수되었는데 Atom의 미래는?)<br>Windows에서의 사용은 아쉬운 부분이 많네요.<br>macOS에서는 문서 작업 시 자주 사용합니다.<br>무료입니다.</p><h3><span id="1c8cfcf0-6a7c-4534-b8cd-cb083d822f3d">Brackets</span><a href="#1c8cfcf0-6a7c-4534-b8cd-cb083d822f3d" class="header-anchor"></a></h3><p><img src="/images/screenshot/html-css-starter/logo_brackets.jpg" alt="Brackets Icon"></p><p><a href="http://brackets.io/" target="_blank" rel="noopener">http://brackets.io/</a></p><p>어도비(Adobe)에서 만든 텍스트 에디터입니다.<br>Creative Cloud 제품군이 아니고 오픈소스로 풀려 있습니다.<br>Live Preview 기능이 기본으로 제공되는 등 시각적인 결과물을 확인하는데 특화되어 있으나(역시 어도비!) 확장 기능이나 특히 성능 최적화에 대해선 아쉬운 감이 있습니다.(역시 어도비..)<br>무료입니다.</p><h3><span id="e58f8aa7-a7a9-4f0c-8646-9a3914829fe0">VS Code</span><a href="#e58f8aa7-a7a9-4f0c-8646-9a3914829fe0" class="header-anchor"></a></h3><p><img src="/images/screenshot/html-css-starter/logo_vs_code.jpg" alt="VS Code Icon"></p><p><a href="https://code.visualstudio.com/" target="_blank" rel="noopener">https://code.visualstudio.com/</a></p><p>VS Code(VisualStudio Code)는 마이크로소프트(MS)에서 만든 텍스트 에디터입니다.<br>가볍게 시작할 수 있고 확장 기능이 상당히 많습니다.<br>2018 Stack Overflow 설문조사에서 에디터 인기도 1위를 기록했다고 하네요.<br>무료입니다.</p><h3><span id="469461a1-1b80-43be-b3b0-5e850bc84f6b">WebStorm</span><a href="#469461a1-1b80-43be-b3b0-5e850bc84f6b" class="header-anchor"></a></h3><p><img src="/images/screenshot/html-css-starter/logo_webstorm.jpg" alt="WebStorm Icon"></p><p><a href="https://www.jetbrains.com/webstorm/" target="_blank" rel="noopener">https://www.jetbrains.com/webstorm/</a></p><p>JetBrain에서 만드는 <a href="https://ko.wikipedia.org/wiki/%ED%86%B5%ED%95%A9_%EA%B0%9C%EB%B0%9C_%ED%99%98%EA%B2%BD" target="_blank" rel="noopener">통합 개발 환경(IDE)</a> 프로그램 중 하나입니다.<br>별도의 확장 기능이 거의 필요 없으며 대부분의 프로젝트를 바로 시작할 수 있습니다.<br>한 번 쓰면 다른 에디터로 넘어가기 어려울 정도로 편한 권장 프로그램이지만 유료라는 게 함정.<br>단, 학생 라이선스를 얻으면 무료로 사용하실 수 있고 30일 평가판도 있으니 여유가 있다면 써보시길 권장합니다.</p><blockquote><p>구독형 프로그램으로 <a href="https://www.jetbrains.com/webstorm/buy/#edition=personal" target="_blank" rel="noopener">개인 라이선스</a>는 1년 $59입니다. 비싼 편은 아니라고 생각합니다.</p></blockquote><h2><span id="44732bda-8014-47b4-8218-dc74f9dc07e7">VS Code 설치 및 설정</span><a href="#44732bda-8014-47b4-8218-dc74f9dc07e7" class="header-anchor"></a></h2><p>저는 무료 프로그램으로 VS Code를 선택했습니다.(회사에서는 WebStorm을 사용합니다)<br>설치부터 단축키 사용, 확장 기능 등 웹 개발을 위한 에디터를 준비합시다!</p><h3><span id="d4b10d29-759c-4cf4-b3f1-8240e1d42899">설치</span><a href="#d4b10d29-759c-4cf4-b3f1-8240e1d42899" class="header-anchor"></a></h3><p>자신의 운영체제(Windows, macOS, Linux)에 맞는 Stable 버전을 설치하세요.<br>별도 설정값을 무시하고 다음/다음을 눌러 진행하세요.</p><p><img src="/images/screenshot/html-css-starter/vs_code_download.jpg" alt="VS Code 다운로드"></p><h4><span id="a578441d-96e8-420a-b2c6-80af92e019e6">인터페이스</span><a href="#a578441d-96e8-420a-b2c6-80af92e019e6" class="header-anchor"></a></h4><p><img src="/images/screenshot/html-css-starter/vs_code_interface.jpg" alt="VS Code Interface"></p><h3><span id="b5af4acf-d4de-42bc-b33b-025410bfffdb">확장 기능(Extensions)</span><a href="#b5af4acf-d4de-42bc-b33b-025410bfffdb" class="header-anchor"></a></h3><p><img src="/images/screenshot/html-css-starter/vs_code_extensions_icon.jpg" alt="사용자 인터페이스에서 확장 기능 아이콘"></p><p>다른 에디터도 동일하지만 VS Code는 확장 기능을 제공하며 최초 설치한 버전에서 제공하지 않는 다양한 확장 기능을 외부에서 다운로드받아(설치) 연결 후 사용할 수 있습니다.(대부분 에디터 자체에서 확장 기능을 검색할 수 있습니다)<br>에디터의 버전업 과정에서 자체적으로 흡수하는 기능도 있을 수 있으므로 몇몇 확장 기능은 에디터의 버전에 따라 이미 지원하고 있을 수 있으니 해당 기능이 제공되는지 확인하고 설치하시는 것을 추천합니다.</p><h4><span id="04eeabf3-c0fb-4cd2-ad9f-68729745240b">Korean Language Pack for Visual Studio Code</span><a href="#04eeabf3-c0fb-4cd2-ad9f-68729745240b" class="header-anchor"></a></h4><p>VS Code의 거의 모든 메뉴가 한국어로 변경됩니다.</p><h4><span id="6e4d6fbf-5143-4482-9d44-1d6c123e3b16">Beautify</span><a href="#6e4d6fbf-5143-4482-9d44-1d6c123e3b16" class="header-anchor"></a></h4><p>코드 가독성을 위해 코드 작성 스타일을 (아름답게) 수정합니다.<br>입문자들에게 적극 추천합니다.</p><ol><li><code>Preferences &gt; Keyboard Shortcut</code> 선택</li><li><code>HookyQR.beautify</code>를 검색(<code>HookyQR.beautifyFile</code> 아니에요!)</li><li><code>키 바인딩</code> 선택</li><li>원하는 단축키 등록</li></ol><blockquote><p>“Alt + Ctrl + L”(Windows) / “Alt + Cmd + L”(macOS)을 추천합니다.</p></blockquote><h4><span id="a887475b-23ec-431c-a91d-62cf0b5c8901">Live Server</span><a href="#a887475b-23ec-431c-a91d-62cf0b5c8901" class="header-anchor"></a></h4><p>하단 상태 바(Status bar)에서 <code>Go Live</code> 선택<br>또는,<br>HTML 화면에서 우클릭 &gt; <code>Open with Live Server</code> 선택</p><blockquote><p>VS Code 버전에 따라서 이미 설치되어 있을 수 있습니다.</p></blockquote><h4><span id="0251fe34-1a48-4369-973f-51425f972d62">Auto Rename Tag</span><a href="#0251fe34-1a48-4369-973f-51425f972d62" class="header-anchor"></a></h4><p>태그 이름을 수정할 때 열린 태그와 닫힌 태그가 쌍으로 수정됩니다.<br>각각 수정해야 하는 번거로움을 줄일 수 있습니다.</p><h4><span id="4c23822f-a1f4-429e-af68-82e97504d210">그 외 추천</span><a href="#4c23822f-a1f4-429e-af68-82e97504d210" class="header-anchor"></a></h4><p>다음 확장 기능들이 당장 필요하지는 않을 겁니다.<br>나중에 살펴보세요.</p><p><a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.terminal" target="_blank" rel="noopener">Terminal</a><br><a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.live-sass" target="_blank" rel="noopener">Live Sass Compiler</a><br><a href="https://marketplace.visualstudio.com/items?itemName=ChakrounAnas.turbo-console-log" target="_blank" rel="noopener">Turbo Console log</a><br><a href="https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments" target="_blank" rel="noopener">Better Comments</a><br><a href="https://marketplace.visualstudio.com/items?itemName=vincaslt.highlight-matching-tag" target="_blank" rel="noopener">Highlight Matching Tag</a></p><p><a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens" target="_blank" rel="noopener">GitLens</a><br><a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client" target="_blank" rel="noopener">REST Client</a></p><h3><span id="80bb44df-4790-4e87-8056-41d6ccdb595a">알아두면 좋은 단축키</span><a href="#80bb44df-4790-4e87-8056-41d6ccdb595a" class="header-anchor"></a></h3><p><code>Preferences &gt; Keyboard Shortcut(바로 가기 키)</code> 에서 단축키를 확인하거나 변경할 수 있습니다.</p><blockquote><p><code>&quot;</code>를 사용하면 키 바인딩을 검색할 수 있습니다.<br><code>&quot;</code>를 닫으면 정확한 검색을 요구하므로 <code>&quot;cmd + p</code>와 같이 <code>&quot;</code>를 닫지 않고 검색하는 것을 추천합니다.</p></blockquote><table><thead><tr><th>Windows 단축키</th><th>macOS 단축키</th><th>설명</th></tr></thead><tbody><tr><td>“Ctrl + B”</td><td>“Cmd + B”</td><td>사이드바 열기/닫기</td></tr><tr><td>“Ctrl + P”</td><td>“Cmd + P”</td><td>빠른 열기(파일이나 기호 탐색)</td></tr><tr><td>“Ctrl + Shift + P”</td><td>“Cmd + Shift + P”</td><td>모든 명령 표시(에디터의 모든 명령에 접근)</td></tr><tr><td>“Ctrl + F”</td><td>“Cmd + F”</td><td>찾기(검색)</td></tr><tr><td>“Ctrl + H”</td><td>“Cmd + Opt(Alt) + F”</td><td>찾기(검색)/바꾸기(대체)</td></tr><tr><td>“Alt + Up”</td><td>“Alt + Up”</td><td>줄 위로 이동</td></tr><tr><td>“Alt + Down”</td><td>“Alt + Down”</td><td>줄 아래로 이동</td></tr><tr><td>“Shift + Alt + UpArrow”</td><td>“Shift + Alt + UpArrow”</td><td>위에 줄 복사</td></tr><tr><td>“Shift + Alt + DownArrow”</td><td>“Shift + Alt + DownArrow”</td><td>아래 줄 복사</td></tr><tr><td>“Tab”</td><td>“Tab”</td><td>들여쓰기</td></tr><tr><td>“Shift + Tab”</td><td>“Shift + Tab”</td><td>내어쓰기</td></tr><tr><td>“Ctrl + PageUp”</td><td>“Cmd + Shift + &#91;”</td><td>이전 편집기 열기(좌측 창으로 전환)</td></tr><tr><td>“Ctrl + PageDown”</td><td>“Cmd + Shift + &#93;”</td><td>다음 편집기 열기(우측 창으로 전환)</td></tr><tr><td>“Ctrl + &#92;”</td><td>“Cmd + &#92;”</td><td>편집기 분할(백슬래쉬)</td></tr><tr><td>“Ctrl + 숫자”</td><td>“Cmd + 숫자”</td><td><code>숫자</code>번째 분할된 편집기 그룹에 포커스</td></tr><tr><td>“Ctrl + W”</td><td>“Cmd + W”</td><td>편집기 닫기</td></tr></tbody></table><h4><span id="54650e6b-c42e-4256-b192-e82da4fdbc38">약어로 랩핑(Wrap)</span><a href="#54650e6b-c42e-4256-b192-e82da4fdbc38" class="header-anchor"></a></h4><ol><li>랩핑할 코드 선택</li><li>모든 명령 표시 실행 / “Ctrl + Shift + P”(Windows), “Cmd + Shift + P”(macOs)</li><li><code>Emmet: Wrap with Abbreviation(Emmet: 약어로 랩핑)</code>를 입력하거나 목록에서 선택(“Enter”)</li><li><code>div</code>, <code>span</code> 등의 Emmet 문법(ex: <code>.wrapper</code>, <code>span.bold</code>)을 입력</li><li>완료(“Enter”)</li></ol><h2><span id="00466c29-e659-47ca-b4d2-de962f56a448">웹에서 사용하는 이미지</span><a href="#00466c29-e659-47ca-b4d2-de962f56a448" class="header-anchor"></a></h2><h3><span id="27f07baf-95c9-42d7-86d8-2e8d59c8c744">비트맵과 벡터 이미지</span><a href="#27f07baf-95c9-42d7-86d8-2e8d59c8c744" class="header-anchor"></a></h3><p>이미지(그래픽)는 크게 비트맵과 벡터로 구분됩니다.</p><p>비트맵(Bitmap)은 각 픽셀이 모여 만들어진 정보의 집합으로 레스터(Raster) 이미지라고도 부릅니다.<br>픽셀 단위로 화면에 렌더링합니다.(렌더링: 컴퓨터가 화면에 그림을 그려서 우리가 볼 수 있게 합니다)<br>우리가 일반적으로 사용하는 대부분의 이미지가 비트맵 형식입니다.<br>그림판, 포토샵과 같은 툴로 편집할 수 있습니다.</p><p>벡터(Vector)는 수학적 정보의 형태(Shape)들이 만들어내는 결과물입니다.<br>이미지가 가지고 있는 점, 선, 면의 위치(좌표), 색상 등의 정보를 온전히 가지고 있으며 그를 화면에 렌더링합니다.<br>따라서 좀 더 많은 연산을 해야 하지만, 대신 해상도(픽셀)에 영향을 비트맵 이미지와 달리 해상도로부터 자유롭게 렌더링할 수 있습니다.<br>쉽게 말하면 확대 및 축소를 해도 이미지가 깨지지 않죠.<br>또한 수학적 정보만을 가지고 있기 때문에 이미지 확대/축소에 따른 용량 변화가 없습니다.<br>일러스트 같은 툴로 편집할 수 있습니다.</p><p><img src="/images/screenshot/html-css-starter/difference_bitmap_vector.jpg" alt="비트맵과 벡터의 차이"></p><table><thead><tr><th>이미지 종류</th><th>장점</th><th>단점</th></tr></thead><tbody><tr><td>비트맵</td><td>정교하고 다양한 색상을 자연스럽게 표현</td><td>확대/축소 시 계단 현상, 품질 저하</td></tr><tr><td>벡터</td><td>확대/축소에서 자유로움, 용량 변화가 없음</td><td>정교한 이미지(인물, 풍경 사진 같은)를 표현하기 어려움</td></tr></tbody></table><blockquote><p><a href="https://www.sketch.com/" target="_blank" rel="noopener">Sketch 3</a>는 이미지의 편집보단 벡터 기반의 UI 제작 툴이라고 볼 수 있습니다.</p></blockquote><h3><span id="dc4a69f2-2a10-4ed5-8f78-8328208e47b1">JPG(JPEG)</span><a href="#dc4a69f2-2a10-4ed5-8f78-8328208e47b1" class="header-anchor"></a></h3><p>JPG(Joint Photographic coding Experts Group) Full-color와 Gray-scale의 압축을 위해 만들어졌으며 압축률이 훌륭해 사진이나 예술 분야에서 많이 사용됩니다.</p><ul><li>손실 압축</li><li>표현 색상도(24비트, 약 1600만 색상) 뛰어나 고해상도 표시장치에 적합</li><li>이미지의 품질과 용량을 쉽게 조절 가능</li><li>가장 널리 쓰이는 이미지 포맷</li></ul><blockquote><p>높은 압축률(적은 용량)!</p></blockquote><h3><span id="d9860e3c-4c44-49e3-a373-10061e2fa1ab">PNG</span><a href="#d9860e3c-4c44-49e3-a373-10061e2fa1ab" class="header-anchor"></a></h3><p>PNG(Portable Network Graphics)는 Gif의 대체 포맷으로 개발되었습니다.</p><ul><li>비손실 압축</li><li>8비트(256 색상) / 24비트(약 1600만 색상) 컬러 이미지 처리</li><li>Alpha Channel 지원(투명도)</li><li>W3C 권장 포맷</li></ul><blockquote><p>투명도 지원!</p></blockquote><h3><span id="8db32921-c3b4-40e9-b317-a54d88d90a84">GIF</span><a href="#8db32921-c3b4-40e9-b317-a54d88d90a84" class="header-anchor"></a></h3><p>GIF(Graphics Interchange Format)는 이미지 파일 내에 이미지 및 문자열 같은 정보들을 저장할 수 있습니다.</p><ul><li>비손실 압축</li><li>여러 장의 이미지를 한 개의 파일에 담을 수 있음(움짤, 애니메이션)</li><li>8비트 컬러만 지원(다양한 색상을 표현하는 작업에는 적합하지 않음)</li></ul><blockquote><p>동영상 같은 이미지!(애니메이션)</p></blockquote><h3><span id="f4fcce54-c03b-466b-bf8b-b9684b70f2bd">WEBP</span><a href="#f4fcce54-c03b-466b-bf8b-b9684b70f2bd" class="header-anchor"></a></h3><p>JPG, PNG, GIF를 모두 대체할 수 있는 구글이 개발한 이미지 포맷입니다.</p><ul><li>완벽한 손실/비손실 압축 지원</li><li>GIF 같은 애니메이션 지원</li><li>Alpha Channel 지원(손실, 비손실 모두)</li></ul><blockquote><p>완벽한 포맷! 그러나 지원 브라우저가…</p></blockquote><p><a href="https://caniuse.com/#feat=webp" target="_blank" rel="noopener">Support Browser for Webp</a></p><h3><span id="52a43cec-6ba2-4a42-83e7-dca125e12d20">SVG</span><a href="#52a43cec-6ba2-4a42-83e7-dca125e12d20" class="header-anchor"></a></h3><p>SVG(Scalable Vector Graphics)는 마크업 언어(HTML/XML) 기반의 벡터 그래픽을 표현하는 포맷입니다.</p><ul><li>해상도의 영향에서 자유로움</li><li>CSS로 Styling 가능</li><li>JavaScript로 Event Handling 가능</li><li>코드 혹은 파일로 사용 가능</li></ul><blockquote><p>벡터 이미지에 익숙하지 않다면 다루기 조금 까다로울 수 있습니다.</p></blockquote><h2><span id="80cd6bd2-9072-4e63-bf51-04c65a8c3fa0">특수 문자 용어 정리</span><a href="#80cd6bd2-9072-4e63-bf51-04c65a8c3fa0" class="header-anchor"></a></h2><table><thead><tr><th>기호</th><th>영어(발음)</th><th>한글</th></tr></thead><tbody><tr><td>&#96;</td><td>Grave(그레이브)</td><td>-</td></tr><tr><td>~</td><td>Tilde(틸드)</td><td>물결표시</td></tr><tr><td>!</td><td>Exclamation(엑스클러메이션) mark</td><td>느낌표</td></tr><tr><td>@</td><td>At(엣) sign</td><td>골뱅이</td></tr><tr><td>&#35;</td><td>Number(넘버) sign, Sharp(샵)</td><td>샵, 우물 정</td></tr><tr><td>$</td><td>Dollar(달러) sign</td><td>달러</td></tr><tr><td>%</td><td>Percent(퍼센트) sign</td><td>퍼센트</td></tr><tr><td>^</td><td>Caret(캐럿)</td><td>-</td></tr><tr><td>&amp;</td><td>Ampersand(엠퍼센드)</td><td>-</td></tr><tr><td>&#42;</td><td>Asterisk(에스터리스크)</td><td>별표</td></tr><tr><td>&#45;</td><td>Hyphen(하이픈), Dash(대쉬)</td><td>마이너스</td></tr><tr><td>_</td><td>Underscore(언더스코어), Low dash(로대쉬)</td><td>밑줄</td></tr><tr><td>=</td><td>Equals(이퀄) sign</td><td>이꼬르</td></tr><tr><td>“</td><td>Quotation(쿼테이션) mark</td><td>큰 따옴표</td></tr><tr><td>‘</td><td>Apostrophe(아포스트로피)</td><td>작은 따옴표</td></tr><tr><td>:</td><td>Colon(콜론)</td><td>땡땡이</td></tr><tr><td>;</td><td>Semicolon(세미콜론)</td><td>털 달린 땡땡이</td></tr><tr><td>,</td><td>Comma(콤마)</td><td>쉼표</td></tr><tr><td>.</td><td>Period(피리어드), Dot(닷)</td><td>점, 마침표</td></tr><tr><td>?</td><td>Question(퀘스천) mark</td><td>물음표</td></tr><tr><td>/</td><td>Slash(슬래쉬)</td><td>-</td></tr><tr><td>&#124;</td><td>Vertical bar(버티컬 바)</td><td>-</td></tr><tr><td>&#92;</td><td>Backslash(백슬래쉬)</td><td>-</td></tr><tr><td>()</td><td>Parenthesis(퍼렌서시스)</td><td>(소)괄호</td></tr><tr><td>{}</td><td>Brace(브레이스)</td><td>중괄호</td></tr><tr><td>[]</td><td>Bracket(브래킷)</td><td>대괄호</td></tr><tr><td>&#60;&#62;</td><td>Angle Bracket(앵글 브래킷)</td><td>꺽쇠괄호</td></tr></tbody></table><p><a href="https://www.freeformatter.com/html-entities.html" target="_blank" rel="noopener">HTML Entity List</a></p><h2><span id="f904dd32-192c-4630-90ed-8e6276c3c4d1">오픈소스 라이선스</span><a href="#f904dd32-192c-4630-90ed-8e6276c3c4d1" class="header-anchor"></a></h2><blockquote><p>오픈소스란 어떤 제품을 개발하는 과정에 필요한 소스 코드나 설계도를 누구나 접근해서 열람할 수 있도록 공개하는 것.</p></blockquote><p>오픈소스라 하면 보통 무료 저작권이고 공짜로 사용해도 문제가 없다고 생각하지만 사실 다양한 종류의 오픈소스 라이선스가 존재하며 개인적 이용은 가능하지만, 상업적 이용에 제한이 있거나 경우에 따라 비용을 지불해야 할 수도 있습니다.</p><p>현실적으론 처음부터 끝까지 모든 코드를 직접 작성할 수 없기 때문에 많은 경우 오픈소스에 의존하게 됩니다. 개인적인 사용을 목적으로는 문제가 없겠지만, 회사에서(상업적으로) 아무 생각 없이 사용하다가는 문제가 돼서 해고당하거나 피해 보상을 해줘야 할지도 모릅니다.<br>물론 인터넷에 떠도는 코드 몇 줄 복사해서 썼다고 그 정도 심각한 문제가 되진 않겠지만, 항상 조심하는 것이 좋습니다.<br>회사에서 작업 중에 괜찮은 오픈소스를 찾았다면 반사적으로 ‘License’부터 찾으세요!</p><p>수많은 라이선스를 다 공부할 필요는 없고, 보기만 해도 흐뭇해지는 라이선스를 몇 가지 소개합니다.</p><h3><span id="d6503867-d7f8-4667-97c2-49d167c79e39">Apache License</span><a href="#d6503867-d7f8-4667-97c2-49d167c79e39" class="header-anchor"></a></h3><p>아파치 소프트웨어 재단에서 자체 소프트웨어에 적용하기 위해 만든 라이선스입니다.<br>개인적/상업적 이용, 배포, 수정, 특허 신청이 가능합니다.</p><h3><span id="b7d98b4c-8d2a-4a4e-a817-9429a686d846">MIT License</span><a href="#b7d98b4c-8d2a-4a4e-a817-9429a686d846" class="header-anchor"></a></h3><p>매사추세츠공과대학(MIT)에서 소프트웨어 학생들을 위해 개발한 라이선스입니다.<br>개인 소스에 이 라이선스를 사용하고 있다는 표시만 지켜주면 되며, 나머지 사용에 대한 제약은 없기 때문에 인기가 많습니다.</p><h3><span id="20968c07-7fc8-434d-a375-3c839556279e">BSD License</span><a href="#20968c07-7fc8-434d-a375-3c839556279e" class="header-anchor"></a></h3><p>BSD(Berkeley Software Distribution)는 버클리 캘리포니아대학에서 개발한 라이선스입니다.<br>MIT와 동일하게 라이선스 표시만 지켜주시면 됩니다.</p><h3><span id="3951fa5b-d35f-48e0-a7dd-3a34c1d456f6">Beerware</span><a href="#3951fa5b-d35f-48e0-a7dd-3a34c1d456f6" class="header-anchor"></a></h3><p>오픈소스 개발자에게 맥주를 사줘야 하는 라이선스입니다.<br>물론 만날 수 있다면 말이죠!</p><p>그 외 더 많은 오픈소스 라이선스에 대한 정보는 <a href="https://opensource.org/licenses" target="_blank" rel="noopener">OpenSource.org</a>에서 확인하실 수 있습니다.</p><h1><span id="e1e7400b-2763-4c61-b270-8d7c0089e65b">HTML</span><a href="#e1e7400b-2763-4c61-b270-8d7c0089e65b" class="header-anchor"></a></h1><h2><span id="8d3965dd-746b-4330-8693-feb0471948d5">정말 쉬운 HTML 문법</span><a href="#8d3965dd-746b-4330-8693-feb0471948d5" class="header-anchor"></a></h2><h3><span id="cfd0dd5f-c106-475e-a60a-25b700e312ad">기본 형태</span><a href="#cfd0dd5f-c106-475e-a60a-25b700e312ad" class="header-anchor"></a></h3><p>태그는 각자의 의미를 가지고 있으며 다음과 같은 형태를 가집니다.</p><pre><code class="html">&lt;TAG&gt;&lt;/TAG&gt;&lt;TAG&gt;CONTENT&lt;/TAG&gt;</code></pre><pre><code class="html">&lt;h1&gt;토끼와 거북이&lt;/h1&gt;&lt;p&gt;옛날 옛적에 토끼와 거북이가 살았습니다 ...&lt;/p&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;주제1&gt;토끼와 거북이&lt;/주제1&gt;&lt;문단&gt;옛날 옛적에 토끼와 거북이가 살았습니다 ...&lt;/문단&gt;</code></pre><p>또한 태그는 열리고(open) 닫히는(close) 태그 구조를 가지고 있으며 이는 한 쌍입니다.<br>(시작하고(start) 종료되는(end) 구조라고 부르기도 합니다)<br>이 구조는 태그의 범위를 만들어 줍니다.</p><pre><code class="html">&lt;h1&gt;토끼와 거북이&lt;/h1&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;주제1여기서열림&gt;토끼와 거북이&lt;/주제1여기서닫힘&gt;</code></pre><p>입문자가 주의할 점은 닫히는 태그는 태그 이름 앞에 <code>/</code>(슬래시)가 붙는다는 것입니다.</p><h3><span id="8c09a7d0-7731-45d5-b997-5183cb44a963">속성(Attributes)과 값(Value)</span><a href="#8c09a7d0-7731-45d5-b997-5183cb44a963" class="header-anchor"></a></h3><p>태그(요소)의 기능을 확장하기 위해 ‘속성’을 사용할 수 있습니다.</p><pre><code class="html">&lt;TAG 속성=&quot;값&quot;&gt;&lt;/TAG&gt;</code></pre><pre><code class="html">&lt;img src=&quot;./my_photo.jpg&quot; alt=&quot;내 프로필 사진&quot; /&gt;&lt;div class=&quot;name&quot;&gt;홍길동&lt;/div&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;이미지삽입 소스위치=&quot;./my_photo.jpg&quot; 대체텍스트=&quot;내 프로필 사진&quot; /&gt;&lt;의미없는분할 태그별명=&quot;name&quot;&gt;홍길동&lt;/의미없는분할&gt;</code></pre><p><code>&lt;img /&gt;</code>는 이미지를 삽입할 때 사용하는 태그입니다.<br>하지만 태그만 사용하면 어떤 이미지를 삽입하는지 알 수 없기 때문에 <code>src</code>(source)라는 속성을 사용해서 삽입할 이미지의 경로를 지정합니다. 그리고 <code>alt</code>(alternative)라는 속성은 이미지를 출력하지 못하는 상황에 이미지 대신 보여질 텍스트를 지정합니다.<br><code>&lt;div&gt;&lt;/div&gt;</code>는 의미를 가지지 않는 태그로 어떤 내용이든 묶어낼(Wrap) 수 있습니다.<br>위에선 <code>&#39;홍길동&#39;</code>이라는 텍스트를 묶었으나 그 내용이 무엇을 의미하는지 알 수 없기 때문에 <code>name</code>이라는 태그 별명(<code>class</code>)을 추가했습니다.</p><blockquote><p><code>&lt;img /&gt;</code>는 좀 이상하게 생겼네요. 닫히는 태그가 없으면 빈 태그(Empty Tag)라고 합니다. 다시 설명합니다.</p></blockquote><h3><span id="2e99705d-b76f-46fa-9098-40325c527cee">부모와 자식 요소</span><a href="#2e99705d-b76f-46fa-9098-40325c527cee" class="header-anchor"></a></h3><p>태그A가 태그B의 콘텐츠로 사용되면, 태그B는 태그A의 부모 요소, 태그A는 태그B의 자식 요소라고 합니다.</p><pre><code class="html">&lt;PARENT&gt;  &lt;CHILD&gt;&lt;/CHILD&gt;&lt;/PARENT&gt;</code></pre><pre><code class="html">&lt;section class=&quot;fruits&quot;&gt;  &lt;h1&gt;과일 목록&lt;/h1&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;/ul&gt;&lt;/section&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;섹션영역 태그별명=&quot;fruits&quot;&gt;  &lt;주제1&gt;과일 목록&lt;/주제1&gt;  &lt;순서없는목록&gt;    &lt;항목&gt;사과&lt;/항목&gt;    &lt;항목&gt;딸기&lt;/항목&gt;    &lt;항목&gt;바나나&lt;/항목&gt;    &lt;항목&gt;오렌지&lt;/항목&gt;  &lt;/순서없는목록&gt;&lt;/섹션영역&gt;</code></pre><p><code>&lt;section&gt;&lt;/section&gt;</code> 안에는(콘텐츠) <code>&lt;h1&gt;&lt;/h1&gt;</code>, <code>&lt;ul&gt;&lt;/ul&gt;</code>, <code>&lt;li&gt;&lt;/li&gt;</code>가 있고 <code>&lt;ul&gt;&lt;/ul&gt;</code> 안에는(콘텐츠) <code>&lt;li&gt;&lt;/li&gt;</code>가 있습니다.<br>(이하 닫히는 태그는 생략할게요)<br>이러한 구조에서 <code>&lt;section&gt;</code>는 <code>&lt;h1&gt;</code>과 <code>&lt;ul&gt;</code>의 부모 요소입니다.<br>또한 <code>&lt;ul&gt;</code>은 <code>&lt;li&gt;</code>의 부모 요소입니다.<br>반대로 <code>&lt;h1&gt;</code>과 <code>&lt;ul&gt;</code>은 <code>&lt;section&gt;</code>의 자식 요소입니다.<br>또한 <code>&lt;li&gt;</code>는 <code>&lt;ul&gt;</code>의 자식 요소입니다.</p><p>여기서 <code>&lt;ul&gt;</code>은 <code>&lt;section&gt;</code>의 자식 요소이면서 <code>&lt;li&gt;</code>의 부모 요소입니다.<br>이처럼 부모와 자식 요소는 상대적인 개념입니다.<br>(조금 더 나아가면 <code>&lt;section&gt;</code>은 <code>&lt;li&gt;</code>의 조상(상위) 요소, 반대로 <code>&lt;li&gt;</code>는 <code>&lt;section&gt;</code>의 후손(하위) 요소라고 합니다)</p><p>우리가 기본적인 가계도를 통해 할아버지, 엄마, 삼촌, 형제 같은 호칭을 정의하듯(혹은 반대로 호칭을 통해 가계도를 이해하듯), HTML의 구조도 위와 같은 개념으로 호칭을 정의하여 추후 선택자(Selector)를 통해 CSS와 JS로 HTML을 다룰 때 중요하게 사용됩니다.</p><h3><span id="71ac46c5-337f-489e-ad87-854618a581d5">빈 태그</span><a href="#71ac46c5-337f-489e-ad87-854618a581d5" class="header-anchor"></a></h3><p>HTML에는 닫히는 개념이 없는 태그들이 있습니다.<br>다음과 같은 형태를 가집니다.</p><pre><code class="html">&lt;!-- `/`가 없는 빈 태그 --&gt;&lt;TAG&gt;&lt;!-- `/`가 있는 빈 태그 --&gt;&lt;TAG/&gt;&lt;TAG /&gt;</code></pre><p>HTML5에서는 위 2가지 형태를 다 사용할 수 있는데, XHTML 버전이나 린트(Lint) 환경 혹은 프레임워크 세팅에 따라 <code>/</code>를 사용하는 것이 필수가 될 수 있습니다.</p><blockquote><p>어떤 형태를 써야 한다는 갑론을박이 있는데 이는 실제론 중요치 않습니다. 원하는 형태를 사용하되 혼용하지는 마세요.</p></blockquote><h2><span id="08893d9c-e2de-4e86-b272-71bed4442309">HTML 문서의 범위</span><a href="#08893d9c-e2de-4e86-b272-71bed4442309" class="header-anchor"></a></h2><p><code>index.html</code> 같은 HTML 파일을 우리는 HTML 문서라고 표현할 수 있습니다.<br>HTML 문서의 범위를 나타내는(의미하는) 태그들을 알아봅시다.</p><pre><code class="html">&lt;!DOCTYPE html&gt;&lt;html&gt;  &lt;head&gt;    문서의 정보  &lt;/head&gt;  &lt;body&gt;    문서의 구조  &lt;/body&gt;&lt;/html&gt;</code></pre><pre><code class="html">&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;    &lt;meta charset=&quot;UTF-8&quot;&gt;    &lt;meta name=&quot;author&quot; content=&quot;홍길동&quot;&gt;    &lt;meta name=&quot;description&quot; content=&quot;우리 사이트가 최고!&quot;&gt;    &lt;title&gt;내 사이트&lt;/title&gt;    &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/main.css&quot;&gt;    &lt;script src=&quot;./js/main.js&quot;&gt;&lt;/script&gt;&lt;/head&gt;&lt;body&gt;    &lt;section&gt;      &lt;h1&gt;&lt;/h1&gt;      &lt;div&gt;        &lt;ul&gt;          &lt;li&gt;&lt;/li&gt;          &lt;li&gt;&lt;/li&gt;        &lt;/ul&gt;      &lt;/div&gt;    &lt;/section&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><h3><span id="d16a5dcf-94da-4b59-8e7a-3f406915edd6">HTML(전체 범위)</span><a href="#d16a5dcf-94da-4b59-8e7a-3f406915edd6" class="header-anchor"></a></h3><p><code>&lt;html&gt;</code>는 HTML 문서의 전체 범위를 지정합니다.<br>웹 브라우저가 해석해야 할 HTML 문서가 어디에서 시작하며, 어디에서 끝나는지 알려주는 역할을 합니다.</p><h3><span id="2fd41702-593e-4c3e-80cb-185974e74401">HEAD(정보 범위)</span><a href="#2fd41702-593e-4c3e-80cb-185974e74401" class="header-anchor"></a></h3><p>웹 브라우저가 해석해야 할 HTML 문서의 정보 범위를 지정합니다.<br>여기서 말하는 정보에는 웹 페이지의 제목, 웹 페이지의 문자 인코딩 방식, 연결할 외부 파일의 위치, 웹 페이지를 구조화하기 위한 기본 세팅 값 같은 것들을 말합니다.<br>다르게는 ‘화면을 구성하기 위한 기본 설정’이라고 표현할 수 있습니다.</p><h3><span id="b2671ea4-1c65-4def-b9a8-45ca39b510c7">BODY(구조 범위)</span><a href="#b2671ea4-1c65-4def-b9a8-45ca39b510c7" class="header-anchor"></a></h3><p>웹 브라우저가 해석해야 할 HTML 문서의 구조 범위를 지정합니다.<br>구조는 사용자가 화면을 통해서 볼 수 있는 내용(콘텐츠)의 형태나 레이아웃 등을 의미하며 로고, 헤더, 푸터, 내비게이션, 메뉴, 버튼, 입력창, 팝업, 광고 등 보이는 모든 것들이 구조에 해당합니다.<br>구조는 BODY 범위 안에서만 생성합니다.</p><h3><span id="fc44e1ae-256d-4771-a55d-e6228c9bff5a">DOCTYPE(DTD, 버전 지정)</span><a href="#fc44e1ae-256d-4771-a55d-e6228c9bff5a" class="header-anchor"></a></h3><p>DOCTYPE(DTD, Document Type Definition)은 마크업 언어에서 문서 형식을 정의합니다.<br>이는 웹 브라우저에 우리가 제공할 HTML 문서를 어떤 HTML 버전의 해석 방식으로 구조화하면 되는지를 알려줍니다.<br>(HTML은 크게 1, 2, 3, 4, X-, 5 버전이 있습니다)</p><p>현재의 표준 모드는 HTML5 입니다.</p><pre><code class="html">&lt;!-- HTML 5 --&gt;&lt;!DOCTYPE html&gt;&lt;!-- XHTML 1.0 Transitional --&gt;&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;</code></pre><p><a href="https://en.wikipedia.org/wiki/Document_type_declaration" target="_blank" rel="noopener">더 많은 문서형 정보 보기</a></p><blockquote><p>Windows 운영체제가 95, 98, ME, XP, Vista, 7, 8, 10 버전이 있는 것과 비슷하다고 생각하시면 쉽습니다.</p></blockquote><h2><span id="75cce07a-57fc-4c25-a47c-4e1d15f5bf17">HTML 문서의 정보</span><a href="#75cce07a-57fc-4c25-a47c-4e1d15f5bf17" class="header-anchor"></a></h2><p><code>&lt;head&gt;&lt;/head&gt;</code> 안에서 사용하는 태그들은 HTML 문서의 정보를 가지고 있습니다.</p><h3><span id="cbe6c400-085f-4997-9188-c2d47521e27a">TITLE(웹 페이지의 제목)</span><a href="#cbe6c400-085f-4997-9188-c2d47521e27a" class="header-anchor"></a></h3><p>HTML 문서의 제목을 정의합니다.<br>웹 브라우저의 각 사이트 탭에서 이름으로 표시됩니다.</p><p><img src="/images/screenshot/html-css-starter/browser_tabs.jpg" alt="브라우저 탭에 표시되는 타이틀 태그"></p><pre><code class="html">&lt;head&gt;  &lt;title&gt;네이버&lt;/title&gt;&lt;/head&gt;</code></pre><h3><span id="987bfc15-2511-4d33-b6bd-c449505da36e">META(웹 페이지의 정보)</span><a href="#987bfc15-2511-4d33-b6bd-c449505da36e" class="header-anchor"></a></h3><p>HTML 문서(웹페이지)에 관한 정보(표시 방식, 제작자(소유자), 내용, 키워드 등)를 검색엔진이나 브라우저에 제공합니다.<br>빈(Empty) 태그입니다.</p><pre><code class="html">&lt;head&gt;  &lt;meta charset=&quot;UTF-8&quot;&gt;  &lt;meta name=&quot;author&quot; content=&quot;홍길동&quot;&gt;  &lt;meta name=&quot;description&quot; content=&quot;우리 사이트가 최고!&quot;&gt;&lt;/head&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;문서의정보범위&gt;  &lt;정보 문자인코딩방식=&quot;UTF-8&quot;&gt;  &lt;정보 정보종류=&quot;사이트제작자&quot; 정보값=&quot;홍길동&quot;&gt;  &lt;정보 정보종류=&quot;사이트설명&quot; 정보값=&quot;우리 사이트가 최고!&quot;&gt;&lt;/문서의정보범위&gt;</code></pre><p><code>&lt;meta&gt;</code>에서 사용할 수 있는 속성은 다음과 같습니다.<br>각 태그는 자신이 사용할 수 있는 속성과 값이 정해져 있습니다.<br>어떤 속성을 사용할 수 있고, 사용할 수 없는지 구분할 수 있어야 합니다.<br>잘 사용하지 않는 속성도 있기 때문에 당장 모든 속성과 값을 암기할 필요는 없습니다.<br>(‘전역(Global) 속성’이라고 해서 어느 태그에서나 사용할 수 있는 속성들도 있습니다만 지금은 확인할 필요가 없습니다!)</p><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td><code>charset</code></td><td>문자인코딩 방식</td><td><code>UTF-8</code>, <code>EUC-KR</code> 등..</td></tr><tr><td><code>name</code></td><td>검색엔진 등에 제공하기 위한 정보의 종류(메타 데이터)</td><td><code>author</code>, <code>description</code>, <code>keywords</code>, <code>viewport</code> 등..</td></tr><tr><td><code>content</code></td><td><code>name</code> 이나 <code>http-equiv</code> 속성의 값을 제공</td><td></td></tr></tbody></table><h3><span id="407d8f47-0b5c-44fa-84cd-de26d395d8e2">LINK(CSS 불러오기)</span><a href="#407d8f47-0b5c-44fa-84cd-de26d395d8e2" class="header-anchor"></a></h3><p>외부 문서를 연결할 때 사용합니다.<br>특히 HTML 외부에서 작성된 CSS 문서(<code>xxx.css</code> 파일)를 불러와 연결할 때 사용합니다.<br>빈(Empty) 태그입니다.</p><pre><code class="html">&lt;head&gt;  &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/main.css&quot;&gt;  &lt;link rel=&quot;icon&quot; href=&quot;./favicon.png&quot;&gt;&lt;/head&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;문서의정보범위&gt;  &lt;외부문서연결 관계=&quot;CSS&quot; 문서경로=&quot;./css/main.css&quot;&gt;  &lt;외부문서연결 관계=&quot;사이트대표아이콘&quot; 문서경로=&quot;./favicon.png&quot;&gt;&lt;/문서의정보범위&gt;</code></pre><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td><code>rel</code></td><td>(필수)현재 문서와 외부 문서와의 관계를 지정</td><td><code>stylesheet</code>, <code>icon</code> 등..</td></tr><tr><td><code>href</code></td><td>외부 문서의 위치를 지정</td><td>경로</td></tr></tbody></table><h3><span id="2cb2a710-ecf8-468f-a176-bb77e7ca62be">STYLE(CSS 작성하기)</span><a href="#2cb2a710-ecf8-468f-a176-bb77e7ca62be" class="header-anchor"></a></h3><p>CSS를 외부 문서에서 작성하여 연결하는 것이 아니고 HTML 문서 내부에 작성할 때 사용합니다.</p><pre><code class="html">&lt;style&gt;  img {    width: 100px;    height: 200px;  }  p {    font-size: 20px;    font-weight: bold;  }&lt;/style&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;스타일정의&gt;  &lt;!-- CSS 코드 --&gt;&lt;/스타일정의&gt;</code></pre><h3><span id="477315b9-97c3-481e-94bd-aaee24cf53fa">SCRIPT(JS 불러오거나 작성하기)</span><a href="#477315b9-97c3-481e-94bd-aaee24cf53fa" class="header-anchor"></a></h3><p>HTML 문서에서 CSS는, 작성된 CSS를 <code>&lt;link&gt;</code>로 불러오거나 <code>&lt;style&gt;&lt;/style&gt;</code>안에 작성할 수 있습니다.<br>JS는 <code>&lt;script&gt;&lt;/script&gt;</code>로 이 2가지 방식을 모두 사용할 수 있습니다.</p><pre><code class="html">&lt;!-- 불러오기 --&gt;&lt;script src=&quot;./js/main.js&quot;&gt;&lt;/script&gt;&lt;!-- 작성하기 --&gt;&lt;script&gt;  function windowOnClickHandler(event) {    console.log(event);  }  window.addEventListener(&#39;click&#39;, windowOnClickHandler);&lt;/script&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;!-- 불러오기 --&gt;&lt;자바스크립트 문서경로=&quot;./js/main.js&quot;&gt;&lt;/자바스크립트&gt;&lt;!-- 작성하기 --&gt;&lt;자바스크립트&gt;  &lt;!-- JS 코드 --&gt;&lt;/자바스크립트&gt;</code></pre><h2><span id="f4e01c6d-2979-4690-9829-4ab9413a39d1">HTML 문서의 구조</span><a href="#f4e01c6d-2979-4690-9829-4ab9413a39d1" class="header-anchor"></a></h2><p><code>&lt;body&gt;&lt;/body&gt;</code> 안에서 사용하는 태그들은 HTML 문서의 구조를 나타냅니다.</p><h3><span id="4a21c344-c30b-4465-8c99-24074dad76a0">DIV(막 쓰는 태그)</span><a href="#4a21c344-c30b-4465-8c99-24074dad76a0" class="header-anchor"></a></h3><p><code>&lt;div&gt;&lt;/div&gt;</code>의 ‘div’는 ‘division’으로 약자로 ‘분할’을 뜻하고 ‘문서의 부분이나 섹션을 정의’합니다.<br>명확한 의미를 가지지 않기 때문에 정말 많은 경우 단순히 특정 범위를 묶는(wrap) 용도로 사용합니다.<br>보통 이렇게 묶인 부분들에 CSS나 JS를 적용하게 됩니다.</p><pre><code class="html">&lt;body&gt;  &lt;div&gt;    &lt;p&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;div&gt;    &lt;div&gt;      &lt;h1&gt;&lt;/h1&gt;      &lt;p&gt;&lt;/p&gt;    &lt;/div&gt;  &lt;/div&gt;&lt;/body&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;body&gt;  &lt;묶음1&gt;    &lt;p&gt;&lt;/p&gt;  &lt;/묶음1&gt;  &lt;묶음2&gt;    &lt;묶음2-1&gt;      &lt;h1&gt;&lt;/h1&gt;      &lt;p&gt;&lt;/p&gt;    &lt;/묶음2-1&gt;  &lt;/묶음2&gt;&lt;/body&gt;</code></pre><blockquote><p>“DIV는 아무 의미가 없다. 왜냐하면 아무 의미가 없기 때문이다.”</p></blockquote><h3><span id="64b90b49-b111-4d26-8004-e55ac12db074">IMG(이미지 넣는 태그)</span><a href="#64b90b49-b111-4d26-8004-e55ac12db074" class="header-anchor"></a></h3><p><code>&lt;img&gt;</code>는 HTML에 이미지를 삽입할 때 사용합니다.<br>(웹 페이지에 이미지를 삽입하는 방식은 크게 2가지로, ‘HTML에서 삽입(IMG)’과 ‘CSS에서 삽입(background)’이 있습니다)</p><pre><code class="html">&lt;body&gt;  &lt;img src=&quot;./kitty.png&quot; alt=&quot;냥이&quot;&gt;&lt;/body&gt;&lt;!-- 다음과 같이 이해할 수 있습니다. --&gt;&lt;body&gt;  &lt;이미지 경로=&quot;./kitty.png&quot; 대체텍스트=&quot;냥이&quot;&gt;&lt;/body&gt;</code></pre><table><thead><tr><th>속성</th><th>의미</th><th>값</th></tr></thead><tbody><tr><td><code>src</code></td><td>(필수)이미지의 URL</td><td>URL</td></tr><tr><td><code>alt</code></td><td>(필수)이미지의 대체 텍스트(alternate)를 지정</td><td></td></tr></tbody></table><p>위 표에서 ‘(필수)’라고 되어 있는 속성들(<code>src</code>, <code>alt</code>)은 <code>&lt;img&gt;</code>를 사용할 때 반드시 포함되어야 할 속성(필수 속성, Required Attributes)입니다.<br>만약 <code>&lt;img src=&quot;./kitty.png&quot;&gt;</code>라고 작성하여 <code>alt</code> 속성이 누락되었다면 이는 웹 표준에 어긋납니다.</p><h2><span id="e37d0c03-0719-4b6a-a1fa-a1a27a56edad">웹 표준 검사하기</span><a href="#e37d0c03-0719-4b6a-a1fa-a1a27a56edad" class="header-anchor"></a></h2><p>우리가 작성한 HTML 문서가 표준에 부합하는지 테스트를 해볼 수 있습니다.<br><a href="https://validator.w3.org/#validate_by_upload" target="_blank" rel="noopener">W3C validator</a>에 접속하여 작성한 HTML 문서를 업로드하고 테스트를 시작하세요!<br>기본적인 표준 여부를 판단할 수 있습니다.<br>특히 입문자라면 익숙해질 때까지는 자주 확인하는 것이 좋습니다.</p><p><img src="/images/screenshot/html-css-starter/markup _validation_result_image_kytty.jpg" alt="웹 표준 검사 결과 alt 누락"></p><p>위에서 <code>&lt;img src=&quot;./kitty.png&quot;&gt;</code>라고 작성했을 때 나오는 결과입니다.<br>HTML 문서를 작성하면서 지켜야하는 이러한 규칙들이 많이 있습니다.</p><blockquote><p>테스트 통과가 웹 표준/웹 접근성의 준수 여부를 최종적으로 결정하진 않습니다. 이 테스트는 사실 기본 문법 검사에 가깝습니다.</p></blockquote><p>또는 사이트(페이지) 주소로 검사할 수도 있습니다.<br>다음은 <code>naver.com</code>을 검사한 결과입니다.</p><p><img src="/images/screenshot/html-css-starter/markup _validation_result_naver_com.jpg" alt="웹 표준 검사 결과 naver.com"></p><h2><span id="1ec5e5bb-3003-4c2f-bb31-7363130528b1">HTML 예제</span><a href="#1ec5e5bb-3003-4c2f-bb31-7363130528b1" class="header-anchor"></a></h2><p>다음 이미지는 <a href="https://github.com/" target="_blank" rel="noopener">GitHub</a> 메인 페이지의 일부입니다.(이 예제에 사용된 이미지는 예전 메인 페이지의 모습입니다)<br>이 페이지의 일부를 HTML로 코딩해 봅시다.</p><blockquote><p>완성된 페이지(<a href="https://heropcode.github.io/GitHub-Responsive" target="_blank" rel="noopener">https://heropcode.github.io/GitHub-Responsive/</a>)를 공유합니다. GitHub 메인의 클론 페이지입니다.</p></blockquote><p><img src="/images/screenshot/html-css-starter/guthub_clone_page.jpg" alt="HTML Github 예제"></p><p>입문 단계에서 난이도가 올라가지 않도록(코드가 너무 길어지지 않도록) 위 화면에서 다음에 표시된 부분(Header)의 내용 일부만 구조를 정리해 봅시다.</p><p><img src="/images/screenshot/html-css-starter/guthub_clone_page_header_structure.jpg" alt="HTML Github 예제 header"></p><p>바탕화면 같은 익숙한 곳에서 <code>example1</code>이라는 이름의 프로젝트 디렉터리(폴더)를 생성합니다.(이름은 원하는 대로 자유롭게 지정하세요)<br>이제 VS Code를 실행해 <code>파일/열기</code>를 선택해 생성한 디렉터리를 찾아 오픈합니다.<br>그 안에 <code>index.html</code>이라는 파일을 생성합니다.(<code>파일/새파일</code> &gt; <code>저장</code> &gt; 이름과 확장자 설정)<br>다음 코드와 위 구조를 비교하며 코딩해 보세요.</p><pre><code class="html">&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;    &lt;meta charset=&quot;UTF-8&quot;&gt;    &lt;title&gt;GitHub&lt;/title&gt;&lt;/head&gt;&lt;body&gt;    &lt;div class=&quot;header&quot;&gt;        &lt;div class=&quot;container&quot;&gt;            &lt;div class=&quot;container-left&quot;&gt;                &lt;div class=&quot;logo&quot;&gt;                    &lt;img src=&quot;https://heropcode.github.io/GitHub-Responsive/img/logo.svg&quot; alt=&quot;GitHub Logo&quot;&gt;                &lt;/div&gt;                &lt;div class=&quot;menu&quot;&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Personal&lt;/div&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Open Source&lt;/div&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Business&lt;/div&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Explore&lt;/div&gt;                &lt;/div&gt;            &lt;/div&gt;        &lt;/div&gt;    &lt;/div&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><p>현재는 이 구조를 브라우저에 출력하면 다음과 같이 한장의 이미지와 메뉴에 있는 4개의 TEXT만 나옵니다.(HTML 화면에서 우클릭 &gt; ‘Open with Live Server’ 선택)</p><blockquote><p>‘Open with Live Server’ 메뉴가 없다면 VS Code 확장 기능으로 Live Server를 설치해야 합니다.</p></blockquote><p><img src="/images/screenshot/html-css-starter/html_example_result.jpg" alt="HTML 예제 결과"></p><p>미완성 같지만 HTML은 역할이 끝났습니다.<br>이제 예쁘게 꾸미기(스타일) 위해 CSS가 필요합니다.</p><h1><span id="8a4eee59-b59d-4106-99e5-ec773a72ea34">CSS</span><a href="#8a4eee59-b59d-4106-99e5-ec773a72ea34" class="header-anchor"></a></h1><h2><span id="84dac196-1ca9-4b88-ab1f-d1d8b626c5cd">이보다 쉬울 수 없는 CSS 문법</span><a href="#84dac196-1ca9-4b88-ab1f-d1d8b626c5cd" class="header-anchor"></a></h2><p>CSS 문법은 HTML 보다 더 쉽습니다.<br>아래 예시처럼 선택자, 속성, 값이 있으며 무엇을 의미하는지 이해하는 것으로 기본 문법은 충분합니다!</p><pre><code class="css">div {  font-size: 20px;  color: red;}/* 다음과 같이 이해할 수 있습니다. */선택자 {  속성: 값;  속성: 값;}</code></pre><h3><span id="671a8fe8-c44a-4862-a2c6-edea467269b2">선택자의 역할</span><a href="#671a8fe8-c44a-4862-a2c6-edea467269b2" class="header-anchor"></a></h3><p>선택자는 HTML에 스타일(CSS)을 적용하기 위해 HTML의 특정한 요소를 선택하는 사인(sign)입니다.<br>“빨강 글자색(CSS, <code>color: red;</code>)을 저기 제목(HTML, <code>&lt;h1&gt;&lt;/h1&gt;</code>)에 적용하겠어!”, “파랑 글자색(CSS, <code>color: blue;</code>)은 여기 본문(HTML, <code>&lt;p&gt;&lt;/p&gt;</code>)에 적용하겠어!”와 같이 적용할 스타일을 속성(<code>color</code>)과 값(<code>red</code>, <code>blue</code>)으로 나타내고 적용할 대상(H1, P)을 선택자로 나타냅니다.</p><p>위 내용을 코드로 구성하면 다음과 같습니다.</p><pre><code class="html">&lt;div&gt;  &lt;h1&gt;제목..&lt;/h1&gt;  &lt;p&gt;본문..&lt;/p&gt;&lt;/div&gt;</code></pre><pre><code class="css">h1 {  color: red;}p {  color: blue;}</code></pre><h3><span id="28bbaf8d-40b8-4676-8981-8875586fe77b">속성(Properties)과 값(Value)</span><a href="#28bbaf8d-40b8-4676-8981-8875586fe77b" class="header-anchor"></a></h3><p>속성과 값은 글자색은 무엇, 너비는 얼마, 여백은 얼마 같은 스타일을 지정할 때 사용합니다.</p><pre><code class="css">div {  color: red; /* 글자색은 빨강 */  font-size: 20px; /* 글자 크기는 20px */  width: 300px; /* 가로 너비는 300px */  margin: 20px; /* 바깥 여백은 20px */  padding: 10px 20px; /* 안쪽 여백은 위아래 10px, 좌우 20px */  position: absolute; /* 위치는 부모 요소 기준, 흠.. 이게 무슨 뜻일까? */}</code></pre><p>속성과 값은 많이 아는 것이 중요합니다.<br>글자 크기를 키우고 싶은데 <code>font-size</code>가 어떤 역할을 하는지 모른다면 글자 크기를 조절할 방법이 없습니다.<br>또한 위치(좌표)를 지정하고 싶은데 <code>position</code> 속성을 모르거나 혹은 그 속성의 값으로 사용하는 <code>absolute</code>가 어떤 의미를 갖는지 모르면 역시 방법이 없습니다.</p><p><code>I like</code>처럼 영어가 주어와 동사로 이루진 문법이 전부라고 가정해 봅시다. 문법은 매우 쉽겠지만 주어에 들어갈 단어는 <code>You</code>, <code>She</code>, <code>They</code> 등이 많은 단어가 있을 것이고, 동사에 들어갈 단어는 <code>love</code>, <code>work</code>, <code>eat</code> 등 수많은 단어들 들어갈 수 있겠죠?!<br>많은 단어를 아는 만큼 풍부하고 멋진 문장을 만들 수 있듯, 많은 속성과 값을 아는 만큼 멋진 스타일을 만들어낼 수 있습니다!</p><h2><span id="8815aa0b-26c8-4a6e-939b-e28476c74947">CSS 선언 방식</span><a href="#8815aa0b-26c8-4a6e-939b-e28476c74947" class="header-anchor"></a></h2><p>이제 내가 작성한 CSS 코드를 어떻게 HTML에 적용할 수 있는지 알아봅시다.</p><h3><span id="ab178391-d9bb-478e-81bb-3d0801393e94">태그에 직접 작성하기(인라인)</span><a href="#ab178391-d9bb-478e-81bb-3d0801393e94" class="header-anchor"></a></h3><p>이 방법은 HTML 태그에 직접 작성하기 때문에 선택자가 필요하지 않습니다.</p><pre><code class="html">&lt;div style=&quot;color: red;&quot;&gt;태그에 직접 작성1&lt;/div&gt; &lt;!-- red --&gt;&lt;div style=&quot;color: red;&quot;&gt;태그에 직접 작성2&lt;/div&gt; &lt;!-- red --&gt;&lt;div style=&quot;color: red;&quot;&gt;태그에 직접 작성3&lt;/div&gt; &lt;!-- red --&gt;&lt;div style=&quot;color: red;&quot;&gt;태그에 직접 작성4&lt;/div&gt; &lt;!-- red --&gt;</code></pre><h3><span id="09a05d3e-1841-49a1-aa36-55e0370aa9a1">HTML에 포함하기(내장)</span><a href="#09a05d3e-1841-49a1-aa36-55e0370aa9a1" class="header-anchor"></a></h3><p>CSS만 따로 작성하기 때문에 선택자가 필요합니다.<br>CSS 코드가 HTML의 <code>&lt;style&gt;&lt;/style&gt;</code> 안에 포함되어 있습니다.</p><pre><code class="html">&lt;head&gt;  &lt;style&gt;    div {      color: red;    }  &lt;/style&gt;  &lt;/head&gt;&lt;body&gt;  &lt;div&gt;HTML에 포함1&lt;/div&gt; &lt;!-- red --&gt;  &lt;div&gt;HTML에 포함2&lt;/div&gt; &lt;!-- red --&gt;  &lt;div&gt;HTML에 포함3&lt;/div&gt; &lt;!-- red --&gt;&lt;/body&gt;</code></pre><h3><span id="abd2a66f-682f-407a-9e67-94594ba17f3e">HTML 외부에서 불러오기</span><a href="#abd2a66f-682f-407a-9e67-94594ba17f3e" class="header-anchor"></a></h3><p>CSS 코드를 완전히 분리할 수 있습니다.<br>분리된 하나의 CSS 파일을 여러 HTML 파일이 불러와서 사용할 수 있겠네요.</p><pre><code class="html">&lt;!-- HTML 1 --&gt;&lt;head&gt;  &lt;link rel=&quot;stylesheet&quot; href=&quot;/css/main.css&quot;&gt;&lt;/head&gt;&lt;body&gt;  &lt;div&gt;HTML에 외부에서 불러오기1&lt;/div&gt; &lt;!-- red --&gt;&lt;/body&gt;</code></pre><pre><code class="html">&lt;!-- HTML 2 --&gt;&lt;head&gt;  &lt;link rel=&quot;stylesheet&quot; href=&quot;/css/main.css&quot;&gt;&lt;/head&gt;&lt;body&gt;  &lt;div&gt;HTML에 외부에서 불러오기2&lt;/div&gt; &lt;!-- red --&gt;&lt;/body&gt;</code></pre><pre><code class="css">/* main.css */div {  color: red;}</code></pre><h2><span id="6e3778a3-f1b0-4b4d-ae64-b1a156cc9bb9">선택자</span><a href="#6e3778a3-f1b0-4b4d-ae64-b1a156cc9bb9" class="header-anchor"></a></h2><p>위에서 설명했듯 선택자는 HTML의 특정한 요소를 선택하는 사인(sign)입니다.<br>여러 종류가 있는데 우선 그중 2가지만 알아보겠습니다.</p><h3><span id="3f8849ed-5db3-4914-b833-2a55ae09bdb6">태그로 찾기</span><a href="#3f8849ed-5db3-4914-b833-2a55ae09bdb6" class="header-anchor"></a></h3><p>선택자를 입력하는 부분에 적용하려는(찾으려는) 태그의 이름을 입력합니다.</p><pre><code class="css">/*&lt;h1&gt;은 글자색이 빨강이야!*/h1 {  color: red;}/*&lt;p&gt;는 글자색이 파랑이야!*/p {  color: blue;}</code></pre><pre><code class="html">&lt;h1&gt;제목1&lt;/h1&gt; &lt;!--red--&gt;&lt;h1&gt;제목2&lt;/h1&gt; &lt;!--red--&gt;&lt;p&gt;본문1&lt;/p&gt; &lt;!--blue--&gt;&lt;p&gt;본문2&lt;/p&gt; &lt;!--blue--&gt;</code></pre><p>만약 ‘제목1’과 ‘본문1’에만 색상을 추가하고 싶다면 어떻게 해야 할까요?<br>‘태그 선택자’만 가지고는 어렵겠네요..</p><h3><span id="8c51ffdf-3ca0-447b-a196-d2cc0348d803">클래스로 찾기</span><a href="#8c51ffdf-3ca0-447b-a196-d2cc0348d803" class="header-anchor"></a></h3><p>좀 더 명확하게 원하는 요소를 찾기 위해서 ‘클래스 선택자’라는 것이 존재합니다.<br>예제를 봅시다.</p><pre><code class="css">/*class=&quot;title&quot;은 글자색이 빨강이야!*/.title {  color: red;}/*class=&quot;main-text&quot;는 글자색이 파랑이야!*/.main-text {  color: blue;}</code></pre><pre><code class="html">&lt;h1 class=&quot;title&quot;&gt;제목1&lt;/h1&gt; &lt;!--red--&gt;&lt;h1&gt;제목2&lt;/h1&gt;&lt;p class=&quot;main-text&quot;&gt;본문1&lt;/p&gt; &lt;!--blue--&gt;&lt;p&gt;본문2&lt;/p&gt;</code></pre><p><code>class</code>라는 HTML 속성에 원하는 별명을 입력합니다.<br>제목에는 <code>title</code>을 본문에는 <code>main-text</code>라는 별명을 지정했네요.<br>이제 CSS에서 이 별명을 기준으로 요소를 찾을 수 있습니다.<br>단, 주의할 점은 선택자에 앞에 <code>.</code>이 붙는다는 것입니다.<br><code>.</code>은 클래스를 나타내며 CSS의 <code>.title</code>은 HTML의 <code>class=&quot;title&quot;</code>와 동일합니다.<br><code>.</code>이 없는 선택자 <code>title</code>은 태그 <code>&lt;title&gt;</code>를 의미하게 되니 전혀 다른 뜻으로 인식됩니다.<br>이처럼 <code>.</code>과 같은 특수한 기호들을 이용해서 HTML과 CSS를 매칭하므로 누락하기 쉽습니다. 따라서 꼼꼼한 선택자 작성이 중요합니다.</p><h2><span id="64f1af3b-2249-41d9-99ae-6c9fbcd0fa3a">속성</span><a href="#64f1af3b-2249-41d9-99ae-6c9fbcd0fa3a" class="header-anchor"></a></h2><p>이제 속성을 알아봅시다.<br>크기, 여백, 색상 같은 눈에 보이는 스타일을 지정할 수 있습니다.</p><h3><span id="1fb7594d-3ce8-4aed-97fd-d668f8bf1b36">크기</span><a href="#1fb7594d-3ce8-4aed-97fd-d668f8bf1b36" class="header-anchor"></a></h3><h4><span id="152f7052-7ce2-4331-8aa6-00dc2050847e">width(가로 너비)</span><a href="#152f7052-7ce2-4331-8aa6-00dc2050847e" class="header-anchor"></a></h4><p>요소의 가로 너비를 지정합니다.<br>단위는 <code>px</code>(pixels)을 사용합니다.</p><pre><code class="css">div {  width: 300px;  요소가로너비: 너비값;}</code></pre><h4><span id="a8bd2c6c-a1fa-437b-b433-c9c2330c053d">height(세로 너비)</span><a href="#a8bd2c6c-a1fa-437b-b433-c9c2330c053d" class="header-anchor"></a></h4><p>요소의 세로 너비를 지정합니다.</p><pre><code class="css">div {  height: 150px;  요소세로너비: 너비값;}</code></pre><h4><span id="bb842bdb-748d-4203-aefe-41f5e846c5ca">font-size(글자 크기)</span><a href="#bb842bdb-748d-4203-aefe-41f5e846c5ca" class="header-anchor"></a></h4><p>요소 내용(Text)의 글자 크기를 지정합니다.</p><pre><code class="css">div {  font-size: 16px;  글자크기: 크기값;}</code></pre><h3><span id="d1271262-c3d8-4eba-b98b-c23fe43e3c55">여백</span><a href="#d1271262-c3d8-4eba-b98b-c23fe43e3c55" class="header-anchor"></a></h3><h4><span id="ec7d0c38-e0c5-4956-bbcd-d94bfa360f35">margin(요소의 바깥 여백)</span><a href="#ec7d0c38-e0c5-4956-bbcd-d94bfa360f35" class="header-anchor"></a></h4><p>요소의 바깥 여백을 지정합니다.<br>바깥 여백은 요소와 요소 사이의 여백(거리, 공간)을 생성할 때 사용합니다.</p><pre><code class="css">div {  margin: 20px;  요소바깥여백: 여백값;}</code></pre><p>위 코드는 <code>margin</code>은 요소의 위, 아래, 좌, 우 모두 <code>20px</code>의 여백을 지정하겠다는 의미입니다.<br>세분화하기 위해 아래와 같이 한 방향씩 지정할 수도 있습니다.<br>위 코드와 아래 코드는 같은 의미입니다.</p><pre><code class="css">div {  margin-top: 20px;  margin-right: 20px;  margin-bottom: 20px;  margin-left: 20px;  요소바깥여백-위쪽: 여백값;  요소바깥여백-오른쪽: 여백값;  요소바깥여백-아래쪽: 여백값;  요소바깥여백-왼쪽: 여백값;}</code></pre><h4><span id="711c1395-aecd-4e88-ac82-bc32fc782f7d">padding(요소의 내부 여백)</span><a href="#711c1395-aecd-4e88-ac82-bc32fc782f7d" class="header-anchor"></a></h4><p>요소의 내부 여백을 지정합니다.<br>내부 여백은 자식 요소를 감싸는 여백을 의미합니다.</p><pre><code class="css">div {  padding: 20px;  요소내부여백: 여백값;}</code></pre><p><code>margin</code>과 같이 한 방향씩 지정할 수 있습니다.</p><pre><code class="css">div {  padding-top: 20px;  padding-right: 20px;  padding-bottom: 20px;  padding-left: 20px;  요소내부여백-위쪽: 여백값;  요소내부여백-오른쪽: 여백값;  요소내부여백-아래쪽: 여백값;  요소내부여백-왼쪽: 여백값;}</code></pre><h3><span id="b88d836e-1e16-4d93-a2b5-2f7457875747">색상</span><a href="#b88d836e-1e16-4d93-a2b5-2f7457875747" class="header-anchor"></a></h3><h4><span id="5d6a2c7f-3343-4966-be9e-9d7d30510474">color(글자 색상)</span><a href="#5d6a2c7f-3343-4966-be9e-9d7d30510474" class="header-anchor"></a></h4><p>요소 내용(Text)의 글자 색상을 지정합니다.<br>정말 많은 입문자가 <code>font-color</code>, <code>text-color</code>로 실수를 합니다만 그런 속성은 없습니다.</p><pre><code class="css">div {  color: red;  글자색상: 빨강;}</code></pre><h4><span id="3e3a2dd9-5886-471f-afa3-e550400eb275">background(요소 색상)</span><a href="#3e3a2dd9-5886-471f-afa3-e550400eb275" class="header-anchor"></a></h4><p>요소의 배경 색상을 지정합니다.<br><code>color</code>는 글자의 색만 지정할 수 있으며, 요소의 (배경)색을 바꾸려면 <code>background-color</code>가 필요합니다.</p><pre><code class="css">div {  background-color: red;  요소배경: 빨강;}</code></pre><h2><span id="41b93c77-4d7f-46c0-9a8c-ff4ecdab4ba4">CSS 예제</span><a href="#41b93c77-4d7f-46c0-9a8c-ff4ecdab4ba4" class="header-anchor"></a></h2><p>HTML 끝으로 작성했던 예제에 이어서 CSS를 적용해 봅시다.<br><code>example1</code> 디렉터리 안에 <code>css</code>라는 이름의 디렉터리를 추가로 생성합니다.(Side bar 영역에서 우클릭 &gt; 새폴더 선택)<br>생성한 <code>css</code> 디렉터리 안에 <code>main.css</code> 파일을 생성합니다.</p><p>다음은 이 예제의 디렉터리와 파일 구조입니다.</p><pre><code class="bash">/example1├─ /css│  └─ main.css└─ index.html</code></pre><p>(아직 CSS 내용은 작성하지 않았지만) 생성한 <code>main.css</code> 파일은 기존의 <code>index.html</code> 파일과 연결해야 사용할 수 있습니다.<br><code>&lt;link&gt;</code>를 이용해 HTML 외부에 존재하는 CSS 파일(방금 생성한 <code>main.css</code>)을 연결합니다.</p><pre><code class="html">&lt;head&gt;  &lt;!-- 생략... --&gt;  &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/main.css&quot;&gt;&lt;/head&gt;</code></pre><p>연결이 되었으니 CSS를 다음과 같이 작성합니다.</p><pre><code class="css">body {  /* 브라우저마다 기본 설정으로 BODY 요소에 margin과 padding의 값이 설정되어 있습니다. */  /* 각각의 브라우저마다 BODY 요소가 다른 값을 가지고 있을 수 있으므로 우리가 일정하게 초기화해서 사용합니다. */  /* 0은 단위를 사용하지 않습니다. */  margin: 0;  padding: 0;}.header {  /* 화면에는 다음의 값으로 랜더링 되어 있는데 여러 이유로(설명이 많이 필요합니다) 생략 가능합니다. */  /* width: 100%; */  /* height: 75px; */  background-color: white;  border-bottom: 1px solid lightgray; /* 요소테두리선-아래: 1px두께 가는실선 밝은회색; - header 하단에 회색의 선이 표시됩니다. */}.container {  /* height: 75px; */  width: 980px;  margin: auto; /* 요소바깥여백: 여백자동; - 이 속성과 값은 container를 수평 가운데 정렬하는 속성으로 쓰입니다. */}.container-left {  /* width: 370px; */  /* height: 75px; */  /* float: left; */  padding-top: 20px;  padding-bottom: 20px;}.logo {  margin-right: 5px;  float: left; /* 수평정렬: 왼쪽부터차례대로; - logo와 menu를 수평 정렬하기 위해 사용되었습니다. 이 속성의 정확한 의미는 수평 정렬이 아니지만 쉽게 이해하도록 의역했습니다. */}.logo img { /* logo의 자식(후손) 요소인 img 태그 - 선택자에서 띄어쓰기는 자식(후손)요소를 의미합니다. */  display: block; /* 요소특성: 형태위주; - img(이미지) 하단에 생기는 불필요한 여백을 없애기 위해서 사용되었습니다. */}.menu {  float: left; /* logo와 menu를 수평 정렬하기 위해 사용되었습니다. */}.menu-item {  font-size: 16px;  padding: 8px 10px; /* padding-top: 8px; padding-bottom: 8px; padding-left: 10px; padding-right: 10px; 과 같습니다. */  float: left; /* 각 menu-item들을 수평 정렬하기 위해 사용되었습니다. */  line-height: 1; /* 줄 높이, 행간과 비슷한 개념으로 이해할 수 있습니다. 기본 값은 normal이며 이는 약 1.2배 정도입니다. 그대로 유지하면 .menu-item의 높이가 약 35px이 되기 때문에 1배로 수정하여 32px로 .logo의 크기와 동일하게 작업합니다. */}/* float: left; 를 사용하고 마무리할 때 필요한 코드입니다. *//* float: left;를 사용한 해당 HTML 요소의 부모 요소에게 class=&quot;clearfix&quot;를 입력하여 CSS float 속성에서 발생하는 현상을 해제합니다. */.clearfix::after {  content: &quot;&quot;;  display: block;  clear: both;}</code></pre><p>큰 단위의 요소(<code>header</code>)부터 코딩하는 방법과, 작은 단위 요소(<code>menu-item</code>나 <code>logo</code>)부터 코딩하는 방법을 번갈아 가면서 하나하나 변화를 눈으로 익히며 연습을 하시면 금방 익숙해질 겁니다.</p><p>이 부분이 중요한데, <code>float: left</code>를 사용했다면 사용한 요소의 부모 요소에 꼭 <code>class=&quot;clearfix&quot;</code>를 입력하고 변화를 확인하세요.(정확한 사용법과 의미는 상당한 분량이라서 여기서 설명하지 않겠습니다)<br>최종 HTML은 다음과 같습니다. 기존과 달라진 부분을 잘 살펴보세요.</p><pre><code class="html">&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;    &lt;meta charset=&quot;UTF-8&quot;&gt;    &lt;title&gt;GitHub&lt;/title&gt;    &lt;!-- link 요소가 추가 되었군요! --&gt;    &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/main.css&quot;&gt;&lt;/head&gt;&lt;body&gt;    &lt;div class=&quot;header&quot;&gt;        &lt;!-- clearfix 클래스 값이 추가 되었군요! --&gt;        &lt;!-- class 속성은 띄어쓰기로 여러 클래스를 구분하여 동시에 사용할 수 있습니다. --&gt;        &lt;!-- 아래 요소는 &lt;div class=&quot;container&quot; class=&quot;clearfix&quot;&gt; 와 같습니다. --&gt;        &lt;div class=&quot;container clearfix&quot;&gt;            &lt;!-- clearfix 클래스 값이 추가 되었군요! --&gt;            &lt;div class=&quot;container-left clearfix&quot;&gt;                &lt;div class=&quot;logo&quot;&gt;                    &lt;img src=&quot;https://heropcode.github.io/GitHub-Responsive/img/logo.svg&quot; alt=&quot;GitHub Logo&quot;&gt;                &lt;/div&gt;                &lt;!-- clearfix 클래스 값이 추가 되었군요! --&gt;                &lt;div class=&quot;menu clearfix&quot;&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Personal&lt;/div&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Open Source&lt;/div&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Business&lt;/div&gt;                    &lt;div class=&quot;menu-item&quot;&gt;Explore&lt;/div&gt;                &lt;/div&gt;            &lt;/div&gt;        &lt;/div&gt;    &lt;/div&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><p>다음과 같은 결과가 나와야 합니다.</p><p><img src="/images/screenshot/html-css-starter/html_example_result_final.jpg" alt="HTML 예제 결과 마지막"></p><h1><span id="963eec84-b2ac-453b-9ee4-f2ee79a81d69">앞으로..</span><a href="#963eec84-b2ac-453b-9ee4-f2ee79a81d69" class="header-anchor"></a></h1><p>작성하다 보니 입문자들의 이해를 돕기 위해 생략하거나 의역한 부분이 많습니다.<br>하지만 시작이 반이라고 여기까지 오셨다면 충분히 성공하셨다고 생각합니다.<br>많은 비전공 입문자가 생소한 학습 환경에 좌절하지만, HTML/CSS가 사실 그렇게 어려운 내용은 아닙니다.<br>자신감을 가질 필요가 있습니다!</p><p>자, 그러면 입문은 끝났고 앞으로는 어떻게 학습해야 하는지 간단히 정리하겠습니다.</p><p>원하는(학습하고자 하는) HTML, CSS, JavaScript(ECMAScript, ES)의 내용을 검색할 때는 <code>html div tag mdn</code> 혹은 <code>css background mdn</code>과 같이 ‘MDN’를 함께 검색하세요.<br><a href="https://developer.mozilla.org/ko/" target="_blank" rel="noopener">MDN 웹 문서</a>는 모질라(파이어폭스 웹 브라우저)의 공식 웹사이트로 대부분 문서에서 여러 설명과 예제 그리고 한글을 지원하며 매우 신뢰할 수 있습니다.<br><a href="https://www.w3.org/" target="_blank" rel="noopener">W3C의 웹 표준 문서</a>가 교과서라면 MDN은 해설집 정도로 이해하시면 쉽겠네요.</p><p>또는 <a href="https://www.w3schools.com/" target="_blank" rel="noopener">W3Schools 웹사이트</a>도 추천합니다. MDN과 다르게 영리 목적(배너 광고 등)의 사이트이지만 입문자가 접하기에 좋게 잘 구성되어 있습니다. 단, 정보의 신뢰도는 MDN보다 떨어집니다.</p><p>추가로 매우 주관적인 빠른 학습에 대해 말씀드리면,</p><p>HTML에는 태그 종류가 상당히 많습니다.<br>당장은 너무 자세하게 학습하지 마시고, 어떤 태그들이 있고 어떤 역할을 하는지 정도만 빠르게 훑고 넘어가세요.<br>그리고 필요할 때 찾아보세요!</p><p>CSS는 웹앱 디자이너, 퍼블리셔 그리고 프론트엔드 개발자에게 매우 중요합니다.<br>좋은 UI 프레임워크가 많이 있지만, 원하는 스타일을 만들기 위해서 CSS 학습이 꼭 필요합니다.<br>각 속성의 역할과 가지는 값(기본값)들을 살펴보세요.<br>특히 <code>position</code>, <code>float</code>, <code>flex</code> 속성은 집중적으로 공부하고, <code>grid</code>는 개념 정도만 이해하세요.</p><p>JavaScript는 학습 범위가 아주 넓습니다.<br>배경지식이 필요한 용어, 기술들이 많다 보니 여유를 가지고 학습하지 않으면 금방 지칠 수 있습니다.<br>당장 실무에서 필요하지 않은 내용도 많아 큰 그림을 그리도록 전반적으로 가볍게 훑어보고, 선택적으로 실무에 필요한 내용에 집중하는 것이 좋습니다.<br>발생 가능한 수많은 예외 상황들을 이론에서 모두 다룰 수 없다 보니 실무, 서브 프로젝트, 예제 등 무엇이든 직접 코드를 작성해서 만들어 보세요!</p><p>끝까지 읽어주셔서 감사드리고 이 포스트가 많은 입문자에게 도움이 되길 희망합니다.</p><h2><span id="301da5be-3e99-44af-a51a-2ea9238ebd23">패스트캠퍼스 온라인 강의[FO]</span><a href="#301da5be-3e99-44af-a51a-2ea9238ebd23" class="header-anchor"></a></h2><p>패스트캠퍼스 온라인 강의 자료들이 파편화되어 정리합니다.<br>온라인 강의와 별개로 <a href="https://github.com/HeropCode/Get-Started" target="_blank" rel="noopener">개인 강의 자료</a>도 참고하세요.<br>개인 강의 자료에서 확인 가능한 PPT자료들은 [ESC]를 누르면 전체 내용을 펼쳐볼 수 있습니다.</p><h3><span id="c3a9e27c-5af4-484a-aab4-c0c22c0a4179">[FO] HTML/CSS 입문</span><a href="#c3a9e27c-5af4-484a-aab4-c0c22c0a4179" class="header-anchor"></a></h3><p><a href="https://heropy.blog/2019/04/24/html-css-starter/">입문자에게 추천하는 HTML, CSS 첫걸음</a></p><h3><span id="6c783534-5577-4682-8287-4f0ba73c8345">[FO] HTML</span><a href="#6c783534-5577-4682-8287-4f0ba73c8345" class="header-anchor"></a></h3><p><a href="https://heropy.blog/2019/05/26/html-elements/">한눈에 보는 HTML 요소(Elements &amp; Attributes) 총정리</a><br><a href="https://heropy.blog/2019/06/16/html-img-srcset-and-sizes/">HTML IMG의 srcset과 sizes 속성(updated)</a></p><h3><span id="3e2e656a-c6f9-417f-aebd-3f2c4e56acfe">[FO] CSS</span><a href="#3e2e656a-c6f9-417f-aebd-3f2c4e56acfe" class="header-anchor"></a></h3><p><a href="https://happy-noether-c87ffa.netlify.app/presentations/level1/css/summary/" target="_blank" rel="noopener">CSS 개요</a><br><a href="https://happy-noether-c87ffa.netlify.app/presentations/level1/css/properties/" target="_blank" rel="noopener">CSS 속성</a><br><a href="https://happy-noether-c87ffa.netlify.app/presentations/level2/css3/" target="_blank" rel="noopener">CSS3 속성</a><br><a href="https://heropy.blog/2018/11/24/css-flexible-box/">CSS Flex(Flexible Box) 완벽 가이드</a><br><a href="https://heropy.blog/2019/08/17/css-grid/">CSS Grid 완벽 가이드</a></p><h3><span id="2c2608d0-1d5d-4ae2-9a88-392a8a5a5b32">[FO] SCSS</span><a href="#2c2608d0-1d5d-4ae2-9a88-392a8a5a5b32" class="header-anchor"></a></h3><p><a href="https://heropy.blog/2018/01/31/sass/">Sass(SCSS) 완전 정복!</a></p><h3><span id="c8c15a8f-6c86-47d1-aed0-1a19e2d8cd40">[FO] 마크다운(Markdown)</span><a href="#c8c15a8f-6c86-47d1-aed0-1a19e2d8cd40" class="header-anchor"></a></h3><p><a href="https://heropy.blog/2017/09/30/markdown/">MarkDown 사용법 총정리</a></p><h3><span id="48d76ae1-878e-419a-91e1-bbd9246f1b1f">[FO] VueJS</span><a href="#48d76ae1-878e-419a-91e1-bbd9246f1b1f" class="header-anchor"></a></h3><p><a href="https://github.com/HeropCode/Vue-Todo-app" target="_blank" rel="noopener">Vue Todo App 예제</a><br><a href="https://github.com/HeropCode/Vue-Movie-app" target="_blank" rel="noopener">Vue Movie App 예제</a><br><a href="https://heropy.blog/2020/05/20/vue-test-with-jest/">Jest와 Vue Test Utils(VTU)로 Vue 컴포넌트 단위(Unit) 테스트</a></p><h1><span id="8ab17cf8-bcde-4c72-82bf-4bb74328a7dd">참고자료</span><a href="#8ab17cf8-bcde-4c72-82bf-4bb74328a7dd" class="header-anchor"></a></h1><p><a href="https://namu.wiki/w/%EC%98%A4%ED%94%88%20%EC%86%8C%EC%8A%A4" target="_blank" rel="noopener">https://namu.wiki/w/%EC%98%A4%ED%94%88%20%EC%86%8C%EC%8A%A4</a><br><a href="http://www.bloter.net/archives/209318" target="_blank" rel="noopener">http://www.bloter.net/archives/209318</a><br><a href="https://opensource.org/licenses/" target="_blank" rel="noopener">https://opensource.org/licenses/</a><br><a href="https://www.freeformatter.com/html-entities.html" target="_blank" rel="noopener">https://www.freeformatter.com/html-entities.html</a><br><a href="https://wit.nts-corp.com/2013/10/16/280" target="_blank" rel="noopener">https://wit.nts-corp.com/2013/10/16/280</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;div class=&quot;toc&quot;&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;e564ab1b-f4ff-457e-99a5-8bcce51c6e8f&quot;&gt;개요&lt;/a&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;fc5f06b4-b989-499c-b3a2-4e85c0bc9c9b&quot;&gt;HTML, C
      
    
    </summary>
    
    
      <category term="html" scheme="https://heropy.blog/tags/html/"/>
    
      <category term="css" scheme="https://heropy.blog/tags/css/"/>
    
      <category term="입문" scheme="https://heropy.blog/tags/%EC%9E%85%EB%AC%B8/"/>
    
  </entry>
  
  <entry>
    <title>Blob(블랍) 이해하기</title>
    <link href="https://heropy.blog/2019/02/28/blob/"/>
    <id>https://heropy.blog/2019/02/28/blob/</id>
    <published>2019-02-28T12:48:56.000Z</published>
    <updated>2020-10-30T06:01:57.939Z</updated>
    
    <content type="html"><![CDATA[<div class="toc"><ul><li><a href="0b3cea76-3479-43b8-bb4a-6d9267e13bdd">Blob?</a></li><li><a href="6b672ee5-4ec9-4e70-b017-98f0b291eb85">Blob 생성</a><ul><li><a href="9a36417f-6a3c-4812-ba24-9fd24a7cc9ea">array</a></li><li><a href="dcf20eb0-b3fb-4137-8fbf-1e7549b9fe58">options</a></li></ul></li><li><a href="e7640397-5d72-475d-8648-061036322112">Blob 객체</a><ul><li><a href="81dabed1-cb21-447a-a384-383cdf56d3d8">Properties</a></li><li><a href="4a4198cc-6749-41a1-8728-d428d68740a1">Methods</a></li></ul></li><li><a href="2ade623e-da5e-4946-9329-410ad2f7b160">Blob URL</a><ul><li><a href="c071a9bc-c41f-4cb1-b44c-e97999337c85">createObjectURL</a></li><li><a href="47ee2cce-fca2-425a-8d82-f07590772e25">revokeObjectURL</a></li></ul></li><li><a href="596e8df4-5311-41d8-aefd-23a486726c1c">브라우저 지원 확인</a></li><li><a href="ed212da9-7a0f-41a3-a7b9-4ddaf0b8db6a">AJAX를 이용한 Blob 수신 예제</a></li><li><a href="343684f0-e440-4b30-8a2d-80c8e3237d65">참고 자료(References)</a></li></ul></div><h1><span id="0b3cea76-3479-43b8-bb4a-6d9267e13bdd">Blob?</span><a href="#0b3cea76-3479-43b8-bb4a-6d9267e13bdd" class="header-anchor"></a></h1><p>JavaScript에서 Blob(Binary Large Object, 블랍)은 이미지, 사운드, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용할 수 있습니다.<br>대개 데이터의 크기(Byte) 및 MIME 타입을 알아내거나, 데이터를 송수신을 위한 작은 Blob 객체로 나누는 등의 작업에 사용합니다.<br>이 글에서는 Blob의 생성과 읽고 쓰는 방법들에 대해서 알아보겠습니다.</p><blockquote><p><a href="https://developer.mozilla.org/ko/docs/Web/API/File" target="_blank" rel="noopener">File 객체</a>도 <code>name</code>과 <code>lastModifiedDate</code> 속성이 존재하는 Blob 객체입니다.</p></blockquote><p><img src="/images/screenshot/blob/blob-file-object.jpg" alt="Blob File Object"></p><h1><span id="6b672ee5-4ec9-4e70-b017-98f0b291eb85">Blob 생성</span><a href="#6b672ee5-4ec9-4e70-b017-98f0b291eb85" class="header-anchor"></a></h1><p>Blob 생성자는 새로운 Blob 객체를 반환합니다.<br>생성 시 인수로 <code>array</code>와 <code>options</code>을 받습니다.</p><pre><code class="js">const newBlob = new Blob(array, options);</code></pre><h2><span id="9a36417f-6a3c-4812-ba24-9fd24a7cc9ea">array</span><a href="#9a36417f-6a3c-4812-ba24-9fd24a7cc9ea" class="header-anchor"></a></h2><p>Blob 생성자의 첫번째 인수로 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer" target="_blank" rel="noopener">ArrayBuffer</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView" target="_blank" rel="noopener">ArrayBufferView</a>, Blob(<a href="https://developer.mozilla.org/ko/docs/Web/API/File" target="_blank" rel="noopener">File</a>), <a href="https://developer.mozilla.org/ko/docs/Web/API/DOMString" target="_blank" rel="noopener">DOMString</a> 객체 또는 이러한 객체가 혼합된 Array를 사용할 수 있습니다.</p><pre><code class="js">new Blob([new ArrayBuffer(data)], { type: &#39;video/mp4&#39; });new Blob(new Uint8Array(data), { type: &#39;image/png&#39; });  new Blob([&#39;&lt;div&gt;Hello Blob!&lt;/div&gt;&#39;], {  type: &#39;text/html&#39;,  endings: &#39;native&#39;});</code></pre><h2><span id="dcf20eb0-b3fb-4137-8fbf-1e7549b9fe58">options</span><a href="#dcf20eb0-b3fb-4137-8fbf-1e7549b9fe58" class="header-anchor"></a></h2><p>옵션으로는 <code>type</code>과 <code>endings</code>를 설정할 수 있습니다.<br><code>type</code>은 데이터의 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types" target="_blank" rel="noopener">MIME 타입</a>을 설정하며, 기본값은 <code>&quot;&quot;</code> 입니다.<br><code>endings</code>는 <code>\n</code>을 포함하는 문자열 처리를 <code>&quot;transparent&quot;</code>와 <code>&quot;native&quot;</code>로 지정할 수 있으며, 기본값은 <code>&quot;transparent&quot;</code>입니다.</p><h1><span id="e7640397-5d72-475d-8648-061036322112">Blob 객체</span><a href="#e7640397-5d72-475d-8648-061036322112" class="header-anchor"></a></h1><h2><span id="81dabed1-cb21-447a-a384-383cdf56d3d8">Properties</span><a href="#81dabed1-cb21-447a-a384-383cdf56d3d8" class="header-anchor"></a></h2><p>다음은 약 43KB의 PNG 이미지로 생성한 Blob 객체 입니다.</p><p><img src="/images/screenshot/blob/blob-object.jpg" alt="Blob Object"></p><p>생성자를 통해 만들어진 Blob 객체는 <code>size</code>, <code>type</code>의 속성을 가집니다.<br><code>size</code>는 Blob 객체의 바이트(Byte) 단위 크기를 의미하며, <code>type</code>은 객체의 MIME 타입을 의미합니다.<br>MIME 타입을 알 수 없는 경우 빈 문자열(<code>&quot;&quot;</code>)이 할당됩니다.</p><h2><span id="4a4198cc-6749-41a1-8728-d428d68740a1">Methods</span><a href="#4a4198cc-6749-41a1-8728-d428d68740a1" class="header-anchor"></a></h2><p>Blob 객체에서 사용할 수 있는 <code>slice</code> 메소드는 지정된 바이트 범위의 데이터를 포함하는 새로운 Blob 객체를 만드는 데 사용됩니다.<br>10MB 이상 사이즈가 큰 Blob 객체를 작게 조각내어 사용할 때 유용합니다.</p><pre><code class="js">const blob = new Blob();  // New blob objectblob.slice(start, end, type);</code></pre><p><code>start</code>는 시작 범위(Byte, Number), <code>end</code>는 종료 범위(Byte, Number), <code>type</code>은 새로운 Blob 객체의 MIME 타입(String)을 지정합니다.</p><pre><code class="js">// Blob 객체(blob)에서 첫 1KB의 JPG Blob 객체(chunk)를 생성const chunk = blob.slice(0, 1024, &#39;image/jpeg&#39;);</code></pre><p>다음 예제는 위에서 살펴본 Blob 객체(약 43KB의 PNG 이미지로 생성한)를 10개의 Chunk로 조각냅니다.<br>그리고 각 Chunk를 <code>chunks</code> 배열에 순서대로 저장합니다.</p><pre><code class="js">const chunks = [];const numberOfSlices = 10;const chunkSize = Math.ceil(blob.size / numberOfSlices);for (let i = 0; i &lt; numberOfSlices; i += 1) {  const startByte = chunkSize * i;  chunks.push(    blob.slice(      startByte,      startByte + chunkSize,      blob.type    )  );}console.log(chunks);  // This is as follows..</code></pre><p><img src="/images/screenshot/blob/blob-object-slice.jpg" alt="Blob Slice"></p><p>이렇게 조각낸 Blob 객체들(Chunks)은 필요에 의해 간단하게 다시 합칠 수 있습니다.</p><pre><code class="js">const mergedBlob = new Blob(  chunks,  { type: blob.type });document.getElementById(&#39;merged-image&#39;).src = window.URL.createObjectURL(mergedBlob);document.getElementById(&#39;chunk-image&#39;).src = window.URL.createObjectURL(chunk[0]);// Revoke Blob URL..</code></pre><p>아래 이미지는 위 코드의 결과로 왼쪽 이미지는 <code>#merged-image</code> 요소, 오른쪽 이미지는 <code>#chunk-image</code> 요소입니다.<br>오른쪽 이미지가 약 1/10로 잘려서 출력되는 것을 볼 수 있습니다.</p><blockquote><p>이미지를 시각적으로 분리(조각)하는 용도는 아니며, 이해를 돕기 위해서 첨부합니다.</p></blockquote><p><img src="/images/screenshot/blob/blob-origin-vs-sliced.jpg" alt="Blob Original image VS Sliced image"></p><p>위 코드의 마지막을 보면 <code>URL.createObjectURL()</code>을 사용하였으며, 이는 Blob 객체를 가리키는 URL을 생성하여 DOM에서 참조할 수 있게 합니다.<br>Blob URL에 대해서 간단하게 알아봅시다.</p><h1><span id="2ade623e-da5e-4946-9329-410ad2f7b160">Blob URL</span><a href="#2ade623e-da5e-4946-9329-410ad2f7b160" class="header-anchor"></a></h1><p>Blob 객체를 가리키는 URL을 생성을 위해 <a href="https://developer.mozilla.org/ko/docs/Web/API/URL" target="_blank" rel="noopener">URL 객체</a>의 정적 메소드(Static Method)로 <a href="https://developer.mozilla.org/ko/docs/Web/API/URL/createObjectURL" target="_blank" rel="noopener">createObjectURL</a>과 <a href="https://developer.mozilla.org/ko/docs/Web/API/URL/revokeObjectURL" target="_blank" rel="noopener">revokeObjectURL</a>를 사용할 수 있습니다.</p><h2><span id="c071a9bc-c41f-4cb1-b44c-e97999337c85">createObjectURL</span><a href="#c071a9bc-c41f-4cb1-b44c-e97999337c85" class="header-anchor"></a></h2><p><code>URL.createObjectURL()</code>은 Blob 객체를 나타내는 URL를 포함한 다음과 같은 DOMString를 생성합니다.(<code>blob:URL</code>)<br>이 Blob URL은 생성된 window의 document에서만(브라우저) 유효합니다.<br>다른 window에서 재활용할 수 없으며, URL의 수명이 한정되어 있기 때문에 <code>file:URL</code>과 다르게 보안 이슈에서 벗어날 수 있습니다.</p><pre><code class="plaintext">blob:http://localhost:1234/28ff8746-94eb-4dbe-9d6c-2443b581dd30</code></pre><p>다음과 같이 활용할 수 있습니다.</p><pre><code class="html">&lt;img src=&quot;blob:http://localhost:1234/28ff8746-94eb-4dbe-9d6c-2443b581dd30&quot; alt=&quot;Blob URL Image&quot; /&gt;</code></pre><h2><span id="47ee2cce-fca2-425a-8d82-f07590772e25">revokeObjectURL</span><a href="#47ee2cce-fca2-425a-8d82-f07590772e25" class="header-anchor"></a></h2><p><code>URL.revokeObjectURL()</code>은 <code>URL.createObjectURL()</code>을 통해 생성한 기존 URL을 해제(폐기)합니다.<br><code>revokeObjectURL</code>을 통해 해제하지 않으면 기존 URL를 유효하다고 판단하고 자바스크립트 엔진에서 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management#%EA%B0%80%EB%B9%84%EC%A7%80_%EC%BD%9C%EB%A0%89%EC%85%98" target="_blank" rel="noopener">GC</a> 되지 않습니다.<br>메모리 누수를 방지하기 위해 생성된 URL을 DOM과 바인딩한 후에는 해제하는 것이 좋습니다.</p><pre><code class="js">// Create Blob URLconst blobUrl = window.URL.createObjectURL(blob);// Revoke Blob URL after DOM updates..window.URL.revokeObjectURL(blobUrl);</code></pre><h1><span id="596e8df4-5311-41d8-aefd-23a486726c1c">브라우저 지원 확인</span><a href="#596e8df4-5311-41d8-aefd-23a486726c1c" class="header-anchor"></a></h1><p>Blob 객체가 브라우저에서 지원 가능한지 확인하려면 다음과 같이 작성할 수 있습니다.</p><pre><code class="js">const blobSupported = new Blob([&#39;ä&#39;]).size === 2;  // Boolean</code></pre><h1><span id="ed212da9-7a0f-41a3-a7b9-4ddaf0b8db6a">AJAX를 이용한 Blob 수신 예제</span><a href="#ed212da9-7a0f-41a3-a7b9-4ddaf0b8db6a" class="header-anchor"></a></h1><p>Promise를 반환하는 <code>loadXHR</code> 함수를 추가하고,<br>요청한 Blob 객체를 수신해,<br>이미지 태그 <code>src</code>에 URL로 삽입하는 예제입니다.</p><pre><code class="js">function loadXHR(url) {  return new Promise((resolve, reject) =&gt; {    try {      const xhr = new XMLHttpRequest();      xhr.open(&quot;GET&quot;, url);      xhr.responseType = &quot;blob&quot;;      xhr.onerror = event =&gt; {        reject(`Network error: ${event}`);      };      xhr.onload = () =&gt; {        if (xhr.status === 200) {          resolve(xhr.response);        } else {          reject(`XHR load error: ${xhr.statusText}`);        }      };      xhr.send();    } catch (err) {      reject(err.message);    }  });}</code></pre><pre><code class="js">loadXHR(&#39;https://avatars3.githubusercontent.com/u/16679082?s=460&amp;v=4&#39;)  .then(blob =&gt; {    const url = window.URL.createObjectURL(blob)    document.getElementById(&#39;image&#39;).src = url;    return url;  })  .then(url =&gt; {    // After DOM updates..    process.nextTick(() =&gt; {      window.URL.revokeObjectURL(url);      });  });</code></pre><script src="https://gist.github.com/WebReflection/2953527.js"></script><h1><span id="343684f0-e440-4b30-8a2d-80c8e3237d65">참고 자료(References)</span><a href="#343684f0-e440-4b30-8a2d-80c8e3237d65" class="header-anchor"></a></h1><p><a href="https://developer.mozilla.org/ko/docs/Web/API/Blob#%EC%86%8D%EC%84%B1" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/API/Blob#%EC%86%8D%EC%84%B1</a><br><a href="https://github.com/eligrey/Blob.js/blob/master/Blob.js" target="_blank" rel="noopener">https://github.com/eligrey/Blob.js/blob/master/Blob.js</a><br><a href="https://firejune.com/1788/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98+BLOB+%EA%B0%9D%EC%B2%B4" target="_blank" rel="noopener">https://firejune.com/1788/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98+BLOB+%EA%B0%9D%EC%B2%B4</a><br><a href="https://taegon.kim/archives/5078" target="_blank" rel="noopener">https://taegon.kim/archives/5078</a><br><a href="http://blog.naver.com/PostView.nhn?blogId=horajjan&amp;logNo=220976788530" target="_blank" rel="noopener">http://blog.naver.com/PostView.nhn?blogId=horajjan&amp;logNo=220976788530</a></p>]]></content>
    
    <summary type="html">
    
      JavaScript에서 Blob(Binary Large Object, 블랍)은 이미지, 사운드, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용할 수 있습니다. 대개 데이터의 크기(Byte) 및 MIME 타입을 알아내거나, 데이터를 송수신을 위한 작은 Blob 객체로 나누는 등의 작업에 사용합니다. 이 글에서는 Blob의 생성과 읽고 쓰는 방법들에 대해서 알아보겠습니다.
    
    </summary>
    
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
      <category term="blob" scheme="https://heropy.blog/tags/blob/"/>
    
  </entry>
  
  <entry>
    <title>내 NPM 패키지(모듈) 배포하기</title>
    <link href="https://heropy.blog/2019/01/31/node-js-npm-module-publish/"/>
    <id>https://heropy.blog/2019/01/31/node-js-npm-module-publish/</id>
    <published>2019-01-31T12:48:56.000Z</published>
    <updated>2020-10-30T05:59:33.991Z</updated>
    
    <content type="html"><![CDATA[<p>개발을 위해 <code>npm install xxx</code>로 설치하는 모듈이 많아지면서 자주 사용하는 나의 코드들도 같은 방법으로 제공하고 싶었죠.<br>하지만 ‘코드 복붙’이 더 쉬우니 차일피일 미루던 일을 최근 기회가 생겨 시작했습니다.<br>동시에 NPM 배포에 필요한 과정을 정리했습니다.</p><div class="toc"><ul><li><a href="038b878e-895a-474e-83d8-df1f9671e7c1">NPM 패키지(모듈) 배포</a><ul><li><a href="cc1f15ca-62d7-4b38-bf63-b41d4943127d">패키지 구조</a></li><li><a href="5cda2703-f9f4-4127-9137-d4d4452009be">패키지 형식</a></li><li><a href="dd440c65-9ccb-4b1d-9f3e-fa3ca1d59adc">package.json 설정</a><ul><li><a href="e3d5d6f9-967c-4b11-b74a-fa873db37337">name</a></li><li><a href="331cfa8a-5ace-4cee-b51f-faf6c86d44fa">version</a></li><li><a href="8f07eb6f-cb7b-4e56-a76c-030a372098c4">description</a></li><li><a href="e0c5cf3f-9474-483f-90d9-991ee9b9ec61">keywords</a></li><li><a href="6a529763-d03a-4cd8-9d65-5a5848d6efb2">homepage</a></li><li><a href="33dca56d-0aff-454e-a4c8-841643abb7e5">bugs</a></li><li><a href="e0a47a10-c200-4ab7-b86a-1b54ea2b931e">license</a></li><li><a href="5fe01f59-d1e0-475f-b0d1-41677df555e6">author</a></li><li><a href="64db5c84-bd9c-4677-b667-8431d96dba2f">contributors</a></li><li><a href="886bbafc-0cdf-4f51-85e0-d0b8b799ca0e">main</a></li><li><a href="e3ea4280-8150-4be1-b5f5-60b225b368c7">repository</a></li><li><a href="02188ab7-28fe-4756-ab67-bf67b7ea6d71">files</a></li><li><a href="384fac6a-6f8b-4121-9639-251422b3028e">browser</a></li><li><a href="aa16c7ba-de7f-46f5-8942-4f8ddfbddc83">dependencies</a></li><li><a href="1a07bf86-b5c4-4f63-83dc-123a679801fb">peerDependencies</a></li><li><a href="957ae3eb-4a15-4665-90e7-146fe575afbf">engines</a></li></ul></li><li><a href="cae1b7a9-ee63-4b9b-a4c9-8a04b0fadb10">README.md</a></li><li><a href="95371e09-3f1c-4d8b-938e-ecddbb417372">LICENSE</a></li><li><a href="7dd781d2-bcb7-4243-b4f9-9530d405d7ac">.npmignore</a></li><li><a href="df801c8b-f482-40d7-8104-22a93e46a566">NPM 로그인</a></li><li><a href="46b179b4-11e1-4c4f-89d5-beb3f3c5d4cd">배포 전 테스트</a></li><li><a href="ee5f82de-5c2c-49f2-b41d-6b161a314ec3">배포</a></li><li><a href="39573bba-6d57-40db-955b-c12f1e58a711">사용</a></li></ul></li><li><a href="0b91c11c-4657-44a3-ba95-d18abe1f4412">참고 자료(References)</a></li></ul></div><h1><span id="038b878e-895a-474e-83d8-df1f9671e7c1">NPM 패키지(모듈) 배포</span><a href="#038b878e-895a-474e-83d8-df1f9671e7c1" class="header-anchor"></a></h1><p><img src="/images/screenshot/node-js-npm-module-publish/node-js-npm-module-publish1.jpg" alt="NPM Home"></p><blockquote><p>모듈은 Node.js <code>require()</code> 함수에 의해 로드할 수 있는 <code>node_modules</code> 디렉터리의 파일 또는 디렉터리입니다.<br>모듈은 <code>package.json</code> 파일을 가질 필요가 없기 때문에 모든 모듈이 패키지는 아닙니다.<br><code>package.json</code> 파일을 가진 모듈들만 패키지입니다.</p></blockquote><h2><span id="cc1f15ca-62d7-4b38-bf63-b41d4943127d">패키지 구조</span><a href="#cc1f15ca-62d7-4b38-bf63-b41d4943127d" class="header-anchor"></a></h2><p>다음과 같은 패키지 구조를 추천합니다.</p><ul><li><code>dist/</code>는 배포용 디렉터리입니다. <a href="https://webpack.js.org/" target="_blank" rel="noopener">Webpack</a>이나 <a href="https://parceljs.org/" target="_blank" rel="noopener">Parcel</a>, <a href="https://gulpjs.com/" target="_blank" rel="noopener">Gulp</a> 같은 번들러로 배포용 파일을 생성할 수 있습니다.</li><li><code>examples/</code>는 제공할 예제 디렉터리입니다. 예제 파일과 함께 Demo 페이지를 제공한다면 더욱 좋습니다.</li><li><code>lib/</code>에는 패키지 제작을 위한 원본 파일이 포함되어 있습니다.</li></ul><pre><code class="plaintext">my_npm_module/├── dist/├── examples/├── lib/├── .gitignore├── .npmignore├── LICENSE└── package.json</code></pre><h2><span id="5cda2703-f9f4-4127-9137-d4d4452009be">패키지 형식</span><a href="#5cda2703-f9f4-4127-9137-d4d4452009be" class="header-anchor"></a></h2><p>다음 예시와 같이 <code>AMD</code>, <code>UMD</code>, <code>CommonJS</code>, <code>ES Module</code> 등의 제공할 패키지 형식을 지정합니다.</p><pre><code class="plaintext">dist/├── my_module.js        (UMD)├── my_module.min.js    (UMD, compressed)├── my_module.common.js (CommonJS, default)└── my_module.esm.js    (ES Module)</code></pre><p>패키지 형식에 대한 자세한 내용은 <a href="https://github.com/codepink/codepink.github.com/wiki/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AA%A8%EB%93%88,-%EB%AA%A8%EB%93%88-%ED%8F%AC%EB%A7%B7,-%EB%AA%A8%EB%93%88-%EB%A1%9C%EB%8D%94%EC%99%80-%EB%AA%A8%EB%93%88-%EB%B2%88%EB%93%A4%EB%9F%AC%EC%97%90-%EB%8C%80%ED%95%9C-10%EB%B6%84-%EC%9E%85%EB%AC%B8%EC%84%9C" target="_blank" rel="noopener">자바스크립트 모듈, 모듈 포맷, 모듈 로더와 모듈 번들러에 대한 10분 입문서</a>을 참고하세요.</p><p>패키지 번들러로 Webpack을 사용한다면 <a href="https://webpack.js.org/configuration/output/#output-librarytarget" target="_blank" rel="noopener">output.libraryTarget</a>을, Parcel을 사용한다면 <a href="https://parceljs.org/cli.html#%EB%AA%A8%EB%93%88%EC%9D%84-umd-%EB%A1%9C-%EC%B6%9C%EB%A0%A5" target="_blank" rel="noopener">모듈을 UMD 로 출력</a>을 참고하세요.</p><h2><span id="dd440c65-9ccb-4b1d-9f3e-fa3ca1d59adc">package.json 설정</span><a href="#dd440c65-9ccb-4b1d-9f3e-fa3ca1d59adc" class="header-anchor"></a></h2><p>이해를 돕기 위해 일부 예제에서 <a href="https://github.com/axios/axios/blob/master/package.json" target="_blank" rel="noopener">axios/package.json</a>을 참고했습니다.</p><h3><span id="e3d5d6f9-967c-4b11-b74a-fa873db37337">name</span><a href="#e3d5d6f9-967c-4b11-b74a-fa873db37337" class="header-anchor"></a></h3><p>URL이나 Command Line의 일부로 사용될 소문자로 표기된 214자 이내의 패키지(모듈) 이름으로, 간결하고 직관적인 이름으로 설정하되 다른 모듈과 중복되지 않도록 고유한 이름을 설정합니다.</p><h3><span id="331cfa8a-5ace-4cee-b51f-faf6c86d44fa">version</span><a href="#331cfa8a-5ace-4cee-b51f-faf6c86d44fa" class="header-anchor"></a></h3><p><a href="https://docs.npmjs.com/misc/semver" target="_blank" rel="noopener">SemVer(The semantic versioner for npm)</a>로 분석 가능한 형태의 버전을 지정합니다.</p><pre><code class="json">{  &quot;version&quot;: &quot;0.19.0-beta.1&quot;}</code></pre><h3><span id="8f07eb6f-cb7b-4e56-a76c-030a372098c4">description</span><a href="#8f07eb6f-cb7b-4e56-a76c-030a372098c4" class="header-anchor"></a></h3><p>프로젝트(패키지)의 설명을 지정합니다.<br>(npm search 사용 시 도움이 됩니다.)</p><pre><code class="json">{  &quot;description&quot;: &quot;Promise based HTTP client for the browser and node.js&quot;}</code></pre><h3><span id="e0c5cf3f-9474-483f-90d9-991ee9b9ec61">keywords</span><a href="#e0c5cf3f-9474-483f-90d9-991ee9b9ec61" class="header-anchor"></a></h3><p>프로젝트(패키지)의 키워드를 배열로 지정합니다.<br>(npm search 사용 시 도움이 됩니다.)</p><pre><code class="json">{  &quot;keywords&quot;: [    &quot;xhr&quot;,    &quot;http&quot;,    &quot;ajax&quot;,    &quot;promise&quot;,    &quot;node&quot;  ]}</code></pre><h3><span id="6a529763-d03a-4cd8-9d65-5a5848d6efb2">homepage</span><a href="#6a529763-d03a-4cd8-9d65-5a5848d6efb2" class="header-anchor"></a></h3><p>다음과 같이 프로젝트 홈페이지로 연결되는 URL을 지정합니다.</p><pre><code class="json">{  &quot;homepage&quot;: &quot;https://github.com/axios/axios&quot;}</code></pre><h3><span id="33dca56d-0aff-454e-a4c8-841643abb7e5">bugs</span><a href="#33dca56d-0aff-454e-a4c8-841643abb7e5" class="header-anchor"></a></h3><p>패키지에 문제가 있을 때 보고될 이슈 트래커(추적시스템) 및 이메일 주소 등에 대한 URL을 지정합니다.</p><pre><code class="json">{  &quot;bugs&quot;: {    &quot;url&quot;: &quot;https://github.com/axios/axios/issues&quot;,    &quot;email&quot;: &quot;issues@axios.com&quot;  }}</code></pre><h3><span id="e0a47a10-c200-4ab7-b86a-1b54ea2b931e">license</span><a href="#e0a47a10-c200-4ab7-b86a-1b54ea2b931e" class="header-anchor"></a></h3><p>패키지 사용을 허용하는 방법과 제한 사항을 알 수 있도록 라이센스를 지정합니다.<br><a href="https://opensource.org/licenses" target="_blank" rel="noopener">Open Source Licenses</a></p><pre><code class="json">{  &quot;license&quot;: &quot;MIT&quot;}</code></pre><p>패키지가 여러 공용 라이센스일 경우 <a href="https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60" target="_blank" rel="noopener">SPDX 라이센스 표현식</a>을 사용하세요.</p><pre><code class="json">{  &quot;license&quot;: &quot;(MIT OR Apache-2.0)&quot;}</code></pre><p>더 이상 다음과 같이 사용하지 않습니다.</p><pre><code class="json">// Not valid metadata{  &quot;licenses&quot; : [    {      &quot;type&quot;: &quot;MIT&quot;,      &quot;url&quot;: &quot;https://www.opensource.org/licenses/mit-license.php&quot;    },    {      &quot;type&quot;: &quot;Apache-2.0&quot;,      &quot;url&quot;: &quot;https://opensource.org/licenses/apache2.0.php&quot;    }  ]}</code></pre><h3><span id="5fe01f59-d1e0-475f-b0d1-41677df555e6">author</span><a href="#5fe01f59-d1e0-475f-b0d1-41677df555e6" class="header-anchor"></a></h3><p>제작자의 이름을 지정합니다.</p><pre><code class="json">{  &quot;author&quot;: {    &quot;name&quot;: &quot;Steven Wanderski&quot;,    &quot;email&quot;: &quot;steven@bxcreative.com&quot;,    &quot;url&quot;: &quot;http://stevenwanderski.com&quot;  }}</code></pre><p>하나의 문자열로 줄일 수 있습니다.<br>NPM이 자동으로 파싱합니다.</p><pre><code class="json">{  &quot;author&quot;: &quot;Steven Wanderski &lt;steven@bxcreative.com&gt; (http://stevenwanderski.com)&quot;}</code></pre><h3><span id="64db5c84-bd9c-4677-b667-8431d96dba2f">contributors</span><a href="#64db5c84-bd9c-4677-b667-8431d96dba2f" class="header-anchor"></a></h3><p>제작자가 여러 명일 경우 <code>author</code> 옵션(field) 대신에 사용할 수 있습니다.</p><pre><code class="json">{  &quot;contributors&quot;: [    &quot;Evan &lt;evan@zillinks.com&gt; (https://zillinks.com)&quot;,    &quot;Lewis &lt;lewis@zillinks.com&gt; (https://zillinks.com)&quot;,    &quot;Neo &lt;neo@zillinks.com&gt; (https://zillinks.com)&quot;  ]}</code></pre><h3><span id="886bbafc-0cdf-4f51-85e0-d0b8b799ca0e">main</span><a href="#886bbafc-0cdf-4f51-85e0-d0b8b799ca0e" class="header-anchor"></a></h3><p>프로그램의 기본 진입 점(entry point)를 지정합니다.<br>패키지의 이름이 <code>axios</code>이고, 사용자가 <code>require(&#39;axios&#39;)</code> or <code>import &#39;axios&#39;</code>를 사용하면 진입 점의 메인 모듈에서 exports object가 반환(return)됩니다.</p><pre><code class="json">{  &quot;main&quot;: &quot;dist/my_module.min.js&quot;}</code></pre><h3><span id="e3ea4280-8150-4be1-b5f5-60b225b368c7">repository</span><a href="#e3ea4280-8150-4be1-b5f5-60b225b368c7" class="header-anchor"></a></h3><p>코드가 존재하는 장소(주소)를 지정합니다.<br><code>npm docs</code> 명령을 사용하여 패키지 저장소를 쉽게 찾을 수 있습니다.</p><pre><code class="bash">$ npm docs axios</code></pre><pre><code class="json">{  &quot;repository&quot;: {    &quot;type&quot;: &quot;git&quot;,    &quot;url&quot;: &quot;https://github.com/axios/axios.git&quot;  }}</code></pre><h3><span id="02188ab7-28fe-4756-ab67-bf67b7ea6d71">files</span><a href="#02188ab7-28fe-4756-ab67-bf67b7ea6d71" class="header-anchor"></a></h3><p>패키지가 의존성으로 설치될 때 같이 포함될 파일(디렉터리)들의 배열입니다.<br>생략하면 자동 제외로 설정된 파일을 제외한 모든 파일이 포함됩니다</p><pre><code class="json">{  &quot;files&quot;: [    &quot;dist&quot;,    &quot;lib&quot;,    &quot;!dist/test&quot;  ]}</code></pre><h3><span id="384fac6a-6f8b-4121-9639-251422b3028e">browser</span><a href="#384fac6a-6f8b-4121-9639-251422b3028e" class="header-anchor"></a></h3><p>클라이언트(client-side) 사용을 위하는 경우 자바스크립트 번들러 또는 구성 요소 도구에 대한 힌트로 제공하기 위해 <code>main</code> 옵션(field) 대신에 사용해야 합니다.<br>더 자세한 내용은 <a href="https://github.com/defunctzombie/package-browser-field-spec" target="_blank" rel="noopener">Package browser field spec</a>을 참고하세요.</p><pre><code class="json">{  &quot;browser&quot;: &quot;./browser/specific/main.js&quot;}</code></pre><h3><span id="aa16c7ba-de7f-46f5-8942-4f8ddfbddc83">dependencies</span><a href="#aa16c7ba-de7f-46f5-8942-4f8ddfbddc83" class="header-anchor"></a></h3><p>패키지의 배포 시 포함될 의존성 모듈을 지정합니다.</p><pre><code class="json">{  &quot;dependencies&quot;: {    &quot;follow-redirects&quot;: &quot;^1.4.1&quot;,    &quot;is-buffer&quot;: &quot;^2.0.2&quot;  }}</code></pre><h3><span id="1a07bf86-b5c4-4f63-83dc-123a679801fb">peerDependencies</span><a href="#1a07bf86-b5c4-4f63-83dc-123a679801fb" class="header-anchor"></a></h3><p>패키지의 호환성 모듈을 지정합니다.<br>(npm@3 이후로 배포 시 포함되지 않습니다, 대신 호환성 모듈이 없으면 경고 메시지가 표시됩니다)</p><pre><code class="json">{  &quot;name&quot;: &quot;bootstrap&quot;,  &quot;peerDependencies&quot;: {    &quot;jquery&quot;: &quot;1.9.1 - 3&quot;,    &quot;popper.js&quot;: &quot;^1.12.9&quot;  }}</code></pre><h3><span id="957ae3eb-4a15-4665-90e7-146fe575afbf">engines</span><a href="#957ae3eb-4a15-4665-90e7-146fe575afbf" class="header-anchor"></a></h3><p>패키지가 작동하는 Node 버전을 지정합니다.</p><pre><code class="json">{  &quot;engines&quot;: {    &quot;node&quot;: &quot;&gt;=0.10.3 &lt;0.12&quot;,    &quot;npm&quot; : &quot;~1.0.20&quot;  }}</code></pre><h2><span id="cae1b7a9-ee63-4b9b-a4c9-8a04b0fadb10">README.md</span><a href="#cae1b7a9-ee63-4b9b-a4c9-8a04b0fadb10" class="header-anchor"></a></h2><p>패키지에서 제공할 문서를 작성합니다.<br>패키지에 대한 정의, 사용법, 예제 등을 자세하게 정리할 수 있습니다.<br>NPM과 대부분의 저장소는 <a href="https://heropy.blog/2017/09/30/markdown/">마크다운 문법</a>을 지원합니다.</p><p>다음은 <a href="https://github.com/axios/axios/blob/master/README.md" target="_blank" rel="noopener">axios/README.md</a> 일부입니다.</p><pre><code class="markdown">## Browser Support![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/src/safari/safari_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/src/opera/opera_48x48.png) | ![Edge](https://raw.github.com/alrra/browser-logos/master/src/edge/edge_48x48.png) | ![IE](https://raw.github.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png) |--- | --- | --- | --- | --- | --- |Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | 11 ✔ |[![Browser Matrix](https://saucelabs.com/open_sauce/build_matrix/axios.svg)](https://saucelabs.com/u/axios)## InstallingUsing npm:</code></pre><h2><span id="95371e09-3f1c-4d8b-938e-ecddbb417372">LICENSE</span><a href="#95371e09-3f1c-4d8b-938e-ecddbb417372" class="header-anchor"></a></h2><p>다른 사람들이 쉽게 기여할 수 있도록 저장소에 오픈 소스 라이센스를 포함할 수 있습니다.<br>패키지 전역에 <code>LICENSE</code> 혹은 <code>LICENSE.md</code>(<a href="https://heropy.blog/2017/09/30/markdown/">Markdown</a>) 파일(모두 대문자)을 생성합니다.</p><p>다음은 MIT License 샘플입니다.</p><pre><code class="plaintext">MIT LicenseCopyright (c) 2019 HEROPY &lt;thesecon@gmail.com&gt;Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the &quot;Software&quot;), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.</code></pre><p>GitHub에서 저장소를 관리한다면 라이센스 템플릿(License template)을 선택하여 제출할 수 있습니다.<br>자세한 내용은 <a href="https://help.github.com/articles/adding-a-license-to-a-repository/" target="_blank" rel="noopener">Adding a license to a repository</a>를 참고하세요.</p><h2><span id="7dd781d2-bcb7-4243-b4f9-9530d405d7ac">.npmignore</span><a href="#7dd781d2-bcb7-4243-b4f9-9530d405d7ac" class="header-anchor"></a></h2><p>패키지에서 배포하지 않을 파일이나 디렉터리를 지정합니다.<br><code>.npmignore</code> 없이 <code>.gitignore</code> 파일만 사용할 수도 있습니다.</p><p>단, 빠른 패키지 배포와 효율적인 프로젝트 저장소 관리를 위해 각 파일을 따로 분리해서 작성하는 것이 좋습니다.</p><p><a href="https://www.gitignore.io/" target="_blank" rel="noopener">gitignore.io Template Source</a>를 사용하면 쉽고 빠르게 <code>.npmignore</code> 혹은 <code>.gitignore</code> 파일을 생성할 수 있습니다.</p><p><a href="https://github.com/axios/axios/blob/master/.gitignore" target="_blank" rel="noopener"><code>.gitignore</code> of axios</a></p><pre><code class="plaintext">*.iml.idea.tscache.DS_Storenode_modules/typings/coverage/test/typescript/axios.js*sauce_connect.logpackage-lock.json</code></pre><p><a href="https://github.com/axios/axios/blob/master/.npmignore" target="_blank" rel="noopener"><code>.npmignore</code> of axios</a></p><pre><code class="plaintext">**/.**.imlcoverage/examples/node_modules/typings/sandbox/test/bower.jsonCODE_OF_CONDUCT.mdCOLLABORATOR_GUIDE.mdCONTRIBUTING.mdCOOKBOOK.mdECOSYSTEM.mdGruntfile.jskarma.conf.jswebpack.*.jssauce_connect.log</code></pre><h2><span id="df801c8b-f482-40d7-8104-22a93e46a566">NPM 로그인</span><a href="#df801c8b-f482-40d7-8104-22a93e46a566" class="header-anchor"></a></h2><p><img src="/images/screenshot/node-js-npm-module-publish/node-js-npm-module-publish2.jpg" alt="NPM Home"></p><p>NPM에 모듈을 배포하기 위해서는 사용자 등록(회원가입) 및 로그인을 해야 합니다.<br>이 때 사용해야 하는 CLI 몇 가지를 간단하게 살펴봅시다.</p><p><code>npm adduser</code> or <code>npm login</code></p><p>NPM 회원으로 가입하기(<a href="https://www.npmjs.com/signup" target="_blank" rel="noopener">NPM 홈페이지</a>에 진행하는 것을 추천)<br>아이디가 있으면 NPM 로그인</p><blockquote><p>회원가입 후 바로 이메일 인증을 받으세요!</p></blockquote><p><code>npm logout</code></p><p>NPM에 로그인된 아이디가 있으면 로그아웃</p><p><code>npm whoami</code></p><p>로그인 된 아이디(username)를 표시</p><h2><span id="46b179b4-11e1-4c4f-89d5-beb3f3c5d4cd">배포 전 테스트</span><a href="#46b179b4-11e1-4c4f-89d5-beb3f3c5d4cd" class="header-anchor"></a></h2><p>배포 전에 로컬 환경에서 테스트할 수 있습니다.<br>내가 작성한 패키지 디렉터리(<code>my_npm_module/</code>)를 기준으로 다음과 같이 새로운 테스트 디렉터리를(<code>npm_install_test/</code>)를 생성합니다.</p><pre><code class="bash">$ cd ..$ mkdir npm_install_test$ cd npm_install_test$ npm init -y</code></pre><p>나의 패키지가 정상적으로 동작하는지 확인하기 위해 상대경로로 <code>npm install</code>을 진행할 것입니다.<br>다음과 같이 상대경로는 파일이 아닌 나의 패키지 디렉터리로 합니다.<br>패키지를 제대로 작성했다면 <code>package.json</code>의 <code>main</code>(<code>browser</code>) 옵션에 맞게 잘 동작하겠죠?!</p><pre><code class="bash">$ npm install ../my_npm_module</code></pre><p>새로운 테스트 프로젝트 내에서 나의 패키지를 가져와 정상적으로 동작하는지 테스트 하겠습니다.<br>다음 예시와 같이 내가 원하는 기능이 정상적으로 동작한다면 실제 배포할 준비가 끝났습니다.</p><pre><code class="js">import myModule from &#39;my_npm_module&#39;;// const myModule = require(&#39;my_npm_module&#39;);const message = myModule.run();console.log(message);// &#39;Hello world!&#39;</code></pre><h2><span id="ee5f82de-5c2c-49f2-b41d-6b161a314ec3">배포</span><a href="#ee5f82de-5c2c-49f2-b41d-6b161a314ec3" class="header-anchor"></a></h2><p>이제 작성한 모듈을 NPM에 배포합니다.</p><p><code>npm publish</code></p><p>배포가 정상적으로 이루어졌다면 가상의 모듈 <code>my_npm_module</code>을 기준으로 다음과 같은 메시지를 확인할 수 있습니다.</p><pre><code class="bash"># ...npm notice === Tarball Details ===npm notice name:          my_npm_module                                  npm notice version:       0.1.0                                   npm notice package size:  591.2 kB                                npm notice unpacked size: 6.3 MB                                  npm notice shasum:        ab1234aa331abcd30f0f8d854ab6390123456789npm notice integrity:     sha512-AbcDEfGHuBczK[...]27123Cd65mMTQ==npm notice total files:   422                                     npm notice+ my_npm_module@0.1.0</code></pre><p>실제 NPM 검색 결과에 반영되는 것은 시간이 조금 필요할 수 있습니다.<br>따라서 배포된 나의 패키지를 확인하기 위해 <code>https://www.npmjs.com/package/my_npm_module</code>로 접속할 수 있습니다.<br>설정한 모듈이 정상적으로 나오는지 확인합니다.</p><h2><span id="39573bba-6d57-40db-955b-c12f1e58a711">사용</span><a href="#39573bba-6d57-40db-955b-c12f1e58a711" class="header-anchor"></a></h2><p>이제 모든 것이 끝났습니다.<br>평소 다른 패키지들을 사용하듯 설치하여 사용할 수 있습니다.</p><pre><code class="bash">$ npm install my_npm_module</code></pre><h1><span id="0b91c11c-4657-44a3-ba95-d18abe1f4412">참고 자료(References)</span><a href="#0b91c11c-4657-44a3-ba95-d18abe1f4412" class="header-anchor"></a></h1><p><a href="https://docs.npmjs.com/about-package-readme-files" target="_blank" rel="noopener">https://docs.npmjs.com/about-package-readme-files</a><br><a href="https://help.github.com/articles/adding-a-license-to-a-repository/" target="_blank" rel="noopener">https://help.github.com/articles/adding-a-license-to-a-repository/</a><br><a href="https://blog.outsider.ne.kr/829" target="_blank" rel="noopener">https://blog.outsider.ne.kr/829</a><br><a href="https://heropy.blog/2018/02/18/node-js-npm/">https://heropy.blog/2018/02/18/node-js-npm/</a><br><a href="https://trustyoo86.github.io/webpack/2018/01/10/webpack-configuration.html" target="_blank" rel="noopener">https://trustyoo86.github.io/webpack/2018/01/10/webpack-configuration.html</a><br><a href="https://medium.com/webpack/the-state-of-javascript-modules-4636d1774358" target="_blank" rel="noopener">https://medium.com/webpack/the-state-of-javascript-modules-4636d1774358</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;개발을 위해 &lt;code&gt;npm install xxx&lt;/code&gt;로 설치하는 모듈이 많아지면서 자주 사용하는 나의 코드들도 같은 방법으로 제공하고 싶었죠.&lt;br&gt;하지만 ‘코드 복붙’이 더 쉬우니 차일피일 미루던 일을 최근 기회가 생겨 시작했습니다.
      
    
    </summary>
    
    
      <category term="nodejs" scheme="https://heropy.blog/tags/nodejs/"/>
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
      <category term="npm" scheme="https://heropy.blog/tags/npm/"/>
    
  </entry>
  
  <entry>
    <title>HTML Email Template 만들기</title>
    <link href="https://heropy.blog/2018/12/30/html-email-template/"/>
    <id>https://heropy.blog/2018/12/30/html-email-template/</id>
    <published>2018-12-30T08:00:00.000Z</published>
    <updated>2019-01-02T02:14:55.000Z</updated>
    
    <content type="html"><![CDATA[<p>우리는 웹을 위해 HTML, CSS를 작성할 때 표준을 준수해야 합니다.<br>표준은 누구나 지켜야 하는 규칙이기 때문에 일종의 작성 가이드 역할을 할 수 있습니다.<br>그러나 이메일(Email)에는 표준이 없습니다.<br>수 많은 클라이언트의 랜더링 가능성을 고려해야 하며, 제공하는 이메일(서비스)의 성격에 따라 고려해야 하는 클라이언트가 늘어나거나 줄어들 수 있습니다.</p><div class="toc"><ul><li><a href="26a7e305-c9fc-48fe-bb52-843e0a198da1">HTML Email Template</a><ul><li><a href="8725faa7-4510-4534-9662-00452f42d05f">이메일 클라이언트</a><ul><li><a href="81a91fbc-4b58-464e-a5a2-28016ae7f142">모바일 이메일 클라이언트</a></li><li><a href="a03ce809-6827-4b56-b682-7643743c2f85">데스크톱 이메일 클라이언트</a></li><li><a href="ba629311-aa1b-4a1d-8641-6882dbd2dd75">웹 메일 클라이언트</a></li></ul></li><li><a href="f32df3b7-682e-4155-8b08-f297fb218245">준비하기</a><ul><li><a href="45bd877f-94c2-4459-9007-17d091d42a62">DOCTYPE</a></li><li><a href="d2ce4ce9-3f21-42b5-bf7f-4ce2a50696e3">Head</a></li><li><a href="2a25ce98-008c-4f9e-8084-477efdac5c43">Body</a></li><li><a href="10a1e8a9-4a7b-4e5b-b2d7-e0e8c3e39eff">Style</a></li></ul></li><li><a href="94c12fa9-4239-4736-b145-184a4f0f75e1">작성하기</a><ul><li><a href="260d09b5-aea7-4a3c-bbb0-cb18e8f2ab95">table, tr, td</a></li><li><a href="b23ab0fb-4cbd-47af-8309-ff7e102d37c0">HTML 속성 및 인라인 스타일</a><ul><li><a href="eb8ec0cd-0f5a-4563-bd32-bde4902a6b64">table</a></li><li><a href="4062666b-0fa1-497f-9a20-a7fbd1406150">td</a></li></ul></li><li><a href="95682f5e-fc3e-44f9-8188-3965fea120a0">중첩 테이블</a></li><li><a href="4e18dbe3-8ade-4d6e-bbfe-776c9c0e9842">색상</a></li><li><a href="fd72dbfd-fb61-40d1-b22e-3f4854c421c2">단일 클래스</a></li><li><a href="837d2a2e-0220-4979-9424-0b6563d67ebb">CSS 속성</a></li><li><a href="48111046-d3a8-4ea6-b283-97096af9e334">이미지</a><ul><li><a href="e623b0de-1683-4cda-8b8a-2e92f3503096">고정 이미지 삽입</a></li></ul></li><li><a href="25f02e86-332c-45ec-bcd1-2c65e8d9ab53">여백</a></li><li><a href="16b40368-412b-4971-856f-072cf9266a82">Text</a></li><li><a href="71d33a11-8db4-4542-8f8f-01905303e006">조건부 주석(Conditional Comments)</a></li><li><a href="411ee16e-5381-4815-b7b7-6b949ec8087f">방탄 버튼(Bulletproof Buttons)</a></li></ul></li><li><a href="a3338901-7548-45b2-b2f7-a7fdeb0b100b">검증</a></li></ul></li><li><a href="caeb068e-05e4-428b-b294-6f07d3ab9adf">참고 자료(References)</a></li></ul></div><h1><span id="26a7e305-c9fc-48fe-bb52-843e0a198da1">HTML Email Template</span><a href="#26a7e305-c9fc-48fe-bb52-843e0a198da1" class="header-anchor"></a></h1><p>다양한 랜더링 가능성을 고려하다 보면 <code>&lt;div&gt;</code> 태그나 <code>margin</code> 속성 같이 표준 코딩에서 자주 사용하는 개념을 사용할 수 없거나 거의 모든 부분에서 <code>&lt;table&gt;</code>, <code>&lt;td&gt;</code> 태그에 의존하므로 일반적인 표준 HTML과 CSS의 작성 방식으로는 이메일 템플릿을 소화하기 어렵습니다.</p><p>이메일 템플릿은 하위 호환성을 고려하는 것이 좋습니다.<br>특히 <code>&lt;table&gt;</code>를 사용하는 코딩에 익숙하다면 많은 도움이 될 것입니다.</p><h2><span id="8725faa7-4510-4534-9662-00452f42d05f">이메일 클라이언트</span><a href="#8725faa7-4510-4534-9662-00452f42d05f" class="header-anchor"></a></h2><p>HTML 이메일 템플릿을 제작할 때 고려해야 할 가장 일반적인 이메일 클라이언트 목록을 살펴봅시다.</p><h3><span id="81a91fbc-4b58-464e-a5a2-28016ae7f142">모바일 이메일 클라이언트</span><a href="#81a91fbc-4b58-464e-a5a2-28016ae7f142" class="header-anchor"></a></h3><p>Android 2.3 및 4.0<br>iPhone 5 iOS 6<br>iPhone 4S iOS 6<br>iPhone 3GS iOS 5<br>iPad 2 iOS 6<br>BlackBerry OS 4 &amp; 5<br>Symbian S60<br>Windows Phone 7.5</p><h3><span id="a03ce809-6827-4b56-b682-7643743c2f85">데스크톱 이메일 클라이언트</span><a href="#a03ce809-6827-4b56-b682-7643743c2f85" class="header-anchor"></a></h3><p>Apple Mail 4, 5, 6<br>Lotus Notes 8.5<br>Lotus Notes 8<br>Thunderbird<br>Windows Live Mail<br>Outlook 2013 (v15)<br>Mac 용 Outlook 2011<br>Outlook 2010 (v14)<br>Outlook 2007 (v12)<br>Outlook 2003 (v11)<br>Outlook 2002 / XP (v10)<br>Outlook 2000 (v9)</p><h3><span id="ba629311-aa1b-4a1d-8641-6882dbd2dd75">웹 메일 클라이언트</span><a href="#ba629311-aa1b-4a1d-8641-6882dbd2dd75" class="header-anchor"></a></h3><p>AOL Mail (on any browser)<br>Gmail (on any browser)<br>Outlook.com (on any browser)<br>Yahoo! (on any browser)</p><h2><span id="f32df3b7-682e-4155-8b08-f297fb218245">준비하기</span><a href="#f32df3b7-682e-4155-8b08-f297fb218245" class="header-anchor"></a></h2><h3><span id="45bd877f-94c2-4459-9007-17d091d42a62">DOCTYPE</span><a href="#45bd877f-94c2-4459-9007-17d091d42a62" class="header-anchor"></a></h3><p><a href="https://www.w3.org/wiki/Choosing_the_right_doctype_for_your_HTML_documents" target="_blank" rel="noopener">Doctype</a>은 이메일 클라이언트에게 HTML 유형을 알려주고 <a href="https://validator.w3.org/" target="_blank" rel="noopener">W3C Validator</a> 같은 도구를 사용하여 HTML 품질 검사를 할 수 있습니다.<br>XHTML 1.0 Transitional doctype을 사용하면 이메일 클라이언트에서 신뢰할 수 있는 방식으로 유효성을 검사하고 이메일 렌더링에 도움이 됩니다.</p><p>일부 이메일 클라이언트는 제공하는 Doctype을 자신의 Doctype으로 대체합니다.</p><pre><code class="html">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;&lt;/html&gt;</code></pre><h3><span id="d2ce4ce9-3f21-42b5-bf7f-4ce2a50696e3">Head</span><a href="#d2ce4ce9-3f21-42b5-bf7f-4ce2a50696e3" class="header-anchor"></a></h3><p>텍스트와 특수 문자가 올바르게 표시되도록 문자 인코딩 방식(<code>UTF-8</code>)을 설정합니다.<br>Doctype을 XHTML로 설정했기 때문에 <code>Content-Type</code> 선언을 포함해야 합니다.</p><p>추가로 제목(title), 디바이스의 화면 영역(<a href="https://www.w3schools.com/css/css_rwd_viewport.asp" target="_blank" rel="noopener">viewport</a>)에 대해 설정합니다.<br>XHTML은 빈(Empty) 태그는 반드시 닫아야(<code>/</code>) 합니다. (<code>&lt;link/&gt;</code>, <code>&lt;img/&gt;</code>, <code>&lt;br/&gt;</code>)</p><pre><code class="html">&lt;head&gt;  &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;  &lt;title&gt;HTML Email Template&lt;/title&gt;  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;&lt;/head&gt;</code></pre><h3><span id="2a25ce98-008c-4f9e-8084-477efdac5c43">Body</span><a href="#2a25ce98-008c-4f9e-8084-477efdac5c43" class="header-anchor"></a></h3><p>일부 이메일 클라이언트는 <code>&lt;body&gt;</code>를 제거합니다.<br>따라서 가장 바깥쪽에서 <code>&lt;body&gt;</code> 대신 사용될 Container로 <code>&lt;table&gt;</code>를 생성합니다.<br>Style 적용을 위해 <code>id=&quot;bodyTable&quot;</code>을 추가합니다.</p><p>이메일 템플릿의 가로 너비로 600px은 가장 안전한 최대 가로 너비이며, 다양한 해상도의 이메일 클라이언트에 최적화되어 있습니다.<br>800px까지는 실용적인 상한선으로 넘지 않는 것이 좋습니다.</p><pre><code class="html">&lt;body&gt;  &lt;!-- OUTERMOST CONTAINER TABLE --&gt;  &lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot; id=&quot;bodyTable&quot;&gt;    &lt;tr&gt;      &lt;td&gt;        &lt;!-- 600px - 800px CONTENTS CONTAINER TABLE --&gt;        &lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;600&quot;&gt;          &lt;tr&gt;            &lt;td&gt;            &lt;/td&gt;          &lt;/tr&gt;        &lt;/table&gt;      &lt;/td&gt;    &lt;/tr&gt;  &lt;/table&gt;&lt;/body&gt;</code></pre><h3><span id="10a1e8a9-4a7b-4e5b-b2d7-e0e8c3e39eff">Style</span><a href="#10a1e8a9-4a7b-4e5b-b2d7-e0e8c3e39eff" class="header-anchor"></a></h3><p>인라인 작성 방식을 추천하지만 편의성을 위해 일부 스타일은 <code>&lt;style&gt;</code>로 작성해 포함하거나 나중에 인라인 작성으로 옮길 수 있습니다.<br>만약 NPM을 사용할 수 있는 환경이라면 <code>&lt;link&gt;</code>로 연결된 CSS 파일이나 <code>&lt;style&gt;</code>로 더 쉽게 작성하고, <a href="https://www.npmjs.com/package/inline-email" target="_blank" rel="noopener">inline-email</a> 같은 라이브러리를 통해 추후 간단하게 인라인 방식으로 병합할 수 있습니다.</p><pre><code class="html">&lt;style type=&quot;text/css&quot;&gt;  /* GENERAL STYLE RESETS */  body, #bodyTable { width:100% !important; height:100% !important; margin:0; padding:0; }  #bodyTable { padding: 20px 0 30px 0; background-color: #ffffff; }  img, a img { border:0; outline:none; text-decoration:none; }  .imageFix { display:block; }  table, td { border-collapse:collapse; }&lt;/style&gt;</code></pre><p>다음과 같이 클라이언트 규약에 일부 규칙을 추가하여 몇 가지 단점을 수정할 수 있습니다.</p><blockquote><p>다음 규칙은 추후 인라인 방식으로 병합할 경우를 대비해 사용 직전에 별도 포함해야 합니다.</p></blockquote><pre><code class="html">&lt;style type=&quot;text/css&quot;&gt;  /* CLIENT-SPECIFIC RESETS */  /* Outlook.com(Hotmail)의 전체 너비 및 적절한 줄 높이를 허용 */  .ReadMsgBody{ width: 100%; }  .ExternalClass{ width: 100%; }  .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div { line-height: 100%; }  /* Outlook 2007 이상에서 Outlook이 추가하는 테이블 주위의 간격을 제거 */  table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }  /* Internet Explorer에서 크기가 조정된 이미지를 렌더링하는 방식을 수정 */  img { -ms-interpolation-mode: bicubic; }  /* Webkit 및 Windows 기반 클라이언트가 텍스트 크기를 자동으로 조정하지 않도록 수정 */  body, table, td, p, a, li, blockquote { -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; }&lt;/style&gt;</code></pre><h2><span id="94c12fa9-4239-4736-b145-184a4f0f75e1">작성하기</span><a href="#94c12fa9-4239-4736-b145-184a4f0f75e1" class="header-anchor"></a></h2><h3><span id="260d09b5-aea7-4a3c-bbb0-cb18e8f2ab95">table, tr, td</span><a href="#260d09b5-aea7-4a3c-bbb0-cb18e8f2ab95" class="header-anchor"></a></h3><p>이메일의 구조를 정상적으로 제공하려면 <code>&lt;div&gt;</code>를 사용하지 않고 <code>&lt;table&gt;</code>로 작성해야 합니다.<br>일반적으로 ‘TABLE 코딩’이라 불리는 이 방법은 중첩된 테이블을 많이 사용하기 때문에 TABLE 구조의 마크업이 완료된 후 스타일을 입히는 것이 좋습니다.</p><blockquote><p>수정이 까다롭기 때문에 레이아웃이 확정된 후 작업하는 것이 좋습니다.</p></blockquote><p><code>&lt;thead&gt;</code>, <code>&lt;tbody&gt;</code>, <code>&lt;colgroup&gt;</code>과 같은 <code>&lt;table&gt;</code>과 관련된 다른 태그들을 피하는 것이 좋습니다.<br>가장 가볍고 안전한 방법은 <code>&lt;table&gt;</code>, <code>&lt;tr&gt;</code>, <code>&lt;td&gt;</code> 이 3개의 태그만 활용하는 것입니다.</p><h3><span id="b23ab0fb-4cbd-47af-8309-ff7e102d37c0">HTML 속성 및 인라인 스타일</span><a href="#b23ab0fb-4cbd-47af-8309-ff7e102d37c0" class="header-anchor"></a></h3><p>일부 이메일 클라이언트는 <code>&lt;head&gt;</code>를 제거하거나 <code>&lt;style&gt;</code>를 제대로 지원하지 않습니다.<br>따라서 HTML 속성으로 테이블을 구조화하는 것이 좋습니다.</p><p>특히 <code>&lt;table&gt;</code>은 다음과 같이 초기화해서 사용하는 것이 좋습니다.</p><pre><code class="HTML">&lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&gt;&lt;/table&gt;</code></pre><h4><span id="eb8ec0cd-0f5a-4563-bd32-bde4902a6b64">table</span><a href="#eb8ec0cd-0f5a-4563-bd32-bde4902a6b64" class="header-anchor"></a></h4><table><thead><tr><th>속성</th><th>값</th><th>의미</th></tr></thead><tbody><tr><td>border</td><td><code>1</code><br><code>0</code></td><td>선의 유무</td></tr><tr><td>cellpadding</td><td>Pixels</td><td>셀(td)의 내부 여백</td></tr><tr><td>cellspacing</td><td>Pixels</td><td>셀의 사이의 너비</td></tr><tr><td>width</td><td>Pixels<br><code>%</code></td><td>테이블의 가로 너비</td></tr></tbody></table><h4><span id="4062666b-0fa1-497f-9a20-a7fbd1406150">td</span><a href="#4062666b-0fa1-497f-9a20-a7fbd1406150" class="header-anchor"></a></h4><table><thead><tr><th>속성</th><th>값</th><th>의미</th></tr></thead><tbody><tr><td>align</td><td><code>left</code><br><code>right</code><br><code>center</code><br><code>justify</code><br><code>char</code></td><td>셀의 내용을 수평 정렬</td></tr><tr><td>valign</td><td><code>top</code><br><code>middle</code><br><code>bottom</code><br><code>baseline</code></td><td>셀의 내용을 수직 정렬</td></tr><tr><td>bgcolor</td><td>HEX Colors</td><td>셀의 색상(ex&gt; <code>#ffffff</code>)</td></tr><tr><td>width</td><td>Pixels<br><code>%</code></td><td>셀의 가로 너비</td></tr><tr><td>height</td><td>Pixels<br><code>%</code></td><td>셀의 세로 너비</td></tr></tbody></table><p>인라인 스타일이란 <code>&lt;td style=&quot;color: #ff0000;&quot;&gt;</code>과 같이 HTML 속성으로 스타일을 작성하는 것을 말합니다.<br>이를 사용하면 <code>&lt;style&gt;</code>를 제대로 지원하지 않는 경우 유용하며 추천하는 방법입니다.</p><blockquote><p>인라인 스타일은 스타일 우선순위가 높기 때문에 <code>&lt;style&gt;</code>나 외부 CSS 파일과 혼합해서 사용할 경우 덮어써지는 경우가 많으니 주의합니다.</p></blockquote><h3><span id="95682f5e-fc3e-44f9-8188-3965fea120a0">중첩 테이블</span><a href="#95682f5e-fc3e-44f9-8188-3965fea120a0" class="header-anchor"></a></h3><p>많은 경우 <code>colspan</code>, <code>rowspan</code> 속성 지원이 되질 않습니다.<br>따라서 다음과 같이 셀을 병합(Merge)하는 방식은 피해야 합니다.</p><p><img src="/images/screenshot/html_email_template_table_colspan.jpg" alt="Table data colspan"></p><pre><code class="HTML">&lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&gt;  &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;&lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td colspan=&quot;2&quot;&gt;&lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td colspan=&quot;3&quot;&gt;&lt;/td&gt;  &lt;/tr&gt;&lt;/table&gt;</code></pre><p>테이블을 중첩해 병합된 것과 같은 효과를 만들 수 있습니다.<br>좀 더 복잡하지만 거의 모든 이메일 클라이언트에서 안전하게 랜더링 됩니다.</p><p><img src="/images/screenshot/html_email_template_table_nested.jpg" alt="Nested Table data"></p><pre><code class="html">&lt;table border=&quot;&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&gt;  &lt;tr&gt;    &lt;td&gt;      &lt;table border=&quot;&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&gt;        &lt;tr&gt;          &lt;td&gt;&lt;/td&gt;          &lt;td&gt;&lt;/td&gt;          &lt;td&gt;&lt;/td&gt;        &lt;/tr&gt;      &lt;/table&gt;    &lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;      &lt;table border=&quot;&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&gt;        &lt;tr&gt;          &lt;td&gt;&lt;/td&gt;          &lt;td&gt;&lt;/td&gt;        &lt;/tr&gt;      &lt;/table&gt;    &lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;      &lt;table border=&quot;&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&gt;        &lt;tr&gt;          &lt;td&gt;&lt;/td&gt;        &lt;/tr&gt;      &lt;/table&gt;    &lt;/td&gt;  &lt;/tr&gt;&lt;/table&gt;</code></pre><h3><span id="4e18dbe3-8ade-4d6e-bbfe-776c9c0e9842">색상</span><a href="#4e18dbe3-8ade-4d6e-bbfe-776c9c0e9842" class="header-anchor"></a></h3><p>호환성을 위해 <code>#ffffff</code>처럼 작성하는 Hexadecimal Colors를 사용합니다.<br>RGB, RGBA, HSV 같은 색상은 일부 이메일 클라이언트에서 지원하지 않습니다.</p><blockquote><p><code>#fff</code>와 같은 축약형은 사용하지 않도록 주의합니다.</p></blockquote><p>CSS <code>background</code> 속성보다는 HTML <code>bgcolor</code> 속성을 이용하세요.</p><pre><code class="html">&lt;td bgcolor=&quot;#ff0000&quot;&gt;&lt;/td&gt;</code></pre><h3><span id="fd72dbfd-fb61-40d1-b22e-3f4854c421c2">단일 클래스</span><a href="#fd72dbfd-fb61-40d1-b22e-3f4854c421c2" class="header-anchor"></a></h3><p><code>class</code> 속성의 값을 다중으로 작성하지 마세요.<br>하나의 단일 값으로 작성해야 합니다.</p><pre><code class="html">&lt;!-- MULTIPLE VALUES --&gt;&lt;td class=&quot;table-data description bold&quot;&gt;&lt;/td&gt;&lt;!-- SINGLE VALUE --&gt;&lt;td class=&quot;description&quot;&gt;&lt;/td&gt;</code></pre><h3><span id="837d2a2e-0220-4979-9424-0b6563d67ebb">CSS 속성</span><a href="#837d2a2e-0220-4979-9424-0b6563d67ebb" class="header-anchor"></a></h3><p>다음과 같이 CSS 단축 속성을 사용하지 마세요.</p><pre><code class="css">td {  font: 16px / 1.4 Arial, sans-serif;}</code></pre><p>개별 속성을 사용하세요.</p><pre><code class="css">td {  font-size: 16px;  line-height: 1.4;  font-family: Arial, sans-serif;}</code></pre><h3><span id="48111046-d3a8-4ea6-b283-97096af9e334">이미지</span><a href="#48111046-d3a8-4ea6-b283-97096af9e334" class="header-anchor"></a></h3><p>HTML 이메일에 이미지를 사용할 수 있지만 몇 가지 주의사항이 있습니다.</p><ul><li>절대경로를 사용</li><li>용량은 250kb 미만으로 유지</li><li>가로/세로 너비(width, height)를 입력</li><li>대체 텍스트(alt)를 입력</li></ul><p>이미지를 제거하는 일부 이메일 클라이언트를 위해 <code>width</code>, <code>height</code>, <code>alt</code> 속성을 꼭 입력하세요.</p><pre><code class="html">&lt;img src=&quot;http://via.placeholder.com/200x100&quot; alt=&quot;Some image&quot; width=&quot;200&quot; height=&quot;100&quot;&gt;</code></pre><h4><span id="e623b0de-1683-4cda-8b8a-2e92f3503096">고정 이미지 삽입</span><a href="#e623b0de-1683-4cda-8b8a-2e92f3503096" class="header-anchor"></a></h4><p>많은 경우 <code>&lt;table&gt;</code>은 반응형 레이아웃에 적합하지 않기 때문에 고정된 레이아웃을 유지하여 작성하는데, 일부 이메일 클라이언트에서는 지정한 너비와 상관없이 디바이스 너비로 표시되도록 수정되는 경우가 있습니다.<br>이럴 때는 지정한 너비와 같은 너비의 이미지를 삽입하여 문제를 해결할 수 있습니다.</p><p>단순히 지정한 테이블의 너비를 유지할 용도로 사용하는 것이기 때문에 화면에 표시되지 않도록 다음과 같이 설정합니다.</p><pre><code class="HTML">&lt;td style=&quot;font-size: 0; line-height: 0; height: 0;&quot; height=&quot;0&quot;&gt;  &lt;img alt=&quot;&quot; src=&quot;http://via.placeholder.com/600x1&quot; style=&quot;display: block;&quot; width=&quot;600&quot; height=&quot;0&quot;/&gt;&lt;/td&gt;</code></pre><p>Desktop Gmail(Chrome)에서 확인한 이메일 레이아웃은 다음과 같습니다.</p><p><img src="/images/screenshot/html_email_template_desktop.jpg" alt="HTML Email template desktop"></p><p>Mobile Gmail(Android)에서 확인한 레이아웃에는 변화가 있습니다.<br>의도한 결과가 아니라면 Text 줄바꿈이나 각 셀의 너비가 임의 조정되는 등의 문제가 있을 수 있습니다.</p><p><img src="/images/screenshot/html_email_template_mobile.jpg" alt="HTML Email template mobile"></p><p>고정 이미지를 삽입하면 Desktop에서 확인한 레이아웃과 동일하게 표시할 수 있습니다.<br>단, 이 경우 전반적인 화면 축소를 고려해야 합니다.</p><blockquote><p>600px을 기준으로 했다면 전반적인 화면 축소는 별문제가 되지 않을 것입니다.</p></blockquote><p><img src="/images/screenshot/html_email_template_mobile_use_to_fixed_image.jpg" alt="HTML Email template mobile use to fixed image"></p><h3><span id="25f02e86-332c-45ec-bcd1-2c65e8d9ab53">여백</span><a href="#25f02e86-332c-45ec-bcd1-2c65e8d9ab53" class="header-anchor"></a></h3><p>CSS의 <code>margin</code>은 사용할 수 없습니다.<br>대신 <code>padding</code>과 셀의 너비로 여백을 생성할 수 있습니다.</p><p><code>padding</code>을 사용할 경우 top, bottom, left, right 값을 모두 작성해야 합니다.</p><pre><code class="html">&lt;td style=&quot;padding: 0 0 30px 0&quot;&gt;&lt;/td&gt;</code></pre><p>조금 불편할 수 있지만, 사실 가장 안전한 방식은 다음과 같이 개별 속성을 사용하는 것입니다.</p><pre><code class="html">&lt;td style=&quot;padding-top: 0; padding-right: 0; padding-bottom: 30px; padding-left: 0;&quot;&gt;&lt;/td&gt;</code></pre><p>만약 CSS Pre-processor로 SCSS를 사용한다면 중첩 속성을 이용해 편리하게 작성할 수 있습니다.</p><pre><code class="scss">td {  padding: {    top: 0;    right: 0;    bottom: 30px;    left: 0;  }}</code></pre><p>내부 여백이 아닌 외부 여백을 활용할 경우는 여백 위치(여백으로 사용할)에 빈 셀을 추가합니다.<br>빈 셀은 공백 문자(<code>&amp;nbsp</code>)를 가지고 있습니다.</p><pre><code class="html">&lt;!-- HORIZONTAL MARGIN 30px --&gt;&lt;td width=&quot;30&quot; style=&quot;font-size: 0; line-height: 0;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;!-- VERTICAL MARGIN 30px --&gt;&lt;tr&gt;  &lt;td height=&quot;30&quot; style=&quot;font-size: 0; line-height: 0;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;</code></pre><h3><span id="16b40368-412b-4971-856f-072cf9266a82">Text</span><a href="#16b40368-412b-4971-856f-072cf9266a82" class="header-anchor"></a></h3><p><code>&lt;font&gt;</code>, <code>&lt;b&gt;</code>, <code>&lt;i&gt;</code>, <code>&lt;u&gt;</code> 같은 스타일을 내포하는 태그와 같이 사용하면 더 안전합니다.</p><pre><code class="html">&lt;style type=&quot;text/css&quot;&gt;  .bold {    font-weight: bold;  }&lt;/style&gt;&lt;td&gt;  HTML &lt;b class=&quot;bold&quot;&gt;이메일&lt;/b&gt; 템플릿&lt;/td&gt;</code></pre><p>스타일과 <code>&lt;font&gt;</code>를 함께 쓰는 것은 링크의 기본색인 파란색이 절대로 나타나지 않게 하기 위한 최선의 방법입니다.</p><pre><code class="html">&lt;td&gt;  &lt;a href=&quot;https://google.com&quot; target=&quot;_blank&quot; style=&quot;color: #ff0000;&quot;&gt;&lt;font color=&quot;#ff0000&quot;&gt;GOOGLE&lt;/font&gt;&lt;/a&gt;&lt;/td&gt;</code></pre><p>그러나 <code>&lt;p&gt;</code>, <code>&lt;h1&gt;</code>, <code>&lt;h2&gt;</code>… 같은 단락, 제목 태그는 사용하지 마세요.<br>이는 이메일 클라이언트 전체에서 스타일 일관성이 없이 랜더링 되며 수정하기 매우 까다롭습니다.</p><blockquote><p>대부분의 경우 <code>&lt;td&gt;</code>로 작성하면 됩니다.</p></blockquote><h3><span id="71d33a11-8db4-4542-8f8f-01905303e006">조건부 주석(Conditional Comments)</span><a href="#71d33a11-8db4-4542-8f8f-01905303e006" class="header-anchor"></a></h3><p><code>mso</code>(Microsoft Outlook) 키워드를 조건부 주석에 사용할 수 있습니다.</p><pre><code class="html">&lt;td&gt;  &lt;!--[if mso]&gt;    OUTLOOK CONTENTS  &lt;![endif]--&gt;  &lt;!--[if !mso]&gt;    NON-OUTLOOK CONTENTS  &lt;![endif]--&gt;  &lt;!--[if (gte mso 9)|(IE)]&gt;    GREATER THAN EQUAL OUTLOOK 9 or INTERNET EXPLORER  &lt;![endif]--&gt;&lt;/td&gt;</code></pre><ul><li>Outlook 2000: Version 9</li><li>Outlook 2002: Version 10</li><li>Outlook 2003: Version 11</li><li>Outlook 2007: Version 12</li><li>Outlook 2010: Version 14</li><li>Outlook 2013: Version 15</li></ul><table><thead><tr><th style="text-align:center">기호</th><th style="text-align:center">뜻</th><th style="text-align:center">예시</th><th style="text-align:center">예시 해석</th></tr></thead><tbody><tr><td style="text-align:center"><code>!</code></td><td style="text-align:center">부정<br>(not)</td><td style="text-align:center"><code>&lt;!--[if !IE]&gt;&lt;![endif]--&gt;</code></td><td style="text-align:center">IE브라우저가 아닐 때</td></tr><tr><td style="text-align:center"><code>lt</code></td><td style="text-align:center">작다, 미만<br>(less than)</td><td style="text-align:center"><code>&lt;!--[if lt IE 9]&gt;&lt;![endif]--&gt;</code></td><td style="text-align:center">IE9 미만</td></tr><tr><td style="text-align:center"><code>lte</code></td><td style="text-align:center">작거나 같다, 이하<br>(less than equal)</td><td style="text-align:center"><code>&lt;!--[if lte IE 8]&gt;&lt;![endif]--&gt;</code></td><td style="text-align:center">IE8 이하</td></tr><tr><td style="text-align:center"><code>gt</code></td><td style="text-align:center">크다, 초과<br>(greater than)</td><td style="text-align:center"><code>&lt;!--[if gt IE 6]&gt;&lt;![endif]--&gt;</code></td><td style="text-align:center">IE6 초과</td></tr><tr><td style="text-align:center"><code>gte</code></td><td style="text-align:center">크거나 같다, 이상<br>(greater than equal)</td><td style="text-align:center"><code>&lt;!--[if gte IE 7]&gt;&lt;![endif]--&gt;</code></td><td style="text-align:center">IE7 이상</td></tr><tr><td style="text-align:center"><code>&amp;</code></td><td style="text-align:center">그리고<br>(and)</td><td style="text-align:center"><code>&lt;!--[if (gt IE 6) &amp; (lte IE 9)]&gt;&lt;![endif]--&gt;</code></td><td style="text-align:center">IE6 초과 ~ IE9 이하</td></tr><tr><td style="text-align:center"><code>&#124;</code></td><td style="text-align:center">또는<br>(or)</td><td style="text-align:center">-</td><td style="text-align:center">-</td></tr></tbody></table><h3><span id="411ee16e-5381-4815-b7b7-6b949ec8087f">방탄 버튼(Bulletproof Buttons)</span><a href="#411ee16e-5381-4815-b7b7-6b949ec8087f" class="header-anchor"></a></h3><p>많은 경우 테이블에는 이미지 버튼이 사용되어 왔습니다.<br>하지만 이미지 버튼은 이메일 클라이언트가 이미지 사용을 제거하는 경우 링크가 동작하지 않는 중요한 이슈가 발생하게 됩니다.<br>이런 경우 Microsoft VML(Vector Markup Language)를 통해서 해결하는 방법이 있습니다.</p><pre><code class="html">&lt;td&gt;  &lt;!--[if mso]&gt;    &lt;v:roundrect xmlns:v=&quot;urn:schemas-microsoft-com:vml&quot; xmlns:w=&quot;urn:schemas-microsoft-com:office:word&quot; href=&quot;https://google.com&quot; style=&quot;height: 40px; v-text-anchor: middle; width:200px;&quot; arcsize=&quot;50%&quot; strokecolor=&quot;#1caeba&quot; fillcolor=&quot;#2bcae3&quot;&gt;      &lt;w:anchorlock/&gt;      &lt;center style=&quot;color:#147e94;font-family:sans-serif;font-size:13px;font-weight:bold;&quot;&gt;Button&lt;/center&gt;    &lt;/v:roundrect&gt;  &lt;![endif]--&gt;  &lt;a href=&quot;https://google.com&quot;  style=&quot;background-color: #2bcae3; border: 1px solid #1caeba; border-radius: 20px; color: #147e94; display: inline-block; font-family: sans-serif; font-size: 13px; font-weight: bold; line-height: 40px; text-align: center; text-decoration: none; width: 200px; -webkit-text-size-adjust: none; mso-hide: all;&quot;&gt;Button&lt;/a&gt;&lt;/td&gt;</code></pre><p>하위 호환성을 고려하는 조금 더 쉬운 방법이 있습니다.<br>단, 버튼 전체 영역을 링크 범위로 설정할 수 없습니다.</p><pre><code class="html">&lt;td bgcolor=&quot;#2bcae3&quot; align=&quot;center&quot; width=&quot;200&quot; style=&quot;border: 1px solid #1caeba; border-radius: 20px; -webkit-text-size-adjust: none;&quot;&gt;  &lt;a href=&quot;https://google.com&quot; style=&quot;color: #147e94; font-family: sans-serif; font-size: 13px; font-weight: bold; line-height: 40px; text-decoration: none;&quot;&gt;&lt;font color=&quot;#147e94&quot;&gt;Button&lt;/font&gt;&lt;/a&gt;&lt;/td&gt;</code></pre><p><img src="/images/screenshot/html_email_template_bulletproof_button.jpg" alt="Bulletproof Buttons"></p><h2><span id="a3338901-7548-45b2-b2f7-a7fdeb0b100b">검증</span><a href="#a3338901-7548-45b2-b2f7-a7fdeb0b100b" class="header-anchor"></a></h2><p>작업한 문서를 <a href="https://validator.w3.org/" target="_blank" rel="noopener">W3C Validator</a>에서 검사합니다.<br>XHTML에 익숙하지 않다면 특히 검사 결과를 잘 살펴봐야 합니다.</p><h1><span id="caeb068e-05e4-428b-b294-6f07d3ab9adf">참고 자료(References)</span><a href="#caeb068e-05e4-428b-b294-6f07d3ab9adf" class="header-anchor"></a></h1><p><a href="https://templates.mailchimp.com/development/html/" target="_blank" rel="noopener">https://templates.mailchimp.com/development/html/</a><br><a href="https://webdesign.tutsplus.com/tutorials/what-you-should-know-about-html-email--webdesign-12908" target="_blank" rel="noopener">https://webdesign.tutsplus.com/tutorials/what-you-should-know-about-html-email--webdesign-12908</a><br><a href="https://www.campaignmonitor.com/css/text-fonts/font-face/" target="_blank" rel="noopener">https://www.campaignmonitor.com/css/text-fonts/font-face/</a><br><a href="https://litmus.com/community/learning/13-foundations-email-coding-101" target="_blank" rel="noopener">https://litmus.com/community/learning/13-foundations-email-coding-101</a></p>]]></content>
    
    <summary type="html">
    
      서비스 이메일 푸쉬에 사용할 HTML Email Template를 제작하기 위해 필요한 내용들을 살펴봅니다. 표준 코딩이 아니기 때문에 주의해야 하는 중요한 개념들을 정리합니다.
    
    </summary>
    
    
      <category term="html" scheme="https://heropy.blog/tags/html/"/>
    
      <category term="css" scheme="https://heropy.blog/tags/css/"/>
    
      <category term="email" scheme="https://heropy.blog/tags/email/"/>
    
  </entry>
  
  <entry>
    <title>CSS Flex(Flexible Box) 완벽 가이드</title>
    <link href="https://heropy.blog/2018/11/24/css-flexible-box/"/>
    <id>https://heropy.blog/2018/11/24/css-flexible-box/</id>
    <published>2018-11-24T13:00:00.000Z</published>
    <updated>2020-10-30T05:57:48.320Z</updated>
    
    <content type="html"><![CDATA[<p>대부분 사이트는 전체 레이아웃이 수직 구성이며 ‘위-아래’로 스크롤 하여 사용합니다.<br>레이아웃을 구성할 때 가장 많이 사용하는 요소(Elements)들이 기본적으로 블록(Block) 개념으로 표시(Display)되며 이는 뷰(View)에 수직(위에서 아래로)으로 쌓이기 때문에 수직 구성은 상대적으로 쉽게 만들 수 있습니다.<br>하지만 수평(왼쪽에서 오른쪽으로) 구성의 경우는 상황이 조금 다릅니다.</p><p>문제는 수평 구조를 만드는 속성이 명확하지 않았기 때문인데, 그래서 많은 경우 <code>&lt;table&gt;</code>나 <code>float</code> 혹은 <code>inline-block</code> 등의 도움을 받았습니다.<br>하지만 이러한 방법들은 다양한 문제(Clear, White space 등, 해결은 가능하지만..)를 가지고 있기 때문에 결국 수평 레이아웃 구성의 차선책일 뿐이며, 이제 우리는 Flex(Flexible Box)라는 명확한 개념(속성들)으로 레이아웃을 쉽게 구성할 수 있습니다.</p><blockquote><p>위에서 쉬운 수평 구성을 얘기했지만 Flex는 쉬운 수직 구성도 가능합니다.</p></blockquote><div class="toc"><ul><li><a href="0243eb90-dbec-4d08-a5c0-4bfd7ba479cd">CSS3 Flexible Box</a><ul><li><a href="fe6045b1-93b9-4200-83b1-8437d272a2c7">Flex Container</a><ul><li><a href="6eaf2b18-239c-45ec-be5b-6a2b121d9a3b">display</a></li><li><a href="d7a7a0a1-b5a1-4fdf-a981-bd2093bfe7de">flex-flow</a><ul><li><a href="7878f0cc-1d6c-45c5-8fe3-591942eb2bb6">flex-direction</a></li><li><a href="e893aee8-28a8-46e4-a76d-d9e78a21ace3">주 축(main-axis)과 교차 축(cross-axis)</a></li><li><a href="c6e2ef33-29c4-4162-ab8c-d90117a8d43f">시작점(flex-start)과 끝점(flex-end)</a></li><li><a href="9de9dadc-d6c6-4c2c-bbeb-13f15e1b70e3">flex-wrap</a></li></ul></li><li><a href="258aca8a-b65d-4a06-8f37-e0cb725f4b6f">justify-content</a></li><li><a href="18404e89-96bb-4df9-9912-8fd0c6822a9c">align-content</a></li><li><a href="0a76df98-e4c8-41ba-a23c-27a84e7a544c">align-items</a></li></ul></li><li><a href="49371a60-f81c-4e74-aa72-ae4cb32fec08">Flex Items</a><ul><li><a href="77fac33d-74bf-42c8-ab7b-6bb928d459fb">order</a></li><li><a href="d489d23d-a953-47d2-b195-9de3af0423a1">flex</a><ul><li><a href="c561b2cc-aae3-495c-8b2c-41db1ac470ab">flex-grow</a></li><li><a href="b423f151-d248-4422-95f1-a0ed0524da05">flex-shrink</a></li><li><a href="a00fa25f-7148-4c23-a273-974d6e5a9585">flex-basis</a></li></ul></li><li><a href="b66ac8cc-17c0-487b-b966-0b7c56e55482">align-self</a></li></ul></li></ul></li><li><a href="e8bd7fa6-92a0-4ad6-96f8-9dbba8df7923">참고 자료(References)</a></li></ul></div><p>시작하기에 앞서 간단한 얘제를 살펴봅시다.<br><code>float</code> 속성을 이용한 수평 구성의 경우 다음과 같이 스타일을 작성할 수 있습니다.</p><pre><code class="html">&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;div class=&quot;clear-element&quot;&gt;&lt;/div&gt;</code></pre><pre><code class="css">.box {  float: left;}.clear-element {  clear: both; /* or left */}</code></pre><p>자세한 설명은 생략하고, 위 방법은 보기엔 단순하지만 <code>box</code>를 제외한 <code>clear-element</code>라는 이름(class)의 다음(next) 요소도 있어야 하기 때문에 실제 사용엔 매우 불편하며 명확하지 않은 방법으로써 많은 경우 아래 방식을 사용합니다.</p><pre><code class="html">&lt;div class=&quot;clearfix&quot;&gt;  &lt;div class=&quot;box&quot;&gt;&lt;/div&gt;  &lt;div class=&quot;box&quot;&gt;&lt;/div&gt;  &lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;/div&gt;</code></pre><pre><code class="css">/* IE 핵이나 기타 방식을 제외하고 가장 원리에 충실한 방법 */.clearfix::after {  content: &quot;&quot;;  clear: both;  display: block;}.box {  float: left;}</code></pre><p>예제를 보면 수평이 될 요소들에 직접! <code>float</code>를 적용하고 그 요소들의 Container(부모 요소)에 미리 설정한 <code>clearfix</code>를 적용합니다.</p><p>그러면 Flexible Box(이하 Flex)는 어떻게 작성할 수 있을까요?<br>아주 간단합니다.</p><pre><code class="html">&lt;div class=&quot;box-container&quot;&gt;  &lt;div class=&quot;box&quot;&gt;&lt;/div&gt;  &lt;div class=&quot;box&quot;&gt;&lt;/div&gt;  &lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;/div&gt;</code></pre><pre><code class="css">.box-container {  display: flex;}</code></pre><p>Flex는 수평이 될 요소들의 Container(<code>box-container</code>)에 <code>display: flex;</code>를 적용합니다.<br>(세부 속성이 필요하지 않은 경우도 많기 때문에 상당히 쉽고 빠르게 수평 요소를 구성할 수 있습니다.)</p><h1><span id="0243eb90-dbec-4d08-a5c0-4bfd7ba479cd">CSS3 Flexible Box</span><a href="#0243eb90-dbec-4d08-a5c0-4bfd7ba479cd" class="header-anchor"></a></h1><p>Flex는 요소의 크기가 불분명하거나 동적인 경우에도, 각 요소를 정렬할 수 있는 효율적인 방법을 제공합니다.</p><p>우선 Flex는 2개의 개념으로 나뉩니다.<br>첫 번째는 Container 두 번째는 Items 입니다.<br>위에서 살펴본 바와 같이 Container는 Items를 감싸는 부모 요소이며, 각 Item을 정렬하기 위해선 Container가 필수입니다.</p><p>주의할 부분은 Container와 Items에 적용하는 속성이 구분되어 있다는 것입니다.<br>Container에는 <code>display</code>, <code>flex-flow</code>, <code>justify-content</code> 등의 속성을 사용할 수 있으며,<br>Items에는 <code>order</code>, <code>flex</code>, <code>align-self</code> 등의 속성을 사용할 수 있습니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-base.jpg" alt="Flex"></p><h2><span id="fe6045b1-93b9-4200-83b1-8437d272a2c7">Flex Container</span><a href="#fe6045b1-93b9-4200-83b1-8437d272a2c7" class="header-anchor"></a></h2><p>Flex Container를 위한 속성들은 다음과 같습니다.<br>주 축(main-axis)과 교차 축(cross-axis)의 개념은 뒤에서 살펴봅시다.</p><table><thead><tr><th>속성</th><th>의미</th></tr></thead><tbody><tr><td>display</td><td>Flex Container를 정의</td></tr><tr><td>flex-flow</td><td><code>flex-direction</code>와 <code>flex-wrap</code>의 단축 속성</td></tr><tr><td>flex-direction</td><td>Flex Items의 주 축(main-axis)을 설정</td></tr><tr><td>flex-wrap</td><td>Flex Items의 여러 줄 묶음(줄 바꿈) 설정</td></tr><tr><td>justify-content</td><td>주 축(main-axis)의 정렬 방법을 설정</td></tr><tr><td>align-content</td><td>교차 축(cross-axis)의 정렬 방법을 설정(2줄 이상)</td></tr><tr><td>align-items</td><td>교차 축(cross-axis)에서 Items의 정렬 방법을 설정(1줄)</td></tr></tbody></table><h3><span id="6eaf2b18-239c-45ec-be5b-6a2b121d9a3b">display</span><a href="#6eaf2b18-239c-45ec-be5b-6a2b121d9a3b" class="header-anchor"></a></h3><p><code>display</code> 속성으로 Flex Container를 정의합니다.<br>보통 요소의 표시 방법을 <code>display: block;</code>, <code>display: inline-block</code> 혹은 <code>display: none;</code> 같이 사용하는 경우가 많죠.<br>같은 요소의 표시 방법으로 Block이나 Inline이 아닌 Flex(<code>display: flex</code>, <code>display: inline-flex</code>)로 정의합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>flex</td><td>Block 특성의 Flex Container를 정의</td><td></td></tr><tr><td>inline-flex</td><td>Inline 특성의 Flex Container를 정의</td><td></td></tr></tbody></table><p><code>flex</code>와 <code>inline-flex</code>는 차이는 단순합니다.<br><code>display: flex;</code>로 지정된 Flex Container는 Block 요소와 같은 성향(수직 쌓임)을 가지며,<br><code>display: inline-flex</code>로 지정된 Flex Container는 Inline(Inline Block) 요소와 같은 성향(수평 쌓임)을 가집니다.</p><p>여기서 말하는 수직과 수평 쌓임은 Items가 아니라 Container라는 것에 주의합시다.<br>두 값의 차이는 내부에 Items에는 영향을 주지 않습니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-display.jpg" alt="Flex"></p><h3><span id="d7a7a0a1-b5a1-4fdf-a981-bd2093bfe7de">flex-flow</span><a href="#d7a7a0a1-b5a1-4fdf-a981-bd2093bfe7de" class="header-anchor"></a></h3><p>이 속성은 단축 속성으로 Flex Items의 주 축(main-axis)을 설정하고 Items의 여러 줄 묶음(줄 바꿈)도 설정합니다.</p><pre><code class="plaintext">flex-flow: 주축 여러줄묶음;</code></pre><pre><code class="css">.flex-container {  flex-flow: row-reverse wrap;}</code></pre><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>flex-direction</td><td>Items의 주 축(main-axis)을 설정</td><td><code>row</code></td></tr><tr><td>flex-wrap</td><td>Items의 여러 줄 묶음(줄 바꿈) 설정</td><td><code>nowrap</code></td></tr></tbody></table><p>개별 속성을 알아봅시다.</p><h4><span id="7878f0cc-1d6c-45c5-8fe3-591942eb2bb6">flex-direction</span><a href="#7878f0cc-1d6c-45c5-8fe3-591942eb2bb6" class="header-anchor"></a></h4><p>Items의 주 축(main-axis)을 설정합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>row</td><td>Itmes를 수평축(왼쪽에서 오른쪽으로)으로 표시</td><td><code>row</code></td></tr><tr><td>row-reverse</td><td>Items를 <code>row</code>의 반대 축으로 표시</td><td></td></tr><tr><td>column</td><td>Items를 수직축(위에서 아래로)으로 표시</td><td></td></tr><tr><td>column-reverse</td><td>Items를 <code>column</code>의 반대 축으로 표시</td><td></td></tr></tbody></table><pre><code class="plaintext">flex-direction: 주축;</code></pre><p><img src="/images/screenshot/css-flexible-box/flex-direction.jpg" alt="Flex"></p><h4><span id="e893aee8-28a8-46e4-a76d-d9e78a21ace3">주 축(main-axis)과 교차 축(cross-axis)</span><a href="#e893aee8-28a8-46e4-a76d-d9e78a21ace3" class="header-anchor"></a></h4><p>위에서 언급했었던 주 축(main-axis)과 교차 축(cross-axis)의 개념은 다음과 같습니다.<br>값 <code>row</code>는 Items를 수평축으로 표시하므로 이때는 주 축이 수평이며 교차 축은 수직이 됩니다.<br>반대로 값 <code>column</code>은 Items를 수직축으로 표시하므로 주 축은 수직이며 교차 축은 수평이 됩니다.<br>즉, 방향(수평, 수직)에 따라 주 축과 교차 축이 달라집니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-direction-main-axis.jpg" alt="Flex"></p><h4><span id="c6e2ef33-29c4-4162-ab8c-d90117a8d43f">시작점(flex-start)과 끝점(flex-end)</span><a href="#c6e2ef33-29c4-4162-ab8c-d90117a8d43f" class="header-anchor"></a></h4><p>시작점(flex-start)과 끝점(flex-end)이라는 개념도 있습니다.<br>이는 주 축이나 교차 축의 시작하는 지점과 끝나는 지점을 지칭합니다.<br>역시 방향에 따라 시작점과 끝점이 달라집니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-direction-main-start.jpg" alt="Flex"><br><img src="/images/screenshot/css-flexible-box/flex-direction-cross-start.jpg" alt="Flex"></p><p>뒤에서 언급할 속성 중 값으로 <code>flex-start</code>와 <code>flex-end</code>를 사용하는데 이는 방향에 맞는 그 시작점과 끝점을 의미합니다.</p><h4><span id="9de9dadc-d6c6-4c2c-bbeb-13f15e1b70e3">flex-wrap</span><a href="#9de9dadc-d6c6-4c2c-bbeb-13f15e1b70e3" class="header-anchor"></a></h4><p>Items의 여러 줄 묶음(줄 바꿈)을 설정합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>nowrap</td><td>모든 Itmes를 여러 줄로 묶지 않음(한 줄에 표시)</td><td><code>nowrap</code></td></tr><tr><td>wrap</td><td>Items를 여러 줄로 묶음</td><td></td></tr><tr><td>wrap-reverse</td><td>Items를 <code>wrap</code>의 역 방향으로 여러 줄로 묶음</td><td></td></tr></tbody></table><pre><code class="plaintext">flex-wrap: 여러줄묶음;</code></pre><p>기본적으로 Items는 한 줄에서만 표시되고 줄 바꿈 되지 않습니다.<br>이는 지정된 크기(주 축에 따라 <code>width</code>나 <code>height</code>)를 무시하고 한 줄 안에서만 가변합니다.<br>Items를 줄 바꿈 하려면 값으로 <code>wrap</code>을 사용해야 합니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-wrap.jpg" alt="Flex"></p><h3><span id="258aca8a-b65d-4a06-8f37-e0cb725f4b6f">justify-content</span><a href="#258aca8a-b65d-4a06-8f37-e0cb725f4b6f" class="header-anchor"></a></h3><p>주 축(main-axis)의 정렬 방법을 설정합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>flex-start</td><td>Items를 시작점(flex-start)으로 정렬</td><td><code>flex-start</code></td></tr><tr><td>flex-end</td><td>Items를 끝점(flex-end)으로 정렬</td><td></td></tr><tr><td>center</td><td>Items를 가운데 정렬</td><td></td></tr><tr><td>space-between</td><td>시작 Item은 시작점에, 마지막 Item은 끝점에 정렬되고 나머지 Items는 사이에 고르게 정렬됨</td><td></td></tr><tr><td>space-around</td><td>Items를 균등한 여백을 포함하여 정렬</td><td></td></tr></tbody></table><pre><code class="plaintext">justify-content: 정렬방법;</code></pre><p><img src="/images/screenshot/css-flexible-box/flex-justify-content.jpg" alt="Flex"></p><h3><span id="18404e89-96bb-4df9-9912-8fd0c6822a9c">align-content</span><a href="#18404e89-96bb-4df9-9912-8fd0c6822a9c" class="header-anchor"></a></h3><p>교차 축(cross-axis)의 정렬 방법을 설정합니다.<br>주의할 점은 <code>flex-wrap</code> 속성을 통해 Items가 여러 줄(2줄 이상)이고 여백이 있을 경우만 사용할 수 있습니다.</p><blockquote><p>Items가 한 줄일 경우 <code>align-items</code> 속성을 사용하세요.</p></blockquote><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>stretch</td><td>Container의 교차 축을 채우기 위해 Items를 늘림</td><td><code>stretch</code></td></tr><tr><td>flex-start</td><td>Items를 시작점(flex-start)으로 정렬</td><td></td></tr><tr><td>flex-end</td><td>Items를 끝점(flex-end)으로 정렬</td><td></td></tr><tr><td>center</td><td>Items를 가운데 정렬</td><td></td></tr><tr><td>space-between</td><td>시작 Item은 시작점에, 마지막 Item은 끝점에 정렬되고 나머지 Items는 사이에 고르게 정렬됨</td><td></td></tr><tr><td>space-around</td><td>Items를 균등한 여백을 포함하여 정렬</td><td></td></tr></tbody></table><pre><code class="plaintext">align-content: 정렬방법;</code></pre><p>값 <code>stretch</code>는 교차 축에 해당하는 너비(속성 <code>width</code> 혹은 <code>height</code>)가 값이 <code>auto</code>(기본값)일 경우 교차 축을 채우기 위해 자동으로 늘어납니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-align-content.jpg" alt="Flex"></p><h3><span id="0a76df98-e4c8-41ba-a23c-27a84e7a544c">align-items</span><a href="#0a76df98-e4c8-41ba-a23c-27a84e7a544c" class="header-anchor"></a></h3><p>교차 축(cross-axis)에서 Items의 정렬 방법을 설정합니다.<br>Items가 한 줄일 경우 많이 사용합니다.</p><p>주의할 점은 Items가 <code>flex-wrap</code>을 통해 여러 줄(2줄 이상)일 경우에는 <code>align-content</code> 속성이 우선합니다.<br>따라서 <code>align-items</code>를 사용하려면 <code>align-content</code> 속성을 기본값(<code>stretch</code>)으로 설정해야 합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>stretch</td><td>Container의 교차 축을 채우기 위해 Items를 늘림</td><td><code>stretch</code></td></tr><tr><td>flex-start</td><td>Items를 각 줄의 시작점(flex-start)으로 정렬</td><td></td></tr><tr><td>flex-end</td><td>Items를 각 줄의 끝점(flex-end)으로 정렬</td><td></td></tr><tr><td>center</td><td>Items를 가운데 정렬</td><td></td></tr><tr><td>baseline</td><td>Items를 문자 기준선에 정렬</td><td></td></tr></tbody></table><pre><code class="plaintext">align-items: 정렬방법;</code></pre><p><img src="/images/screenshot/css-flexible-box/flex-align-items.jpg" alt="Flex"></p><h2><span id="49371a60-f81c-4e74-aa72-ae4cb32fec08">Flex Items</span><a href="#49371a60-f81c-4e74-aa72-ae4cb32fec08" class="header-anchor"></a></h2><p>Flex Items를 위한 속성들은 다음과 같습니다.</p><table><thead><tr><th>속성</th><th>의미</th></tr></thead><tbody><tr><td>order</td><td>Flex Item의 순서를 설정</td></tr><tr><td>flex</td><td><code>flex-grow</code>, <code>flex-shrink</code>, <code>flex-basis</code>의 단축 속성</td></tr><tr><td>flex-grow</td><td>Flex Item의 증가 너비 비율을 설정</td></tr><tr><td>flex-shrink</td><td>Flex Item의 감소 너비 비율을 설정</td></tr><tr><td>flex-basis</td><td>Flex Item의 (공간 배분 전) 기본 너비 설정</td></tr><tr><td>align-self</td><td>교차 축(cross-axis)에서 Item의 정렬 방법을 설정</td></tr></tbody></table><h3><span id="77fac33d-74bf-42c8-ab7b-6bb928d459fb">order</span><a href="#77fac33d-74bf-42c8-ab7b-6bb928d459fb" class="header-anchor"></a></h3><p>Item의 순서를 설정합니다.<br>Item에 숫자를 지정하고 숫자가 클수록 순서가 밀립니다.<br>음수가 허용됩니다.</p><blockquote><p>HTML 구조와 상관없이 순서를 변경할 수 있기 때문에 유용합니다.</p></blockquote><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>숫자</td><td>Item의 순서를 설정</td><td><code>0</code></td></tr></tbody></table><pre><code class="plaintext">order: 순서;</code></pre><p><img src="/images/screenshot/css-flexible-box/flex-order.jpg" alt="Flex"></p><h3><span id="d489d23d-a953-47d2-b195-9de3af0423a1">flex</span><a href="#d489d23d-a953-47d2-b195-9de3af0423a1" class="header-anchor"></a></h3><p>Item의 너비(증가, 감소, 기본)를 설정하는 단축 속성입니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>flex-grow</td><td>Item의 증가 너비 비율을 설정</td><td><code>0</code></td></tr><tr><td>flex-shrink</td><td>Item의 감소 너비 비율을 설정</td><td><code>1</code></td></tr><tr><td>flex-basis</td><td>Item의 (공간 배분 전) 기본 너비 설정</td><td><code>auto</code></td></tr></tbody></table><pre><code class="plaintext">flex: 증가너비 감소너비 기본너비;</code></pre><pre><code class="css">.item {  flex: 1 1 20px;  /* 증가너비 감소너비 기본너비 */  flex: 1 1;  /* 증가너비 감소너비 */  flex: 1 20px;  /* 증가너비 기본너비 (단위를 사용하면 flex-basis가 적용됩니다) */}</code></pre><p><code>flex-grow</code>를 제외한 개별 속성은 생략할 수 있습니다.<br>만약 <code>flex: 1;</code>로 작성하면 <code>flex-grow: 1;</code>과 같습니다.<br>그러면 나머지 속성은 생략했으니 기본값이 적용되어 <code>flex-shrink: 1;</code>, <code>flex-basis: auto;</code>가 되겠죠?<br>즉 <code>flex: 1;</code> 혹은 <code>flex: 1 1;</code>은 <code>flex: 1 1 auto;</code>와 같다고 볼 수 있습니다만 그렇지 않습니다.</p><p><code>flex-basis</code>의 기본값은 <code>auto</code>입니다만 단축 속성인 <code>flex</code>에서 그 값을 생략할 경우 <code>0</code>이 적용됩니다.<br>다시 정리하면 <code>flex: 1;</code> 혹은 <code>flex: 1 1;</code>은 <code>flex: 1 1 0;</code>이 된다는 것입니다.<br>이 부분을 기억하지 않으면 엉뚱한 결과가 나올 수 있으니 주의합시다!</p><h4><span id="c561b2cc-aae3-495c-8b2c-41db1ac470ab">flex-grow</span><a href="#c561b2cc-aae3-495c-8b2c-41db1ac470ab" class="header-anchor"></a></h4><p>Item의 증가 너비 비율을 설정합니다.<br>숫자가 크면 더 많은 너비를 가집니다.<br>Item이 가변 너비가 아니거나, 값이 <code>0</code>일 경우 효과가 없습니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>숫자</td><td>Item의 증가 너비 비율을 설정</td><td><code>0</code></td></tr></tbody></table><pre><code class="plaintext">flex-grow: 증가너비;</code></pre><p>모든 Items의 총 증가 너비(<code>flex-grow</code>)에서 각 Item의 증가 너비의 비율 만큼 너비를 가질 수 있습니다.<br>예를 들어 Item이 3개이고 증가 너비가 각각 <code>1</code>, <code>2</code>, <code>1</code>이라면,<br>첫 번째 Item은 총 너비의 25%(1/4)을,<br>두 번째 Item은 총 너비의 50%(2/4)를,<br>세 번째 Item은 총 너비의 25%(1/4)을 가지게 됩니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-grow.jpg" alt="Flex"></p><iframe height="500" scrolling="no" title="flex-grow" src="//codepen.io/heropark/embed/zMLbPw/?height=265&theme-id=0&default-tab=html,result" frameborder="no" allowtransparency="true" allowfullscreen style="width:100%">See the Pen <a href="https://codepen.io/heropark/pen/zMLbPw/" target="_blank" rel="noopener">flex-grow</a> by park young woong (<a href="https://codepen.io/heropark" target="_blank" rel="noopener">@heropark</a>) on <a href="https://codepen.io" target="_blank" rel="noopener">CodePen</a>.<br></iframe><h4><span id="b423f151-d248-4422-95f1-a0ed0524da05">flex-shrink</span><a href="#b423f151-d248-4422-95f1-a0ed0524da05" class="header-anchor"></a></h4><p>Item이 감소하는 너비의 비율을 설정합니다.<br>숫자가 크면 더 많은 너비가 감소합니다.<br>Item이 가변 너비가 아니거나, 값이 <code>0</code>일 경우 효과가 없습니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>숫자</td><td>Item의 감소 너비 비율을 설정</td><td><code>1</code></td></tr></tbody></table><pre><code class="plaintext">flex-shrink: 감소너비;</code></pre><p>감소 너비(<code>flex-shrink</code>)는 요소의 너비에 영향을 받기 때문에 계산하기 까다롭습니다.<br>영향을 받는다는 요소의 너비는 <code>width</code>, <code>height</code>, <code>flex-basis</code> 등으로 너비가 지정된 경우를 의미합니다.<br>Container의 너비가 줄어 Items의 너비에 영향을 미칠 경우, 영향을 미치기 시작한 지점부터 줄어든 거리 만큼 감소 너비 비율에 맞게 Item의 너비가 줄어듭니다.</p><p>예를 들어 Container의 너비가 줄어 Item의 너비에 영향을 미치기 시작한 지점부터 실제 줄어든 거리가 <code>90px</code>일 때,<br>요소 너비가 같은 Item이 2개이고 지정된 감소 너비가 각각 <code>2</code>와 <code>1</code>이라면,<br>감소 너비는 2:1 비율이며,<br>첫 번째 Item은 <code>90px</code>의 2/3인 <code>60px</code> 만큼 너비가 감소하고,<br>두 번째 Item은 <code>90px</code>의 1/3인 <code>30px</code> 만큼 너비가 감소합니다.</p><p>다른 예시로 Container의 너비가 줄어 Item의 너비에 영향을 미치기 시작한 지점부터 실제 줄어든 거리가 <code>90px</code>일 때,<br>요소 너비가 다른 Item이 2개이고 요소 너비는 각각 <code>200</code>과 <code>100</code>이고,<br>지정된 감소 너비가 각각 <code>2</code>와 <code>1</code>이라면,<br><code>200 x 2 = 400</code>과 <code>100 x 1 = 100</code> 즉 감소 너비는 4:1 비율이며,<br>첫 번째 Item은 <code>90px</code>의 4/5인 <code>72px</code> 만큼 너비가 감소하고,<br>두 번째 Item은 <code>90px</code>의 1/5인 <code>18px</code> 만큼 너비가 감소합니다.</p><p><img src="/images/screenshot/css-flexible-box/flex-shrink.jpg" alt="Flex"></p><iframe height="500" scrolling="no" title="flex-shrink" src="//codepen.io/heropark/embed/oeWLVm/?height=265&theme-id=0&default-tab=html,result" frameborder="no" allowtransparency="true" allowfullscreen style="width:100%">See the Pen <a href="https://codepen.io/heropark/pen/oeWLVm/" target="_blank" rel="noopener">flex-shrink</a> by park young woong (<a href="https://codepen.io/heropark" target="_blank" rel="noopener">@heropark</a>) on <a href="https://codepen.io" target="_blank" rel="noopener">CodePen</a>.<br></iframe><p>계산이 까다롭기 때문에 활용도는 조금 떨어진다고 생각합니다.<br>원리 정도만 이해하고 넘어갑시다.</p><h4><span id="a00fa25f-7148-4c23-a273-974d6e5a9585">flex-basis</span><a href="#a00fa25f-7148-4c23-a273-974d6e5a9585" class="header-anchor"></a></h4><p>Item의 (공간 배분 전) 기본 너비를 설정합니다.<br>값이 <code>auto</code>일 경우 <code>width</code>, <code>height</code> 등의 속성으로 Item의 너비를 설정할 수 있습니다.<br>하지만 단위 값이 주어질 경우 설정할 수 없습니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>auto</td><td>가변 Item과 같은 너비</td><td><code>auto</code></td></tr><tr><td>단위</td><td>px, em, cm 등 단위로 지정</td><td></td></tr></tbody></table><pre><code class="plaintext">flex-basis: 기본너비;</code></pre><p><code>flex</code> 속성에서 설명한 것 같이 단축 속성 내에서 <code>flex-basis</code>를 생략하면 값이 <code>0</code>이 되는 것을 주의합시다.</p><p><img src="/images/screenshot/css-flexible-box/flex-basis.jpg" alt="Flex"></p><h3><span id="b66ac8cc-17c0-487b-b966-0b7c56e55482">align-self</span><a href="#b66ac8cc-17c0-487b-b966-0b7c56e55482" class="header-anchor"></a></h3><p>교차 축(cross-axis)에서 개별 Item의 정렬 방법을 설정합니다.</p><p><code>align-items</code>는 Container 내 모든 Items의 정렬 방법을 설정합니다.<br>필요에 의해 일부 Item만 정렬 방법을 변경하려고 할 경우 <code>align-self</code>를 사용할 수 있습니다.<br>이 속성은 <code>align-items</code> 속성보다 우선합니다.</p><table><thead><tr><th>값</th><th>의미</th><th>기본값</th></tr></thead><tbody><tr><td>auto</td><td>Container의 <code>align-items</code> 속성을 상속받음</td><td><code>auto</code></td></tr><tr><td>stretch</td><td>Container의 교차 축을 채우기 위해 Item을 늘림</td><td></td></tr><tr><td>flex-start</td><td>Item을 각 줄의 시작점(flex-start)으로 정렬</td><td></td></tr><tr><td>flex-end</td><td>Item을 각 줄의 끝점(flex-end)으로 정렬</td><td></td></tr><tr><td>center</td><td>Item을 가운데 정렬</td><td></td></tr><tr><td>baseline</td><td>Item을 문자 기준선에 정렬</td><td></td></tr></tbody></table><pre><code class="plaintext">align-self: 정렬방법;</code></pre><p><img src="/images/screenshot/css-flexible-box/flex-align-self.jpg" alt="Flex"></p><h1><span id="e8bd7fa6-92a0-4ad6-96f8-9dbba8df7923">참고 자료(References)</span><a href="#e8bd7fa6-92a0-4ad6-96f8-9dbba8df7923" class="header-anchor"></a></h1><p><a href="https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Flexible_Box_Layout/Flexbox%EC%9D%98_%EA%B8%B0%EB%B3%B8_%EA%B0%9C%EB%85%90" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Flexible_Box_Layout/Flexbox%EC%9D%98_%EA%B8%B0%EB%B3%B8_%EA%B0%9C%EB%85%90</a></p>]]></content>
    
    <summary type="html">
    
      많은 경우 float, inline-block, table 등의 도움을 받아서 수평 레이아웃을 구성하지만 이는 차선책이며, 우리는 Flex(Flexible Box)라는 명확한 개념(속성들)으로 레이아웃을 쉽게 구성할 수 있습니다. CSS Flex에 대해서 알아봅시다.
    
    </summary>
    
    
      <category term="css3" scheme="https://heropy.blog/tags/css3/"/>
    
      <category term="flex" scheme="https://heropy.blog/tags/flex/"/>
    
  </entry>
  
  <entry>
    <title>정규표현식, 이렇게 시작하자!</title>
    <link href="https://heropy.blog/2018/10/28/regexp/"/>
    <id>https://heropy.blog/2018/10/28/regexp/</id>
    <published>2018-10-28T08:00:00.000Z</published>
    <updated>2019-10-21T01:42:08.000Z</updated>
    
    <content type="html"><![CDATA[<p>자바스크립트를 사용하면서 정규표현식을 한 번도 사용하지 않은 분은 없을 것입니다.<br>하지만 많은 분이 ‘정규표현식은 검색해서 쓰는 정도’로 “나중에 배워야지”라는 생각을 하는 듯합니다.<br>물론 정규표현식이라는 것이 개발하면서 자주 사용되는 것도 아니고, 가독성이 좋다고 볼 수도 없기 때문에 패턴을 외우지 않으면 사용하기 어려워 공부를 미뤄오신 분이 많을 것으로 봅니다.<br>‘무조건 배워야 해!’ 보다는 조금 더 쉽게 배울 방법에 대해서 고민해 보고자 이 문서에 정리해 보았습니다.<br>정규표현식을 처음 시작하시는 분들에게 도움이 되었으면 합니다.</p><div class="toc"><ul><li><a href="b160a55f-49a1-41ba-a0a9-14e6bc3d1a00">정규표현식 with JavaScript</a><ul><li><a href="c14609fd-7d62-4790-b352-bd852c8e2fc8">정규표현식 테스트 사이트</a></li><li><a href="a2647780-d292-4b03-9c66-2a25ab69ebf7">자바스크립트 정규식 생성</a><ul><li><a href="f0d53620-23db-4a0e-8d71-a94cfb72d81f">생성자 함수 방식</a></li><li><a href="8a2f6cc2-14f1-4a5a-8329-1b919cd8fbc3">리터럴(Literal) 방식</a></li><li><a href="78a878b9-e45c-4fdb-8d2b-a10e4d74327e">재할당(Re-compile)</a></li></ul></li><li><a href="4a669e74-38db-49ac-a500-734ae3297ee1">자바스크립트 속성</a></li><li><a href="7137eb37-bb40-40ac-9479-0f86c287c72f">자바스크립트 메소드</a></li><li><a href="55fea5e2-f492-4065-8bc3-721cf1e5fed1">플래그</a><ul><li><a href="22fd1b8e-5477-4cd6-b151-bbc5da75270a">g(global)</a></li><li><a href="67966c57-3d17-4e2c-9e4a-4280d82d7f2e">m(multi line)</a></li><li><a href="80141b76-f877-4f43-9510-8bc1e964bab0">u(unicode)</a></li><li><a href="fa3de505-6a99-47b3-8ea6-cff095f6663b">y(sticky)</a></li></ul></li><li><a href="7085a129-b220-4a59-b741-6fdb695febe4">정규식 패턴(표현식)</a><ul><li><a href="de07327a-68ba-42dc-9a89-20960b1771ff">패턴 ^ (줄의 시작에서 일치)</a></li><li><a href="cfaa14fc-5911-41d3-827a-5009ac864570">패턴 $ (줄의 끝에서 일치)</a></li><li><a href="28abe5ef-699a-45e7-a420-354f95db8b5f">패턴 . (임의의 한 문자와 일치)</a></li><li><a href="9e795a0f-e992-427b-a2fa-000b1392b79a">패턴 a|b (a 또는 b와 일치)</a></li><li><a href="46635a08-96ab-448a-9ca0-b4017e062f68">패턴 * (0회 이상 연속으로 반복되는 문자와 가능한 많이 일치)</a><ul><li><a href="a5310c1e-1f35-4d9a-964a-75f47262bfb0">Advanced! 선택적 패턴</a></li></ul></li><li><a href="1617ab8a-c9c0-45f5-a01c-f1d5b3c2df04">패턴 *? (0회 이상 연속으로 반복되는 문자와 가능한 적게 일치)</a></li><li><a href="1c59fa4c-fed9-4f17-bd4b-8f18a47c2c59">패턴 + (1회 이상 연속으로 반복되는 문자에 가능한 많이 일치)</a></li><li><a href="fdc2f0ff-0683-49e7-8e31-9065798305f3">패턴 +? (1회 이상 연속으로 반복되는 문자에 가능한 적게 일치)</a></li><li><a href="acd26fec-20c3-47e0-a7aa-fe83f9f6018d">패턴 ? (없거나 1회 가능한 많이 일치)</a></li><li><a href="177f5f4c-f9a6-46e5-ab66-dc899442eb89">패턴 ?? (없거나 1회 가능한 적게 일치)</a></li><li><a href="16dfbd16-d1b8-4006-bbbf-fdefdbf28d96">패턴 {} (연속 일치)</a></li><li><a href="7e3575fd-2688-41ee-a612-cd1205c73382">패턴 () (캡처할 그룹)</a><ul><li><a href="e086e797-bd45-4be2-9273-fdbacd62357b">그룹화</a></li><li><a href="44db6fb8-8c8a-4df7-9238-c6cbf6e3a995">캡처(capturing)</a></li><li><a href="ed831c63-c672-40c2-ac40-ea6a3ca7c633">정규식 내 캡처된 값 참조</a></li><li><a href="d6ea69d2-acb5-4c98-b369-2cf4e060b546">문자열 대체 시 캡처된 값 참조</a></li><li><a href="115a8490-66a2-43d9-9a2e-ff4a1123bbc7">Advanced! 선택적 캡처 그룹</a></li></ul></li><li><a href="edfaaee5-ebad-4f18-b08f-02384ceadb39">패턴 (?&lt;&gt;) 캡처 그룹 이름 지정</a></li><li><a href="65f68d9d-4328-42f6-a2c7-ec3aab06f267">패턴 (?:) (캡처하지 않는 그룹)</a></li><li><a href="57b5acdc-b79a-43d9-b210-61040201c705">패턴 (?=) (앞쪽 일치)</a></li><li><a href="71f17b1d-afdb-49de-b50a-310b50f8cbb1">패턴 (?!) (부정 앞쪽 일치)</a></li><li><a href="0a13ff8f-acc7-407e-bfd0-0a4bfebcf87b">패턴 (?&lt;=) (뒤쪽 일치)</a></li><li><a href="94a907cf-54ee-4f08-ad87-ec71b75217c2">패턴 (?&lt;!) (부정 뒤쪽 일치)</a></li><li><a href="7e072053-4766-461b-b649-705b2b6d4988">패턴 [abc] (a 또는 b 또는 c와 일치)</a><ul><li><a href="75e62112-a62c-4cb9-8b4d-a21cdbedbf87">문자 범위 지정</a></li><li><a href="c0e64f8c-56ee-40f8-83b8-736337061ed0">이스케이프 문자</a></li></ul></li><li><a href="7ada1fb5-832e-46a5-98e9-9fe72a8612d5">패턴 [^abc] (a 또는 b 또는 c가 아닌 나머지 문자에 일치)</a></li></ul></li></ul></li><li><a href="d51f15da-5642-4f50-9450-3a196d242e35">정규표현식 예제</a><ul><li><a href="e07bf56d-630b-4199-84d4-f1eac9767e33">정규표현식에 변수 삽입</a></li><li><a href="53600c98-85d4-4016-9598-fa125e94f94b">괄호 사이 값 추출</a></li></ul></li><li><a href="499652d0-a3a7-4574-a630-0449e7102453">참고 자료(References)</a></li></ul></div><h1><span id="b160a55f-49a1-41ba-a0a9-14e6bc3d1a00">정규표현식 with JavaScript</span><a href="#b160a55f-49a1-41ba-a0a9-14e6bc3d1a00" class="header-anchor"></a></h1><p>정규표현식이란 문자열을 검색하고 대체하는 데 사용 가능한 일종의 형식 언어(패턴)입니다.<br>간단한 문자 검색부터 이메일, 패스워드 검사 등의 복잡한 문자 일치 기능 등을 정규식 패턴으로 빠르게 수행할 수 있습니다.<br>단 정규식 패턴이 수행 내용과 매치가 잘 안 되어 가독성이 많이 떨어지기 때문에 입문자들이 어려워하는 경우가 많습니다.<br>하지만 초반 개념만 잘 잡으면 금방 익숙해질 수 있습니다.</p><p>정규표현식은 크게 다음과 같은 역할을 수행합니다.</p><ol><li>문자 검색(search)</li><li>문자 대체(replace)</li><li>문자 추출(extract)</li></ol><p>자바스크립트는 직접 빌드된 정규표현식을 지원하는 언어 중 하나로,<br>이 포스트에서는 자바스크립트에서 사용하는 정규표현식(정규식)을 기준으로 내용을 살펴보겠습니다.</p><blockquote><p>특정한 언어나 환경에서만 동작하는 패턴이 있습니다.<br>모든 정규식을 다룰 수 없어 자바스크립트를 기준으로 문서를 정리했습니다.</p></blockquote><h2><span id="c14609fd-7d62-4790-b352-bd852c8e2fc8">정규표현식 테스트 사이트</span><a href="#c14609fd-7d62-4790-b352-bd852c8e2fc8" class="header-anchor"></a></h2><p>내용을 이해하면서 실제로 적용해 보는 것이 좋습니다.<br>아래의 사이트들을 이용하여 정규식을 테스트해 봅시다.</p><p>단 각 사이트의 설정된 환경이 다르기 때문에 일부 작동하지 않거나 자바스크립트에서 다루는 정규식과 다르게 작동할 수 있습니다.<br>사이트에서 테스트한 정규식의 결과를 맹신하지 말고 자신의 환경에 맞는지 꼭 테스트하세요.<br>(따라서 특정 정규식의 작동 여부가 해당 사이트의 우수성을 말하진 않습니다)</p><p><a href="https://regex101.com/" target="_blank" rel="noopener">https://regex101.com/</a><br><a href="https://regexr.com/" target="_blank" rel="noopener">https://regexr.com/</a><br><a href="https://regexper.com/" target="_blank" rel="noopener">https://regexper.com/</a></p><h2><span id="a2647780-d292-4b03-9c66-2a25ab69ebf7">자바스크립트 정규식 생성</span><a href="#a2647780-d292-4b03-9c66-2a25ab69ebf7" class="header-anchor"></a></h2><h3><span id="f0d53620-23db-4a0e-8d71-a94cfb72d81f">생성자 함수 방식</span><a href="#f0d53620-23db-4a0e-8d71-a94cfb72d81f" class="header-anchor"></a></h3><p><code>RegExp</code> 생성자 함수를 호출하여 사용할 수 있습니다.</p><pre><code class="js">const regexp1 = new RegExp(&quot;^abc&quot;);// new RegExg(표현식)const regexp2 = new RegExp(&quot;^abc&quot;, &quot;gi&quot;);// new RegExg(표현식, 플래그)</code></pre><h3><span id="8a2f6cc2-14f1-4a5a-8329-1b919cd8fbc3">리터럴(Literal) 방식</span><a href="#8a2f6cc2-14f1-4a5a-8329-1b919cd8fbc3" class="header-anchor"></a></h3><p>정규표현식은 <code>/</code>로 감싸진 패턴을 리터럴로 사용합니다.</p><pre><code class="js">const regexp1 = /^abc/;// /표현식/const regexp2 = /^abc/gi;// /표현식/플래그</code></pre><p>보통의 경우에는 리터럴 방식이 훨씬 편리합니다.<br>하지만 상황에 따라 <code>RegExg</code> 생성자 함수를 써야만 하는 경우도 있습니다.</p><h3><span id="78a878b9-e45c-4fdb-8d2b-a10e4d74327e">재할당(Re-compile)</span><a href="#78a878b9-e45c-4fdb-8d2b-a10e4d74327e" class="header-anchor"></a></h3><p>사용 중인 정규식을 재할당할 수 있습니다.<br>단 상수가 아닌 변수로 선언해야 합니다.</p><pre><code class="js">let regexp1 = /ipsum/g;regexp1 = /lorem/i;console.log(regexp1);// /lorem/iconst regexp2 = /ipsum/g;regexp2 = /lorem/i;  // TypeError</code></pre><h2><span id="4a669e74-38db-49ac-a500-734ae3297ee1">자바스크립트 속성</span><a href="#4a669e74-38db-49ac-a500-734ae3297ee1" class="header-anchor"></a></h2><p>자바스크립트에는 정규표현식에서 제공하는 다양한 속성(Properties)이 있습니다.</p><table><thead><tr><th>속성</th><th>설명</th></tr></thead><tbody><tr><td><code>flags</code></td><td>플래그(String) 반환, <code>/^abc/gi.flags</code></td></tr><tr><td><code>source</code></td><td>표현식(String) 반환, <code>/^abc/gi.source</code></td></tr><tr><td><code>global</code></td><td>플래그 <code>g</code> 여부(Boolean) 반환, <code>/^abc/gi.global</code></td></tr><tr><td><code>ignoreCase</code></td><td>플래그 <code>i</code> 여부(Boolean) 반환</td></tr><tr><td><code>multiline</code></td><td>플래그 <code>m</code> 여부(Boolean) 반환</td></tr><tr><td><code>sticky</code></td><td>플래그 <code>y</code> 여부(Boolean) 반환</td></tr><tr><td><code>unicode</code></td><td>플래그 <code>u</code> 여부(Boolean) 반환</td></tr></tbody></table><p>정규식에서 플래그만 추출할 경우 <code>flags</code> 속성을 유용하게 사용할 수 있습니다.<br>알파벳 순서대로 값이 반환됩니다.</p><pre><code class="js">new RegExp(&quot;^abc&quot;, &quot;gi&quot;).flags;// &quot;gi&quot;/^abc/igy.flags;// &quot;giy&quot;</code></pre><p>표현식을 추출할 경우는 <code>source</code> 속성을 사용합니다.</p><pre><code class="js">new RegExp(&quot;^abc&quot;, &quot;gi&quot;).source;// &quot;^abc&quot;/^abc/igy.source;// &quot;^abc&quot;</code></pre><p>플래그 포함 여부도 확인할 수 있습니다.</p><pre><code class="js">/^abc/igy.global;// true/^abc/igy.ignoreCase;// true/^abc/igy.unicode;// false</code></pre><h2><span id="7137eb37-bb40-40ac-9479-0f86c287c72f">자바스크립트 메소드</span><a href="#7137eb37-bb40-40ac-9479-0f86c287c72f" class="header-anchor"></a></h2><p>정규표현식을 다루는 다양한 메소드(Methods)들을 살펴봅시다.</p><table><thead><tr><th>메소드</th><th>문법</th><th>설명</th></tr></thead><tbody><tr><td><code>exec</code></td><td><code>정규식.exec(문자열)</code></td><td>일치하는 하나의 정보(Array) 반환</td></tr><tr><td><code>test</code></td><td><code>정규식.test(문자열)</code></td><td>일치 여부(Boolean) 반환</td></tr><tr><td><code>match</code></td><td><code>문자열.match(정규식)</code></td><td>일치하는 문자열의 배열(Array) 반환</td></tr><tr><td><code>search</code></td><td><code>문자열.search(정규식)</code></td><td>일치하는 문자열의 인덱스(Number) 반환</td></tr><tr><td><code>replace</code></td><td><code>문자열.replace(정규식,대체문자)</code></td><td>일치하는 문자열을 대체하고 대체된 문자열(String) 반환</td></tr><tr><td><code>split</code></td><td><code>문자열.split(정규식)</code></td><td>일치하는 문자열을 분할하여 배열(Array)로 반환</td></tr><tr><td><code>toString</code></td><td><code>생성자_정규식.toString()</code></td><td>생성자 함수 방식의 정규식을 리터럴 방식의 문자열(String)로 반환</td></tr></tbody></table><p>우선 테스트를 위해서 다음과 같은 문장을 준비합니다.</p><pre><code class="js">const str = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry&#39;s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`;</code></pre><p>예를 들어 <code>.exec()</code>는 다음과 같이 사용할 수 있습니다.<br>일치하는 결과가 없다면 <code>null</code>을 반환합니다.</p><pre><code class="js">// 정규식.exec(문자열);/ipsum/.exec(str);// null/ipsum/i.exec(str);// [&quot;Ipsum&quot;, index: 6, input: &quot;Lorem Ipsum is simply dummy text of the printing a…ldus PageMaker including versions of Lorem Ipsum.&quot;, groups: undefined]</code></pre><p>정규식과 문자열의 순서를 바꿔서 작성하는 메소드 <code>.match()</code>는 다음과 같이 사용할 수 있습니다.<br>역시 일치하는 결과가 없다면 <code>null</code>을 반환합니다.</p><pre><code class="js">// 문자열.match(정규식);str.match(/ipsum/);// nullstr.match(/ipsum/i);// [&quot;Ipsum&quot;, index: 6, input: &quot;Lorem Ipsum is simply dummy text of the printing a…ldus PageMaker including versions of Lorem Ipsum.&quot;, groups: undefined]</code></pre><p>위 결과들을 자세히 살펴보면 반환 결과는 전혀 다르지만 플래그 자리에 <code>i</code>의 삽입 여부만 차이가 있습니다.<br>이렇게 플래그 <code>i</code>가 무엇을 의미하는지 모른다면 결과를 전혀 유추할 수 없기 때문에 정규표현식이 어렵다고 생각합니다.<br>(사실 플래그보단 표현식 패턴의 난해한 특수기호들이 더욱 문제이지만..)<br>참고로 플래그 <code>i</code>는 ‘영어 대소문자를 구분하지 않겠다’라는 의미로 사용되었습니다.</p><h2><span id="55fea5e2-f492-4065-8bc3-721cf1e5fed1">플래그</span><a href="#55fea5e2-f492-4065-8bc3-721cf1e5fed1" class="header-anchor"></a></h2><p>위에서 살펴본 것처럼 플래그(flags)에 따라서 전혀 다른 결과가 나올 수 있습니다.<br>플래그는 표현식의 옵션으로 표현식으로 검색하려는 문자 패턴에 추가적인 옵션을 넣어 원하는 문자 검색 결과를 반환하도록 할 수 있습니다.</p><table><thead><tr><th>플래그</th><th>설명</th></tr></thead><tbody><tr><td><code>g</code></td><td>모든 문자와 여러 줄 일치(global)</td></tr><tr><td><code>i</code></td><td>영어 대소문자를 구분 않고 일치(insensitive, ignore case)</td></tr><tr><td><code>m</code></td><td>여러 줄 일치(multi line)</td></tr><tr><td><code>u</code></td><td>유니코드(unicode)</td></tr><tr><td><code>y</code></td><td><code>lastIndex</code> 속성으로 지정된 인덱스에서만 1회 일치(sticky)</td></tr></tbody></table><h3><span id="22fd1b8e-5477-4cd6-b151-bbc5da75270a">g(global)</span><a href="#22fd1b8e-5477-4cd6-b151-bbc5da75270a" class="header-anchor"></a></h3><p>비교적 많이 사용되는 플래그는 <code>g</code> 입니다.<br>다음을 예제를 보겠습니다.</p><pre><code class="js">str.match(/ipsum/i);// [&quot;Ipsum&quot;, index: 6, input: &quot;Lorem Ipsum is simply dummy text of the printing a…ldus PageMaker including versions of Lorem Ipsum.&quot;, groups: undefined]str.match(/ipsum/ig);// (4) [&quot;Ipsum&quot;, &quot;Ipsum&quot;, &quot;Ipsum&quot;, &quot;Ipsum&quot;]</code></pre><p>상수 <code>str</code>에 입력된 문자에서 ‘ipsum’이라는 문자를 검색하는 데 하나는 플래그로 <code>i</code>만 추가했고 다른 하나는 <code>ig</code>를 추가했습니다.<br>플래그 <code>g</code>는 ‘모든 문자를 검색하겠다’라는 의미로 사용되었습니다.<br>따라서 <code>g</code>가 없는 표현식은 하나의(최초의) 검색 결과만 반환했고 <code>g</code>가 있는 표현식은 모든 검색 결과를 배열로 반환했습니다.</p><h3><span id="67966c57-3d17-4e2c-9e4a-4280d82d7f2e">m(multi line)</span><a href="#67966c57-3d17-4e2c-9e4a-4280d82d7f2e" class="header-anchor"></a></h3><p>여러 줄(줄바꿈이 들어간) 모드에서 검색할지를 설정합니다.<br>우선 플래그 <code>m</code>을 테스트하기 위해 위에서 설정한 <code>str</code> 변수 내 문장에서 마침표(<code>.</code>)가 있는 곳마다 줄바꿈을 삽입합니다</p><blockquote><p>각 줄에서 Enter 키를 누르세요.</p></blockquote><pre><code class="js">const strMultyLine = `Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum has been the industry&#39;s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`;</code></pre><p>우리는 아직 패턴을 시작하지 않았기 때문에 표현식에 집중하지 말고 플래그만 보세요.<br>다음 표현식은 위 문장에서 ‘줄의 끝’을 검색하겠다는 의미입니다.<br>전체 문장의 가장 끝부분이 검색되어 ‘빈 문자열’인 <code>&quot;&quot;</code>을 하나 반환합니다.</p><pre><code class="js">strMultyLine.match(/$/g);// [&quot;&quot;]</code></pre><p>여기서 플래그 <code>m</code>을 추가하면 여러 줄에서 끝부분을 검색하겠다는 의미로 사용할 수 있습니다.<br>따라서 줄바꿈 된 모든 줄에서의 ‘줄의 끝’을 검색합니다.<br>우리가 삽입한 총 3번의 줄바꿈과 함께 전체 문장의 가장 끝부분을 포함하여 총 4개의 결과를 반환합니다.</p><pre><code class="js">strMultyLine.match(/$/gm);// [&quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;]</code></pre><h3><span id="80141b76-f877-4f43-9510-8bc1e964bab0">u(unicode)</span><a href="#80141b76-f877-4f43-9510-8bc1e964bab0" class="header-anchor"></a></h3><p>유니 코드 문자를 처리하기 위해서 필요합니다.</p><pre><code class="js">/🐘{2,}/.test(&quot;🐘🐘🐘🐘&quot;);// false/🐘{2,}/u.test(&quot;🐘🐘🐘🐘&quot;);// true</code></pre><p>아래에서 살펴볼 패턴 <code>[]</code>을 이용하여 유니 코드 문자의 범위(구간)를 지정할 수도 있습니다.</p><pre><code class="js">/[0-9]/.test(&quot;5&quot;);// true/[😀-😇]/u.test(&quot;😂&quot;);// true</code></pre><h3><span id="fa3de505-6a99-47b3-8ea6-cff095f6663b">y(sticky)</span><a href="#fa3de505-6a99-47b3-8ea6-cff095f6663b" class="header-anchor"></a></h3><p>생성된 정규표현식 인스턴스에서는 <code>lastIndex</code>라는 속성을 사용할 수 있습니다.<br>이 <code>lastIndex</code> 속성에 숫자를 지정한 뒤 플래그 <code>y</code>로 검색하면 그 숫자와 일치하는 <code>index</code>의 문자만 검색합니다.</p><pre><code class="js">const str = &quot;Have a nice day!&quot;;// index =&gt;  0123456789012345// 일반 정규식const regexp = /nice/;// Sticky 정규식const stickyRegexp = /nice/y;stickyRegexp.lastIndex = 3;str.match(regexp);// [&quot;nice&quot;, index: 7, input: &quot;Have a nice day!&quot;, groups: undefined]str.search(regexp);// 7str.match(stickyRegexp);// nullstickyRegexp.lastIndex = 7;stickyRegexp.lastIndex;// 7str.match(stickyRegexp);// [&quot;nice&quot;, index: 7, input: &quot;Have a nice day!&quot;, groups: undefined]str.match(stickyRegexp);// nullstickyRegexp.lastIndex;// 0</code></pre><p><code>lastIndex</code> 속성은 1회용이며 ‘Read-only’입니다.<br>따라서 한번 검색하는 데 사용되었다면 값이 초기화됩니다.</p><p>플래그 <code>y</code>와 <code>g</code>를 함께 사용할 경우 플래그 <code>g</code>는 무시되며 되도록 단독으로 사용하는 것을 추천합니다.<br>또한 비교적 최근에 지원되기 시작했으니 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky#Browser_compatibility" target="_blank" rel="noopener">Browser support</a>을 잘 확인하고 사용하세요.</p><h2><span id="7085a129-b220-4a59-b741-6fdb695febe4">정규식 패턴(표현식)</span><a href="#7085a129-b220-4a59-b741-6fdb695febe4" class="header-anchor"></a></h2><p>표현식의 다양한 특수기호(패턴)는 그 기호의 의미(기능)와 매칭되어 인식되지 않기 때문에 따로 외우지 않으면 의미를 파악할 수가 없습니다.<br>우선 기본적인 의미를 아래의 표에 정리했습니다.</p><table><thead><tr><th>정규식 패턴</th><th>설명</th></tr></thead><tbody><tr><td><code>^</code></td><td>줄(Line)의 시작에서 일치, <code>/^abc/</code></td></tr><tr><td><code>$</code></td><td>줄(Line)의 끝에서 일치, <code>/xyz$/</code></td></tr><tr><td><code>.</code></td><td>임의의 한 문자와 일치</td></tr><tr><td><code>a&verbar;b</code></td><td>a 또는 b와 일치, 인덱스가 작은 것을 우선 반환</td></tr><tr><td><code>*</code></td><td>0회 이상 연속으로 반복되는 문자와 가능한 많이 일치, <code>{0,}</code>와 동일</td></tr><tr><td><code>*?</code></td><td>0회 이상 연속으로 반복되는 문자와 가능한 적게 일치(lazy), <code>{0}</code>와 동일</td></tr><tr><td><code>+</code></td><td>1회 이상 연속으로 반복되는 문자에 가능한 많이 일치, <code>{1,}</code>와 동일</td></tr><tr><td><code>+?</code></td><td>1회 이상 연속으로 반복되는 문자에 가능한 적게 일치(lazy), <code>{1}</code>와 동일</td></tr><tr><td><code>?</code></td><td>없거나 1회 가능한 많이 일치</td></tr><tr><td><code>??</code></td><td>없거나 1회 가능한 적게 일치(lazy)</td></tr><tr><td><code>{3}</code></td><td>3(숫자)개 연속 일치</td></tr><tr><td><code>{3,}</code></td><td>3개 이상 연속 일치</td></tr><tr><td><code>{3,5}</code></td><td>3개 이상 5개 이하(3~5개) 연속 일치</td></tr><tr><td><code>{3,5}?</code></td><td>3개 이상 5개 이하(3~5개) 연속 중 가능한 적은 3개 연속 일치(lazy), <code>{3}</code>와 동일</td></tr><tr><td><code>()</code></td><td>캡처(Capture)할 그룹</td></tr><tr><td><code>(?&lt;&gt;)</code></td><td>캡처 그룹 이름 지정, <code>/(?&lt;name&gt;pattern)/</code> <strong>ES2018</strong></td></tr><tr><td><code>\1~9</code></td><td>정규식 내 캡처된 값 참조, <code>/(abc)\1/</code></td></tr><tr><td><code>(?:)</code></td><td>캡처(Capture)하지 않는 그룹</td></tr><tr><td><code>(?=)</code></td><td>앞쪽 일치(Lookahead), <code>/ab(?=c)/</code></td></tr><tr><td><code>(?!)</code></td><td>부정 앞쪽 일치(Negative Lookahead), <code>/ab(?!c)/</code></td></tr><tr><td><code>(?&lt;=)</code></td><td>뒤쪽 일치(Lookbehind), <code>/(?&lt;=ab)c/</code> <strong>ES2018</strong></td></tr><tr><td><code>(?&lt;!)</code></td><td>부정 뒤쪽 일치(Negative Lookbehind), <code>/(?&lt;!ab)c/</code> <strong>ES2018</strong></td></tr><tr><td><code>[abc]</code></td><td>a 또는 b 또는 c와 일치, 점(<code>.</code>)이나 별표(<code>*</code>) 같은 특수 문자는 <code>[]</code>안에서 특수 문자가 아님, <code>/\.[.]/</code></td></tr><tr><td><code>[a-z]</code></td><td>a부터 z 사이의 문자 구간에 일치(영어 소문자)</td></tr><tr><td><code>[A-Z]</code></td><td>A부터 Z 사이의 문자 구간에 일치(영어 대문자)</td></tr><tr><td><code>[0-9]</code></td><td>0부터 9 사이의 문자 구간에 일치(숫자)</td></tr><tr><td><code>[가-힣]</code></td><td>가부터 힣 사이의 문자 구간에 일치(한글)</td></tr><tr><td><code>[2-7]</code></td><td>2부터 7 사이의 문자 구간에 일치(2,3,4,5,6,7)</td></tr><tr><td><code>[b-f]</code></td><td>b부터 f 사이의 문자 구간에 일치(b,c,d,e,f)</td></tr><tr><td><code>[다-바]</code></td><td>다부터 바 사이의 문자 구간에 일치(다,라,마,바)</td></tr><tr><td><code>[^abc]</code></td><td>a 또는 b 또는 c가 아닌 나머지 문자에 일치(부정)</td></tr><tr><td><code>\</code></td><td>이스케이프 문자, <code>/\.\?\/\$\^/</code></td></tr><tr><td><code>\b</code></td><td>63개 문자(영문 대소문자 52개 + 숫자 10개 + <code>_</code>(underscore))가 아닌 나머지 문자에 일치하는 경계(boundary)</td></tr><tr><td><code>\B</code></td><td>63개 문자에 일치하는 경계</td></tr><tr><td><code>\d</code></td><td>숫자(Digit)에 일치</td></tr><tr><td><code>\D</code></td><td>숫자가 아닌 문자에 일치</td></tr><tr><td><code>\p{}</code></td><td>유니코드 속성(Property) 집합에 맞는 문자에 일치, <code>/\p{Emoji}/u</code> <strong>ES2018</strong></td></tr><tr><td><code>\P{}</code></td><td>유니코드 속성 집합에 맞지 않는 문자에 일치, <code>/\p{Uppercase}/u</code> <strong>ES2018</strong></td></tr><tr><td><code>\s</code></td><td>공백(Space, Tab 등)에 일치</td></tr><tr><td><code>\S</code></td><td>공백이 아닌 문자에 일치</td></tr><tr><td><code>\w</code></td><td>63개 문자(Word, 영문 대소문자 52개 + 숫자 10개 + <code>_</code>)에 일치</td></tr><tr><td><code>\W</code></td><td>63개 문자가 아닌 나머지 문자에 일치</td></tr><tr><td><code>\x</code></td><td>16진수 문자에 일치, <code>/\x61/</code>는 <code>a</code>에 일치</td></tr><tr><td><code>\0</code></td><td>8진수 문자에 일치, <code>/\141/</code>은 <code>a</code>에 일치</td></tr><tr><td><code>\u</code></td><td>유니코드(Unicode) 문자에 일치, <code>/\u0061/</code>는 <code>a</code>에 일치</td></tr><tr><td><code>\c</code></td><td>제어(Control) 문자에 일치</td></tr><tr><td><code>\f</code></td><td>폼 피드(FF, U+000C) 문자에 일치</td></tr><tr><td><code>\n</code></td><td>줄 바꿈(LF, U+000A) 문자에 일치</td></tr><tr><td><code>\r</code></td><td>캐리지 리턴(CR, U+000D) 문자에 일치</td></tr><tr><td><code>\t</code></td><td>탭 (U+0009) 문자에 일치</td></tr><tr><td><code>$&grave;</code></td><td>문자 대체(replace) 시 일치한 문자 이전 값 참조</td></tr><tr><td><code>$&#39;</code></td><td>문자 대체(replace) 시 일치한 문자 이후 값 참조</td></tr><tr><td><code>$+</code></td><td>문자 대체(replace) 시 마지막으로 캡처된 값 참조</td></tr><tr><td><code>$&amp;</code></td><td>문자 대체(replace) 시 일치한 문자 결과 전체 참조</td></tr><tr><td><code>$&lowbar;</code></td><td>문자 대체(replace) 시 입력(input)된 문자 전체 참조</td></tr><tr><td><code>$1~9</code></td><td>문자 대체(replace) 시 캡처(Capture)된 값 참조</td></tr></tbody></table><p>지속해서 표를 참고하면 공부에 도움이 될 것입니다.</p><p>아래에서는 자주 사용하거나 중요한 패턴들 위주로 살펴보겠습니다.</p><p>이하 각 표현식 정리에서 사용할 공통 문자열로 다음과 같이 지정합니다.<br>문장은 <a href="https://www.lipsum.com/" target="_blank" rel="noopener">LoremIpsum</a> 페이지에서 참고하세요.<br>그리고 <a href="https://regex101.com/" target="_blank" rel="noopener">https://regex101.com/</a> 에서 테스트해 봅시다.</p><p><img src="/images/screenshot/regex101.com_screenshot_1.jpg" alt="RegExp"></p><h3><span id="de07327a-68ba-42dc-9a89-20960b1771ff">패턴 ^ (줄의 시작에서 일치)</span><a href="#de07327a-68ba-42dc-9a89-20960b1771ff" class="header-anchor"></a></h3><p><code>^</code>는 Line의 시작 지점을 의미합니다.</p><pre><code class="js">/^lorem/gi</code></pre><p>위 예제를 간단히 풀어보자면,<br><code>^</code>(시작 지점) 다음에 <code>&quot;lorem&quot;</code>이라는 문자가 있는 패턴을 검색하라는 표현식입니다.<br>플래그 <code>g</code>와 <code>i</code>를 사용했기 때문에 문자열 전역에서 대소문자 구분 없이 검색합니다.<br>다음과 같은 결과가 나옵니다.</p><p><img src="/images/screenshot/regex101.com_screenshot_2.jpg" alt="RegExp"></p><h3><span id="cfaa14fc-5911-41d3-827a-5009ac864570">패턴 $ (줄의 끝에서 일치)</span><a href="#cfaa14fc-5911-41d3-827a-5009ac864570" class="header-anchor"></a></h3><p><code>$</code>는 Line의 끝 지점을 의미합니다.</p><pre><code class="js">/ipsum$/gi</code></pre><p>위 예제로 검색하면 아무것도 검색되지 않습니다.<br>왜냐하면 실제 문장은 <code>&quot;Ipsum.&quot;</code>(마침표가 있습니다)으로 끝나기 때문입니다.<br>따라서 다음과 같이 작성해야 합니다.</p><pre><code class="js">/ipsum\.$/gi</code></pre><p>여기서 <code>.</code>은 정규식 내에서 특수한 의미(임의의 한 문자와 일치)를 가지기 때문에 단순히 마침표의 의미로 사용할 수 없습니다.<br>따라서 앞에 <code>\</code>(백슬래시)를 붙여줍니다.<br>백슬래시를 붙이는 이유는 다음과 같습니다.</p><blockquote><p>특수 문자 앞에 위치한 백슬래시는 ‘다음에 나오는 문자는 특별하지 않고 문자 그대로 해석되어야 한다.’는 사실을 가리킵니다.</p></blockquote><p><img src="/images/screenshot/regex101.com_screenshot_3.jpg" alt="RegExp"></p><h3><span id="28abe5ef-699a-45e7-a420-354f95db8b5f">패턴 . (임의의 한 문자와 일치)</span><a href="#28abe5ef-699a-45e7-a420-354f95db8b5f" class="header-anchor"></a></h3><p>위에서 보았지만 <code>.</code>은 마침표의 의미로 사용되지 않습니다. (<code>\.</code>으로 사용해야 마침표라는 의미를 가집니다)<br>패턴 <code>.</code>은 임의의 한 문자를 의미합니다.</p><pre><code class="js">/./gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_4.jpg" alt="RegExp"></p><p><code>.</code>은 임의의 한 문자를 의미하기 때문에 모든 문자(특수기호, 띄어쓰기 등 포함)가 선택됩니다.</p><pre><code class="js">/r./gi</code></pre><p><code>&quot;r&quot;</code>로 시작하며 임의의 한 문자를 포함하는 총 2개의 문자(2글자)가 플래그 <code>g</code>로 인해 모두 검색됩니다.</p><p><img src="/images/screenshot/regex101.com_screenshot_5.jpg" alt="RegExp"></p><h3><span id="9e795a0f-e992-427b-a2fa-000b1392b79a">패턴 a|b (a 또는 b와 일치)</span><a href="#9e795a0f-e992-427b-a2fa-000b1392b79a" class="header-anchor"></a></h3><p>‘or’(또는) 조건으로 검색합니다.<br><code>a|b</code>는 ‘a’도 검색하고 ‘b’도 검색합니다.</p><pre><code class="js">/of|te/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_6.jpg" alt="RegExp"></p><p>플래그 <code>g</code>를 제거하면 다음과 같습니다.<br>플래그 <code>g</code>가 없어 전역 검색이 아니기 때문에 검색된 결과의 인덱스가 가장 작은 값(가장 앞서 일치한)을 반환합니다.</p><p><img src="/images/screenshot/regex101.com_screenshot_7.jpg" alt="RegExp"></p><h3><span id="46635a08-96ab-448a-9ca0-b4017e062f68">패턴 * (0회 이상 연속으로 반복되는 문자와 가능한 많이 일치)</span><a href="#46635a08-96ab-448a-9ca0-b4017e062f68" class="header-anchor"></a></h3><pre><code class="js">/ing*/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_8.jpg" alt="RegExp"></p><p><code>&quot;in&quot;</code>을 검색하고 <code>&quot;g&quot;</code>는 0회 이상 연속으로 반복되는 문자와 가능한 많이 일치시킵니다.<br>즉 <code>&quot;g&quot;</code>는 0회 포함되거나(포함되지 않거나) 연속으로 몇 번을 반복 포함하나 검색이 됩니다.</p><p>예를들면,<br><code>&quot;in&quot;</code>은 <code>&quot;g&quot;</code>가 0회 포함되며(포함되지 않으며),<br><code>&quot;ing&quot;</code>는 <code>&quot;g&quot;</code>가 1번 포함되며,<br><code>&quot;ingg&quot;</code>는 <code>&quot;g&quot;</code>가 2번 포함되며,<br><code>&quot;ingggggggggg&quot;</code>은 <code>&quot;g&quot;</code>가 10번 포함됩니다.<br>따라서 <code>&quot;in&quot;</code>, <code>&quot;ing&quot;</code>, <code>&quot;ingg&quot;</code>, <code>&quot;ingggggggggg&quot;</code> 모두 검색됩니다.</p><blockquote><p>‘가능한 많이 일치’, ‘가능한 적게 일치’라는 개념은 검색된 문자 배열(결과)의 개수를 의미하는 것이 아닙니다.<br>뒤에서 좀 더 살펴봅시다.</p></blockquote><h4><span id="a5310c1e-1f35-4d9a-964a-75f47262bfb0">Advanced! 선택적 패턴</span><a href="#a5310c1e-1f35-4d9a-964a-75f47262bfb0" class="header-anchor"></a></h4><blockquote><p><code>Advanced!</code>는 이해가 안 되면 넘어가세요!</p></blockquote><p>우선 다음의 예제를 살펴봅시다.<br>플래그 <code>g</code>가 없음을 주의합시다!</p><pre><code class="js">&quot;abccc&quot;.match(/c*/);// [&quot;&quot;, index: 0, input: &quot;abccc&quot;, groups: undefined]</code></pre><p><img src="/images/screenshot/regexp_homemade_exam_1.jpg" alt="RegExp"></p><p>여기서 표현식 <code>c*</code>는 문자 <code>&quot;c&quot;</code>를 0회 포함하거나(포함하지 않거나) 반복 포함하면 일치 조건이 충족됩니다.<br>따라서 검색하려는 문자열(string) <code>&quot;abccc&quot;</code>에서 첫 글자 <code>&quot;a&quot;</code> 앞에 <code>&quot;&quot;</code>(빈 문자열, 시작 지점)은 <code>&quot;c&quot;</code>를 ‘0회 포함하거나’라는 일치 조건에 충족하므로 <code>.match()</code>의 반환 값으로 출력됩니다.</p><pre><code class="js">&quot;ccabccc&quot;.match(/c*/);// [&quot;cc&quot;, index: 0, input: &quot;ccabccc&quot;, groups: undefined]</code></pre><p><img src="/images/screenshot/regexp_homemade_exam_2.jpg" alt="RegExp"></p><p>다음 예제도 살펴봅시다.</p><pre><code class="js">&quot;ccacc&quot;.match(/c*/g);// [&quot;cc&quot;, &quot;&quot;, &quot;cc&quot;, &quot;&quot;]</code></pre><p>여기서는 일치하는 모든 결과를 배열로 반환하기 위해 플래그 <code>g</code>를 사용했습니다.<br>반환된 배열 값 중간에 <code>&quot;&quot;</code>(빈 문자열)이 포함되어 있습니다.<br>이는 패턴 <code>*</code>가 <code>&quot;c&quot;</code>를 ‘0회 포함하거나’라는 일치 조건을 가지기 때문입니다.<br>따라서 문자열의 시작 지점, 끝 지점, 문자 중간중간의 <code>&quot;&quot;</code>(빈 문자열)을 검색하는 조건이 충족됨을 의미합니다.</p><pre><code class="js">&quot;ccacc&quot;.match(/d*/g);// [&quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;]</code></pre><p>언뜻 보면 일치하는 결과가 없어 보이지만 위에서 설명한 내용과 같습니다.<br>문자열 <code>&quot;ccacc&quot;</code>에서 표현식 <code>d*</code>의 <code>&quot;d&quot;</code>와 일치하는 조건은 전혀 없지만 <code>*</code>는 ‘0회 포함하거나’라는 일치 조건을 가지기 때문에 <code>&quot;&quot;</code> 값이 시작 지점, 끝 지점을 포함한 문자 중간중간의 모든 <code>&quot;&quot;</code>을 반환하게 됩니다.</p><h3><span id="1617ab8a-c9c0-45f5-a01c-f1d5b3c2df04">패턴 *? (0회 이상 연속으로 반복되는 문자와 가능한 적게 일치)</span><a href="#1617ab8a-c9c0-45f5-a01c-f1d5b3c2df04" class="header-anchor"></a></h3><pre><code class="js">/pass*/gi</code></pre><p><code>*</code>는 가능한 많이 일치하기 위해서 <code>&quot;pas&quot;</code>에 0회 이상 일치하는 <code>&quot;s&quot;</code>를 포함해서 검색합니다.<br>따라서 다음과 같이 <code>&quot;pass&quot;</code>가 검색됩니다.</p><p><img src="/images/screenshot/regex101.com_screenshot_10.jpg" alt="RegExp"></p><pre><code class="js">/pass*?/gi</code></pre><p>하지만 패턴 <code>*?</code>는 가능한 적게 일치하기 위해서 <code>&quot;pas&quot;</code>에 0회 이상 일치하는 <code>&quot;s&quot;</code>를 포함하지 않습니다.<br>따라서 다음과 같이 <code>&quot;pas&quot;</code>가 검색됩니다.</p><p><img src="/images/screenshot/regex101.com_screenshot_9.jpg" alt="RegExp"></p><h3><span id="1c59fa4c-fed9-4f17-bd4b-8f18a47c2c59">패턴 + (1회 이상 연속으로 반복되는 문자에 가능한 많이 일치)</span><a href="#1c59fa4c-fed9-4f17-bd4b-8f18a47c2c59" class="header-anchor"></a></h3><p>패턴 <code>*</code>의 경우 일치하는 문자를 포함하지 않아도(0회 이상) 검색이 되었습니다.<br>하지만 <code>+</code>의 경우 일치하는 문자를 포함해야(1회 이상) 합니다.<br>다음의 예제의 차이를 확인하세요.</p><pre><code class="js">/dum*/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_11.jpg" alt="RegExp"></p><pre><code class="js">/dum+/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_12.jpg" alt="RegExp"></p><p>패턴 <code>+</code>의 경우 1개 이상은 포함해야 하므로 <code>&quot;du&quot;</code>에서 <code>&quot;m&quot;</code>이 1개 이상 포함된 문자를 검색합니다.<br>따라서 <code>&quot;du&quot;</code>는 일치하지 않지만 <code>&quot;dum&quot;</code>, <code>&quot;dumm&quot;</code>, <code>&quot;dummm&quot;</code>, <code>&quot;dummmmmmmmmm&quot;</code>은 일치하여 검색할 수 있습니다.</p><h3><span id="fdc2f0ff-0683-49e7-8e31-9065798305f3">패턴 +? (1회 이상 연속으로 반복되는 문자에 가능한 적게 일치)</span><a href="#fdc2f0ff-0683-49e7-8e31-9065798305f3" class="header-anchor"></a></h3><p>패턴 <code>+</code>와 <code>+?</code>도 패턴 <code>*</code>와 <code>*?</code>의 같은 차이점을 가집니다.</p><pre><code class="js">/dum+?/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_13.jpg" alt="RegExp"></p><p><code>&quot;m&quot;</code>이 1개 이상 포함하지만 가장 적게 일치하는 문자를 검색합니다.</p><h3><span id="acd26fec-20c3-47e0-a7aa-fe83f9f6018d">패턴 ? (없거나 1회 가능한 많이 일치)</span><a href="#acd26fec-20c3-47e0-a7aa-fe83f9f6018d" class="header-anchor"></a></h3><pre><code class="js">/ble?/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_14.jpg" alt="RegExp"></p><p><code>&quot;bl&quot;</code>을 검색하고 <code>&quot;e&quot;</code>는 없거나(포함하지 않거나) 1회(1번만 포함하는) 일치하는 문자를 검색합니다.<br>가능한 많이 일치하려고 하므로 <code>&quot;e&quot;</code>를 포함하여 일치합니다.<br>가능한 적게 일치하려고 한다면 <code>&quot;e&quot;</code>를 포함하지 않으려고 하겠죠?!</p><h3><span id="177f5f4c-f9a6-46e5-ab66-dc899442eb89">패턴 ?? (없거나 1회 가능한 적게 일치)</span><a href="#177f5f4c-f9a6-46e5-ab66-dc899442eb89" class="header-anchor"></a></h3><pre><code class="js">/ble??/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_15.jpg" alt="RegExp"></p><p>이제 ‘가능한 많이’와 ‘가능한 적게’의 차이점에 대해서 이해가 되나요?</p><h3><span id="16dfbd16-d1b8-4006-bbbf-fdefdbf28d96">패턴 {} (연속 일치)</span><a href="#16dfbd16-d1b8-4006-bbbf-fdefdbf28d96" class="header-anchor"></a></h3><p>패턴 <code>{}</code>은 위에서 봤던 패턴 <code>*</code>, <code>*?</code>, <code>+</code>, <code>+?</code>의 확장판으로 생각하시면 쉽습니다.<br>‘0개 이상’, ‘1개 이상’이 전부인 위 방식과 다르게 직접 숫자를 넣어서 연속되는 개수를 설정할 수 있습니다.</p><p>다음의 표를 보고 위에서 봤던 패턴들과 비교해 보세요.</p><table><thead><tr><th>정규식 패턴</th><th>설명</th></tr></thead><tbody><tr><td><code>*</code></td><td>0회 이상 연속으로 반복되는 문자와 가능한 많이 일치, <code>{0,}</code>와 동일</td></tr><tr><td><code>*?</code></td><td>0회 이상 연속으로 반복되는 문자와 가능한 적게 일치(lazy), <code>{0}</code>와 동일</td></tr><tr><td><code>+</code></td><td>1회 이상 연속으로 반복되는 문자에 가능한 많이 일치, <code>{1,}</code>와 동일</td></tr><tr><td><code>+?</code></td><td>1회 이상 연속으로 반복되는 문자에 가능한 적게 일치(lazy), <code>{1}</code>와 동일</td></tr><tr><td><code>{3}</code></td><td>3(숫자)개 연속 일치</td></tr><tr><td><code>{3,}</code></td><td>3개 이상 연속 일치</td></tr><tr><td><code>{3,5}</code></td><td>3개 이상 5개 이하(3~5개) 연속 일치</td></tr><tr><td><code>{3,5}?</code></td><td>3개 이상 5개 이하(3~5개) 연속 중 가능한 적은 3개 연속 일치(lazy), <code>{3}</code>와 동일</td></tr></tbody></table><pre><code class="js">/pas{0,1}/gi</code></pre><p><img src="/images/screenshot/regex101.com_screenshot_16.jpg" alt="RegExp"></p><h3><span id="7e3575fd-2688-41ee-a612-cd1205c73382">패턴 () (캡처할 그룹)</span><a href="#7e3575fd-2688-41ee-a612-cd1205c73382" class="header-anchor"></a></h3><p>패턴 <code>()</code>는 묶는다는(그룹화) 단순한 의미뿐만 아니라 여러 다른 의미들을 가지기 때문에 어려울 수 있습니다.<br>먼저 쉬운 그룹화 개념부터 살펴봅시다.</p><h4><span id="e086e797-bd45-4be2-9273-fdbacd62357b">그룹화</span><a href="#e086e797-bd45-4be2-9273-fdbacd62357b" class="header-anchor"></a></h4><p>표현식의 일부를 패턴 <code>()</code>로 묶어주어 그 안의 내용을 하나의 결과로 그룹화합니다.<br>그룹화한 결과와 그렇지 않은 결과를 비교해 보세요.</p><pre><code class="js">const ko = &#39;kokokoko&#39;;const koooo = &#39;kooookoooo&#39;;ko.match(/ko+/);// [&quot;ko&quot;, index: 0, input: &quot;kokokoko&quot;, groups: undefined]koooo.match(/ko+/);// [&quot;koooo&quot;, index: 0, input: &quot;kooookoooo&quot;, groups: undefined]ko.match(/(ko)+/);// [&quot;kokokoko&quot;, &quot;ko&quot;, index: 0, input: &quot;kokokoko&quot;, groups: undefined]koooo.match(/(ko)+/);// [&quot;ko&quot;, &quot;ko&quot;, index: 0, input: &quot;kooookoooo&quot;, groups: undefined]</code></pre><p>표현식 <code>ko+</code>는 <code>&quot;k&quot;</code>를 검색하고 <code>&quot;o&quot;</code>를 1회 이상 연속으로 반복되는 문자로 검색합니다.<br>그 결과로 <code>&quot;koooo&quot;</code>가 반환되었습니다.</p><p>하지만 표현식 <code>(ko)+</code>는 <code>&quot;k&quot;</code>와 <code>&quot;o&quot;</code>를 묶었기(그룹화) 때문에 <code>&quot;ko&quot;</code>를 1회 이상 연속으로 반복되는 문자로 검색합니다.<br>따라서 결과가 <code>&quot;kokokoko&quot;</code>가 반환되었습니다.</p><p>그룹화는 어렵지 않죠?!</p><p>마지막으로 패턴 <code>()</code>를 사용한 정규식들의 결과를 잘 보시면 일치한 결과가 2개입니다.<br>플래그 <code>g</code>를 사용하지 않았는데 어떻게 2개의 결과가 나왔을까요?</p><h4><span id="44db6fb8-8c8a-4df7-9238-c6cbf6e3a995">캡처(capturing)</span><a href="#44db6fb8-8c8a-4df7-9238-c6cbf6e3a995" class="header-anchor"></a></h4><p>패턴 <code>()</code>는 괄호 안에 있는 표현식을 캡처하여 사용합니다.<br>캡처는 일종의 복사본을 생성하는 개념입니다.</p><blockquote><p>복사라는 단어는 이해를 돕기 위해서만 사용하며, 실제 개념과는 다릅니다.</p></blockquote><p>위의 예제 중 다음 표현식을 살펴봅시다.</p><pre><code class="js">ko.match(/(ko)+/);// [&quot;kokokoko&quot;, &quot;ko&quot;, index: 0, input: &quot;kokokoko&quot;, groups: undefined]</code></pre><p>패턴 <code>()</code>안에 있는 <code>&quot;ko&quot;</code>를 그룹화하여 캡처합니다.<br>우선 캡처된 표현식은 당장 사용되지 않으며, 그룹화된 <code>&quot;ko&quot;</code>를 패턴 <code>+</code>로 1회 이상 연속으로 반복되는 문자로 검색합니다.<br>그렇게 캡처 외 표현식이 모두 작동하고 난 뒤 복사했던(캡처된) 표현식 <code>&quot;ko&quot;</code>가 검색됩니다.<br>검색 순서를 정리하자면 다음과 같습니다.</p><ol><li>그룹화된 <code>&quot;ko&quot;</code>를 패턴 <code>+</code>로 1회 이상 연속으로 반복하여 검색하여 <code>&quot;kokokoko&quot;</code>를 반환하고</li><li>캡처된 <code>&quot;ko&quot;</code>로 검색하여 <code>&quot;ko&quot;</code>를 반환합니다.</li></ol><p>다른 예제를 살펴보겠습니다.</p><pre><code class="js">&#39;123abc&#39;.match(/(\d+)(\w)/);// [&quot;123a&quot;, &quot;123&quot;, &quot;a&quot;, index: 0, input: &quot;123abc&quot;, groups: undefined]</code></pre><blockquote><p>캡처는 밖에서 안으로, 왼쪽에서 오른쪽으로 순으로 이루어 집니다.</p></blockquote><p>여기서 아직 살펴보지 않은 패턴이 있네요.<br><code>\d</code>는 숫자에 일치하며, <code>\w</code>는 63개 문자(영문 대소문자 52개 + 숫자 10개 + <code>_</code>(underscore))에 일치합니다.<br>간단히 설명하면 숫자와 문자(숫자 포함)를 검색하는 패턴입니다.<br>뒤에서 다시 설명하니 우선 넘어갑시다.</p><p>검색 순서는 다음과 같습니다.</p><ol><li>패턴 <code>()</code>안의 표현식을 순서대로 캡처합니다. <code>\d+</code>, <code>\w</code></li><li>캡처 후 남은 표현식으로 검색합니다.</li><li>패턴 <code>\d</code>로 숫자를 검색하되 패턴 <code>+</code>로 1개 이상 연속되는 숫자를 일치합니다. <code>&quot;123&quot;</code></li><li>다음 패턴 <code>\w</code>는 문자를 검색하니 <code>&quot;a&quot;</code>가 일치합니다.</li><li>그렇게 <code>&quot;123a&quot;</code>가 반환됩니다.</li><li>첫 번째 캡처한 표현식 <code>\d+</code>로 숫자를 검색하되 패턴 <code>+</code>로 1개 이상 연속되는 숫자를 일치합니다. <code>&quot;123&quot;</code>가 일치하여 반환됩니다.</li><li>다음 캡처한 표현식 <code>\w</code>로 문자를 검색하니 <code>&quot;a&quot;</code>가 일치하여 반환됩니다.</li></ol><h4><span id="ed831c63-c672-40c2-ac40-ea6a3ca7c633">정규식 내 캡처된 값 참조</span><a href="#ed831c63-c672-40c2-ac40-ea6a3ca7c633" class="header-anchor"></a></h4><p>위에서 캡처는 일종의 복사본이라고 설명했습니다.<br>그렇게 캡처된(복사된) 값은 정규식 내부에서 <code>\1</code>부터 <code>\9</code>까지 총 9개의 패턴으로 참조할 수 있습니다.<br><code>var abc = &quot;abc&quot;;</code>와 같이 변수(variable)에 값을 할당하고 변수의 이름으로 값을 참조하는 것과 같은 개념입니다.</p><pre><code class="js">&quot;aabbcc&quot;.match(/(a)\1/);// [&quot;aa&quot;, &quot;a&quot;, index: 0, input: &quot;aabbcc&quot;, groups: undefined]</code></pre><p>패턴 <code>()</code>로 <code>&quot;a&quot;</code>를 캡처합니다.<br>그리고 남은 표현식에서 <code>/1</code>은 캡처한 <code>&quot;a&quot;</code>를 참조하므로 표현식은 <code>/aa/</code>와 같으며, 그에 일치하는 값을 검색하여 <code>&quot;aa&quot;</code>가 반환됩니다.<br>그 후 캡처된 <code>&quot;a&quot;</code>로 일치하는 값을 검색하여 <code>&quot;a&quot;</code>가 반환됩니다.</p><p>같은 개념으로 다음 예제를 직접 분석해보세요.</p><pre><code class="js">&quot;aabbbcc&quot;.match(/((b))\1\2/);// [&quot;bbb&quot;, &quot;b&quot;, &quot;b&quot;, index: 2, input: &quot;aabbbcc&quot;, groups: undefined]</code></pre><h4><span id="d6ea69d2-acb5-4c98-b369-2cf4e060b546">문자열 대체 시 캡처된 값 참조</span><a href="#d6ea69d2-acb5-4c98-b369-2cf4e060b546" class="header-anchor"></a></h4><p>캡처된 값은 정규식 외부에서 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/replace" target="_blank" rel="noopener">.replace()</a>에서 문자 대체 시 <code>$1</code>부터 <code>$9</code>까지 총 9개의 패턴으로 참조할 수 있습니다.</p><pre><code class="js">&quot;hello.world&quot;.replace(/(\w+)\.(\w+)/, &quot;$2.$1&quot;);// &quot;world.hello&quot;</code></pre><p><code>&quot;hello.world&quot;</code>라는 문장에서 단어 <code>&quot;hello&quot;</code>와 <code>&quot;world&quot;</code>의 위치를 바꾸는 예시입니다.</p><p>정규식을 통해서 검색된 일치하는 문자는 <code>&quot;hello.world&quot;</code>이며 패턴 <code>()</code>로 캡처된 값은,<br>첫 번째 <code>\w+</code>로 캡처된 <code>&quot;hello&quot;</code>와 두 번째 <code>\w+</code>로 캡처된 <code>&quot;world&quot;</code>입니다.<br>각 캡처된 값은 첫 번째는 <code>$1</code>으로 참조할 수 있고 두 번째는 <code>$2</code>로 참조할 수 있습니다.<br>참조된 값을 <code>&quot;$2.$1&quot;</code>로 대체하므로 <code>$2</code> 자리에 <code>&quot;world&quot;</code>가 <code>$1</code> 자리에 <code>&quot;hello&quot;</code>가 삽입되어 <code>&quot;world.hello&quot;</code>가 반환됩니다.</p><h4><span id="115a8490-66a2-43d9-9a2e-ff4a1123bbc7">Advanced! 선택적 캡처 그룹</span><a href="#115a8490-66a2-43d9-9a2e-ff4a1123bbc7" class="header-anchor"></a></h4><blockquote><p><code>Advanced!</code>는 이해가 안 되면 넘어가세요!</p></blockquote><p>위에서 살펴봤던 패턴들 중 다음과 같이 ‘0회(없거나) 이상 일치’이라는 개념을 가진 것들이 있습니다.<br><code>|</code>, <code>*</code>, <code>*?</code>, <code>?</code>, <code>??</code>, <code>{0}</code>, <code>{0,}</code></p><p>이러한 패턴들이 <code>()?</code>와 같이 패턴 <code>()</code>(캡처 그룹) 뒤에 붙게되면 선택적 캡쳐 그룹(Optional group)을 생성합니다.<br>선택적 캡쳐 그룹은 검색하지 못한 결과로 <code>undefined</code>로 반환함을 주의합시다!</p><p>다음의 예제를 살펴봅시다.</p><pre><code class="js">&quot;abc&quot;.match(/(b)?/);// [&quot;&quot;, undefined, index: 0, input: &quot;abc&quot;, groups: undefined]</code></pre><p>결과로 <code>&quot;&quot;</code>와 <code>undefined</code>가 반환되었습니다.<br>우리는 패턴 <code>*</code>의 ‘Advanced! 선택적 패턴’에서 ‘0회 포함하거나’라는 일치 조건을 가지면 시작 지점, 끝 지점, 문자 중간중간의 “”(빈 문자열)을 검색하는 조건이 충족됨을 살펴보았습니다.<br>따라서 위 예제는 빈 문자열 <code>&quot;&quot;</code>이 먼저 일치하고 그 다음으로 캡처된 <code>&quot;b&quot;</code>를 검색하기 때문에 검색된 결과는 존지하지 않고 <code>undefined</code>가 배열 슬롯에 포함됩니다.</p><pre><code class="js">&quot;ab&quot;.match(/(b)?(a)/);// [&quot;a&quot;, undefined, &quot;a&quot;, index: 0, input: &quot;ab&quot;, groups: undefined]&quot;ab&quot;.match(/(b)?(a)(b)/);//  [&quot;ab&quot;, undefined, &quot;a&quot;, &quot;b&quot;, index: 0, input: &quot;ab&quot;, groups: undefined]&quot;abc&quot;.match(/(b)?(a)?(c)?(b)?/);// [&quot;ab&quot;, undefined, &quot;a&quot;, undefined, &quot;b&quot;, index: 0, input: &quot;abc&quot;, groups: undefined]</code></pre><p><img src="/images/screenshot/regexp_homemade_exam_3.jpg" alt="RegExp"></p><p>다음과 같이 선택적 캡처 그룹은 모두 같은 결과가 나옵니다.</p><pre><code class="js">&quot;football&quot;.match(/(ball)|(foot)/);&quot;football&quot;.match(/(ball)*(foot)/);&quot;football&quot;.match(/(ball)*?(foot)/);&quot;football&quot;.match(/(ball)?(foot)/);&quot;football&quot;.match(/(ball)??(foot)/);&quot;football&quot;.match(/(ball){0}(foot)/);&quot;football&quot;.match(/(ball){0,}(foot)/);// [&quot;foot&quot;, undefined, &quot;foot&quot;, index: 0, input: &quot;football&quot;, groups: undefined]</code></pre><h3><span id="edfaaee5-ebad-4f18-b08f-02384ceadb39">패턴 (?&lt;&gt;) 캡처 그룹 이름 지정</span><a href="#edfaaee5-ebad-4f18-b08f-02384ceadb39" class="header-anchor"></a></h3><p>우리는 많은 예제의 결과로 다음과 같이 결과에 <code>groups</code>라는 속성이 포함된 것을 보았습니다.</p><pre><code class="js">&quot;aabbcc&quot;.match(/(a)/);// [&quot;a&quot;, &quot;a&quot;, index: 0, input: &quot;aabbcc&quot;, groups: undefined]</code></pre><p>이 속성은 캡처한 결과를 저장하는 그룹으로, 이름을 지정해야만 사용할 수 있습니다.<br>캡처 그룹 패턴 <code>()</code> 내부 앞에 <code>?&lt;이름&gt;</code>와 같이 작성하여 캡처 그룹의 이름을 지정할 수 있습니다.</p><pre><code class="js">const result = &quot;aabbcc&quot;.match(/(?&lt;myName&gt;a)/);// [&quot;a&quot;, &quot;a&quot;, index: 0, input: &quot;aabbcc&quot;, groups: {myName: &quot;a&quot;}]console.log(result.groups.myName);// &quot;a&quot;</code></pre><p><code>groups</code>는 ‘object’ 타입이므로 ‘camelCase’ 표기법를 사용하여 이름을 지정합니다.</p><pre><code class="js">&quot;aabbcc&quot;.match(/(?&lt;my-name&gt;a)/);// Uncaught SyntaxError</code></pre><blockquote><p>패턴 <code>(?&lt;&gt;)</code>는 ECMAScript 2018의 새로운 기능입니다.<br>사용할 때 호환성 확인을 하세요!</p></blockquote><h3><span id="65f68d9d-4328-42f6-a2c7-ec3aab06f267">패턴 (?:) (캡처하지 않는 그룹)</span><a href="#65f68d9d-4328-42f6-a2c7-ec3aab06f267" class="header-anchor"></a></h3><p>캡처 그룹 패턴 <code>()</code>는 그룹화 외 캡처 같은 여러 의미를 가지기 때문에 생각보다 다루기 어려울 수 있습니다.<br>그래서 표현식의 그룹화 의미만 가지는 패턴 <code>(?:)</code>를 사용할 수 있습니다.</p><p>패턴의 모양은 복잡해 보이지만 의미는 아주 단순합니다.<br>패턴 <code>()</code> 내부 앞에 <code>?:</code>를 붙여서 사용합니다.</p><pre><code class="js">const ko = &#39;kokokoko&#39;;ko.match(/(ko)+/);// [&quot;kokokoko&quot;, &quot;ko&quot;, index: 0, input: &quot;kokokoko&quot;, groups: undefined]ko.match(/(?:ko)+/);// [&quot;kokokoko&quot;, index: 0, input: &quot;kokokoko&quot;, groups: undefined]</code></pre><p>표현식 캡처를 하지 않기 때문에 <code>&quot;k&quot;</code>와 <code>&quot;o&quot;</code>를 그룹화한 <code>&quot;ko&quot;</code>만으로 검색합니다.</p><h3><span id="57b5acdc-b79a-43d9-b210-61040201c705">패턴 (?=) (앞쪽 일치)</span><a href="#57b5acdc-b79a-43d9-b210-61040201c705" class="header-anchor"></a></h3><p>패턴 <code>(?=)</code>은 그 앞에 위치하는 표현식이 패턴 내 표현식(<code>(?=여기)</code>)에 일치하는 문자의 앞에 있어야 함을 의미합니다.</p><p>다음 예제는 패턴 <code>(?=)</code>의 앞에 위치하는 표현식 <code>ab</code>가 패턴 내 표현식 <code>c</code>의 앞에 있기 때문에 결과로 <code>&quot;ab&quot;</code>를 반환합니다.<br>여기서 <code>(?=c)</code>은 그 앞에 위치하는 표현식을 검색하기 위한 조건일 뿐입니다.<br>즉, <code>&quot;ab&quot;</code>를 검색하되 그다음에 <code>&quot;c&quot;</code>가 있어야만 일치한다는 의미가 됩니다.</p><pre><code class="js">&quot;abc&quot;.match(/ab(?=c)/);// [&quot;ab&quot;, index: 0, input: &quot;abc&quot;, groups: undefined]&quot;abd&quot;.match(/ab(?=c)/);// null</code></pre><p>‘앞쪽 일치’라는 단어처럼 패턴의 앞에 있는 표현식을 검색하는 것이기 때문에 순서를 바꾸면 전혀 다른 결과가 반환됩니다.<br>다음 첫 번째 예제는 패턴 내 표현식 <code>c</code> 앞에 <code>&quot;&quot;</code>(빈 문자열)을 일치한 후 <code>&quot;ab&quot;</code>를 검색하기 때문에 일치하는 결과가 없게 됩니다.</p><pre><code class="js">&quot;abc&quot;.match(/(?=c)ab/);// null&quot;abc&quot;.match(/(?=c)/);// [&quot;&quot;, index: 2, input: &quot;abc&quot;, groups: undefined]`</code></pre><h3><span id="71f17b1d-afdb-49de-b50a-310b50f8cbb1">패턴 (?!) (부정 앞쪽 일치)</span><a href="#71f17b1d-afdb-49de-b50a-310b50f8cbb1" class="header-anchor"></a></h3><p>패턴 <code>(?!)</code>는 패턴 <code>(?=)</code>의 부정의 의미를 가집니다.</p><pre><code class="js">&quot;abc&quot;.match(/ab(?!c)/);// null&quot;abd&quot;.match(/ab(?!c)/);// [&quot;ab&quot;, index: 0, input: &quot;abd&quot;, groups: undefined]</code></pre><h3><span id="0a13ff8f-acc7-407e-bfd0-0a4bfebcf87b">패턴 (?&lt;=) (뒤쪽 일치)</span><a href="#0a13ff8f-acc7-407e-bfd0-0a4bfebcf87b" class="header-anchor"></a></h3><p>패턴 <code>(?&lt;=)</code>은 그 뒤에 위치하는 표현식이 패턴 내 표현식에 일치하는 문자의 뒤에 있어야 함을 의미합니다.<br>‘앞쪽 일치(<code>(?=)</code>)’에 대해서 이해했다면 어렵지 않을 것입니다.</p><pre><code class="js">&quot;xyz&quot;.match(/(?&lt;=x)yz/);// [&quot;yz&quot;, index: 1, input: &quot;xyz&quot;, groups: undefined]&quot;ayz&quot;.match(/(?&lt;=x)yz/);// null</code></pre><h3><span id="94a907cf-54ee-4f08-ad87-ec71b75217c2">패턴 (?&lt;!) (부정 뒤쪽 일치)</span><a href="#94a907cf-54ee-4f08-ad87-ec71b75217c2" class="header-anchor"></a></h3><p>패턴 <code>(?&lt;!)</code>는 패턴 <code>(?&lt;=)</code>의 부정의 의미를 가집니다.</p><pre><code class="js">&quot;xyz&quot;.match(/(?&lt;!x)yz/);// null&quot;ayz&quot;.match(/(?&lt;!x)yz/);// [&quot;yz&quot;, index: 1, input: &quot;ayz&quot;, groups: undefined]</code></pre><h3><span id="7e072053-4766-461b-b649-705b2b6d4988">패턴 [abc] (a 또는 b 또는 c와 일치)</span><a href="#7e072053-4766-461b-b649-705b2b6d4988" class="header-anchor"></a></h3><p>패턴 <code>[]</code> 안에 있는 문자는 각각 ‘or’(또는)의 의미를 가집니다.<br>따라서 앞서 살펴봤던 것처럼 표현식 <code>[abc]</code>는 <code>a|b|c</code>와 같습니다.</p><pre><code class="js">&quot;abc&quot;.match(/[bca]/);// [&quot;a&quot;, index: 0, input: &quot;abc&quot;, groups: undefined]&quot;abc&quot;.match(/b|c|a/);// [&quot;a&quot;, index: 0, input: &quot;abc&quot;, groups: undefined]</code></pre><p>결과가 <code>&quot;a&quot;</code>인 이유는 패턴 <code>|</code>와 같이 인덱스가 가장 작은 값(가장 앞에서 일치된)을 반환하기 때문입니다.</p><pre><code class="js">const str = &quot;character class or charectar set&quot;;str.match(/char[ae]ct[ae]r/g);// [&quot;character&quot;, &quot;charectar&quot;]</code></pre><p>위와 틀린 문자가 포함된 단어를 검색할 때도 유용하게 사용할 수 있습니다.</p><h4><span id="75e62112-a62c-4cb9-8b4d-a21cdbedbf87">문자 범위 지정</span><a href="#75e62112-a62c-4cb9-8b4d-a21cdbedbf87" class="header-anchor"></a></h4><p>패턴 <code>[]</code> 안에서 <code>-</code>을 이용하면 문자의 범위(구간)를 지정할 수 있습니다.</p><pre><code class="js">&quot;abcdef&quot;.match(/[a-e]/);// [&quot;a&quot;, index: 0, input: &quot;abcdef&quot;, groups: undefined]&quot;abcdef&quot;.match(/[abcde]/);// [&quot;a&quot;, index: 0, input: &quot;abcdef&quot;, groups: undefined]&quot;abcdef&quot;.match(/[a-e]/g);  // 플래그 `g`// [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;]&quot;01234567890&quot;.match(/[1-0]/);  // SyntaxError&quot;01234567890&quot;.match(/[0-9]/)// [&quot;0&quot;, index: 0, input: &quot;01234567890&quot;, groups: undefined]&quot;01234567890&quot;.match(/[0-9]/g)// [&quot;0&quot;, &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;0&quot;]&quot;가나다라마바사아&quot;.match(/[다-바]/g)  // [가-힣] 한글 문자 구간// [&quot;다&quot;, &quot;라&quot;, &quot;마&quot;, &quot;바&quot;]/[😀-😇]/u.test(&quot;😂&quot;);  // 유니 코드 문자 구간// true</code></pre><h4><span id="c0e64f8c-56ee-40f8-83b8-736337061ed0">이스케이프 문자</span><a href="#c0e64f8c-56ee-40f8-83b8-736337061ed0" class="header-anchor"></a></h4><p>패턴 <code>[]</code> 안에서는 특수문자 앞에 <code>\</code>를 붙이지 않아도 사용할 수 있습니다.</p><pre><code class="js">&quot;Hmm....&quot;.match(/\.[.]/);// [&quot;..&quot;, index: 3, input: &quot;Hmm....&quot;, groups: undefined]</code></pre><p>패턴 <code>.</code>은 ‘임의의 한 문자’를 의미하지만 단순한 마침표 <code>.</code>으로 사용하기 위해선 앞에 <code>\</code>를 붙여서 사용할 수 있습니다.<br>하지만 패턴 <code>[]</code> 안에서 사용할 경우 <code>\</code> 없이도 마침표 <code>.</code>으로 사용할 수 있습니다.</p><h3><span id="7ada1fb5-832e-46a5-98e9-9fe72a8612d5">패턴 [^abc] (a 또는 b 또는 c가 아닌 나머지 문자에 일치)</span><a href="#7ada1fb5-832e-46a5-98e9-9fe72a8612d5" class="header-anchor"></a></h3><p>패턴 <code>[^]</code>는 패턴 <code>[]</code>의 반대(부정) 의미를 가집니다.<br>표현식 <code>[^abc]</code>는 <code>a|b|c</code>를 제외한 나머지 문자를 검색합니다.</p><pre><code class="js">&quot;abcd&quot;.match(/[^bca]/);// [&quot;d&quot;, index: 3, input: &quot;abcd&quot;, groups: undefined]</code></pre><p><code>&quot;b&quot;</code>, <code>&quot;c&quot;</code>, <code>&quot;a&quot;</code>를 제외한 나머지 문자에 일치하기 때문에 <code>&quot;d&quot;</code>가 반환되었습니다.</p><pre><code class="js">&quot;hello123 world456&quot;.replace(/[^\d]/g, &quot;&quot;);// &quot;123456&quot;</code></pre><p>문자 <code>&quot;hello123 world456&quot;</code>에서 숫자(<code>\d</code>)를 제외한 나머지 문자를 플래그 <code>g</code>로 모두 찾아서 빈 문자열(<code>&quot;&quot;</code>)로 변환(replace)합니다.<br>같은 결과를 반환하는 방법으로 숫자에 일치하는 패턴 <code>\d</code>의 반대인 <code>\D</code>(숫자가 아닌 문자에 일치)를 사용할 수 있습니다.</p><pre><code class="js">&quot;hello123 world456&quot;.replace(/\D/g, &quot;&quot;);// &quot;123456&quot;</code></pre><h1><span id="d51f15da-5642-4f50-9450-3a196d242e35">정규표현식 예제</span><a href="#d51f15da-5642-4f50-9450-3a196d242e35" class="header-anchor"></a></h1><p>너무 복잡하지 않은 선에서 좋은 예제가 있으면 지속해서 추가하겠습니다.<br>추천하는 좋은 예제가 있으신 분은 댓글로 부탁드립니다.</p><h2><span id="e07bf56d-630b-4199-84d4-f1eac9767e33">정규표현식에 변수 삽입</span><a href="#e07bf56d-630b-4199-84d4-f1eac9767e33" class="header-anchor"></a></h2><p>정규표현식에 변수를 넣기 위해서 <code>RegExp</code>객체를 이용합니다.</p><pre><code class="js">new RegExp(`{${minNum},${maxNum}}`)</code></pre><h2><span id="53600c98-85d4-4016-9598-fa125e94f94b">괄호 사이 값 추출</span><a href="#53600c98-85d4-4016-9598-fa125e94f94b" class="header-anchor"></a></h2><p><code>g</code> 플래그를 사용하지 않음을 주의합니다.</p><pre><code class="js">const str = &#39;Heropy(Young-Woong Park)&#39;str.match(/\((.*)\)/)[1] // Young-Woong Park</code></pre><h1><span id="499652d0-a3a7-4574-a630-0449e7102453">참고 자료(References)</span><a href="#499652d0-a3a7-4574-a630-0449e7102453" class="header-anchor"></a></h1><p><a href="https://www.asciitable.com/" target="_blank" rel="noopener">https://www.asciitable.com/</a><br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/%EC%A0%95%EA%B7%9C%EC%8B%9D" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/%EC%A0%95%EA%B7%9C%EC%8B%9D</a><br><a href="https://flaviocopes.com/javascript-regular-expressions/" target="_blank" rel="noopener">https://flaviocopes.com/javascript-regular-expressions/</a><br><a href="https://javascript.info/regexp-groups" target="_blank" rel="noopener">https://javascript.info/regexp-groups</a></p>]]></content>
    
    <summary type="html">
    
      매일 쓰는 것도, 가독성이 좋은 것도 아니지만, 모르면 안되는 정규표현식. 저는 이렇게 공부하기 시작했습니다! (자바스크립트를 기준으로 설명합니다)
    
    </summary>
    
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
      <category term="regexp" scheme="https://heropy.blog/tags/regexp/"/>
    
  </entry>
  
  <entry>
    <title>Node.js 테스트 프레임워크 Mocha</title>
    <link href="https://heropy.blog/2018/03/16/mocha/"/>
    <id>https://heropy.blog/2018/03/16/mocha/</id>
    <published>2018-03-16T05:42:06.000Z</published>
    <updated>2018-03-16T07:07:10.000Z</updated>
    
    <content type="html"><![CDATA[<p>단순한 자바스크립트 개발에서는 단위 테스트가 필요하지 않았습니다.<br>오히려 테스트를 기반으로 개발한다는 개념 자체가 개발을 더욱 어렵게 했었죠. 뭐, 저는 그랬습니다.<br>하지만 자바스크립트 개발이 점차 복잡해지면서 작성한 함수가 잘 동작하고 제대로된 값을 반환하는지 검증하는 과정은 간단한 기능에서조차 필수가 되어버린 듯합니다.</p><div class="toc"><ul><li><a href="3356b342-4b69-4526-9ad1-e8b977269b28">설치</a></li><li><a href="ada1c8c4-23eb-4bcd-a2f9-2214d43780a4">테스트 예제</a><ul><li><a href="7e6f8972-7887-4c15-85de-c93eab43a65a">비동기 테스트</a></li></ul></li><li><a href="e5190da2-3a09-46e5-a4ce-90c25de7de62">중첩</a></li><li><a href="0c653ae8-11e5-4ce8-a79a-87cf74f57f1f">Hooks</a></li><li><a href="b4df9a65-de2a-48af-b520-054aaa005e11">Options</a></li></ul></div><p>그리하여 살펴볼 <a href="https://mochajs.org/" target="_blank" rel="noopener">Mocha(모카)</a>는 테스트 러너를 지원하는 테스트 프레임워크 입니다.<br>자체 Assertion(어써션)은 지원하지 않으며, 필요한 Assertion 라이브러리를 가져와 사용할 수 있습니다.</p><blockquote><p>Assertion은 에러가 없는 프로그램을 작성하기 위한 하나의 수법을 의미합니다.</p></blockquote><p>Node.js에서 지원하는 <a href="https://nodejs.org/dist/latest-v8.x/docs/api/assert.html" target="_blank" rel="noopener">Assert</a> 모듈을 사용할 수도 있으며, <a href="http://chaijs.com/" target="_blank" rel="noopener">Chai</a>나 <a href="https://shouldjs.github.io/" target="_blank" rel="noopener">Should.js</a> 같은 BDD, TDD 방식의 다양한 Assertion 라이브러리가 있습니다.<br>Mocha는 각 모듈에서 별도로 호출하지 않으며, 사용할 Assertion 라이브러리만 <code>require()</code>로 호출합니다.</p><h1><span id="3356b342-4b69-4526-9ad1-e8b977269b28">설치</span><a href="#3356b342-4b69-4526-9ad1-e8b977269b28" class="header-anchor"></a></h1><p>전역으로 설치하면 <code>mocha</code> 명령어로 실행할 수 있습니다.</p><pre><code class="bash">$ npm install mocha -g</code></pre><p>개발 의존성 모듈로 설치할 경우 <code>package.json</code>의 <code>scripts</code> 항목에 명령을 설정합니다.</p><pre><code class="bash">$ npm install mocha --save-dev</code></pre><p><code>package.json</code>:</p><pre><code class="json">{  &quot;scripts&quot;: {    &quot;test&quot;: &quot;mocha&quot;  }}</code></pre><h1><span id="ada1c8c4-23eb-4bcd-a2f9-2214d43780a4">테스트 예제</span><a href="#ada1c8c4-23eb-4bcd-a2f9-2214d43780a4" class="header-anchor"></a></h1><p>Mocha에 대해서 살펴보기 전에, 아주 간단하게 테스트용 코드를 작성합니다.</p><p><code>app.js</code>:</p><pre><code class="js">module.exports = {  sayHello: function () {    return &#39;hello&#39;;  }}</code></pre><p><code>test/</code>디렉토리에 별도의 파일(<code>app.spec.js</code>)을 생성한 후 테스트할 코드를 <code>require()</code>로 가져와 테스트를 진행합니다.</p><p><code>test/app.spec.js</code>:</p><pre><code class="js">const sayHello = require(&#39;../app&#39;).sayHello;if (sayHello) {  console.log(&#39;sayHello should return &quot;hello&quot;&#39;);  if (sayHello() === &#39;hello&#39;) {    console.log(&#39;Success&#39;);  } else {    console.log(&#39;Fail&#39;);  }}</code></pre><p>예전에는 위와 같이 Alert이나 Console로 작동 여부를 확인했지만, 우리는 Mocha를 활용합니다.<br>내용을 다음과 같이 수정하여 단위 테스트를 진행합니다.</p><p><code>describe()</code>는 테스트의 범위를 설정하고, <code>it()</code>는 단위 테스트를 설정합니다.<br>인수로 사용한 <code>done</code>은 비동기 단위 테스트를 완료할 때 유용합니다.</p><p><code>test/app.spec.js</code>:</p><pre><code class="js">const sayHello = require(&#39;../app&#39;).sayHello;describe(&#39;App test!&#39;, function () {  it(&#39;sayHello should return hello&#39;, function (done) {    if (sayHello() === &#39;hello&#39;) {      done();    }  });});</code></pre><p><code>mocha &lt;filename&gt;</code>으로 실행할 경우 해당 파일의 테스트를 진행합니다.<br>만약 <code>mocha</code> 명령과 함께 실행 파일(filename)을 지정하지 않으면, 현재 경로의 <code>test/</code> 디렉토리에 있는 모든 <code>.js</code>파일을 실행합니다.<br>우리는 <code>test/</code> 디렉토리 안에 <code>app.spec.js</code>를 생성하였으므로 다음과 같이 실행할 수 있습니다.</p><pre><code class="bash">$ mocha# or$ npm test# or$ mocha test/app.spec.js</code></pre><pre><code class="bash">App test!  ✓ sayHello should return hello1 passing (5ms)</code></pre><p>Node.js에서 제공하는 <code>assert</code> 모듈을 활용하면 좀 더 편리하게 테스트할 수 있습니다.</p><pre><code class="js">const assert = require(&#39;assert&#39;);  // Node.js `assert` moduleconst sayHello = require(&#39;../app&#39;).sayHello;describe(&#39;App test!&#39;, function () {  it(&#39;sayHello should return &quot;hello&quot;&#39;, function () {    assert.equal(sayHello(), &#39;hello&#39;);  });});</code></pre><h2><span id="7e6f8972-7887-4c15-85de-c93eab43a65a">비동기 테스트</span><a href="#7e6f8972-7887-4c15-85de-c93eab43a65a" class="header-anchor"></a></h2><p>Mocha를 이용하면 간단하게 비동기 테스트를 진행할 수 있습니다.<br><code>it()</code>의 콜백 인수로 <code>done</code>를 사용하면 자동으로 비동기 테스트를 인식하고, 비동기 로직이 완료 후 <code>done()</code>을 실행하면 테스트가 완료됩니다.</p><pre><code class="js">const fs = require(&#39;fs&#39;);describe(&#39;App test1&#39;, function () {  it(&#39;async test&#39;, function (done) {    fs.readFile(__filename, done);  });});</code></pre><p>비동기 테스트는 제한 시간(timeout)으로 2초가 경과하면 자동으로 테스트 실패가 됩니다.<br>테스트를 진행할 때 <code>-t</code>, <code>--timeout</code> 옵션을 이용하거나, 단일 테스트 내 <code>this.timeout()</code>을 이용하여 제한 시간을 설정할 수 있습니다.</p><pre><code class="bash"># 전체 테스트의 제한 시간 설정$ mocha -t 3000</code></pre><p>혹은</p><pre><code class="js">const fs = require(&#39;fs&#39;);describe(&#39;App test1&#39;, function () {  it(&#39;async test&#39;, function (done) {    this.timeout(3000);  // 단일 테스트의 제한 시간 설정    fs.readFile(__filename, done);  });});</code></pre><h1><span id="e5190da2-3a09-46e5-a4ce-90c25de7de62">중첩</span><a href="#e5190da2-3a09-46e5-a4ce-90c25de7de62" class="header-anchor"></a></h1><p><code>describe()</code> 안에 또 다른 <code>describe()</code>를 중첩하여 사용할 수 있습니다.</p><p><code>app.js</code>:</p><pre><code class="js">module.exports = {  sayHello: function () {    return &#39;hello&#39;;  },  addNumbers: function (a, b) {    return a + b;  }};</code></pre><p>Assetion 라이브러리로 Chai의 Should 스타일을 사용했습니다.</p><p><code>test/app.spec.js</code>:</p><pre><code class="js">const should = require(&#39;chai&#39;).should();const app = require(&#39;../app&#39;);describe(&#39;# App test&#39;, function () {  describe(&#39;# sayHello&#39;, function () {    it(&#39;should return hello&#39;, function () {      app.sayHello().should.equal(&#39;hello&#39;);    });    it(&#39;should a string type&#39;, function () {      app.sayHello().should.be.a(&#39;string&#39;);    });  });  describe(&#39;# addNumbers&#39;, function () {    it(&#39;should greater than 5&#39;, function () {      app.addNumbers(3, 4).should.be.above(5);    });  });});</code></pre><pre><code class="bash">$ npm test</code></pre><pre><code class="bash"># App test    # sayHello      ✓ should return hello      ✓ should a string type    # addNumbers      ✓ should greater than 53 passing (9ms)</code></pre><h1><span id="0c653ae8-11e5-4ce8-a79a-87cf74f57f1f">Hooks</span><a href="#0c653ae8-11e5-4ce8-a79a-87cf74f57f1f" class="header-anchor"></a></h1><p>Mocha에서는 <code>before()</code>, <code>after()</code>, <code>beforeEach()</code>, <code>afterEach()</code>의 4가지 함수(hook)를 제공하여 테스트 코드 전후를 제어할 수 있습니다.</p><pre><code class="js">describe(&#39;hooks&#39;, function() {  before(function() {    // 블록 범위 내 모든 테스트 전에 실행  });  after(function() {    // 블록 범위 내 모든 테스트 후에 실행  });  beforeEach(function() {    // 블록 범위 내 각 테스트 직전에 실행  });  afterEach(function() {    // 블록 범위 내 각 테스트 직후에 실행  });  // test cases});</code></pre><pre><code class="js">const assert = require(&#39;assert&#39;);const app = require(&#39;../app&#39;);describe(&#39;App test!&#39;, function () {  before(function () {    console.log(&#39;before hook&#39;);  });  after(function () {    console.log(&#39;after hook&#39;);  });  beforeEach(function () {    console.log(&#39;beforeEach hook&#39;);  });  afterEach(function () {    console.log(&#39;afterEach hook&#39;);  });  it(&#39;A test&#39;, function () {    assert.equal(app.a(), &#39;A!&#39;);  });  it(&#39;B test&#39;, function () {    assert.equal(app.b(), &#39;B!&#39;);  });});</code></pre><pre><code class="bash">$ npm test</code></pre><pre><code class="bash">  App test!before hookbeforeEach hook    ✓ A testafterEach hookbeforeEach hook    ✓ B testafterEach hookafter hook    2 passing (11ms)</code></pre><h1><span id="b4df9a65-de2a-48af-b520-054aaa005e11">Options</span><a href="#b4df9a65-de2a-48af-b520-054aaa005e11" class="header-anchor"></a></h1><p><code>mocha</code> 명령어로 테스트를 진행할 때 다음과 같은 옵션을 사용할 수 있습니다.</p><pre><code class="bash">$ mocha [debug] [options] [files]</code></pre><p><code>-V</code>, <code>--version</code>: Mocha의 버전을 확인합니다.<br><code>-A</code>, <code>--async-only</code>: 비동기 테스트만 허용합니다.<br><code>-c</code>, <code>--colors</code>: 코드 색상을 활성화합니다.<br><code>-C</code>, <code>--no-colors</code>: 코드 색상을 비활성화합니다.<br><code>-R</code>, <code>--reporter &lt;name&gt;</code>: 테스트 결과에 대한 리포팅 방식을 설정합니다.<br><code>-b</code>, <code>--bail</code>: 첫번째 실패한 테스트만 확인합니다.<br><code>-g</code>, <code>--grep &lt;pattern&gt;</code>: 지정한 정규표현식 패턴과 일치하는 테스트만 진행합니다.<br><code>-f</code>, <code>--fgrep &lt;string&gt;</code>: 지정한 문자열을 포함하는 테스트만 진행합니다.<br><code>-i</code>, <code>--invert</code>: <code>--grep</code>, <code>--fgrep</code> 결과와 일치하지 않는 테스트만 진행합니다. ex&gt; <code>mocha -f &#39;abc&#39; -i</code><br><code>-r</code>, <code>--require &lt;name&gt;</code>: 가져올 모듈(<code>should.js</code>, <code>chai</code> 같은 Assertion 라이브러리)을 지정합니다.<br><code>-t</code>, <code>--timeout &lt;ms&gt;</code>: 지정한 테스트가 제한 시간(기본값 <code>2000</code>)을 지나면 실패합니다. 단일 테스트의 제한 시간을 설정할 수 있습니다.<br><code>-u</code>, <code>--ui &lt;name&gt;</code>: 사용자 인터페이스를 지정합니다. ex&gt; <code>mocha -u tdd</code><br><code>-w</code>, <code>--watch</code>: 테스트 대상 파일들을 감시합니다.</p><p><a href="https://mochajs.org/#usage" target="_blank" rel="noopener">기타 옵션들</a></p>]]></content>
    
    <summary type="html">
    
      Mocha는 Node.js에서 사용하는 테스트 러너를 지원하는 테스트 프레임워크 입니다. Node.js에서 제공하는 Assert 모듈부터 Should.js나 Chai 같은 다양한 Assertion 라이브러리를 사용할 수 있습니다.
    
    </summary>
    
    
      <category term="nodejs" scheme="https://heropy.blog/tags/nodejs/"/>
    
      <category term="mocha" scheme="https://heropy.blog/tags/mocha/"/>
    
  </entry>
  
  <entry>
    <title>처음 시작하는 Node.js 개발 - 3 - Globals</title>
    <link href="https://heropy.blog/2018/03/06/node-js-globals/"/>
    <id>https://heropy.blog/2018/03/06/node-js-globals/</id>
    <published>2018-03-06T07:38:00.000Z</published>
    <updated>2020-10-30T05:59:33.994Z</updated>
    
    <content type="html"><![CDATA[<p>타이머 함수를 사용하거나 현재 모듈의 디렉토리 이름을 알아내는 등 모든 모듈에서 사용할 수 있는 Node.js의 전역 객체들에 대해서 알아봅시다.</p><div class="toc"><ul><li><a href="62f58f88-7fe3-4249-bb37-93219bf8502e">REPL</a><ul><li><a href="1377aeed-2307-463b-bd62-c8d06da1e64b">REPL Commands</a></li></ul></li><li><a href="6cf36a88-d68c-49e5-8b8a-4e5b772806b7">global</a></li><li><a href="55036164-039e-4ff1-8b8e-f8c8bc63aea0">Console</a><ul><li><a href="fab0a7be-9d5e-4470-842d-89be7575921b">console.log()</a><ul><li><a href="9485f82f-e619-416c-b3cc-5153e32a2ae2">Placeholders</a></li></ul></li></ul></li><li><a href="ebffeb6e-04ca-4bab-834f-603f92b59f5d">Timers</a><ul><li><a href="fe74eca6-e496-4cd0-a4b7-d88e9c28d831">setInterval()</a><ul><li><a href="882ea8b1-f5b9-49ad-9940-2f9305a92d75">clearInterval()</a></li></ul></li><li><a href="d1d23c40-e8f7-4bdb-9c7c-5d9512620884">setTimeout()</a><ul><li><a href="fc6f7413-fee0-4036-970c-e8f416f2b75c">clearTimeout()</a></li></ul></li><li><a href="3c5b553f-c1f6-40e6-83ca-f7b7ed60cad3">setImmediate()</a><ul><li><a href="4946c9d0-4de3-46fe-8aaf-a6b167f6d174">clearImmediate()</a></li></ul></li></ul></li><li><a href="cf301780-761f-4aeb-85a6-5138dc84575a">Modules</a><ul><li><a href="2506e3d0-3656-46d6-bb9a-ed20a6c8c1ef"><code>__</code>dirname</a></li><li><a href="b9a4a012-31de-4edc-9cea-7f0e4c336b7e"><code>__</code>filename</a></li><li><a href="4a31fe34-38c9-46c7-b7a7-73d4239550a6">module</a><ul><li><a href="c693d0c1-4161-4624-bfe3-f5df0159f893">module.exports</a></li><li><a href="e7ef7b2f-4d0e-4e5c-9db4-67ceb369d452">exports</a></li></ul></li><li><a href="c129901b-2bed-4461-a2e4-5274303b4dac">require()</a></li></ul></li><li><a href="3c7147d0-3644-473e-ad18-f65a0fabab54">Process</a><ul><li><a href="b711edc6-d7a7-4907-86bc-1541f6eb6156">process.arch</a></li><li><a href="1b65f633-83f7-486f-a741-3caab1a26b9a">process.env</a></li><li><a href="a2910dfc-7605-4bbd-9f59-19bfa02303c1">process.exit()</a></li><li><a href="933289dc-1ddb-4fbf-9da6-f54e28d5d42b">process.nextTick()</a></li><li><a href="66c009f4-b6ba-49ce-a28b-b06bf1d8b0c2">process.platform</a></li><li><a href="22ddc5fb-b6d0-4111-bfa3-92dab2daf2b4">process.memoryUsage()</a></li><li><a href="ec7a8042-0370-4a11-80a0-2044edf9d827">process.uptime()</a></li><li><a href="21506e12-374a-4720-9903-76609ad38a30">process.versions</a></li></ul></li><li><a href="a31506a2-0d7b-452c-a5ea-55e2b7bf637a">Buffers</a><ul><li><a href="40365326-b078-4066-885c-7d6db4e57e85">Buffer.alloc()</a></li><li><a href="ddb9cb79-b66e-4456-94cd-20e57c7357ef">Buffer.from()</a></li><li><a href="30f2c3ad-9c8b-4d11-b15e-592f49a2e107">Buffer.concat()</a></li><li><a href="2bc1d67a-5ec7-4944-b0fc-fa7806849110">Buffer.isBuffer()</a></li><li><a href="499447d4-d17d-4876-aab9-a64426602601">buf.copy()</a></li><li><a href="c544ec8d-dfd5-4e0a-947e-369562eded0d">buf.length</a></li><li><a href="48f76ad9-bf53-4fba-aafb-359e076fab88">buf.toString()</a></li><li><a href="13dac8d9-0ef2-46e8-aa23-8253a9a90c7a">buf.write()</a></li></ul></li><li><a href="c1d4ae0d-8939-4d2f-b302-c04a5c5ba7d4">참고 자료(References)</a></li></ul></div><h1><span id="62f58f88-7fe3-4249-bb37-93219bf8502e">REPL</span><a href="#62f58f88-7fe3-4249-bb37-93219bf8502e" class="header-anchor"></a></h1><p>Node.js의 REPL(Read-Eval-Print-Loop) 기능은 Node.js 코드를 실험하고 JavaScript 코드를 디버깅하는 데 매우 유용합니다.</p><p><b>Read</b>: 사용자의 입력을 읽고, JavaScript 데이터 구조로 입력을 파싱하고, 메모리에 저장합니다.<br><b>Eval</b>: 데이터 구조를 평가합니다.<br><b>Print</b>: 결과를 인쇄합니다.<br><b>Loop</b>: 사용자가 <code>ctrl</code>+<code>c</code>를 두 번 누를 때까지 명령을 반복합니다.</p><p>REPL은 다음과 같이 Shell에서 <code>node</code>를 실행하면 시작할 수 있습니다.</p><pre><code class="bash">$ node</code></pre><p>간단한 연산이나 로그를 사용할 수 있습니다.</p><pre><code class="bash">&gt; 1 + ( 2 * 3 ) - 43&gt; console.log(&#39;hello REPL!&#39;);hello REPL!</code></pre><p>REPL은 여러 줄 표현(Multiline Expression)도 지원합니다.</p><pre><code class="bash">&gt; for (var i = 0; i &lt; 4; i++) {... console.log(i);... }0123</code></pre><h2><span id="1377aeed-2307-463b-bd62-c8d06da1e64b">REPL Commands</span><a href="#1377aeed-2307-463b-bd62-c8d06da1e64b" class="header-anchor"></a></h2><p><code>ctrl</code>+<code>c</code>: 현재 명령을 종료합니다.<br><code>ctrl</code>+<code>c</code>(2번): Node REPL을 종료합니다.<br><code>ctrl</code>+<code>d</code>: Node REPL을 종료합니다.<br><code>Up</code>/<code>Down</code>키: 명령 기록을 탐색하여 이전 명령을 수정할 수 있습니다.<br><code>tab</code>키: 현재 작성된 명령으로 시작하는 지정된 명령이나 데이터를 표시합니다.<br><code>.help</code>: 모든 REPL 커멘드 목록을 표시합니다.<br><code>.break</code>: 여러 줄 표현을 종료합니다.<br><code>.clear</code>: 여러 줄 표현을 종료합니다.<br><code>.save &lt;filename&gt;</code>: 현재 Node REPL 세션을 파일로 저장합니다. 예를 들면 <code>.save log.txt</code>, <code>.save session.js</code>.<br><code>.load &lt;filename&gt;</code>: 현재 Node REPL 세션에 파일을 불러옵니다.<br><code>.editor</code>: 에디터 모드를 사용합니다.(<code>ctrl</code>+<code>d</code>: 에디터 모드 완료, <code>ctrl</code>+<code>c</code>: 에디터 모드 취소)</p><pre><code class="bash">&gt; .editor// Entering editor mode (^D to finish, ^C to cancel)function welcome(name) {  return `Hello ${name}!`;}welcome(&#39;Node.js&#39;);// ^D(ctrl+d) 완료&#39;Hello Node.js&#39;</code></pre><p><code>_</code>(Underscore Variable)를 사용하면 마지막 결과의 값을 참조합니다.</p><pre><code class="bash">&gt; 1 + 12&gt; _ * 24&gt;</code></pre><h1><span id="6cf36a88-d68c-49e5-8b8a-4e5b772806b7">global</span><a href="#6cf36a88-d68c-49e5-8b8a-4e5b772806b7" class="header-anchor"></a></h1><p>Node.js의 전역 정보를 가지는 객체를 반환합니다.</p><blockquote><p>일반적으로 브라우저에서의 최상위 범위(Top-level scope)는 전역 범위(Global scope)입니다. 하지만 Node.js에서 최상위 범위는 전역 범위가 아닙니다. 모듈 내부의 내용은 해당 모듈에 대한 지역이 됩니다.</p></blockquote><pre><code class="js">console.log(global);console.log(window);  // window is not defined</code></pre><h1><span id="55036164-039e-4ff1-8b8e-f8c8bc63aea0">Console</span><a href="#55036164-039e-4ff1-8b8e-f8c8bc63aea0" class="header-anchor"></a></h1><p><a href="https://nodejs.org/dist/latest-v8.x/docs/api/console.html" target="_blank" rel="noopener">Node.js Console</a><br>웹 브라우저에서 제공하는 JavaScript 콘솔 메커니즘과 유사한 표준 출력(stdout)과 표준 오류(stderr)를 표시하는 디버깅 콘솔을 제공합니다.</p><h2><span id="fab0a7be-9d5e-4470-842d-89be7575921b">console.log()</span><a href="#fab0a7be-9d5e-4470-842d-89be7575921b" class="header-anchor"></a></h2><p>표준 출력(stdout)으로 디버깅 콘솔에 데이터를 표시합니다.</p><pre><code class="js">console.log(&#39;hello world!&#39;);</code></pre><h3><span id="9485f82f-e619-416c-b3cc-5153e32a2ae2">Placeholders</span><a href="#9485f82f-e619-416c-b3cc-5153e32a2ae2" class="header-anchor"></a></h3><p>각 Placeholder 토큰은 해당 인수(Arguments) 값으로 대체됩니다.<br>다음과 같은 Placeholder 토큰들이 지원됩니다.</p><p><code>%s</code>: 문자열<br><code>%d</code>: 숫자(정수, 부동 소수점 값)<br><code>%i</code>: 정수<br><code>%f</code>: 부동 소수점 값<br><code>%j</code>: JSON<br><code>%o</code>: 일반 JavaScript 객체</p><pre><code class="js">console.log(&#39;My name is %s, I am %d.&#39;, &#39;HEROPY&#39;, 19);</code></pre><pre><code class="bash">My name is HEROPY, I am 19.</code></pre><h1><span id="ebffeb6e-04ca-4bab-834f-603f92b59f5d">Timers</span><a href="#ebffeb6e-04ca-4bab-834f-603f92b59f5d" class="header-anchor"></a></h1><p>Node.js의 타이머는 특정 기간 후에 주어진 함수를 호출합니다.<br>타이머는 실행하기를 원하는 정확한 시간이 아니라 제공된 콜백이 일정 시간 후에 실행되어야 하는 기준 시간을 지정합니다.<br>타이머 콜백은 지정한 시간이 지난 후에 스케줄링 될 수 있는 가장 이른 시간에 실행됩니다.<br>하지만 운영체제 스케줄링이나 다른 콜백 실행 때문에 지연될 수 있습니다.</p><h2><span id="fe74eca6-e496-4cd0-a4b7-d88e9c28d831">setInterval()</span><a href="#fe74eca6-e496-4cd0-a4b7-d88e9c28d831" class="header-anchor"></a></h2><p>지연 시간(ms)마다 콜백 함수를 반복 실행합니다.<br><code>clearInterval()</code>에서 사용할 Timeout 객체를 반환합니다.<br>지연 시간이 <code>2147483647</code>보다 크거나 <code>1</code>보다 작으면 지연 시간은 <code>1</code>로 설정됩니다.</p><pre><code class="js">setInterval(callback, delay)</code></pre><pre><code class="js">let sec = 0;setInterval(() =&gt; {  sec++;  console.log(sec);}, 1000);</code></pre><h3><span id="882ea8b1-f5b9-49ad-9940-2f9305a92d75">clearInterval()</span><a href="#882ea8b1-f5b9-49ad-9940-2f9305a92d75" class="header-anchor"></a></h3><p><code>setInterval()</code>로 생성된 Timeout 객체를 취소합니다.</p><pre><code class="js">let sec = 0;const timer = setInterval(() =&gt; {  sec++;  console.log(sec);  if (sec &gt; 9) {    clearInterval(timer);    return;  }}, 1000);</code></pre><h2><span id="d1d23c40-e8f7-4bdb-9c7c-5d9512620884">setTimeout()</span><a href="#d1d23c40-e8f7-4bdb-9c7c-5d9512620884" class="header-anchor"></a></h2><p>지연 시간(ms) 후에 일회성 콜백 함수를 실행합니다.<br><code>clearTimeout()</code>에서 사용할 Timeout 객체를 반환합니다.<br>Node.js는 콜백이 발생하는 정확한 타이밍이나 순서를 보장하지 않습니다.<br>콜백 함수는 지정된 시간에 최대한 가깝게 호출됩니다.<br>지연 시간이 <code>2147483647</code>보다 크거나 <code>1</code>보다 작으면 지연 시간은 <code>1</code>로 설정됩니다.</p><h3><span id="fc6f7413-fee0-4036-970c-e8f416f2b75c">clearTimeout()</span><a href="#fc6f7413-fee0-4036-970c-e8f416f2b75c" class="header-anchor"></a></h3><p><code>setTimeout()</code>로 생성된 Timeout 객체를 취소합니다.</p><h2><span id="3c5b553f-c1f6-40e6-83ca-f7b7ed60cad3">setImmediate()</span><a href="#3c5b553f-c1f6-40e6-83ca-f7b7ed60cad3" class="header-anchor"></a></h2><p>I/O 주기 내에서 콜백을 즉시 실행합니다.<br><code>clearImmediate()</code>에서 사용할 Timeout 객체를 반환합니다.<br><code>setImmediate()</code>를 여러 번 호출하면 콜백 함수가 생성 순서대로 실행을 대기하며, 얼마나 많은 타이머가 존재하냐에 상관없이 I/O 주기 내에서 스케줄 된 어떤 타이머보다 항상 먼저 실행됩니다.</p><pre><code class="js">const fs = require(&#39;fs&#39;);fs.readFile(__filename, () =&gt; {  setTimeout(() =&gt; {    console.log(&#39;timeout&#39;);  }, 0);  setImmediate(() =&gt; {    console.log(&#39;immediate&#39;);  });  console.log(&#39;start&#39;);  console.log(&#39;end&#39;);});</code></pre><pre><code class="bash">startendimmediatetimeout</code></pre><h3><span id="4946c9d0-4de3-46fe-8aaf-a6b167f6d174">clearImmediate()</span><a href="#4946c9d0-4de3-46fe-8aaf-a6b167f6d174" class="header-anchor"></a></h3><p><code>setImmediate()</code>로 생성된 Timeout 객체를 취소합니다.</p><h1><span id="cf301780-761f-4aeb-85a6-5138dc84575a">Modules</span><a href="#cf301780-761f-4aeb-85a6-5138dc84575a" class="header-anchor"></a></h1><h2><span id="2506e3d0-3656-46d6-bb9a-ed20a6c8c1ef"><code>__</code>dirname</span><a href="#2506e3d0-3656-46d6-bb9a-ed20a6c8c1ef" class="header-anchor"></a></h2><p>현재 모듈의 디렉토리 이름(경로)을 나타냅니다.</p><pre><code class="js">console.log(__dirname);</code></pre><pre><code class="bash">/Users/myDir/Project/</code></pre><h2><span id="b9a4a012-31de-4edc-9cea-7f0e4c336b7e"><code>__</code>filename</span><a href="#b9a4a012-31de-4edc-9cea-7f0e4c336b7e" class="header-anchor"></a></h2><p>현재 모듈의 파일 이름(경로)을 나타냅니다.</p><pre><code class="js">console.log(__filename);</code></pre><pre><code class="bash">/Users/myDir/Project/app.js</code></pre><h2><span id="4a31fe34-38c9-46c7-b7a7-73d4239550a6">module</span><a href="#4a31fe34-38c9-46c7-b7a7-73d4239550a6" class="header-anchor"></a></h2><p>각 모듈에서 <code>module</code>은 현재 모듈을 나타내는 객체에 대한 참조이며, 전역(Global)이 아니라 각 모듈의 지역(Local)입니다.</p><h3><span id="c693d0c1-4161-4624-bfe3-f5df0159f893">module.exports</span><a href="#c693d0c1-4161-4624-bfe3-f5df0159f893" class="header-anchor"></a></h3><p>모듈에서 선언한 내용은 모듈 안에서 참조해야 하는 유효범위(Scope)가 존재합니다.<br>만약 모듈 내용을 모듈 밖에서 참조하려면 <code>module.exports</code>를 사용해서 외부 참조가 가능하도록 만들 수 있습니다.</p><p>외부에서 특정 모듈을 참조하려면 <code>require()</code>가 필요합니다.</p><p><code>name.js</code>:</p><pre><code class="js">const myName = &#39;HEROPY&#39;;module.exports = myName;  // 내보내기</code></pre><p><code>hello.js</code>:</p><pre><code class="js">const name = require(&#39;./name&#39;);  // 가져오기 / `.js`는 생략합니다.console.log(`Hello ${name}`);</code></pre><pre><code class="bash">Hello HEROPY</code></pre><h3><span id="e7ef7b2f-4d0e-4e5c-9db4-67ceb369d452">exports</span><a href="#e7ef7b2f-4d0e-4e5c-9db4-67ceb369d452" class="header-anchor"></a></h3><p><code>exports</code>는 <code>module.exports</code>의 단축 형태(shortcut)입니다.<br>하지만 똑같진 않습니다. 다음 예제를 보겠습니다.</p><p><code>exports.js</code>:</p><pre><code class="js">const array = [1, 2, 3];exports = array;</code></pre><p><code>module-exports.js</code>:</p><pre><code class="js">const array = [1, 2, 3];module.exports = array;</code></pre><p><code>app.js</code>:</p><pre><code class="js">const e = require(&#39;./exports&#39;);const me = require(&#39;./module-exports&#39;);console.log(`exports: ${e[1]}`);console.log(`module.exports: ${me[1]}`);</code></pre><pre><code class="bash">exports: undefinedmodule.exports: 2</code></pre><p><code>module.exports</code>에서 <code>.exports</code>는 <code>module</code> 객체의 프로퍼티(property)로서 값(일반 자료형)을 가질 수 있습니다.<br>하지만 <code>exports</code>는 하나의 객체(object) 데이터로 프로퍼티나 메소드를 가져야 합니다.<br>따라서 위 예제의 <code>exports.js</code>를 다음과 같이 수정해서 사용할 수 있습니다.</p><p><code>exports.js</code>:</p><pre><code class="js">const array = [1, 2, 3];exports.array = array;</code></pre><p><code>app.js</code>:</p><pre><code class="js">const e = require(&#39;./exports&#39;);console.log(`exports: ${e.array[1]}`);</code></pre><p>이러한 방식이 차이점이 불편하다면 <code>module.exports</code>를 사용할 것을 권장합니다.</p><h2><span id="c129901b-2bed-4461-a2e4-5274303b4dac">require()</span><a href="#c129901b-2bed-4461-a2e4-5274303b4dac" class="header-anchor"></a></h2><p>정의된 모듈을 <code>require()</code>를 통해 사용합니다.</p><p><code>msg.js</code>:</p><pre><code class="js">// 모듈 정의module.exports = &#39;Hello World!&#39;;</code></pre><p><code>log.js</code>:</p><pre><code class="js">// 모듈 사용const msg = require(&#39;./msg&#39;);console.log(msg);</code></pre><pre><code class="bash">Hello World!</code></pre><h1><span id="3c7147d0-3644-473e-ad18-f65a0fabab54">Process</span><a href="#3c7147d0-3644-473e-ad18-f65a0fabab54" class="header-anchor"></a></h1><p><code>process</code> 객체는 Node.js 프로세스에 대한 정보를 제공하고 제어합니다.</p><h2><span id="b711edc6-d7a7-4907-86bc-1541f6eb6156">process.arch</span><a href="#b711edc6-d7a7-4907-86bc-1541f6eb6156" class="header-anchor"></a></h2><p><code>&#39;arm&#39;</code>, <code>&#39;ia32&#39;</code>과 같은 Node.js 프로세스가 현재 실행 중인 프로세서 아키텍처 식별자를 반환합니다.</p><pre><code class="bash">&#39;x64&#39;</code></pre><h2><span id="1b65f633-83f7-486f-a741-3caab1a26b9a">process.env</span><a href="#1b65f633-83f7-486f-a741-3caab1a26b9a" class="header-anchor"></a></h2><p>사용자 환경 정보(변수)를 가진 객체를 반환합니다.</p><pre><code class="js">const PORT = process.env.PORT || 3000;</code></pre><p><code>process.env</code>는 객체이기 때문에 값(환경 변수)을 설정할 수 있으며, 문자열로 변환되어 할당됩니다.</p><pre><code class="js">process.env.MY_VARIABLE = true;console.log(process.env.MY_VARIABLE);</code></pre><pre><code class="bash">`true`</code></pre><p>Windows 운영 체제에서는 대소문자를 구분하지 않으니 주의합니다.</p><pre><code class="js">process.env.MY_VARIABLE = true;console.log(process.env.my_variable);</code></pre><pre><code class="bash">`true`</code></pre><p><code>delete</code>를 이용하여 환경 변수를 삭제할 수 있습니다.</p><pre><code class="js">delete process.env.MY_VARIABLE;console.log(process.env.MY_VARIABLE);</code></pre><pre><code>undefined</code></pre><h2><span id="a2910dfc-7605-4bbd-9f59-19bfa02303c1">process.exit()</span><a href="#a2910dfc-7605-4bbd-9f59-19bfa02303c1" class="header-anchor"></a></h2><p>Node.js가 프로세스를 종료하도록 지시합니다.<br><code>code</code>는 <code>0</code>이 생략 가능한 기본값으로, <code>0</code>은 ‘정상(성공) 종료’, <code>1</code>은 ‘비정상(실패) 종료’를 의미합니다.</p><pre><code class="js">process.exit(1);</code></pre><h2><span id="933289dc-1ddb-4fbf-9da6-f54e28d5d42b">process.nextTick()</span><a href="#933289dc-1ddb-4fbf-9da6-f54e28d5d42b" class="header-anchor"></a></h2><p><code>process.nextTick()</code>은 콜백을 ‘다음 틱 대기열(Next Tick Queue)’에 추가합니다.</p><pre><code class="js">process.nextTick(callback)</code></pre><blockquote><p>틱(tick)은 ‘이벤트 루프 대기열(Event Loop Queue)’에서 이벤트를 꺼내어 그 이벤트를 실행하는 것입니다.</p></blockquote><p>이것은 <code>setTimeout(fn, 0)</code>, <code>setImmediate()</code>에 대한 간단한 별칭이 아닙니다.<br>객체를 생성한 후 I/O가 발생하기 전에 사용자에게 이벤트 핸들러를 할당할 수 있는 기회를 제공하기 위해 사용합니다.</p><p><a href="https://nodejs.org/ko/docs/guides/event-loop-timers-and-nexttick/" target="_blank" rel="noopener">이벤트 루프란?</a></p><h2><span id="66c009f4-b6ba-49ce-a28b-b06bf1d8b0c2">process.platform</span><a href="#66c009f4-b6ba-49ce-a28b-b06bf1d8b0c2" class="header-anchor"></a></h2><p>Node.js 프로세스가 실행중인 운영 체제 플랫폼 식별자를 반환합니다.<br><code>&#39;darwin&#39;</code>, <code>&#39;freebsd&#39;</code>, <code>&#39;linux&#39;</code>, <code>&#39;sunos&#39;</code> 또는 <code>&#39;win32&#39;</code></p><pre><code class="bash">&#39;darwin&#39;</code></pre><h2><span id="22ddc5fb-b6d0-4111-bfa3-92dab2daf2b4">process.memoryUsage()</span><a href="#22ddc5fb-b6d0-4111-bfa3-92dab2daf2b4" class="header-anchor"></a></h2><p>Node.js 프로세스의 메모리 사용량을 바이트 단위로 설명하는 객체를 반환합니다.</p><pre><code class="bash">{  rss: 11145216,  heapTotal: 7184384,  heapUsed: 4982952,  external: 8644}</code></pre><p><code>heapTotal</code>, <code>heapUsed</code>: V8의 메모리 사용량을 나타냅니다.<br><code>external</code>: V8에서 관리하는 JavaScript 객체에 바인딩 된 C++ 객체의 메모리 사용량을 나타냅니다.<br><code>rss</code>: rss(Resident Set Size)는 프로세스의 heap, code segment, stack을 포함하는 주 메모리 장치(총 할당 된 메모리의 하위 집합)에 차지하는 공간의 크기입니다.</p><blockquote><p>heap은 객체, 문자열 및 클로저가 저장되는 곳입니다. 변수는 Stack에 저장되고, 실제 JavaScript 코드는 code segment에 있습니다.</p></blockquote><h2><span id="ec7a8042-0370-4a11-80a0-2044edf9d827">process.uptime()</span><a href="#ec7a8042-0370-4a11-80a0-2044edf9d827" class="header-anchor"></a></h2><p>현재 Node.js 프로세스가 실행된 시간(sec)을 반환합니다.</p><pre><code class="js">console.log(  process.uptime(),  Math.floor(process.uptime()))</code></pre><pre><code class="bash">130.072130</code></pre><h2><span id="21506e12-374a-4720-9903-76609ad38a30">process.versions</span><a href="#21506e12-374a-4720-9903-76609ad38a30" class="header-anchor"></a></h2><p>Node.js와 그 종속성의 버전을 나열하는 객체를 반환합니다.</p><pre><code class="bash">{  http_parser: &#39;2.7.0&#39;,  node: &#39;8.9.4&#39;,  v8: &#39;6.1.534.50&#39;,  uv: &#39;1.15.0&#39;,  zlib: &#39;1.2.11&#39;,  ares: &#39;1.10.1-DEV&#39;,  modules: &#39;57&#39;,  nghttp2: &#39;1.25.0&#39;,  openssl: &#39;1.0.2n&#39;,  icu: &#39;59.1&#39;,  unicode: &#39;9.0&#39;,  cldr: &#39;31.0.1&#39;,  tz: &#39;2017b&#39;}</code></pre><h1><span id="a31506a2-0d7b-452c-a5ea-55e2b7bf637a">Buffers</span><a href="#a31506a2-0d7b-452c-a5ea-55e2b7bf637a" class="header-anchor"></a></h1><p>Buffer 모듈은 바이너리 데이터(Binary data)의 스트림(Stream)을 처리하는 방법을 제공합니다.</p><blockquote><p>Buffer(버퍼)란 I/O 수행의 속도 차이 극복을 위해 한 장소에서 다른 장소로 전송되는 데이터 묶음(Chunk)에 대한 임시 저장 공간 의미합니다.</p></blockquote><h2><span id="40365326-b078-4066-885c-7d6db4e57e85">Buffer.alloc()</span><a href="#40365326-b078-4066-885c-7d6db4e57e85" class="header-anchor"></a></h2><p>원하는 길이(Size)의 안전한 새로운 Buffer를 생성합니다.</p><pre><code class="js">Buffer.alloc(size, fill, encoding)</code></pre><blockquote><p>기존 <code>new Buffer()</code>는 v6.x부터 ‘deprecated’ 되었습니다.</p></blockquote><pre><code class="js">const buf = Buffer.alloc(4);  // 4byteconsole.log(buf);</code></pre><pre><code class="bash">&lt;Buffer 00 00 00 00&gt;</code></pre><h2><span id="ddb9cb79-b66e-4456-94cd-20e57c7357ef">Buffer.from()</span><a href="#ddb9cb79-b66e-4456-94cd-20e57c7357ef" class="header-anchor"></a></h2><p><code>String</code>, <code>Array</code> 또는 <code>Buffer</code>로 채워진 새로운 버퍼를 생성합니다.</p><pre><code class="js">Buffer.from()</code></pre><blockquote><p>기존 <code>new Buffer()</code>는 v6.x부터 ‘deprecated’ 되었습니다.</p></blockquote><p><code>hello world</code>라는 문자열(String)로 새로운 버퍼를 생성합니다.<br>문자열 생성시 두번째 인수로는 인코딩을 설정할 수 있으며, 생략할 수 있고 기본값은 <code>&#39;utf8&#39;</code>입니다.</p><pre><code class="js">var buf1 = Buffer.from(&#39;hello world&#39;, &#39;utf8&#39;);console.log(buf1);</code></pre><pre><code class="bash">&lt;Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64&gt;</code></pre><h2><span id="30f2c3ad-9c8b-4d11-b15e-592f49a2e107">Buffer.concat()</span><a href="#30f2c3ad-9c8b-4d11-b15e-592f49a2e107" class="header-anchor"></a></h2><pre><code class="js">Buffer.concat([buffer1, buffer2], totalLength)</code></pre><p>Buffer 객체들을 하나의 Buffer 객체로 결합(이어 붙이기)합니다.</p><pre><code class="js">var buf1 = Buffer.from(&#39;abc&#39;);  // 3var buf2 = Buffer.from(&#39;def&#39;);  // 3var buf3 = Buffer.concat([buf1, buf2], 6);console.log(  buf3,  buf3.toString())</code></pre><pre><code class="bash">&lt;Buffer 61 62 63 64 65 66&gt;&#39;abcdef&#39;</code></pre><p><code>totalLength</code>를 지정하지 않으면 자동으로 계산됩니다. 그러나 <code>totalLength</code>를 계산하기 위해 추가 루프가 실행되므로 값을 알고 있다면 제공하는 것이 더 빠릅니다.<br>버퍼의 결합 길이가 <code>totalLength</code>를 넘으면 결과는 잘립니다.</p><pre><code class="js">var buf1 = Buffer.from(&#39;abc&#39;);  // 3var buf2 = Buffer.from(&#39;def&#39;);  // 3var buf3 = Buffer.concat([buf1, buf2], 4);console.log(  buf3,  buf3.toString())</code></pre><pre><code class="bash">&lt;Buffer 61 62 63 64&gt;&#39;abcd&#39;</code></pre><h2><span id="2bc1d67a-5ec7-4944-b0fc-fa7806849110">Buffer.isBuffer()</span><a href="#2bc1d67a-5ec7-4944-b0fc-fa7806849110" class="header-anchor"></a></h2><p>Buffer 객체인지 검사합니다.</p><pre><code class="js">Buffer.isBuffer(obj)</code></pre><pre><code class="js">var fs = require(&#39;fs&#39;)fs.readFile(&#39;./abc.txt&#39;, function (err, data) {  console.log(    Buffer.isBuffer(data)  );});</code></pre><pre><code class="bash">true</code></pre><h2><span id="499447d4-d17d-4876-aab9-a64426602601">buf.copy()</span><a href="#499447d4-d17d-4876-aab9-a64426602601" class="header-anchor"></a></h2><p>Buffer 객체를 복사하여 대상 Buffer 객체에 붙여넣습니다.</p><pre><code class="js">buf.copy(targetBuf, targetStart, sourceStart, sourceEnd)</code></pre><p><code>target</code>: 복사된 Buffer를 붙여넣을 대상 Buffer를 지정합니다.<br><code>targetStart</code>: 복사된 Buffer의 시작 위치를 지정합니다. 기본값은 <code>0</code>입니다.<br><code>sourceStart</code>: 복사할 Buffer의 시작 위치를 지정합니다. 기본값은 <code>0</code>입니다.<br><code>sourceEnd</code>: 복사할 Buffer 객체의 끝 위치를 지정합니다. 기본값은 <code>buf.length</code>입니다.</p><pre><code class="js">var buf1 = Buffer.from(&#39;abcdef&#39;);var buf2 = Buffer.from(&#39;ABCDEF&#39;);buf1.copy(buf2, 1, 0, 2);// buf2: 붙여넣을 대상// 1: 대상의 어느 위치부터 붙일까요?// 0: buf1의 어디서부터 복사할까요?// 2: buf1의 어디까지 복사할까요?console.log(  buf2,  buf2.toString());</code></pre><pre><code class="bash">&lt;Buffer 41 61 62 44 45 46&gt;&#39;AabDEF&#39;</code></pre><h2><span id="c544ec8d-dfd5-4e0a-947e-369562eded0d">buf.length</span><a href="#c544ec8d-dfd5-4e0a-947e-369562eded0d" class="header-anchor"></a></h2><p>할당된 메모리를 바이트 단위로 반환합니다.</p><pre><code class="js">var buf1 = Buffer.from(&#39;abc&#39;);var buf2 = Buffer.from(&#39;가나다&#39;);console.log(  buf1.length,  // 3byte  buf2.length  // 9byte);</code></pre><pre><code class="bash">39</code></pre><blockquote><p>한글은 ‘3byte’, 띄어쓰기는 ‘1byte’입니다.</p></blockquote><h2><span id="48f76ad9-bf53-4fba-aafb-359e076fab88">buf.toString()</span><a href="#48f76ad9-bf53-4fba-aafb-359e076fab88" class="header-anchor"></a></h2><p>Buffer 객체를 문자 인코딩에 따라 문자열로 디코딩합니다.</p><pre><code class="js">buf.toString(encoding, start, end)</code></pre><p><code>encoding</code>: 디코드할 문자 인코딩을 설정합니다. 기본값은 <code>&#39;utf8&#39;</code>입니다.<br><code>start</code>: 디코드할 시작 위치를 지정합니다. 기본값은 <code>0</code>입니다.<br><code>end</code>: 디코드할 끝 위치를 지정합니다. 기본값은 <code>buf.length</code>입니다.</p><p><code>abc.txt</code>:</p><pre><code class="plaintext">abcdefghijklmnopqrstuvwxyz</code></pre><p><code>app.js</code>:</p><pre><code class="js">var fs = require(&#39;fs&#39;)fs.readFile(&#39;./abc.txt&#39;, function (err, data) {  console.log(    data,    data.toString()  );});</code></pre><pre><code class="bash">&lt;Buffer 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a&gt;abcdefghijklmnopqrstuvwxyz</code></pre><h2><span id="13dac8d9-0ef2-46e8-aa23-8253a9a90c7a">buf.write()</span><a href="#13dac8d9-0ef2-46e8-aa23-8253a9a90c7a" class="header-anchor"></a></h2><p>지정된 문자열을 Buffer 객체에 작성합니다.</p><pre><code class="js">buf.write(string, offset, length, encoding)</code></pre><pre><code class="js">var data = &#39;가나다라&#39;;var buf = Buffer.alloc(12);buf.write(data);console.log(  buf,  buf.toString());</code></pre><pre><code class="bash">&lt;Buffer ea b0 80 eb 82 98 eb 8b a4 eb 9d bc&gt;&#39;가나다라&#39;</code></pre><h1><span id="c1d4ae0d-8939-4d2f-b302-c04a5c5ba7d4">참고 자료(References)</span><a href="#c1d4ae0d-8939-4d2f-b302-c04a5c5ba7d4" class="header-anchor"></a></h1><p><a href="https://nodejs.org/dist/latest-v8.x/docs/api/" target="_blank" rel="noopener">https://nodejs.org/dist/latest-v8.x/docs/api/</a><br><a href="https://vimeo.com/96425312" target="_blank" rel="noopener">https://vimeo.com/96425312</a><br><a href="http://voidcanvas.com/setimmediate-vs-nexttick-vs-settimeout/" target="_blank" rel="noopener">http://voidcanvas.com/setimmediate-vs-nexttick-vs-settimeout/</a><br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/EventLoop" target="_blank" rel="noopener">https://developer.mozilla.org/ko/docs/Web/JavaScript/EventLoop</a><br><a href="https://www.tutorialspoint.com/nodejs/nodejs_repl_terminal.htm" target="_blank" rel="noopener">https://www.tutorialspoint.com/nodejs/nodejs_repl_terminal.htm</a></p>]]></content>
    
    <summary type="html">
    
      모든 모듈에서 사용할 수 있는 Node.js의 전역 객체들에 대해서 알아봅니다. (REPL / global / Console / Timers / Modules / Process / Buffers)
    
    </summary>
    
    
      <category term="nodejs" scheme="https://heropy.blog/tags/nodejs/"/>
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>처음 시작하는 Node.js 개발 - 2 - npm</title>
    <link href="https://heropy.blog/2018/02/18/node-js-npm/"/>
    <id>https://heropy.blog/2018/02/18/node-js-npm/</id>
    <published>2018-02-18T02:34:28.000Z</published>
    <updated>2018-02-26T07:49:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>npm(Node Package Manager)은 JavaScript 및 세계 최대의 소프트웨어 레지스트리 패키지 관리자로 <a href="/2018/02/17/node-js-start/">Node.js를 설치</a>하면 같이 설치되어 사용할 수 있습니다.<br>npm에는 Node.js에서 사용되는 각종 코드 패키지들이 모여있고, 우리는 그 패키지를 다운로드 받아 사용할 수 있습니다.<br>좀 더 쉽게 npm은 Node.js 생태계의 앱스토어나 플레이스토어 같은 역할을 합니다.</p><p>npm 레지스트리에는 640,000개가 넘는 패키지가 포함되어 있으며, 패키지는 의존성(dependencies) 및 버전을 추적할 수 있도록 구성됩니다.<br>이 페이지에서는 npm의 주요 CLI(Command Line Interface)와 관련된 정보들에 대해서 살펴보겠습니다.</p><div class="toc"><ul><li><a href="370e0b9b-5466-4003-9f1e-dabfb73ab87d">init (초기화)</a><ul><li><a href="cc314c54-9453-4084-8acd-4c931d83b8b3">package.json</a><ul><li><a href="1544b819-3fe7-48e4-8811-f806def38f8b">name</a></li><li><a href="42561ebb-ed9c-4db8-9704-1ea6a87b0c66">version</a></li><li><a href="d5c818ba-6841-416c-b003-933954b62cdc">description</a></li><li><a href="65d521ac-31f6-404a-9cc0-b215beaa44c2">keywords</a></li><li><a href="67341df0-6dde-4232-acf6-9cbfa60f0dd8">homepage</a></li><li><a href="00d5bc65-6243-4b1a-b3a4-8008edc1e012">bugs</a></li><li><a href="b9ef2f9f-ae24-4a2a-9ba9-d9158fb930db">license</a></li><li><a href="a998f307-0207-4143-8870-e676c61a6a94">author</a></li><li><a href="e417520a-0a91-48fa-8cc5-f328fb2bed73">files</a></li><li><a href="d83e3a3d-3dd4-4ca9-ba7d-cf184947054f">main</a></li><li><a href="0d3c4a74-bea2-4b4b-bdb6-dd0f27ff5fdb">repository</a></li><li><a href="83d62bd0-942f-4e41-9ca7-bc390718ae35">script</a></li><li><a href="0173cf74-a465-46e1-b38b-2bc3e0b406c1">dependencies</a></li><li><a href="1afd745c-dd21-44c2-8986-670d20835e85">devDependencies</a></li><li><a href="12787508-9450-4d0b-a76b-630067b42394">peerDependencies</a></li><li><a href="5873b51b-2f04-4aa2-8ed8-856a1fb89f08">bundledDependencies</a></li><li><a href="d056df5d-c52a-4ad0-8cc5-e7a70c31afff">optionalDependencies</a></li><li><a href="477baf10-378f-4382-9bff-2d0b55b3ced6">engines</a></li><li><a href="6dcdb7b2-1476-4985-bfac-27a90707fe53">private</a></li></ul></li><li><a href="8a6ed116-089c-4319-ad5f-934dceea2fc1">package-lock.json</a></li></ul></li><li><a href="396a2967-6b79-40a5-b6c2-f974d32c0467">install (패키지 설치)</a><ul><li><a href="f7dd98e0-9415-4355-b01a-09c016bd3c5a">이미 정의된 의존성 모듈 설치</a></li><li><a href="9e120b01-ba5a-432d-a1f5-689f488544af">아직 정의되지 않은 의존성 모듈 설치</a><ul><li><a href="58ce3943-1cf4-440b-bdfb-cbc3c614c542"><code>--</code>save</a></li><li><a href="d87cf74a-0e66-43c8-b8e8-4ccef3e5ec1f"><code>--</code>save-dev / -D</a></li><li><a href="cc61a136-2fa1-4786-b64c-0538ff9b4986"><code>--</code>save-optional / -O</a></li><li><a href="d1b4ee61-f2ba-4522-919d-fbce09713476"><code>--</code>save-exact / -E</a></li><li><a href="2746d278-8cf1-44aa-8a49-56fdbefcb384"><code>--</code>save-bundle / -B</a></li><li><a href="3440ec7a-7851-419e-bda2-76df5d739946">전역 설치</a></li></ul></li></ul></li><li><a href="0cbb0f07-364d-44c2-b046-5cafbee693da">유의적 버전(Semver)이란?</a><ul><li><a href="966074f1-eb54-436a-95aa-005270f6c601">범위(Ranges)</a></li></ul></li><li><a href="59a34ea6-c58f-49de-b0b5-d2ce1e6f67de">기타 자주 사용하는 CLI commands</a><ul><li><a href="9f2905ed-4f79-4bb0-ad4d-a4c24f3ad4a9">outdated (오래된 패키지 확인)</a></li><li><a href="de1937b8-b7f6-483b-bd7d-fe2108a64268">update (버전 업데이트)</a></li><li><a href="e9149056-9c8d-45f6-b7b9-3e3b57b5aff0">uninstall (패키지 제거)</a></li><li><a href="2d5c41ac-c16f-4806-8ed8-1d1d0660574e">version (패키지 버전 관리)</a></li><li><a href="7223f622-c10f-4378-b52b-6d776e3d7a34">ls (패키지 목록)</a><ul><li><a href="8242faa2-3bdc-4007-9623-0da413ad5aff"><code>--</code>json</a></li><li><a href="a3cddc1d-c37b-4ed1-8697-5c5b38a125f3"><code>--</code>long</a></li><li><a href="acbe6d04-0c0b-40a0-8952-b27838a89d19"><code>--</code>depth</a></li><li><a href="d407471e-d4a8-449e-ad88-a2ba0ec80259"><code>--</code>prod / <code>--</code>production</a></li><li><a href="c4d60881-f28b-44c7-ba60-6687895943dc"><code>--</code>dev</a></li><li><a href="be02990f-19b3-426b-a69f-93bd6071157b">전역 설치된 패키지 목록</a></li></ul></li><li><a href="d1338c07-ec89-49fc-a4dd-41bf98cf68d2">view (패키지 정보 보기)</a></li></ul></li><li><a href="188ac056-1c0a-4c01-9f8e-84ad2576b614">참고 자료(References)</a></li></ul></div><h1><span id="370e0b9b-5466-4003-9f1e-dabfb73ab87d">init (초기화)</span><a href="#370e0b9b-5466-4003-9f1e-dabfb73ab87d" class="header-anchor"></a></h1><pre><code class="bash">$ npm init</code></pre><ul><li>package name</li><li>version</li><li>description</li><li>entry point</li><li>test command</li><li>git repository</li><li>keywords</li><li>author</li><li>license</li></ul><p>여러 가지 질문에 답하면(옵션을 추가하면) <code>package.json</code> 파일을 작성합니다.<br>각 질문을 넘어가면 기본값을 사용합니다.</p><p>질문 없이 바로 시작하고 싶다면 <code>-f</code>(<code>--force</code>), <code>-y</code>(<code>--yes</code>) 중 하나의 플래그를 추가하는 것을 추천합니다.</p><pre><code class="bash">$ npm init -y</code></pre><p>이미 <code>package.json</code> 파일을 가지고 있다면, 먼저 그 파일을 읽고 난 후 옵션을 기본값으로 사용합니다.</p><h2><span id="cc314c54-9453-4084-8acd-4c931d83b8b3">package.json</span><a href="#cc314c54-9453-4084-8acd-4c931d83b8b3" class="header-anchor"></a></h2><pre><code class="json">{  &quot;name&quot;: &quot;project-name&quot;,  &quot;version&quot;: &quot;1.0.0&quot;,  &quot;keywords&quot;: [],  &quot;description&quot;: &quot;&quot;,  &quot;main&quot;: &quot;index.js&quot;,  &quot;scripts&quot;: {    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;  },  &quot;author&quot;: &quot;HEROPY&quot;,  &quot;license&quot;: &quot;MIT&quot;,  &quot;dependencies&quot;: {},  &quot;devDependencies&quot;: {}}</code></pre><p><code>package.json</code>은 프로젝트 정보와 의존성(dependencies)을 관리하는 문서입니다.<br>이미 작성된 <code>package.json</code> 문서는 어느 곳에서도 동일한 개발 환경을 구축할 수 있게 해줍니다.<br>JSON 포맷으로 작성해야 하며, 다음과 같은 옵션들이 추가될 수 있습니다.</p><h3><span id="1544b819-3fe7-48e4-8811-f806def38f8b">name</span><a href="#1544b819-3fe7-48e4-8811-f806def38f8b" class="header-anchor"></a></h3><p>URL이나 Command Line의 일부로 사용될 소문자로 표기된 214자 이내의 프로젝트(패키지) 이름으로, 간결하고 직관적인 이름으로 설정하되 다른 모듈과 동일한 이름을 피하세요.</p><h3><span id="42561ebb-ed9c-4db8-9704-1ea6a87b0c66">version</span><a href="#42561ebb-ed9c-4db8-9704-1ea6a87b0c66" class="header-anchor"></a></h3><p><a href="https://docs.npmjs.com/misc/semver" target="_blank" rel="noopener">SemVer(The semantic versioner for npm)</a>로 분석 가능한 형태의 버전을 지정합니다.</p><h3><span id="d5c818ba-6841-416c-b003-933954b62cdc">description</span><a href="#d5c818ba-6841-416c-b003-933954b62cdc" class="header-anchor"></a></h3><p>프로젝트(패키지)의 설명을 지정합니다.<br>(npm search 사용 시 도움이 됩니다.)</p><h3><span id="65d521ac-31f6-404a-9cc0-b215beaa44c2">keywords</span><a href="#65d521ac-31f6-404a-9cc0-b215beaa44c2" class="header-anchor"></a></h3><p>프로젝트(패키지)의 키워드를 배열로 지정합니다.<br>(npm search 사용 시 도움이 됩니다.)</p><pre><code class="json">{  &quot;keywords:&quot;: [    &quot;array&quot;,    &quot;string&quot;  ]}</code></pre><h3><span id="67341df0-6dde-4232-acf6-9cbfa60f0dd8">homepage</span><a href="#67341df0-6dde-4232-acf6-9cbfa60f0dd8" class="header-anchor"></a></h3><p>프로젝트 홈페이지로 연결되는 URL을 지정합니다.</p><h3><span id="00d5bc65-6243-4b1a-b3a4-8008edc1e012">bugs</span><a href="#00d5bc65-6243-4b1a-b3a4-8008edc1e012" class="header-anchor"></a></h3><p>패키지에 문제가 있을 때 보고될 이슈 트래커(추적시스템) 및 이메일 주소 등에 대한 URL을 지정합니다.</p><pre><code class="json">{  &quot;bugs&quot;: {    &quot;url&quot;: &quot;https://github.com/owner/project/issues&quot;,    &quot;email&quot;: &quot;thesecon@gmail.com&quot;  }}</code></pre><h3><span id="b9ef2f9f-ae24-4a2a-9ba9-d9158fb930db">license</span><a href="#b9ef2f9f-ae24-4a2a-9ba9-d9158fb930db" class="header-anchor"></a></h3><p>패키지 사용을 허용하는 방법과 제한 사항을 알 수 있도록 라이센스를 지정합니다.<br><a href="https://opensource.org/licenses" target="_blank" rel="noopener">Open Source Licenses</a></p><h3><span id="a998f307-0207-4143-8870-e676c61a6a94">author</span><a href="#a998f307-0207-4143-8870-e676c61a6a94" class="header-anchor"></a></h3><p>제작자의 이름을 지정합니다.</p><h3><span id="e417520a-0a91-48fa-8cc5-f328fb2bed73">files</span><a href="#e417520a-0a91-48fa-8cc5-f328fb2bed73" class="header-anchor"></a></h3><p>패키지가 의존성으로 설치될 때 같이 포함될 파일들의 배열입니다.<br>생략하면 자동 제외로 설정된 파일을 제외한 모든 파일이 포함됩니다</p><pre><code class="json">{  &quot;files&quot;: [    &quot;dist/&quot;,    &quot;js/{src,dist}/&quot;,    &quot;scss/&quot;  ]}</code></pre><h3><span id="d83e3a3d-3dd4-4ca9-ba7d-cf184947054f">main</span><a href="#d83e3a3d-3dd4-4ca9-ba7d-cf184947054f" class="header-anchor"></a></h3><p>프로그램의 기본 진입 점(entry point)를 지정합니다.<br>패키지의 이름이 <code>jquery</code>이고, 사용자가 <code>require(&#39;jquery&#39;)</code>를 사용하면 진입 점의 메인 모듈에서 exports object가 반환(return)됩니다.</p><h3><span id="0d3c4a74-bea2-4b4b-bdb6-dd0f27ff5fdb">repository</span><a href="#0d3c4a74-bea2-4b4b-bdb6-dd0f27ff5fdb" class="header-anchor"></a></h3><p>코드가 존재하는 장소를 지정합니다.<br>GitHub를 사용하면 <code>npm docs</code> 명령을 사용하여 찾을 수 있습니다.</p><pre><code class="json">{  &quot;repository&quot;: {    &quot;type&quot;: &quot;git&quot;,    &quot;url&quot;: &quot;https://github.com/npm/npm.git&quot;  }}</code></pre><h3><span id="83d62bd0-942f-4e41-9ca7-bc390718ae35">script</span><a href="#83d62bd0-942f-4e41-9ca7-bc390718ae35" class="header-anchor"></a></h3><p>패키지 라이프 사이클에서 여러 번 실행되는 스크립트 명령을 포함합니다.</p><pre><code class="json">{  &quot;scripts&quot;: {    &quot;lint&quot;: &quot;bash lint.sh&quot;,    &quot;dev&quot;: &quot;webpack-dev-server&quot;,    &quot;prod&quot;: &quot;webpack -p&quot;  }}</code></pre><pre><code class="bash"># `webpack -p` 와 동일$ npm run prod</code></pre><h3><span id="0173cf74-a465-46e1-b38b-2bc3e0b406c1">dependencies</span><a href="#0173cf74-a465-46e1-b38b-2bc3e0b406c1" class="header-anchor"></a></h3><p>패키지의 배포 시 포함될 의존성 모듈을 지정합니다.</p><h3><span id="1afd745c-dd21-44c2-8986-670d20835e85">devDependencies</span><a href="#1afd745c-dd21-44c2-8986-670d20835e85" class="header-anchor"></a></h3><p>패키지의 개발 시 사용될 의존성 모듈을 지정합니다.<br>(배포 시 포함되지 않습니다)</p><h3><span id="12787508-9450-4d0b-a76b-630067b42394">peerDependencies</span><a href="#12787508-9450-4d0b-a76b-630067b42394" class="header-anchor"></a></h3><p>패키지의 호환성 모듈을 지정합니다.<br>(npm@3 이후로 배포 시 포함되지 않습니다, 대신 호환성 모듈이 없으면 경고 메시지가 표시됩니다)</p><pre><code class="json">{  &quot;name&quot;: &quot;bootstrap&quot;,  &quot;peerDependencies&quot;: {    &quot;jquery&quot;: &quot;1.9.1 - 3&quot;,    &quot;popper.js&quot;: &quot;^1.12.9&quot;  }}</code></pre><h3><span id="5873b51b-2f04-4aa2-8ed8-856a1fb89f08">bundledDependencies</span><a href="#5873b51b-2f04-4aa2-8ed8-856a1fb89f08" class="header-anchor"></a></h3><p>패키지를 게시할 때 번들로 묶을 패키지 이름을 <strong>배열</strong>로 지정합니다.<br>npm 패키지를 로컬에서 보존해야 하거나 단일 파일 다운로드를 통해 사용할 수있는 경우 <code>npm pack</code>을 실행하여 패키지를 <code>&lt;name&gt;-&lt;version&gt;.tgz</code> 형태의 <a href="https://ko.wikipedia.org/wiki/Tar_(%ED%8C%8C%EC%9D%BC_%ED%8F%AC%EB%A7%B7" target="_blank" rel="noopener">tarball 파일</a>)로 묶을 수 있습니다.</p><pre><code class="json">{  &quot;bundledDependencies&quot;: [    &quot;renderized&quot;,    &quot;super-streams&quot;  ]}</code></pre><h3><span id="d056df5d-c52a-4ad0-8cc5-e7a70c31afff">optionalDependencies</span><a href="#d056df5d-c52a-4ad0-8cc5-e7a70c31afff" class="header-anchor"></a></h3><p>npm을 찾을 수 없거나 설치에 실패한 경우 계속 진행하려면 optionDependencies 객체에 넣을 수 있습니다.<br>dependencies 동일하게 배포 시 포함될 의존성 모듈을 지정하지만, 빌드 실패로 인해 설치 과정이 중단되지 않습니다.</p><pre><code class="json">{  &quot;optionalDependencies&quot;: {    &quot;7zip-bin-mac&quot;: &quot;^1.x.x&quot;,    &quot;7zip-bin-win&quot;: &quot;^2.x.x&quot;  }}</code></pre><h3><span id="477baf10-378f-4382-9bff-2d0b55b3ced6">engines</span><a href="#477baf10-378f-4382-9bff-2d0b55b3ced6" class="header-anchor"></a></h3><p>패키지가 작동하는 Node 버전을 지정합니다.</p><pre><code class="json">{  &quot;engines&quot;: {    &quot;node&quot;: &quot;&gt;=0.10.3 &lt;0.12&quot;,    &quot;npm&quot; : &quot;~1.0.20&quot;  }}</code></pre><h3><span id="6dcdb7b2-1476-4985-bfac-27a90707fe53">private</span><a href="#6dcdb7b2-1476-4985-bfac-27a90707fe53" class="header-anchor"></a></h3><p>개인 저장소의 우연한 발행을 방지하기 위해 npm에서 패키지의 공개 여부를 지정합니다.</p><pre><code class="json">{  &quot;private&quot;: true}</code></pre><h2><span id="8a6ed116-089c-4319-ad5f-934dceea2fc1">package-lock.json</span><a href="#8a6ed116-089c-4319-ad5f-934dceea2fc1" class="header-anchor"></a></h2><p><code>package.json</code>이 동일한 개발 환경 구축을 위한 정보를 가지고 있지만, 다양한 경우에 의해 동일한 개발 환경 구축에 문제가 발생할 수 있습니다.</p><p>예를 들어, 다음과 같은 어떤 상위 모듈(<code>dashdash</code>)에서 사용하는 하위 모듈 중 하나(<code>assert-plus</code>)의 버전이 변경되었다고 가정합니다.<br>그렇다면 상위 모듈 버전이 동일하다고 하더라도 내부적으로 다른 결과를 출력할 수 있습니다.</p><pre><code class="json">{  &quot;dashdash&quot;: {    &quot;version&quot;: &quot;1.14.1&quot;,    &quot;requires&quot;: {      &quot;assert-plus&quot;: &quot;1.0.0&quot;    },    &quot;dependencies&quot;: {      &quot;assert-plus&quot;: {        &quot;version&quot;: &quot;1.0.0&quot;      }    }  }}</code></pre><p>이를 방지하기 위해 npm으로 <code>node_modules</code>의 구성 트리 또는 <code>package.json</code>을 수정하는 모든 작업에 대해 <code>package-lock.json</code>이 자동으로 생성됩니다.<br>모든 작업에 대해 자동 생성하므로 의존성 업데이트와 같은 버전 변경에 대해서도 동일한 모듈 트리를 생성할 수 있게 됩니다.</p><h1><span id="396a2967-6b79-40a5-b6c2-f974d32c0467">install (패키지 설치)</span><a href="#396a2967-6b79-40a5-b6c2-f974d32c0467" class="header-anchor"></a></h1><pre><code class="json">{  &quot;dependencies&quot;: {    &quot;bootstrap&quot;: &quot;^4.0.0&quot;,    &quot;jquery&quot;: &quot;^3.3.1&quot;  },  &quot;devDependencies&quot;: {    &quot;babel-preset-env&quot;: &quot;^1.6.1&quot;,    &quot;node-sass&quot;: &quot;^4.7.2&quot;  }}</code></pre><h2><span id="f7dd98e0-9415-4355-b01a-09c016bd3c5a">이미 정의된 의존성 모듈 설치</span><a href="#f7dd98e0-9415-4355-b01a-09c016bd3c5a" class="header-anchor"></a></h2><p><code>package.json</code> 파일에 이미 정의된 의존성(<code>dependencies</code>) 모듈(Modules)이 있으면, 나열된 모든 모듈을 로컬 <code>node_modules</code> 폴더에 설치합니다.</p><pre><code class="bash">$ npm install</code></pre><h2><span id="9e120b01-ba5a-432d-a1f5-689f488544af">아직 정의되지 않은 의존성 모듈 설치</span><a href="#9e120b01-ba5a-432d-a1f5-689f488544af" class="header-anchor"></a></h2><h3><span id="58ce3943-1cf4-440b-bdfb-cbc3c614c542"><code>--</code>save</span><a href="#58ce3943-1cf4-440b-bdfb-cbc3c614c542" class="header-anchor"></a></h3><p>프로젝트(패키지)가 배포(Deploy) 시 사용될 의존성 모듈을 정의하고 설치합니다.<br><code>--save</code> 플래그(Flag)를 사용하거나 생략합니다.<br><code>&quot;dependencies&quot;</code>에 나열됩니다.</p><blockquote><p>npm will –save by default now. Additionally, package-lock.json will be automatically created unless an npm-shrinkwrap.json exists.</p><ul><li>npm releases note v5.0.0</li></ul></blockquote><pre><code class="bash">$ npm install --save &lt;package&gt;  $ npm install &lt;package&gt;  # npm@5 부터 `--save` 생략 가능$ npm install &lt;package&gt;@&lt;version&gt;  # 버전 설정$ npm i &lt;package&gt;  # alias</code></pre><p>Example:</p><pre><code class="bash">$ npm install --save jquery  # jquery ^3.3.1$ npm install jquery$ npm install jquery@1.12.4$ npm i jquery</code></pre><h3><span id="d87cf74a-0e66-43c8-b8e8-4ccef3e5ec1f"><code>--</code>save-dev / -D</span><a href="#d87cf74a-0e66-43c8-b8e8-4ccef3e5ec1f" class="header-anchor"></a></h3><p>개발 단계에서만 사용하는 의존성 모듈을 정의하고 설치합니다.<br><code>-D</code>(<code>--save-dev</code>) 플래그를 사용합니다.<br><code>&quot;devDependencies&quot;</code>에 나열됩니다.</p><pre><code class="bash">$ npm install --save-dev &lt;package&gt;  $ npm install -D &lt;package&gt;$ npm install -D &lt;package&gt;@&lt;version&gt;  # 버전 설정$ npm i -D &lt;package&gt;  # alias</code></pre><p>Example:</p><pre><code class="bash">$ npm install --save-dev babel-preset-env$ npm i -D node-sass</code></pre><h3><span id="cc61a136-2fa1-4786-b64c-0538ff9b4986"><code>--</code>save-optional / -O</span><a href="#cc61a136-2fa1-4786-b64c-0538ff9b4986" class="header-anchor"></a></h3><p>선택적 의존성 모듈을 정의하고 설치합니다.<br><code>-O</code>(<code>--save-optional</code>) 플래그를 사용합니다.<br><code>&quot;optionalDependencies&quot;</code>에 나열됩니다.</p><h3><span id="d1b4ee61-f2ba-4522-919d-fbce09713476"><code>--</code>save-exact / -E</span><a href="#d1b4ee61-f2ba-4522-919d-fbce09713476" class="header-anchor"></a></h3><p>npm의 기본 SemVer 연산자(<code>^</code>, <code>~</code> 같은)를 사용하는 대신 정확한 버전으로 설치합니다.<br><code>-E</code>(<code>--save-exact</code>) 플래그를 사용합니다.<br><code>&quot;dependencies&quot;</code>에 나열됩니다.</p><p><code>npm i bootstrap</code>:</p><pre><code class="json">{  &quot;dependencies&quot;: {    &quot;bootstrap&quot;: &quot;^4.0.0&quot;  }}</code></pre><p><code>npm i bootstrap -E</code>:</p><pre><code class="json">{  &quot;dependencies&quot;: {    &quot;bootstrap&quot;: &quot;4.0.0&quot;  }}</code></pre><h3><span id="2746d278-8cf1-44aa-8a49-56fdbefcb384"><code>--</code>save-bundle / -B</span><a href="#2746d278-8cf1-44aa-8a49-56fdbefcb384" class="header-anchor"></a></h3><p>번들로 묶을 패키지 의존성 모듈을 정의하고 설치합니다.<br><code>-B</code>(<code>--save-bundle</code>) 플래그를 사용합니다.<br><code>&quot;bundledDependencies&quot;</code>에 나열됩니다.</p><h3><span id="3440ec7a-7851-419e-bda2-76df5d739946">전역 설치</span><a href="#3440ec7a-7851-419e-bda2-76df5d739946" class="header-anchor"></a></h3><p>패키지를 command line tool로 사용하려면 전역(Global)으로 설치할 수 있습니다.<br>전역으로 설치된 패키지는 디렉토리에 관계없이 작동합니다.<br><code>-g</code> 플래그를 사용합니다.</p><pre><code class="bash">$ npm install -g webpack</code></pre><h1><span id="0cbb0f07-364d-44c2-b046-5cafbee693da">유의적 버전(Semver)이란?</span><a href="#0cbb0f07-364d-44c2-b046-5cafbee693da" class="header-anchor"></a></h1><p>의존성 모듈의 버전을 너무 엄격하거나 느슨하지 않게 관리하기 위해서 npm에서는 Semver(Semantic Versioning)를 지원합니다.</p><p>우선 버전을 <code>1.0.0</code>과 같이 <strong>X.Y.Z(Major.Minor.Patch)</strong> 형식으로 정합니다.<br>API에 호환되지 않는 변경이라면 <strong>Major</strong> 버전을 올리고,<br>API가 호환되면서 바꾸거나 추가하는 경우에는 <strong>Minor</strong> 버전을 올리고,<br>API가 영향이 없는 버그 수정은 <strong>Patch</strong> 버전을 올립니다.<br>이러한 시스템을 <a href="https://semver.org/lang/ko/" target="_blank" rel="noopener">유의적 버전(Semver)</a>이라고 합니다.</p><h2><span id="966074f1-eb54-436a-95aa-005270f6c601">범위(Ranges)</span><a href="#966074f1-eb54-436a-95aa-005270f6c601" class="header-anchor"></a></h2><pre><code class="json">{  &quot;dependencies&quot;: {    &quot;bootstrap&quot;: &quot;^4.0.0&quot;,    &quot;jquery&quot;: &quot;&gt;=1.12.0&quot;  }}</code></pre><p>npm은 위 코드의 <code>&quot;^4.0.0&quot;</code>, <code>&quot;&gt;=1.12.0&quot;</code>과 같이 연산자(Operator)와 버전(Version)으로 의존성 모듈의 버전 범위(Ranges)를 구성합니다.<br>주로 사용하는 연산자들은 다음과 같습니다.</p><table><thead><tr><th style="text-align:center">연산자</th><th style="text-align:left">설명</th></tr></thead><tbody><tr><td style="text-align:center"><code>&lt;</code></td><td style="text-align:left">버전보다 작은 범위</td></tr><tr><td style="text-align:center"><code>&lt;=</code></td><td style="text-align:left">버전보다 작거나 같은 범위</td></tr><tr><td style="text-align:center"><code>&gt;</code></td><td style="text-align:left">버전보다 큰 범위</td></tr><tr><td style="text-align:center"><code>&gt;=</code></td><td style="text-align:left">버전보다 크거나 같은 범위</td></tr><tr><td style="text-align:center"><code>=</code></td><td style="text-align:left">같은 범위(일반적으로 생략됩니다)</td></tr><tr><td style="text-align:center"><code>^</code></td><td style="text-align:left">(Caret) Minor Level 범위</td></tr><tr><td style="text-align:center"><code>~</code></td><td style="text-align:left">(Tilde) Patch Level 범위</td></tr></tbody></table><p>예를 들어, <code>~4.0.7</code>은 Patch Level 범위인 <code>4.0.0</code>~<code>4.0.99</code>까지는 일치하지만, <code>4.2.5</code>, <code>3.0.1</code> 같은 그 외 Level 범위의 버전은 일치하지 않습니다.</p><p>사용하고자 하는 의존성 모듈의 버전 범위는 <a href="https://semver.npmjs.com/" target="_blank" rel="noopener">npm semver calculator</a>에서 확인할 수 있습니다.</p><h1><span id="59a34ea6-c58f-49de-b0b5-d2ce1e6f67de">기타 자주 사용하는 CLI commands</span><a href="#59a34ea6-c58f-49de-b0b5-d2ce1e6f67de" class="header-anchor"></a></h1><h2><span id="9f2905ed-4f79-4bb0-ad4d-a4c24f3ad4a9">outdated (오래된 패키지 확인)</span><a href="#9f2905ed-4f79-4bb0-ad4d-a4c24f3ad4a9" class="header-anchor"></a></h2><p>설치된 패키지가 현재 구형(오래된 패키지)인지 여부를 확인하기 위해 레지스트리를 검사합니다.</p><pre><code class="bash">$ npm outdated</code></pre><h2><span id="de1937b8-b7f6-483b-bd7d-fe2108a64268">update (버전 업데이트)</span><a href="#de1937b8-b7f6-483b-bd7d-fe2108a64268" class="header-anchor"></a></h2><p>패키지를 Semver에 맞는 최신 버전으로 업데이트합니다.<br>패키지 이름을 지정하지 않으면 지정된 위치(Global/Local)의 모든 패키지를 업데이트합니다.</p><pre><code class="bash">$ npm update &lt;package&gt;# aliases$ npm up$ npm upgrade</code></pre><p>예를 들어, 다음과 같은 버전 정보를 가지는 <code>jquery</code> 모듈을 업데이트할 경우를 살펴봅시다.</p><pre><code class="bash">{  name: &#39;jquery&#39;,  versions: [    # ...    &#39;1.11.1&#39;,    &#39;1.11.2&#39;,    &#39;1.11.3&#39;,    &#39;1.12.0&#39;,    &#39;1.12.1&#39;,    &#39;1.12.2&#39;,    &#39;1.12.3&#39;,    &#39;1.12.4&#39;,    &#39;2.1.0-beta2&#39;,    &#39;2.1.0-beta3&#39;,    # ...  ]}</code></pre><p>다음과 같이 모듈 버전에 범위 연산자를 <code>^</code>(Caret)으로 사용했다면, Minor Level 범위에서 가장 최신 버전으로 업데이트합니다.<br>따라서 <code>1.12.4</code> 버전을 설치합니다.</p><pre><code class="json">{  &quot;dependencies&quot;: {    &quot;jquery&quot;: &quot;^1.11.1&quot;  }}</code></pre><pre><code class="bash">$ npm update jquery</code></pre><p>이번엔 모듈 버전에 범위 연산자를 <code>~</code>(Tilde)로 사용한 경우를 봅시다.<br>Patch Level 범위에서 가장 최신 버전으로 업데이트합니다.<br>따라서 <code>1.11.3</code> 버전을 설치합니다.</p><pre><code class="json">{  &quot;dependencies&quot;: {    &quot;jquery&quot;: &quot;~1.11.1&quot;  }}</code></pre><pre><code class="bash">$ npm update jquery</code></pre><p><code>npm@2.6.1</code>부터 최상위 패키지만 업데이트하고, 이전 버전의 npm은 모든 종속성을 재귀적으로 검사합니다.<br>이전 동작을 사용하려면 <code>npm --depth 9999 update</code>를 사용하세요.</p><h2><span id="e9149056-9c8d-45f6-b7b9-3e3b57b5aff0">uninstall (패키지 제거)</span><a href="#e9149056-9c8d-45f6-b7b9-3e3b57b5aff0" class="header-anchor"></a></h2><p>설치된 패키지를 제거합니다.<br><code>npm install</code>과 동일하게 <code>--save</code>, <code>--save-dev</code> 같은 플래그들을 사용할 수 있습니다.</p><pre><code class="bash">$ npm uninstall &lt;package&gt;# aliases$ npm remove$ npm rm$ npm r$ npm un$ npm unlink</code></pre><h2><span id="2d5c41ac-c16f-4806-8ed8-1d1d0660574e">version (패키지 버전 관리)</span><a href="#2d5c41ac-c16f-4806-8ed8-1d1d0660574e" class="header-anchor"></a></h2><p>자신의 프로젝트(패키지)의 버전을 관리하기 위해서 <code>npm version</code>을 사용합니다.</p><pre><code class="bash">$ npm version major  # Major 버전을 올립니다.$ npm version minor  # Minor 버전을 올립니다.$ npm version patch  # Patch 버전을 올립니다.</code></pre><p><code>-m</code>(<code>--message</code>) 플래그는 버전 커밋(Commit)을 할 때 메시지로 사용합니다.<br>메시지에 <code>%s</code>가 있으면 버전 번호로 바뀝니다.</p><pre><code class="bash">$ npm version patch -m &quot;Upgrade to %s for reasons&quot;# ex&gt; &quot;Upgrade to 1.0.1 for reasons&quot;</code></pre><h2><span id="7223f622-c10f-4378-b52b-6d776e3d7a34">ls (패키지 목록)</span><a href="#7223f622-c10f-4378-b52b-6d776e3d7a34" class="header-anchor"></a></h2><p>설치된 패키지를 나열합니다.</p><pre><code class="bash">$ npm ls# aliases$ npm list$ npm la$ npm ll</code></pre><h3><span id="8242faa2-3bdc-4007-9623-0da413ad5aff"><code>--</code>json</span><a href="#8242faa2-3bdc-4007-9623-0da413ad5aff" class="header-anchor"></a></h3><p>패키지 정보를 JSON 형식으로 표시합니다.</p><pre><code class="bash">$ npm ls --json</code></pre><h3><span id="a3cddc1d-c37b-4ed1-8697-5c5b38a125f3"><code>--</code>long</span><a href="#a3cddc1d-c37b-4ed1-8697-5c5b38a125f3" class="header-anchor"></a></h3><p>패키지의 확장 정보를 표시합니다.</p><pre><code class="bash"># ...├── babel-preset-env@1.6.1│   A Babel preset for each environment.│   git+https://github.com/babel/babel-preset-env.git│   https://babeljs.io/├── bootstrap@4.0.0│   The most popular front-end framework for developing responsive, mobile first projects on the web.│   git+https://github.com/twbs/bootstrap.git│   https://getbootstrap.com# ...</code></pre><h3><span id="acbe6d04-0c0b-40a0-8952-b27838a89d19"><code>--</code>depth</span><a href="#acbe6d04-0c0b-40a0-8952-b27838a89d19" class="header-anchor"></a></h3><p>의존성 트리를 표시하는 최대 깊이를 정수(Integer)로 지정합니다.</p><pre><code class="bash">$ npm ls --depth=1</code></pre><h3><span id="d407471e-d4a8-449e-ad88-a2ba0ec80259"><code>--</code>prod / <code>--</code>production</span><a href="#d407471e-d4a8-449e-ad88-a2ba0ec80259" class="header-anchor"></a></h3><p><code>dependencies</code> 의존성 모듈만 표시합니다.</p><h3><span id="c4d60881-f28b-44c7-ba60-6687895943dc"><code>--</code>dev</span><a href="#c4d60881-f28b-44c7-ba60-6687895943dc" class="header-anchor"></a></h3><p><code>devDependencies</code> 의존성 모듈만 표시합니다.</p><h3><span id="be02990f-19b3-426b-a69f-93bd6071157b">전역 설치된 패키지 목록</span><a href="#be02990f-19b3-426b-a69f-93bd6071157b" class="header-anchor"></a></h3><p>전역 설치된 패키지들의 목록을 나열합니다.</p><pre><code class="bash">npm ls -g --depth=0</code></pre><h2><span id="d1338c07-ec89-49fc-a4dd-41bf98cf68d2">view (패키지 정보 보기)</span><a href="#d1338c07-ec89-49fc-a4dd-41bf98cf68d2" class="header-anchor"></a></h2><p>패키지에 관한 데이터(정보)를 보여줍니다.<br>필드가 객체이면 JavaScript 객체 리터럴로 출력됩니다.<br><code>--json</code> 플래그가 주어지면, JSON 형식으로 출력됩니다.</p><pre><code class="bash">$ npm view &lt;package&gt; &lt;field&gt;# aliases$ npm info$ npm show$ npm v# examples$ npm view jquery$ npm view jquery --json$ npm view lodash license$ npm view express contributors[0].email</code></pre><h1><span id="188ac056-1c0a-4c01-9f8e-84ad2576b614">참고 자료(References)</span><a href="#188ac056-1c0a-4c01-9f8e-84ad2576b614" class="header-anchor"></a></h1><p><a href="https://docs.npmjs.com/" target="_blank" rel="noopener">https://docs.npmjs.com/</a><br><a href="https://docs.npmjs.com/misc/semver" target="_blank" rel="noopener">https://docs.npmjs.com/misc/semver</a><br><a href="https://docs.npmjs.com/getting-started/semantic-versioning" target="_blank" rel="noopener">https://docs.npmjs.com/getting-started/semantic-versioning</a><br><a href="https://semver.org/" target="_blank" rel="noopener">https://semver.org/</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;npm(Node Package Manager)은 JavaScript 및 세계 최대의 소프트웨어 레지스트리 패키지 관리자로 &lt;a href=&quot;/2018/02/17/node-js-start/&quot;&gt;Node.js를 설치&lt;/a&gt;하면 같이 설치되어 사용할 수 
      
    
    </summary>
    
    
      <category term="nodejs" scheme="https://heropy.blog/tags/nodejs/"/>
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
      <category term="npm" scheme="https://heropy.blog/tags/npm/"/>
    
  </entry>
  
  <entry>
    <title>처음 시작하는 Node.js 개발 - 1 - 설치 및 버전 관리(NVM, n)</title>
    <link href="https://heropy.blog/2018/02/17/node-js-install/"/>
    <id>https://heropy.blog/2018/02/17/node-js-install/</id>
    <published>2018-02-17T14:26:00.000Z</published>
    <updated>2018-02-26T07:50:04.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://nodejs.org/en/" target="_blank" rel="noopener">Node.js</a>는 <a href="https://developers.google.com/v8/" target="_blank" rel="noopener">V8</a>이라는 구글에서 개발한 고성능 자바스크립트 엔진으로 빌드된 <strong>서버 사이드 개발용 소프트웨어 플랫폼</strong>입니다.</p><p>기본적으로 자바스크립트는 웹 브라우저(클라이언트 측)에서 실행되는데, Node.js 런타임 환경에서는 모든 종류의 서버 사이드 도구들을 제공하여 자바스크립트로 서버 개발을 할 수 있습니다.<br>따라서 자바스크립트를 이용하여 클라이언트 측(Front-end)과 서버 측(Back-end)를 모두 개발할 수 있으니, 웹 개발을 위해 추가적인 언어 학습이 필요하지 않습니다.</p><p>또한 Node.js의 패키지 생태계인 <a href="https://www.npmjs.com/" target="_blank" rel="noopener">npm(Node Packaged Manager)</a>은 세계에서 가장 큰 오픈 소스 라이브러리 생태계이기도 합니다.<br>Node.js를 설치하면 npm이 같이 설치되어 사용할 수 있습니다.</p><div class="toc"><ul><li><a href="4c137977-cb27-4da6-8e27-81bcbb96c552">설치</a><ul><li><a href="0225a222-b689-444f-813b-fccb1176e702">직접 설치</a><ul><li><a href="2924fe3e-4662-4d4d-b38f-1d31b2636116">LTS vs Current</a></li></ul></li><li><a href="73485f59-b85d-4899-b0ce-4d8401b31ad0">패키지 매니저로 설치</a><ul><li><a href="8a22de7a-9a64-4ae8-95e5-ddc0f9f416b6">macOS - Homebrew</a></li><li><a href="dcc6dacb-f2bc-48c4-a394-3658d5157da7">Windows - Chocolatey</a></li></ul></li></ul></li><li><a href="4b26e16f-3b76-4f7a-b558-2987b5021924">버전 관리</a><ul><li><a href="37745f45-60eb-471c-bdfd-3e892fc7e716">NVM</a><ul><li><a href="f5335836-4a14-42d1-a851-ee98bba4beaa">nvm-windows</a></li><li><a href="a904fc85-7509-48ee-a8ae-07abed0de9e4">nodist</a></li></ul></li><li><a href="50e16389-5d52-4215-85ec-dca3591dc8c9">n</a></li></ul></li><li><a href="7bfbb6fb-dfc9-45c9-b650-a6baed427c7a">참고 자료(References)</a></li></ul></div><h1><span id="4c137977-cb27-4da6-8e27-81bcbb96c552">설치</span><a href="#4c137977-cb27-4da6-8e27-81bcbb96c552" class="header-anchor"></a></h1><p>Node.js를 설치하기 위한 몇 가지 추천 방법을 설명합니다.</p><h2><span id="0225a222-b689-444f-813b-fccb1176e702">직접 설치</span><a href="#0225a222-b689-444f-813b-fccb1176e702" class="header-anchor"></a></h2><p><a href="https://nodejs.org/" target="_blank" rel="noopener">Node.js 홈페이지</a>로 이동하여 설치 파일을 받아 설치합니다.</p><h3><span id="2924fe3e-4662-4d4d-b38f-1d31b2636116">LTS vs Current</span><a href="#2924fe3e-4662-4d4d-b38f-1d31b2636116" class="header-anchor"></a></h3><p>Node.js 홈페이지에 접속하면 설치 가능한 다운로드 버튼이 다음과 같이 ‘LTS’와 ‘Current’(현재 버전)으로 나뉩니다.</p><p><img src="/images/screenshot/node-js-home.jpg" alt="choosing the nodejs version"></p><p>LTS(Long Term Supported)는 장기적으로 안정되고 신뢰도가 높은 지원이 보장되는 버전으로, 유지/보수와 보안(서버 운영 등)에 초점을 맞춰 대부분 사용자에게 추천되는 버전입니다.<br><b>짝수 버전(ex. 8.x.x)이 LTS 버전</b>입니다.</p><p>Current(현재 버전)은 최신 기능을 제공하고 기존 API의 기능 개선에 초점이 맞춰진 버전으로, 업데이트가 잦고 기능이 변경될 가능성이 높기 때문에 간단한 개발 및 테스트에 적당한 버전입니다.<br><b>홀수 버전(ex. 9.x.x)이 Current 버전</b>입니다.</p><p>여기서는 LTS 버전으로 설치하겠습니다.</p><p>설치 후 터미널에 다음과 같이 입력하여 설치 여부와 버전을 확인할 수 있습니다.<br>(Node.js를 설치하면 npm이 같이 설치됩니다)</p><pre><code class="bash">$ node -v# v8.9.4$ npm -v# 5.6.0</code></pre><h2><span id="73485f59-b85d-4899-b0ce-4d8401b31ad0">패키지 매니저로 설치</span><a href="#73485f59-b85d-4899-b0ce-4d8401b31ad0" class="header-anchor"></a></h2><p>좀 더 다양한 방법으로 설치하기를 원하면 <a href="https://nodejs.org/ko/download/package-manager/" target="_blank" rel="noopener">패키지 매니저로 Node.js 설치하기</a>를 확인하세요.</p><h3><span id="8a22de7a-9a64-4ae8-95e5-ddc0f9f416b6">macOS - Homebrew</span><a href="#8a22de7a-9a64-4ae8-95e5-ddc0f9f416b6" class="header-anchor"></a></h3><p>macOS 용 패키지 관리자인 <a href="https://brew.sh/index_ko.html" target="_blank" rel="noopener">Homebrew</a>를 사용하여 LTS 버전을 설치합니다.<br>버전의 Major Number만 입력합니다.</p><pre><code class="bash">$ brew install node@8</code></pre><h3><span id="dcc6dacb-f2bc-48c4-a394-3658d5157da7">Windows - Chocolatey</span><a href="#dcc6dacb-f2bc-48c4-a394-3658d5157da7" class="header-anchor"></a></h3><p>Windows 용 패키지 관리자인 <a href="https://chocolatey.org/" target="_blank" rel="noopener">Chocolatey</a>를 사용하여 설치합니다.</p><pre><code class="bash">$ choco install nodejs-lts</code></pre><h1><span id="4b26e16f-3b76-4f7a-b558-2987b5021924">버전 관리</span><a href="#4b26e16f-3b76-4f7a-b558-2987b5021924" class="header-anchor"></a></h1><p>Node.js의 새로운 버전이 나올 경우 버전을 업그레이드(Upgrade)를 해야 하고, 하위 호환성을 위해서 버전을 다운그레이드(Downgrade)를 해야 할 수 있습니다.<br>다양한 경우에 의해 버전의 변경이 자주 발생하므로 Node.js를 설치할 때부터 버전 관리가 가능한 형태로 설치하는 것이 좋습니다.<br>물론, 아직 버전 관리가 필요하지 않다면 이 단계를 건너뛰어도 좋습니다.</p><p>Node.js의 버전 관리를 위한 대표적인 관리 매니저로 <a href="https://github.com/creationix/nvm" target="_blank" rel="noopener">NVM</a>과 <a href="https://github.com/tj/n" target="_blank" rel="noopener">n</a>이 있습니다.<br>NVM과 n은 서로 유사한 기능을 제공합니다.<br>단, 이 글을 작성하는 2018년 2월을 기준으로 NVM과 n은 아직 Windows OS에서 지원되지 않습니다.<br>(NVM의 대체재로 nvm-windows나 nodist를 사용할 수 있습니다.)</p><h2><span id="37745f45-60eb-471c-bdfd-3e892fc7e716">NVM</span><a href="#37745f45-60eb-471c-bdfd-3e892fc7e716" class="header-anchor"></a></h2><p><a href="https://github.com/creationix/nvm" target="_blank" rel="noopener">https://github.com/creationix/nvm</a></p><p>충돌을 피하기 위해 NVM(Node Version Manager)을 설치하기 전 기존에 설치한 버전의 Node.js는 제거하는 것이 좋습니다.<br>NVM(Node Version Manager)을 설치하거나 업데이트하려면 <a href="https://ko.wikipedia.org/wiki/CURL" target="_blank" rel="noopener">cURL</a>로 설치 스크립트를 사용할 수 있습니다.</p><pre><code class="bash">$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash</code></pre><p>혹은 Homebrew를 사용해 설치할 수도 있습니다.</p><pre><code class="bash">$ brew install nvm</code></pre><p>설치가 되면 <code>~/.bash_profile</code>, <code>~/.zshrc</code>, <code>~/.profile</code> 등의 프로파일에 <code>nvm.sh</code>이 실행되도록 다음 스크립트가 추가됩니다.</p><pre><code class="bash">export NVM_DIR=&quot;$HOME/.nvm&quot;[ -s &quot;$NVM_DIR/nvm.sh&quot; ] &amp;&amp; \. &quot;$NVM_DIR/nvm.sh&quot; # This loads nvm</code></pre><p>혹시 추가되지 않았다면 직접 추가합니다.</p><pre><code class="bash"># Node 버전 설치  $ nvm install &lt;version&gt;  # ex&gt; nvm install 8.9.4# 설치된 Node 버전 목록 확인$ nvm ls  # 사용할 Node 설정  $ nvm use &lt;version&gt;  # ex&gt; nvm use 8.9.4  $ nvm use &lt;alias&gt;  # ex&gt; nvm use default# 사용할 alias 설정$ nvm alias &lt;alias&gt; &lt;version&gt;  # ex&gt; nvm alias test-v 8.9.4</code></pre><p>추가 <strong><a href="https://github.com/creationix/nvm#usage-1" target="_blank" rel="noopener">사용법(Usage)</a></strong></p><p>NVM은 Windows OS를 지원하지 않지만, 비공식적으로 <a href="https://github.com/coreybutler/nvm-windows" target="_blank" rel="noopener">nvm-windows</a>와 <a href="https://github.com/marcelklehr/nodist" target="_blank" rel="noopener">nodist</a>를 Windows에서 사용할 수 있습니다.</p><h3><span id="f5335836-4a14-42d1-a851-ee98bba4beaa">nvm-windows</span><a href="#f5335836-4a14-42d1-a851-ee98bba4beaa" class="header-anchor"></a></h3><p><a href="https://github.com/coreybutler/nvm-windows" target="_blank" rel="noopener">https://github.com/coreybutler/nvm-windows</a></p><p>nvm-windows은 설치 프로그램으로 <code>nvm-setup.zip</code>을 <a href="https://github.com/coreybutler/nvm-windows/releases" target="_blank" rel="noopener">다운로드</a> 받아 설치합니다.</p><p><strong><a href="https://github.com/coreybutler/nvm-windows#usage" target="_blank" rel="noopener">사용법(Usage)</a></strong></p><h3><span id="a904fc85-7509-48ee-a8ae-07abed0de9e4">nodist</span><a href="#a904fc85-7509-48ee-a8ae-07abed0de9e4" class="header-anchor"></a></h3><p><a href="https://github.com/marcelklehr/nodist" target="_blank" rel="noopener">https://github.com/marcelklehr/nodist</a></p><p>nodist도 <a href="https://github.com/marcelklehr/nodist/releases" target="_blank" rel="noopener">설치 프로그램을 다운로드</a> 받아 설치합니다.</p><p>혹은 Chocolatey를 사용해 설치할 수 있습니다.</p><pre><code class="bash">$ choco install nodist</code></pre><p><strong><a href="https://github.com/marcelklehr/nodist/blob/master/README.md#commands" target="_blank" rel="noopener">사용법(Usage)</a></strong></p><h2><span id="50e16389-5d52-4215-85ec-dca3591dc8c9">n</span><a href="#50e16389-5d52-4215-85ec-dca3591dc8c9" class="header-anchor"></a></h2><p><a href="https://github.com/tj/n" target="_blank" rel="noopener">n</a>은 기존에 설치된 Node.js를 제거할 필요가 없기 때문에 좀 더 쉽게 설치할 수 있습니다.<br>npm으로 전역(Global) 설치합니다.</p><pre><code class="bash">$ npm install -g n# 관리자 권한을 요구할 경우$ sudo npm install -g n</code></pre><p>역시 간단한 사용법을 소개합니다.<br>관리자 권한을 요구할 경우(<code>Error: sudo required</code>) 앞에 <code>sudo</code>를 붙입니다. 또한 제거(rm)할 경우에도 필요할 수 있습니다.</p><pre><code class="bash"># 설치된 Node 버전 사용(목록에서 선택 후 Ctrl + c)$ n# 모든 Node 버전 중 설치된 버전 확인$ n ls  # Node 버전 설치  $ n &lt;version&gt;  # ex&gt; n 8.9.4$ n latest  # 최신 LTS Node 버전 설치# 제거할 Node 설정$ n rm &lt;version ...&gt;  #ex&gt; n rm 8.9.1 8.9.2$ n prune  # 현재 버전을 제외한 모든 버전 제거</code></pre><p>추가 <strong><a href="https://github.com/tj/n#usage" target="_blank" rel="noopener">사용법(Usage)</a></strong></p><h1><span id="7bfbb6fb-dfc9-45c9-b650-a6baed427c7a">참고 자료(References)</span><a href="#7bfbb6fb-dfc9-45c9-b650-a6baed427c7a" class="header-anchor"></a></h1><p><a href="https://nodejs.org/dist/latest-v6.x/docs/api/documentation.html" target="_blank" rel="noopener">https://nodejs.org/dist/latest-v6.x/docs/api/documentation.html</a><br><a href="https://ko.wikipedia.org/wiki/Node.js" target="_blank" rel="noopener">https://ko.wikipedia.org/wiki/Node.js</a><br><a href="https://medium.com/gitignore/nvm-vs-n-f34ebca314ea" target="_blank" rel="noopener">https://medium.com/gitignore/nvm-vs-n-f34ebca314ea</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;a href=&quot;https://nodejs.org/en/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Node.js&lt;/a&gt;는 &lt;a href=&quot;https://developers.google.com/v8/&quot; target=&quot;_blank&quot; 
      
    
    </summary>
    
    
      <category term="nodejs" scheme="https://heropy.blog/tags/nodejs/"/>
    
      <category term="javascript" scheme="https://heropy.blog/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>Sass(SCSS) 완전 정복!</title>
    <link href="https://heropy.blog/2018/01/31/sass/"/>
    <id>https://heropy.blog/2018/01/31/sass/</id>
    <published>2018-01-31T00:34:42.000Z</published>
    <updated>2021-06-13T12:20:39.593Z</updated>
    
    <content type="html"><![CDATA[<p>CSS는 상대적으로 배우기 쉽고 재미있습니다.<br>웹 개발 초심자에게는 이만큼 접근하기 좋은 게 없죠.</p><p>CSS는 분명 쉽고 재밌지만, 작업이 고도화될수록 불편함도 같이 커집니다.<br>불필요한 선택자(Selector)의 과용과 연산 기능의 한계, 구문(Statement)의 부재 등 프로젝트의 규모가 커질수록 아쉬움도 같이 커지죠.<br>하지만 웹에서는 표준 CSS만 동작할 수 있기 때문에 다른 선택권이 없습니다.</p><p>그렇다면 우리는 앞으로 계속 CSS만 사용해야 할까요?</p><div class="toc"><ul><li><a href="c8446468-b895-493f-9ecb-4141de4773bb">CSS Preprocessor 란?</a><ul><li><a href="d8159232-45c4-4a67-9bc2-72ac9146301d">어떻게 사용하나요?</a></li><li><a href="d17d5b8a-5b28-45ab-b97a-e43e114b3bf4">컴파일은 어떻게 하나요?</a></li><li><a href="c217b374-69be-4ebd-8c35-94c25d99136d">왜 Sass(SCSS)죠?</a></li><li><a href="f739432f-4fc6-42b2-bb1b-a1fdb6f40e89">Sass와 SCSS는 차이점은 뭔가요?</a></li></ul></li><li><a href="0ccef65f-d162-48da-89bb-e82363b70bf1">컴파일 방법</a><ul><li><a href="c5dbf186-9f7f-4047-9730-2f52fb431f21">SassMeister</a></li><li><a href="86433358-ad8c-4c3b-89b9-141c6acfd7ea">node-sass</a></li><li><a href="2824b08d-4510-4cc8-8a88-0d107f7666ee">Gulp</a></li><li><a href="85d48197-d4fb-4301-aa96-43ac03369c7f">Webpack</a></li><li><a href="a1685dfb-42ab-4429-93cc-92a9f1d3655d">Parcel</a></li></ul></li><li><a href="b8bc904b-f81b-4417-aeac-0891f9e8850a">문법(Syntax)</a><ul><li><a href="b0f1e727-c706-40a6-b036-a97e21eb5d8f">주석(Comment)</a></li><li><a href="390b7969-de84-4e73-9687-f5da80b56595">데이터 종류(Data Types)</a><ul><li><a href="ee8ed89a-f646-4bcc-88cd-9e8faed48d49">특이사항</a></li></ul></li><li><a href="ac9bb79a-2458-474b-aa7d-df0b6c194cb7">중첩(Nesting)</a><ul><li><a href="d9e62dc0-7688-4dd7-b914-370408261710">Ampersand (상위 선택자 참조)</a></li><li><a href="8176d3bf-1e25-40ad-b3fa-08f23a41e789">@at-root (중첩 벗어나기)</a></li><li><a href="55daa0a8-9737-44e5-8869-ec8d642c28fc">중첩된 속성</a></li></ul></li><li><a href="b649cb33-a9fc-4e54-b701-09b5a3cc02de">변수(Variables)</a><ul><li><a href="6b3bea74-d59b-4e3a-9901-0385a911ee2a">변수 유효범위(Variable Scope)</a></li><li><a href="253df3a8-f6c7-4db3-b4ba-2568d2773de8">변수 재 할당(Variable Reassignment)</a></li><li><a href="68bbe956-737a-48db-8241-40229ef817b9">!global (전역 설정)</a></li><li><a href="bc08e2dd-f8b0-4545-8662-a5a84a778731">!default (초깃값 설정)</a></li><li><a href="95dc418c-b9e4-48b4-bb1d-39b22662d9e6">#{} (문자 보간)</a></li></ul></li><li><a href="18749bbc-90d1-4fce-aec9-68edcd34c2ad">가져오기(Import)</a><ul><li><a href="4bc2a96b-9a65-42ce-b861-1d0e3e615e73">여러 파일 가져오기</a></li><li><a href="477c40ed-637b-4bcd-9f5e-4d45b8def40e">파일 분할(Partials)</a></li></ul></li><li><a href="5c5fce1f-708a-4591-8462-99028609597a">연산(Operations)</a><ul><li><a href="b14ba330-5cd1-43b3-9d22-0a2ab4bcc18f">숫자(Numbers)</a><ul><li><a href="d70d8437-a378-4d06-88ba-75ba759800ee">상대적 단위 연산</a></li><li><a href="a00b1696-c657-48d5-a5b4-b832cb38afde">나누기 연산의 주의사항</a></li></ul></li><li><a href="c707d1e7-e536-43ad-bf59-bb2821f4d549">문자(Strings)</a></li><li><a href="0c329832-d26b-45d6-bd60-f5673d56a247">색상(Colors)</a></li><li><a href="3b81c0af-2dff-4a14-9278-27cb0d21afba">논리(Boolean)</a></li></ul></li><li><a href="aae4630b-e340-4c39-ad4a-b000b4c051d2">재활용(Mixins)</a><ul><li><a href="20406419-af76-4d55-bef9-d692b1a11eb1">@mixin</a></li><li><a href="94682999-c661-435f-83bc-fece390980ca">@include</a></li><li><a href="8816d35d-06b4-4ac9-8142-6bd3f74e27fe">인수(Arguments)</a><ul><li><a href="9726624b-ee44-4b0e-a5c3-2af93bcfab59">인수의 기본값 설정</a></li><li><a href="b8d48886-91ad-4808-a24b-87eef0445f40">키워드 인수(Keyword Arguments)</a></li><li><a href="d52cdd33-717b-4e87-99bd-a635de5ae9ca">가변 인수(Variable Arguments)</a></li></ul></li><li><a href="7bf5ce54-0056-46a1-8b92-08470be9504a">@content</a></li></ul></li><li><a href="41c5d48a-77b4-49e5-b42f-7de574bf0a6c">확장(Extend)</a></li><li><a href="1514fd60-5673-4b70-81e7-a945347e22ef">함수(Functions)</a></li><li><a href="618fb25f-75d4-4c9d-9c79-e4c5d3b89bda">조건과 반복(Control Directives / Expressions)</a><ul><li><a href="1c4c5336-ae59-44cb-993a-4a9136e89fb8">if (함수)</a></li><li><a href="8b99ec7c-eb79-4ae0-b9b2-a0a1711d2e54">@if (지시어)</a></li><li><a href="c5b450f6-c6f8-485b-be94-07befd6acfc7">@for</a></li><li><a href="b6450f51-6227-4f76-a8e4-ef3a33d1bc14">@each</a></li><li><a href="ae8d3deb-89fb-4343-917a-a88069e589a6">@while</a></li></ul></li><li><a href="99a45312-957d-49b0-b84c-3b8a7149be0e">내장 함수(Built-in Functions)</a><ul><li><a href="c2ec257f-5aa6-4947-8855-cdcf4ce2d1ed">색상(RGB / HSL / Opacity) 함수</a></li><li><a href="858f0629-890c-49c1-95b1-8204e8627aca">문자(String) 함수</a></li><li><a href="ff901358-135c-4c56-a3e3-f98967c98e63">숫자(Number) 함수</a></li><li><a href="3225a970-20d9-4d91-b3eb-6208c4aa978d">List 함수</a></li><li><a href="f47f1fb5-4e79-4e6f-8e0a-ecd24e1ec2b1">Map 함수</a></li><li><a href="b708fdec-6afc-44a0-bbfa-b4f58bdad505">관리(Introspection) 함수</a></li></ul></li></ul></li><li><a href="0c028d61-4972-493e-a7a3-70d64afe42da">참고 자료(References)</a></li></ul></div><h1><span id="c8446468-b895-493f-9ecb-4141de4773bb">CSS Preprocessor 란?</span><a href="#c8446468-b895-493f-9ecb-4141de4773bb" class="header-anchor"></a></h1><p>HTML, CSS를 다루는 분이라면 한 번은 들어봤을 Sass, Less 등이 있습니다.<br>이 친구들은 CSS 전(예비)처리기 입니다.<br>보통 CSS Preprocessor 라고 부릅니다.</p><p>CSS가 동작하기 전에 사용하는 기능으로,<br>웹에서는 분명 CSS가 동작하지만 우리는 CSS의 불편함을 이런 확장 기능으로 상쇄할 수 있습니다.</p><blockquote><p>사스는 기초 언어에 힘과 우아함을 더해주는 CSS의 확장이다.</p></blockquote><h3><span id="d8159232-45c4-4a67-9bc2-72ac9146301d">어떻게 사용하나요?</span><a href="#d8159232-45c4-4a67-9bc2-72ac9146301d" class="header-anchor"></a></h3><p>위에서 언급한 것처럼 웹에서는 CSS만 동작합니다.<br><a href="https://sass-lang.com/" target="_blank" rel="noopener">Sass</a>, <a href="http://lesscss.org/" target="_blank" rel="noopener">Less</a>, <a href="http://stylus-lang.com/" target="_blank" rel="noopener">Stylus</a> 같은 전처리기(이하 ‘전처리기’로 표기)는 직접 동작시킬 수 없습니다.<br>그렇다면 어떻게 사용할 수 있을까요?</p><p>CSS는 불편하니 일단 배제하고 우선 전처리기로 작성(코딩)합니다.<br>전처리기는 CSS 문법과 굉장히 유사하지만 선택자의 중첩(Nesting)이나 조건문, 반복문, 다양한 단위(Unit)의 연산 등… 표준 CSS 보다 훨씬 많은 기능을 사용해서 편리하게 작성할 수 있습니다.<br>단, 웹에서는 직접 동작하지 않으니 이렇게 작성한 전처리기를 웹에서 동작 가능한 표준의 CSS로 컴파일(Compile)합니다.<br>전처리기로 작성하고 CSS로 컴파일해서 동작시키는 거죠.</p><h3><span id="d17d5b8a-5b28-45ab-b97a-e43e114b3bf4">컴파일은 어떻게 하나요?</span><a href="#d17d5b8a-5b28-45ab-b97a-e43e114b3bf4" class="header-anchor"></a></h3><p>전처리기 종류마다 방법이 조금씩 다르고 여러 방식을 제공합니다.<br>보통의 경우 컴파일러(Compiler)가 필요합니다.<br>우리는 이제 Sass(SCSS)를 알아볼 것이고 컴파일 방법에 대해서도 같이 살펴보겠습니다.</p><h3><span id="c217b374-69be-4ebd-8c35-94c25d99136d">왜 Sass(SCSS)죠?</span><a href="#c217b374-69be-4ebd-8c35-94c25d99136d" class="header-anchor"></a></h3><p>보통 언급되는 전처리기 3대장으로 Less, Sass(SCSS), Stylus가 있습니다.</p><p>저는 가장 많이 사용하고 진입장벽이 비교적 낮았던 Less를 처음 사용했습니다.<br>기본적인 기능은 전처리기들이 다 비슷합니다만 개인적으로 Less는 몇몇 기능에 큰 아쉬움이 있었습니다.<br>정확하게 언급하진 않겠지만 프로젝트 진행 중 Less에서 제공하는 기능의 한계로 막히는 경우가 몇 번 있었는데 그 기능이 Sass나 Stylus에는 있었습니다.<br>하지만 진입장벽이 낮기 때문에 접하기 쉽고 그만큼 많이 사용되는 듯합니다.</p><p>Stylus 같은 경우는 현재 이 블로그(HEROPY)를 만들면서 사용하고 있습니다.<br>깔끔하고 좀 더 세련됐으며 기능도 훌륭합니다.<br>하지만 덜 사용되며(덜 유명하며) 비교적 늦게 나왔기 때문에 성숙도는 떨어집니다.<br>그 때문인지 컴파일 후 사소한 버그가 몇몇 보입니다.</p><p>Sass(SCSS)는 언급한 두 가지 전처리기의 장점을 모두 가집니다.<br>문법은 Sass가 Stylus와 비슷하고, SCSS는 Less와 비슷하며, Sass와 SCSS는 하나의 컴파일러로 모두 컴파일 가능합니다.<br>또한, 2006년부터 시작하여 가장 오래된 CSS 확장 언어이며 그만큼 높은 성숙도와 많은 커뮤니티를 가지고 있고 기능도 훌륭합니다.<br>그래서 저는 Sass(SCSS)를 선택했습니다.</p><h3><span id="f739432f-4fc6-42b2-bb1b-a1fdb6f40e89">Sass와 SCSS는 차이점은 뭔가요?</span><a href="#f739432f-4fc6-42b2-bb1b-a1fdb6f40e89" class="header-anchor"></a></h3><p>Sass(Syntactically Awesome Style Sheets)의 3버전에서 새롭게 등장한 SCSS는 CSS 구문과 완전히 호환되도록 새로운 구문을 도입해 만든 Sass의 모든 기능을 지원하는 CSS의 상위집합(Superset) 입니다.<br>즉, SCSS는 CSS와 거의 같은 문법으로 Sass 기능을 지원한다는 말입니다.</p><p>더 쉽고 간단한 차이는 <code>{}</code>(중괄호)와 <code>;</code>(세미콜론)의 유무입니다.<br>아래의 예제를 비교해 보세요.</p><p>Sass:</p><pre><code class="sass">.list  width: 100px  float: left  li    color: red    background: url(&quot;./image.jpg&quot;)    &amp;:last-child      margin-right: -10px</code></pre><p>SCSS:</p><pre><code class="scss">.list {  width: 100px;  float: left;  li {    color: red;    background: url(&quot;./image.jpg&quot;);    &amp;:last-child {      margin-right: -10px;    }  }}</code></pre><p>Sass는 선택자의 유효범위를 ‘들여쓰기’로 구분하고, SCSS는 <code>{}</code>로 범위를 구분합니다.<br>Sass 방식과 SCSS 방식 중 어떤 방식이 마음에 드세요?</p><p>거의 유일합니다만, 다른 차이도 있습니다.<br>아래는 Mixins(‘믹스인’은 재사용 가능한 기능을 만드는 방식의 의미합니다) 예제입니다.<br>Sass는 단축 구문으로 사용합니다.</p><p>:Sass</p><pre><code class="sass">=border-radius($radius)  -webkit-border-radius: $radius  -moz-border-radius:    $radius  -ms-border-radius:     $radius  border-radius:         $radius.box  +border-radius(10px)</code></pre><p>:SCSS</p><pre><code class="scss">@mixin border-radius($radius) {  -webkit-border-radius: $radius;     -moz-border-radius: $radius;      -ms-border-radius: $radius;          border-radius: $radius;}.box { @include border-radius(10px); }</code></pre><p>Sass는 <code>=</code>와 <code>+</code> 기호로 Mixins 기능을 사용했고,<br>SCSS는 <code>@mixin</code>과 <code>@include</code>로 기능을 사용했습니다.</p><p>단순한 몇 가지를 제외하면 거의 차이가 없지만 복잡한 문장이 될 경우 여러 환경에 따른 장단점이 있을 수 있습니다.<br>Sass는 좀 더 간결하고 작성하기 편리하며, <code>{}</code>나 <code>;</code>를 사용하지 않아도 되니 코드가 훨씬 깔끔해집니다.<br>SCSS는 인라인 코드(한 줄 작성)를 작성할 수 있고, CSS와 유사한 문법을 가지기 때문에 코드 통합이 훨씬 쉽습니다.</p><p>이렇게 몇몇 장단점이 있기 때문에 회사나 팀에서 원하는 방식을 사용해야 하거나, 개인 취향에 따라서 선택할 수 있습니다.<br>단지 상황에 맞는, 원하는 방식으로 골라서 사용하면 됩니다.</p><p>보통의 경우 SCSS를 추천합니다.</p><h1><span id="0ccef65f-d162-48da-89bb-e82363b70bf1">컴파일 방법</span><a href="#0ccef65f-d162-48da-89bb-e82363b70bf1" class="header-anchor"></a></h1><p>Sass(SCSS)는 웹에서 직접 동작할 수 없습니다.<br>어디까지나 최종에는 표준 CSS로 동작해야 하며, 우리는 전처리기로 작성 후 CSS로 컴파일해야 합니다.<br>다양한 방법으로 컴파일이 가능하지만 자바스크립트 개발 환경(<a href="https://nodejs.org/ko/" target="_blank" rel="noopener">Node.js</a>)에서 추천하는 몇가지 방법을 소개합니다.</p><h3><span id="c5dbf186-9f7f-4047-9730-2f52fb431f21">SassMeister</span><a href="#c5dbf186-9f7f-4047-9730-2f52fb431f21" class="header-anchor"></a></h3><p>간단한 Sass 코드는 컴파일러를 설치하는게 부담될 수 있습니다.<br>그럴 경우 <a href="https://www.sassmeister.com/" target="_blank" rel="noopener">SassMeister</a>를 사용할 수 있습니다.</p><p>페이지 접속 후 바로 Sass나 SCSS 문법으로 코딩하면 CSS로 실시간 변환됩니다.<br>HTML를 작성하여 적용된 결과를 보거나 Sass 버전 설정 등 여러 환경 설정들을 지원하니 학습에 도움이 될 것입니다.</p><h3><span id="86433358-ad8c-4c3b-89b9-141c6acfd7ea">node-sass</span><a href="#86433358-ad8c-4c3b-89b9-141c6acfd7ea" class="header-anchor"></a></h3><p><a href="https://github.com/sass/node-sass" target="_blank" rel="noopener">node-sass</a>는 Node.js를 컴파일러인 <a href="https://sass-lang.com/libsass" target="_blank" rel="noopener">LibSass</a>에 바인딩한 라이브러리 입니다.<br>NPM으로 전역 설치하여 사용합니다.</p><pre><code class="bash">$ npm install -g node-sass</code></pre><p>컴파일하려는 파일의 경로와 컴파일된 파일이 저장될 경로를 설정합니다.<br><code>[]</code>는 선택사항입니다.</p><pre><code class="bash">$ node-sass [옵션] &lt;입력파일경로&gt; [출력파일경로]</code></pre><pre><code class="bash">$ node-sass scss/main.scss public/main.css</code></pre><p>여러 출력 경로를 설정할 수 있습니다.</p><pre><code class="bash">$ node-sass scss/main.scss public/main.css dist/style.css</code></pre><p>옵션을 적용할 수도 있습니다.<br>옵션으로 <code>--watch</code> 혹은 <code>-w</code>를 입력하면, 런타임 중 파일을 감시하여 저장 시 자동으로 변경 사항을 컴파일합니다.</p><pre><code class="bash">$ node-sass --watch scss/main.scss public/main.css</code></pre><p>기타 옵션은 <a href="https://github.com/sass/node-sass#command-line-interface" target="_blank" rel="noopener">node-sass CLI</a>에서 확인할 수 있습니다.</p><h3><span id="2824b08d-4510-4cc8-8a88-0d107f7666ee">Gulp</span><a href="#2824b08d-4510-4cc8-8a88-0d107f7666ee" class="header-anchor"></a></h3><p>빌드 자동화 도구(JavaScript Task Runner)인 <a href="https://gulpjs.com/" target="_blank" rel="noopener">Gulp</a>에서는 <code>gulpfile.js</code>을 만들어 아래와 같이 설정할 수 있습니다.<br>먼저 <code>gulp</code> 명령을 사용하기 위해서는 전역 설치가 필요합니다.</p><pre><code class="bash">$ npm install -g gulp</code></pre><p>Gulp와 함께 Sass 컴파일러인 <a href="https://github.com/dlmanning/gulp-sass" target="_blank" rel="noopener">gulp-sass</a>를 개발 의존성(devDependency) 모드로 설치합니다.<br>gulp-sass는 위에서 살펴본 node-sass를 Gulp에서 사용할 수 있도록 만들어진 플러그인입니다.</p><pre><code class="bash">$ npm install --save-dev gulp gulp-sass</code></pre><pre><code class="js">// gulpfile.jsvar gulp = require(&#39;gulp&#39;)var sass = require(&#39;gulp-sass&#39;)// 일반 컴파일gulp.task(&#39;sass&#39;, function () {  return gulp.src(&#39;./src/scss/*.scss&#39;)  // 입력 경로    .pipe(sass().on(&#39;error&#39;, sass.logError))    .pipe(gulp.dest(&#39;./dist/css&#39;));  // 출력 경로});// 런타임 중 파일 감시gulp.task(&#39;sass:watch&#39;, function () {  gulp.watch(&#39;./src/scss/*.scss&#39;, [&#39;sass&#39;]);  // 입력 경로와 파일 변경 감지 시 실행할 Actions(Task Name)});</code></pre><p>환경을 설정했으니 컴파일합니다.</p><pre><code class="bash">$ gulp sass</code></pre><p>런타임 중 파일 감시 모드로 실행할 수도 있습니다.</p><pre><code class="bash">$ gulp sass:watch</code></pre><h3><span id="85d48197-d4fb-4301-aa96-43ac03369c7f">Webpack</span><a href="#85d48197-d4fb-4301-aa96-43ac03369c7f" class="header-anchor"></a></h3><p>JavaScript 모듈화 도구인 <a href="https://webpack.js.org/" target="_blank" rel="noopener">Webpack</a>의 설정은 좀 더 복잡합니다.<br><a href="https://heropy.blog/2017/10/18/webpack_1_start_ejs_sass/">Webpack - 1 - 시작하기 / EJS / SASS(SCSS)</a> 포스트를 참고하세요.</p><h3><span id="a1685dfb-42ab-4429-93cc-92a9f1d3655d">Parcel</span><a href="#a1685dfb-42ab-4429-93cc-92a9f1d3655d" class="header-anchor"></a></h3><p>웹 애플리케이션 번들러 <a href="https://parceljs.org/" target="_blank" rel="noopener">Parcel</a>은 굉장히 단순하게 컴파일할 수 있습니다.<br>좀 더 자세한 내용은 <a href="https://heropy.blog/2018/01/20/parcel-1-start/">Parcel - 시작하기 / SASS / PostCSS / Babel / Production</a>을 참고하세요.</p><p>우선 Parcel를 전역으로 설치합니다.</p><pre><code class="bash">$ npm install -g parcel-bundler</code></pre><p>프로젝트에 Sass 컴파일러(node-sass)를 설치합니다.</p><pre><code class="bash">$ npm install --save-dev node-sass</code></pre><p>이제 HTML에 <code>&lt;link&gt;</code>로 Sass 파일만 연결하면 됩니다.<br>다른 설정은 필요하지 않습니다.</p><pre><code class="html">&lt;link rel=&quot;stylesheet&quot; href=&quot;scss/main.scss&quot;&gt;</code></pre><pre><code class="bash">$ parcel index.html# 혹은$ parcel build index.html</code></pre><p><code>dist/</code>에서 컴파일된 Sass 파일을 볼 수 있고,<br>별도의 포트 번호를 설정하지 않았다면 <code>http://localhost:1234</code>에 접속하여 적용 상태를 확인할 수 있습니다.</p><h1><span id="b8bc904b-f81b-4417-aeac-0891f9e8850a">문법(Syntax)</span><a href="#b8bc904b-f81b-4417-aeac-0891f9e8850a" class="header-anchor"></a></h1><p>위의 ‘Sass와 SCSS의 차이점’에서 설명한대로 Sass와 SCSS의 기능은 동일하니, 편의를 위해 SCSS 문법으로 설명을 진행합니다.<br>단, Sass와 SCSS의 차이점이 있다면 나눠 설명합니다.</p><h2><span id="b0f1e727-c706-40a6-b036-a97e21eb5d8f">주석(Comment)</span><a href="#b0f1e727-c706-40a6-b036-a97e21eb5d8f" class="header-anchor"></a></h2><p>CSS 주석은 <code>/* ... */</code> 입니다.<br>Sass(SCSS)는 JavaScript처럼 두 가지 스타일의 주석을 사용합니다.</p><pre><code class="scss">// 컴파일되지 않는 주석/* 컴파일되는 주석 */</code></pre><p>Sass의 경우 컴파일되는 여러 줄 주석을 사용할 때 각 줄 앞에 <code>*</code>을 붙여야 하고, 중요한 것은 <code>*</code>의 라인을 맞춰줘야 합니다.</p><p>Sass:</p><pre><code class="sass">/* 컴파일되는 * 여러 줄 * 주석 */// Error/* 컴파일되는* 여러 줄    * 주석 */</code></pre><p>SCSS는 각 줄에 <code>*</code>이 없어도 문제되지 않습니다. 따라서 기존 CSS와 호환이 쉽습니다.</p><p>SCSS:</p><pre><code class="scss">/*컴파일되는여러 줄주석*/</code></pre><h2><span id="390b7969-de84-4e73-9687-f5da80b56595">데이터 종류(Data Types)</span><a href="#390b7969-de84-4e73-9687-f5da80b56595" class="header-anchor"></a></h2><table><thead><tr><th>데이터</th><th>설명</th><th>예시</th></tr></thead><tbody><tr><td>Numbers</td><td>숫자</td><td><code>1</code>, <code>.82</code>, <code>20px</code>, <code>2em</code>…</td></tr><tr><td>Strings</td><td>문자</td><td><code>bold</code>, <code>relative</code>, <code>&quot;/images/a.png&quot;</code>, <code>&quot;dotum&quot;</code></td></tr><tr><td>Colors</td><td>색상 표현</td><td><code>red</code>, <code>blue</code>, <code>#FFFF00</code>, <code>rgba(255,0,0,.5)</code></td></tr><tr><td>Booleans</td><td>논리</td><td><code>true</code>, <code>false</code></td></tr><tr><td>Nulls</td><td>아무것도 없음</td><td><code>null</code></td></tr><tr><td>Lists</td><td>공백이나 <code>,</code>로 구분된 값의 목록</td><td><code>(apple, orange, banana)</code>, <code>apple orange</code></td></tr><tr><td>Maps</td><td>Lists와 유사하나 값이 <code>Key: Value</code> 형태</td><td><code>(apple: a, orange: o, banana: b)</code></td></tr></tbody></table><h3><span id="ee8ed89a-f646-4bcc-88cd-9e8faed48d49">특이사항</span><a href="#ee8ed89a-f646-4bcc-88cd-9e8faed48d49" class="header-anchor"></a></h3><p>Sass에서 사용하는 데이터 종류들의 몇 가지 특이사항을 소개합니다.</p><ul><li>Numbers: 숫자에 단위가 있거나 없습니다.</li><li>Strings: 문자에 따옴표가 있거나 없습니다.</li><li>Nulls: 속성값으로 <code>null</code>이 사용되면 컴파일하지 않습니다.</li><li>Lists: <code>()</code>를 붙이거나 붙이지 않습니다.</li><li>Maps: <code>()</code>를 꼭 붙여야 합니다.</li></ul><h2><span id="ac9bb79a-2458-474b-aa7d-df0b6c194cb7">중첩(Nesting)</span><a href="#ac9bb79a-2458-474b-aa7d-df0b6c194cb7" class="header-anchor"></a></h2><p>Sass는 중첩 기능을 사용할 수 있습니다.<br>상위 선택자의 반복을 피하고 좀 더 편리하게 복잡한 구조를 작성할 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">.section {  width: 100%;  .list {    padding: 20px;    li {      float: left;    }  }}</code></pre><p>Compiled to:</p><pre><code class="css">.section {  width: 100%;}.section .list {  padding: 20px;}.section .list li {  float: left;}</code></pre><h3><span id="d9e62dc0-7688-4dd7-b914-370408261710">Ampersand (상위 선택자 참조)</span><a href="#d9e62dc0-7688-4dd7-b914-370408261710" class="header-anchor"></a></h3><p>중첩 안에서 <code>&amp;</code> 키워드는 상위(부모) 선택자를 참조하여 치환합니다.</p><p>SCSS:</p><pre><code class="scss">.btn {  position: absolute;  &amp;.active {    color: red;  }}.list {  li {    &amp;:last-child {      margin-right: 0;    }  }}</code></pre><p>Compiled to:</p><pre><code class="css">.btn {  position: absolute;}.btn.active {  color: red;}.list li:last-child {  margin-right: 0;}</code></pre><p><code>&amp;</code> 키워드가 참조한 상위 선택자로 치환되는 것이기 때문에 다음과 같이 응용할 수도 있습니다.</p><p>SCSS:</p><pre><code class="scss">.fs {  &amp;-small { font-size: 12px; }  &amp;-medium { font-size: 14px; }  &amp;-large { font-size: 16px; }}</code></pre><p>Compiled to:</p><pre><code class="css">.fs-small {  font-size: 12px;}.fs-medium {  font-size: 14px;}.fs-large {  font-size: 16px;}</code></pre><h3><span id="8176d3bf-1e25-40ad-b3fa-08f23a41e789">@at-root (중첩 벗어나기)</span><a href="#8176d3bf-1e25-40ad-b3fa-08f23a41e789" class="header-anchor"></a></h3><p>중첩에서 벗어나고 싶을 때 <code>@at-root</code> 키워드를 사용합니다.<br>중첩 안에서 생성하되 중첩 밖에서 사용해야 경우에 유용합니다.</p><p>SCSS:</p><pre><code class="scss">.list {  $w: 100px;  $h: 50px;  li {    width: $w;    height: $h;  }  @at-root .box {    width: $w;    height: $h;  }}</code></pre><p>Compiled to:</p><pre><code class="css">.list li {  width: 100px;  height: 50px;}.box {  width: 100px;  height: 50px;}</code></pre><p>아래 예제 처럼 <code>.list</code> 안에 있는 특정 변수를 범위 밖에서 사용할 수 없기 때문에, 위 예제 처럼 <code>@at-root</code> 키워드를 사용해야 합니다.(변수는 아래에서 설명합니다)</p><pre><code class="scss">.list {  $w: 100px;  $h: 50px;  li {    width: $w;    height: $h;  }}// Error.box {  width: $w;  height: $h;}</code></pre><h3><span id="55daa0a8-9737-44e5-8869-ec8d642c28fc">중첩된 속성</span><a href="#55daa0a8-9737-44e5-8869-ec8d642c28fc" class="header-anchor"></a></h3><p><code>font-</code>, <code>margin-</code> 등과 같이 동일한 네임 스페이스를 가지는 속성들을 다음과 같이 사용할 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">.box {  font: {    weight: bold;    size: 10px;    family: sans-serif;  };  margin: {    top: 10px;    left: 20px;  };  padding: {    bottom: 40px;    right: 30px;  };}</code></pre><p>Compiled to:</p><pre><code class="css">.box {  font-weight: bold;  font-size: 10px;  font-family: sans-serif;  margin-top: 10px;  margin-left: 20px;  padding-bottom: 40px;  padding-right: 30px;}</code></pre><h2><span id="b649cb33-a9fc-4e54-b701-09b5a3cc02de">변수(Variables)</span><a href="#b649cb33-a9fc-4e54-b701-09b5a3cc02de" class="header-anchor"></a></h2><p>반복적으로 사용되는 값을 변수로 지정할 수 있습니다.<br>변수 이름 앞에는 항상 <code>$</code>를 붙입니다.</p><pre><code class="scss">$변수이름: 속성값;</code></pre><p>SCSS:</p><pre><code class="scss">$color-primary: #e96900;$url-images: &quot;/assets/images/&quot;;$w: 200px;.box {  width: $w;  margin-left: $w;  background: $color-primary url($url-images + &quot;bg.jpg&quot;);}</code></pre><p>Compiled to:</p><pre><code class="css">.box {  width: 200px;  margin-left: 200px;  background: #e96900 url(&quot;/assets/images/bg.jpg&quot;);}</code></pre><h3><span id="6b3bea74-d59b-4e3a-9901-0385a911ee2a">변수 유효범위(Variable Scope)</span><a href="#6b3bea74-d59b-4e3a-9901-0385a911ee2a" class="header-anchor"></a></h3><p>변수는 사용 가능한 유효범위가 있습니다.<br>선언된 블록(<code>{}</code>) 내에서만 유효범위를 가집니다.</p><p>변수 <code>$color</code>는 <code>.box1</code>의 블록 안에서 설정되었기 때문에, 블록 밖의 <code>.box2</code>에서는 사용할 수 없습니다.</p><pre><code class="scss">.box1 {  $color: #111;  background: $color;}// Error.box2 {  background: $color;}</code></pre><h3><span id="253df3a8-f6c7-4db3-b4ba-2568d2773de8">변수 재 할당(Variable Reassignment)</span><a href="#253df3a8-f6c7-4db3-b4ba-2568d2773de8" class="header-anchor"></a></h3><p>다음과 같이 변수에 변수를 할당할 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">$red: #FF0000;$blue: #0000FF;$color-primary: $blue;$color-danger: $red;.box {  color: $color-primary;  background: $color-danger;}</code></pre><p>Compiled to:</p><pre><code class="css">.box {  color: #0000FF;  background: #FF0000;}</code></pre><h3><span id="68bbe956-737a-48db-8241-40229ef817b9">!global (전역 설정)</span><a href="#68bbe956-737a-48db-8241-40229ef817b9" class="header-anchor"></a></h3><p><code>!global</code> 플래그를 사용하면 변수의 유효범위를 전역(Global)로 설정할 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">.box1 {  $color: #111 !global;  background: $color;}.box2 {  background: $color;}</code></pre><p>Compiled to:</p><pre><code class="css">.box1 {  background: #111;}.box2 {  background: #111;}</code></pre><p>대신 기존에 사용하던 같은 이름의 변수가 있을 경우 값이 덮어져 사용될 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">$color: #000;.box1 {  $color: #111 !global;  background: $color;}.box2 {  background: $color;}.box3 {  $color: #222;  background: $color;}</code></pre><p>Compiled to:</p><pre><code class="css">.box1 {  background: #111;}.box2 {  background: #111;}.box3 {  background: #222;}</code></pre><h3><span id="bc08e2dd-f8b0-4545-8662-a5a84a778731">!default (초깃값 설정)</span><a href="#bc08e2dd-f8b0-4545-8662-a5a84a778731" class="header-anchor"></a></h3><p><code>!default</code> 플래그는 할당되지 않은 변수의 초깃값을 설정합니다.<br>즉, 할당되어있는 변수가 있다면 변수가 기존 할당 값을 사용합니다.</p><p>SCSS:</p><pre><code class="scss">$color-primary: red;.box {  $color-primary: blue !default;  background: $color-primary;}</code></pre><p>Compiled to:</p><pre><code class="css">.box {  background: red;}</code></pre><p>좀 더 유용하게, ‘변수와 값을 설정하겠지만, 혹시 기존 변수가 있을 경우는 현재 설정하는 변수의 값은 사용하지 않겠다’는 의미로 쓸 수 있습니다.<br>예를 들어, <a href="https://github.com/twbs/bootstrap/tree/v4-dev/scss" target="_blank" rel="noopener">Bootstrap</a> 같은 외부 Sass(SCSS) 라이브러리를 연결했더니 변수 이름이 같아 내가 작성한 코드의 변수들이 Overwrite(덮어쓰기) 된다면 문제가 있겠죠.<br>반대로 내가 만든 Sass(SCSS) 라이브러리가 다른 사용자 코드의 변수들을 Overwrite 한다면, 사용자들은 그 라이브러리를 더 이상 사용하지 않을 것입니다.<br>이럴 때 Sass(SCSS) 라이브러리(혹은 새롭게 만든 모듈)에서 사용하는 변수에 <code>!default</code> 플래그가 있다면 기존 코드(원본)를 Overwrite 하지 않고도 사용할 수 있습니다.</p><pre><code class="scss">// _config.scss$color-active: red;</code></pre><pre><code class="scss">// main.scss@import &#39;config&#39;;$color-active: blue !default;.box {  background: $color-active;  // red}</code></pre><p>다음은 Bootstrap 코드(<a href="https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss" target="_blank" rel="noopener">_variables.scss</a>)의 일부입니다.</p><script src="https://gist.github.com/ParkYoungWoong/3c1c1c326a38753161e06e78c8264b3d.js"></script><h3><span id="95dc418c-b9e4-48b4-bb1d-39b22662d9e6">#{} (문자 보간)</span><a href="#95dc418c-b9e4-48b4-bb1d-39b22662d9e6" class="header-anchor"></a></h3><p><code>#{}</code>를 이용해서 코드의 어디든지 변수 값을 넣을 수 있습니다.</p><pre><code class="scss">$family: unquote(&quot;Droid+Sans&quot;);@import url(&quot;http://fonts.googleapis.com/css?family=#{$family}&quot;);</code></pre><pre><code class="css">@import url(&quot;http://fonts.googleapis.com/css?family=Droid+Sans&quot;);</code></pre><p>Sass의 내장 함수 <code>unquote()</code>는 문자에서 따옴표를 제거합니다.</p><h2><span id="18749bbc-90d1-4fce-aec9-68edcd34c2ad">가져오기(Import)</span><a href="#18749bbc-90d1-4fce-aec9-68edcd34c2ad" class="header-anchor"></a></h2><p><code>@import</code>로 외부에서 가져온 Sass 파일은 모두 단일 CSS 출력 파일로 병합됩니다.<br>또한, 가져온 파일에 정의된 모든 변수 또는 Mixins 등을 주 파일에서 사용할 수 있습니다.</p><p>Sass <code>@import</code>는 기본적으로 Sass 파일을 가져오는데, CSS <code>@import</code> 규칙으로 컴파일되는 몇 가지 상황이 있습니다.</p><ul><li>파일 확장자가 <code>.css</code>일 때</li><li>파일 이름이 <code>http://</code>로 시작하는 경우</li><li><code>url()</code>이 붙었을 경우</li><li>미디어쿼리가 있는 경우</li></ul><p>위의 경우 CSS <code>@import</code> 규칙대로 컴파일 됩니다.</p><pre><code class="scss">@import &quot;hello.css&quot;;@import &quot;http://hello.com/hello&quot;;@import url(hello);@import &quot;hello&quot; screen;</code></pre><h3><span id="4bc2a96b-9a65-42ce-b861-1d0e3e615e73">여러 파일 가져오기</span><a href="#4bc2a96b-9a65-42ce-b861-1d0e3e615e73" class="header-anchor"></a></h3><p>하나의 <code>@import</code>로 여러 파일을 가져올 수도 있습니다.<br>파일 이름은 <code>,</code>로 구분합니다.</p><pre><code class="scss">@import &quot;header&quot;, &quot;footer&quot;;</code></pre><h3><span id="477c40ed-637b-4bcd-9f5e-4d45b8def40e">파일 분할(Partials)</span><a href="#477c40ed-637b-4bcd-9f5e-4d45b8def40e" class="header-anchor"></a></h3><p>프로젝트 규모가 커지면 파일들을 <code>header</code>나 <code>side-menu</code> 같이 각 기능과 부분으로 나눠 유지보수가 쉽도록 관리하게 됩니다.<br>이 경우 파일이 점점 많아지는데, 모든 파일이 컴파일 시 각각의 <code>~.css</code> 파일로 나눠서 저장된다면 관리나 성능 차원에서 문제가 될 수 있겠죠.<br>그래서 Sass는 Partials 기능을 지원합니다.<br>파일 이름 앞에 <code>_</code>를 붙여(<code>_header.scss</code>와 같이) <code>@import</code>로 가져오면 컴파일 시 <code>~.css</code> 파일로 컴파일하지 않습니다.</p><p>예를 들어보겠습니다.<br>다음과 같이 <code>scss/</code> 안에 3개의 Sass 파일이 있습니다.</p><pre><code class="bash">Sass-App  # ...  ├─scss  │  ├─header.scss  │  ├─side-menu.scss  │  └─main.scss  # ...</code></pre><p><code>main.scss</code>로 나머지 <code>~.scss</code> 파일을 가져옵니다.</p><pre><code class="scss">// main.scss@import &quot;header&quot;, &quot;side-menu&quot;;</code></pre><p>그리고 이 파일들을 <code>css/</code>디렉토리로 컴파일합니다.<br>(컴파일은 위에서 설명한 <code>node-sass</code>로 진행합니다.)</p><pre><code class="bash"># `scss`디렉토리에 있는 파일들을 `css`디렉토리로 컴파일$ node-sass scss --output css</code></pre><p>컴파일 후 확인하면 아래와 같이 <code>scss/</code>에 있던 파일들이 <code>css/</code> 안에 각 하나씩의 파일로 컴파일됩니다.</p><pre><code class="bash">Sass-App  # ...  ├─css  │  ├─header.css  │  ├─side-menu.css  │  └─main.css  ├─scss  │  ├─header.scss  │  ├─side-menu.scss  │  └─main.scss  # ...</code></pre><p>자, 이번에는 가져올 파일 이름에 <code>_</code>를 붙이겠습니다.<br>메인 파일인 <code>main.scss</code>에서는 <code>_</code>를 사용하지 않습니다.</p><pre><code class="bash">Sass-App  # ...  ├─scss  │  ├─_header.scss  │  ├─_side-menu.scss  │  └─main.scss  # ...</code></pre><pre><code class="scss">// main.scss@import &quot;header&quot;, &quot;side-menu&quot;;</code></pre><p>같은 방법으로 컴파일하면…</p><pre><code class="bash">$ node-sass scss --output css</code></pre><p>아래처럼 별도의 파일로 컴파일되지 않고 사용됩니다.</p><pre><code class="bash">Sass-App  # ...  ├─css  │  └─main.css  # main + header + side-menu  ├─scss  │  ├─header.scss  │  ├─side-menu.scss  │  └─main.scss  # ...</code></pre><p><code>Webpack</code>이나 <code>Parcel</code>, <code>Gulp</code> 같은 일반적인 빌드툴에서는 Partials 기능을 사용할 필요 없이, 설정된 값에 따라 빌드됩니다. 하지만 되도록 <code>_</code>를 사용할 것을 권장합니다.</p><h2><span id="5c5fce1f-708a-4591-8462-99028609597a">연산(Operations)</span><a href="#5c5fce1f-708a-4591-8462-99028609597a" class="header-anchor"></a></h2><p>Sass는 기본적인 연산 기능을 지원합니다.<br>레이아웃 작업시 상황에 맞게 크기를 계산을 하거나 정해진 값을 나눠서 작성할 경우 유용합니다.<br>다음은 Sass에서 사용 가능한 연산자 종류 입니다.</p><p>산술 연산자:</p><table><thead><tr><th style="text-align:center">종류</th><th style="text-align:center">설명</th><th style="text-align:center">주의사항</th></tr></thead><tbody><tr><td style="text-align:center"><code>+</code></td><td style="text-align:center">더하기</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"><code>-</code></td><td style="text-align:center">빼기</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"><code>*</code></td><td style="text-align:center">곱하기</td><td style="text-align:center">하나 이상의 값이 반드시 숫자(Number)</td></tr><tr><td style="text-align:center"><code>/</code></td><td style="text-align:center">나누기</td><td style="text-align:center">오른쪽 값이 반드시 숫자(Number)</td></tr><tr><td style="text-align:center"><code>%</code></td><td style="text-align:center">나머지</td><td style="text-align:center"></td></tr></tbody></table><p>비교 연산자:</p><table><thead><tr><th style="text-align:center">종류</th><th style="text-align:center">설명</th></tr></thead><tbody><tr><td style="text-align:center"><code>==</code></td><td style="text-align:center">동등</td></tr><tr><td style="text-align:center"><code>!=</code></td><td style="text-align:center">부등</td></tr><tr><td style="text-align:center"><code>&lt;</code></td><td style="text-align:center">대소 / 보다 작은</td></tr><tr><td style="text-align:center"><code>&gt;</code></td><td style="text-align:center">대소 / 보다 큰</td></tr><tr><td style="text-align:center"><code>&lt;=</code></td><td style="text-align:center">대소 및 동등 / 보다 작거나 같은</td></tr><tr><td style="text-align:center"><code>&gt;=</code></td><td style="text-align:center">대소 및 동등 / 보다 크거나 같은</td></tr></tbody></table><p>논리(불린, Boolean) 연산자:</p><table><thead><tr><th style="text-align:center">종류</th><th style="text-align:center">설명</th></tr></thead><tbody><tr><td style="text-align:center"><code>and</code></td><td style="text-align:center">그리고</td></tr><tr><td style="text-align:center"><code>or</code></td><td style="text-align:center">또는</td></tr><tr><td style="text-align:center"><code>not</code></td><td style="text-align:center">부정</td></tr></tbody></table><h3><span id="b14ba330-5cd1-43b3-9d22-0a2ab4bcc18f">숫자(Numbers)</span><a href="#b14ba330-5cd1-43b3-9d22-0a2ab4bcc18f" class="header-anchor"></a></h3><h4><span id="d70d8437-a378-4d06-88ba-75ba759800ee">상대적 단위 연산</span><a href="#d70d8437-a378-4d06-88ba-75ba759800ee" class="header-anchor"></a></h4><p>일반적으론 절댓값을 나타내는 <code>px</code> 단위로 연산을 합니다만, 상대적 단위(<code>%</code>, <code>em</code>, <code>vw</code> 등)의 연산의 경우 <a href="https://developer.mozilla.org/ko/docs/Web/CSS/calc" target="_blank" rel="noopener">CSS calc()</a>로 연산해야 합니다.</p><pre><code class="scss">width: 50% - 20px;  // 단위 모순 에러(Incompatible units error)width: calc(50% - 20px);  // 연산 가능</code></pre><h4><span id="a00b1696-c657-48d5-a5b4-b832cb38afde">나누기 연산의 주의사항</span><a href="#a00b1696-c657-48d5-a5b4-b832cb38afde" class="header-anchor"></a></h4><p>CSS는 속성 값의 숫자를 분리하는 방법으로 <code>/</code>를 허용하기 때문에 <code>/</code>가 나누기 연산으로 사용되지 않을 수 있습니다.<br>예를 들어, <code>font: 16px / 22px serif;</code> 같은 경우 <code>font-size: 16px</code>와 <code>line-height: 22px</code>의 속성값 분리를 위해서 <code>/</code>를 사용합니다.<br>아래 예제를 보면 나누기 연산자만 연산 되지 않고 그대로 컴파일됩니다.</p><p>SCSS:</p><pre><code class="scss">div {  width: 20px + 20px;  // 더하기  height: 40px - 10px;  // 빼기  font-size: 10px * 2;  // 곱하기  margin: 30px / 2;  // 나누기}</code></pre><p>Compiled to:</p><pre><code class="css">div {  width: 40px;  /* OK */  height: 30px;  /* OK */  font-size: 20px;  /* OK */  margin: 30px / 2;  /* ?? */}</code></pre><p>따라서 <code>/</code>를 나누기 연산 기능으로 사용하려면 다음과 같은 조건을 충족해야 합니다.</p><ul><li>값 또는 그 일부가 변수에 저장되거나 함수에 의해 반환되는 경우</li><li>값이 <code>()</code>로 묶여있는 경우</li><li>값이 다른 산술 표현식의 일부로 사용되는 경우</li></ul><p>SCSS:</p><pre><code class="scss">div {  $x: 100px;  width: $x / 2;  // 변수에 저장된 값을 나누기  height: (100px / 2);  // 괄호로 묶어서 나누기  font-size: 10px + 12px / 3;  // 더하기 연산과 같이 사용}</code></pre><p>Compiled to:</p><pre><code class="css">div {  width: 50px;  height: 50px;  font-size: 14px;}</code></pre><h3><span id="c707d1e7-e536-43ad-bf59-bb2821f4d549">문자(Strings)</span><a href="#c707d1e7-e536-43ad-bf59-bb2821f4d549" class="header-anchor"></a></h3><p>문자 연산에는 <code>+</code>가 사용됩니다.<br>문자 연산의 결과는 첫 번째 피연산자를 기준으로 합니다.<br>첫 번째 피연산자에 따옴표가 붙어있다면 연산 결과를 따옴표로 묶습니다.<br>반대로 첫 번째 피연산자에 따옴표가 붙어있지 않다면 연산 결과도 따옴표를 처리하지 않습니다.</p><p>SCSS:</p><pre><code class="scss">div::after {  content: &quot;Hello &quot; + World;  flex-flow: row + &quot;-reverse&quot; + &quot; &quot; + wrap}</code></pre><p>Compiled to:</p><pre><code class="css">div::after {  content: &quot;Hello World&quot;;  flex-flow: row-reverse wrap;}</code></pre><h3><span id="0c329832-d26b-45d6-bd60-f5673d56a247">색상(Colors)</span><a href="#0c329832-d26b-45d6-bd60-f5673d56a247" class="header-anchor"></a></h3><p>색상도 연산할 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">div {  color: #123456 + #345678;  // R: 12 + 34 = 46  // G: 34 + 56 = 8a  // B: 56 + 78 = ce  background: rgba(50, 100, 150, .5) + rgba(10, 20, 30, .5);  // R: 50 + 10 = 60  // G: 100 + 20 = 120  // B: 150 + 30 = 180  // A: Alpha channels must be equal}</code></pre><p>Compiled to:</p><pre><code class="css">div {  color: #468ace;  background: rgba(60, 120, 180, 0.5);}</code></pre><p>RGBA에서 Alpha 값은 연산되지 않으며 서로 동일해야 다른 값의 연산이 가능합니다.<br>Alpha 값을 연산하기 위한 다음과 같은 색상 함수(Color Functions)를 사용할 수 있습니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#opacify-instance_method" target="_blank" rel="noopener">opacify()</a>, <a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#transparentize-instance_method" target="_blank" rel="noopener">transparentize()</a></p><p>SCSS:</p><pre><code class="scss">$color: rgba(10, 20, 30, .5);div {  color: opacify($color, .3);  // 30% 더 불투명하게 / 0.5 + 0.3  background-color: transparentize($color, .2);  // 20% 더 투명하게 / 0.5 - 0.2}</code></pre><p>Compiled to:</p><pre><code class="css">div {  color: rgba(10, 20, 30, 0.8);  background-color: rgba(10, 20, 30, 0.3);}</code></pre><h3><span id="3b81c0af-2dff-4a14-9278-27cb0d21afba">논리(Boolean)</span><a href="#3b81c0af-2dff-4a14-9278-27cb0d21afba" class="header-anchor"></a></h3><p>Sass의 <code>@if</code> 조건문에서 사용되는 논리(Boolean) 연산에는 ‘그리고’,’ 또는’, ‘부정’이 있습니다.<br>자바스크립트 문법에 익숙하다면 <code>&amp;&amp;</code>, <code>||</code>, <code>!</code>와 같은 기능으로 생각하면 됩니다.</p><table><thead><tr><th style="text-align:center">종류</th><th style="text-align:center">설명</th></tr></thead><tbody><tr><td style="text-align:center"><code>and</code></td><td style="text-align:center">그리고</td></tr><tr><td style="text-align:center"><code>or</code></td><td style="text-align:center">또는</td></tr><tr><td style="text-align:center"><code>not</code></td><td style="text-align:center">부정(반대)</td></tr></tbody></table><p>간단한 예제를 확인하고, 더 자세한 내용은 조건문에서 살펴보겠습니다.</p><p>SCSS:</p><pre><code class="scss">$width: 90px;div {  @if not ($width &gt; 100px) {    height: 300px;  }}</code></pre><p>Compiled to:</p><pre><code class="css">div {  height: 300px;}</code></pre><h2><span id="aae4630b-e340-4c39-ad4a-b000b4c051d2">재활용(Mixins)</span><a href="#aae4630b-e340-4c39-ad4a-b000b4c051d2" class="header-anchor"></a></h2><p>Sass Mixins는 스타일 시트 전체에서 <strong>재사용 할 CSS 선언 그룹</strong> 을 정의하는 아주 훌륭한 기능입니다.<br>약간의 Mixin(믹스인)으로 다양한 스타일을 만들어낼 수 있습니다.</p><p>우선, Mixin은 두 가지만 기억하면 됩니다.<br>선언하기(<code>@mixin</code>)와 포함하기(<code>@include</code>) 입니다.<br>만들어서(선언), 사용(포함)하는 거죠!</p><h3><span id="20406419-af76-4d55-bef9-d692b1a11eb1">@mixin</span><a href="#20406419-af76-4d55-bef9-d692b1a11eb1" class="header-anchor"></a></h3><p>기본적인 Mixin 선언은 아주 간단합니다.<br><code>@mixin</code> 지시어를 이용하여 스타일을 정의합니다.</p><pre><code class="scss">// SCSS@mixin 믹스인이름 {  스타일;}// Sass=믹스인이름  스타일</code></pre><pre><code class="scss">// SCSS@mixin large-text {  font-size: 22px;  font-weight: bold;  font-family: sans-serif;  color: orange;}// Sass=large-text  font-size: 22px  font-weight: bold  font-family: sans-serif  color: orange</code></pre><p>Mixin은 선택자를 포함 가능하고 상위(부모) 요소 참조(<code>&amp;</code> 같은)도 할 수 있습니다.</p><pre><code class="scss">@mixin large-text {  font: {    size: 22px;    weight: bold;    family: sans-serif;  }  color: orange;  &amp;::after {    content: &quot;!!&quot;;  }  span.icon {    background: url(&quot;/images/icon.png&quot;);  }}</code></pre><h3><span id="94682999-c661-435f-83bc-fece390980ca">@include</span><a href="#94682999-c661-435f-83bc-fece390980ca" class="header-anchor"></a></h3><p>선언된 Mixin을 사용(포함)하기 위해서는 <code>@include</code>가 필요합니다.<br>위에서 선언한 Mixin을 사용해 보겠습니다.</p><pre><code class="scss">// SCSS@include 믹스인이름;// Sass+믹스인이름</code></pre><p>SCSS:</p><pre><code class="scss">// SCSSh1 {  @include large-text;}div {  @include large-text;}// Sassh1  +large-textdiv  +large-text</code></pre><p>Compiled to:</p><pre><code class="css">h1 {  font-size: 22px;  font-weight: bold;  font-family: sans-serif;  color: orange;}h1::after {  content: &quot;!!&quot;;}h1 span.icon {  background: url(&quot;/images/icon.png&quot;);}div {  font-size: 22px;  font-weight: bold;  font-family: sans-serif;  color: orange;}div::after {  content: &quot;!!&quot;;}div span.icon {  background: url(&quot;/images/icon.png&quot;);}</code></pre><h3><span id="8816d35d-06b4-4ac9-8142-6bd3f74e27fe">인수(Arguments)</span><a href="#8816d35d-06b4-4ac9-8142-6bd3f74e27fe" class="header-anchor"></a></h3><p>Mixin은 함수(Functions)처럼 인수(Arguments)를 가질 수 있습니다.<br>하나의 Mixin으로 다양한 결과를 만들 수 있습니다.</p><pre><code class="scss">// SCSS@mixin 믹스인이름($매개변수) {  스타일;}@include 믹스인이름(인수);// Sass=믹스인이름($매개변수)  스타일+믹스인이름(인수)</code></pre><blockquote><p>매개변수(Parameters)란 변수의 한 종류로, 제공되는 여러 데이터 중 하나를 가리키기 위해 사용된다.<br>제공되는 여러 데이터들을 전달인수(Arguments) 라고 부른다.</p></blockquote><p>SCSS:</p><pre><code class="scss">@mixin dash-line($width, $color) {  border: $width dashed $color;}.box1 { @include dash-line(1px, red); }.box2 { @include dash-line(4px, blue); }</code></pre><p>Compiled to:</p><pre><code class="css">.box1 {  border: 1px dashed red;}.box2 {  border: 4px dashed blue;}</code></pre><h4><span id="9726624b-ee44-4b0e-a5c3-2af93bcfab59">인수의 기본값 설정</span><a href="#9726624b-ee44-4b0e-a5c3-2af93bcfab59" class="header-anchor"></a></h4><p>인수(argument)는 기본값(default value)을 가질 수 있습니다.<br><code>@include</code> 포함 단계에서 별도의 인수가 전달되지 않으면 기본값이 사용됩니다.</p><pre><code class="scss">@mixin 믹스인이름($매개변수: 기본값) {  스타일;}</code></pre><p>SCSS:</p><pre><code class="scss">@mixin dash-line($width: 1px, $color: black) {  border: $width dashed $color;}.box1 { @include dash-line; }.box2 { @include dash-line(4px); }</code></pre><p>Compiled to:</p><pre><code class="css">.box1 {  border: 1px dashed black;}.box2 {  border: 4px dashed black;}</code></pre><h4><span id="b8d48886-91ad-4808-a24b-87eef0445f40">키워드 인수(Keyword Arguments)</span><a href="#b8d48886-91ad-4808-a24b-87eef0445f40" class="header-anchor"></a></h4><pre><code class="scss">@mixin 믹스인이름($매개변수A: 기본값, $매개변수B: 기본값) {  스타일;}@include 믹스인이름($매개변수B: 인수);</code></pre><p>Mixin에 전달할 인수를 입력할 때 명시적으로 키워드(변수)를 입력하여 작성할 수 있습니다.<br>별도의 인수 입력 순서를 필요로 하지 않아 편리하게 작성할 수 있습니다.<br>단, 작성하지 않은 인수가 적용될 수 있도록 기본값을 설정해 주는 것이 좋습니다.</p><pre><code class="scss">@mixin position(  $p: absolute,  $t: null,  $b: null,  $l: null,  $r: null) {  position: $p;  top: $t;  bottom: $b;  left: $l;  right: $r;}.absolute {  // 키워드 인수로 설정할 값만 전달  @include position($b: 10px, $r: 20px);}.fixed {  // 인수가 많아짐에 따라 가독성을 확보하기 위해 줄바꿈  @include position(    fixed,    $t: 30px,    $r: 40px  );}</code></pre><pre><code class="css">.absolute {  position: absolute;  bottom: 10px;  right: 20px;}.fixed {  position: fixed;  top: 30px;  right: 40px;}</code></pre><h4><span id="d52cdd33-717b-4e87-99bd-a635de5ae9ca">가변 인수(Variable Arguments)</span><a href="#d52cdd33-717b-4e87-99bd-a635de5ae9ca" class="header-anchor"></a></h4><p>때때로 입력할 인수의 개수가 불확실한 경우가 있습니다.<br>그럴 경우 가변 인수를 사용할 수 있습니다.<br>가변 인수는 매개변수 뒤에 <code>...</code>을 붙여줍니다.</p><pre><code class="scss">@mixin 믹스인이름($매개변수...) {  스타일;}@include 믹스인이름(인수A, 인수B, 인수C);</code></pre><pre><code class="scss">// 인수를 순서대로 하나씩 전달 받다가, 3번째 매개변수($bg-values)는 인수의 개수에 상관없이 받음@mixin bg($width, $height, $bg-values...) {  width: $width;  height: $height;  background: $bg-values;}div {  // 위의 Mixin(bg) 설정에 맞게 인수를 순서대로 전달하다가 3번째 이후부터는 개수에 상관없이 전달  @include bg(    100px,    200px,    url(&quot;/images/a.png&quot;) no-repeat 10px 20px,    url(&quot;/images/b.png&quot;) no-repeat,    url(&quot;/images/c.png&quot;)  );}</code></pre><pre><code class="css">div {  width: 100px;  height: 200px;  background: url(&quot;/images/a.png&quot;) no-repeat 10px 20px,              url(&quot;/images/b.png&quot;) no-repeat,              url(&quot;/images/c.png&quot;);}</code></pre><p>위에선 인수를 받는 매개변수에 <code>...</code>을 사용하여 가변 인수를 활용했습니다.<br>이번엔 반대로 가변 인수를 전달할 값으로 사용해 보겠습니다.</p><pre><code class="scss">@mixin font(  $style: normal,  $weight: normal,  $size: 16px,  $family: sans-serif) {  font: {    style: $style;    weight: $weight;    size: $size;    family: $family;  }}div {  // 매개변수 순서와 개수에 맞게 전달  $font-values: italic, bold, 16px, sans-serif;  @include font($font-values...);}span {  // 필요한 값만 키워드 인수로 변수에 담아 전달  $font-values: (style: italic, size: 22px);  @include font($font-values...);}a {  // 필요한 값만 키워드 인수로 전달  @include font((weight: 900, family: monospace)...);}</code></pre><pre><code class="css">div {  font-style: italic;  font-weight: bold;  font-size: 16px;  font-family: sans-serif;}span {  font-style: italic;  font-weight: normal;  font-size: 22px;  font-family: sans-serif;}a {  font-style: normal;  font-weight: 900;  font-size: 16px;  font-family: monospace;}</code></pre><h3><span id="7bf5ce54-0056-46a1-8b92-08470be9504a">@content</span><a href="#7bf5ce54-0056-46a1-8b92-08470be9504a" class="header-anchor"></a></h3><p>선언된 Mixin에 <code>@content</code>이 포함되어 있다면 해당 부분에 원하는 <strong>스타일 블록</strong> 을 전달할 수 있습니다.<br>이 방식을 사용하여 기존 Mixin이 가지고 있는 기능에 선택자나 속성 등을 추가할 수 있습니다.</p><pre><code class="scss">@mixin 믹스인이름() {  스타일;  @content;}@include 믹스인이름() {  // 스타일 블록  스타일;}</code></pre><p>SCSS:</p><pre><code class="scss">@mixin icon($url) {  &amp;::after {    content: $url;    @content;  }}.icon1 {  // icon Mixin의 기존 기능만 사용  @include icon(&quot;/images/icon.png&quot;);}.icon2 {  // icon Mixin에 스타일 블록을 추가하여 사용  @include icon(&quot;/images/icon.png&quot;) {    position: absolute;  };}</code></pre><p>Compiled to:</p><pre><code class="css">.icon1::after {  content: &quot;/images/icon.png&quot;;}.icon2::after {  content: &quot;/images/icon.png&quot;;  position: absolute;}</code></pre><p>Mixin에게 전달된 스타일 블록은 Mixin의 범위가 아니라 스타일 블록이 정의된 범위에서 평가됩니다.<br>즉, Mixin의 매개변수는 전달된 스타일 블록 안에서 사용되지 않고 전역 값으로 해석됩니다.<br>전역 변수(Global variables)와 지역 변수(Local variables)를 생각하면 좀 더 쉽습니다.</p><p>SCSS:</p><pre><code class="scss">$color: red;@mixin colors($color: blue) {  // Mixin의 범위  @content;  background-color: $color;  border-color: $color;}div {  @include colors() {    // 스타일 블록이 정의된 범위    color: $color;  }}</code></pre><p>Compiled to:</p><pre><code class="css">div {  color: red;  background-color: blue;  border-color: blue;}</code></pre><h2><span id="41c5d48a-77b4-49e5-b42f-7de574bf0a6c">확장(Extend)</span><a href="#41c5d48a-77b4-49e5-b42f-7de574bf0a6c" class="header-anchor"></a></h2><p>특정 선택자가 다른 선택자의 모든 스타일을 가져야하는 경우가 종종 있습니다.<br>이럴 경우 선택자의 확장 기능을 사용할 수 있습니다.<br>다음 예제를 봅시다.</p><pre><code class="scss">@extend 선택자;</code></pre><p>SCSS:</p><pre><code class="scss">.btn {  padding: 10px;  margin: 10px;  background: blue;}.btn-danger {  @extend .btn;  background: red;}</code></pre><p>Compiled to:</p><pre><code class="css">.btn, .btn-danger {  padding: 10px;  margin: 10px;  background: blue;}.btn-danger {  background: red;}</code></pre><p>컴파일된 결과가 마음에 드시나요?<br>결과를 보면 <code>,</code>로 구분하는 다중 선택자(Multiple Selector)가 만들어졌습니다.</p><p>사실 <code>@extend</code>는 다음과 같은 문제를 고려해야 합니다.</p><ul><li>내 현재 선택자(위 예제의 <code>.btn-danger</code>)가 어디에 첨부될 것인가?</li><li>원치 않는 부작용이 초래될 수도 있는가?</li><li>이 한 번의 확장으로 얼마나 큰 CSS가 생성되는가?</li></ul><p>결과적으로 확장(Extend) 기능은 무해하거나 혹은 유익할 수도 있지만 그만큼 <strong>부작용</strong>을 가지고 있을 수 있습니다.<br>따라서 확장은 사용을 권장하지 않으며, 위에서 살펴본 <strong>Mixin을 대체 기능으로 사용</strong>하세요.</p><p>사용을 권장하지 않는 이유에 대해서 좀 더 자세한 정보를 원하면 <a href="https://sass-guidelin.es/ko/#extend" target="_blank" rel="noopener">Sass Guidelines Extend</a>를 참고하세요.</p><h2><span id="1514fd60-5673-4b70-81e7-a945347e22ef">함수(Functions)</span><a href="#1514fd60-5673-4b70-81e7-a945347e22ef" class="header-anchor"></a></h2><p>자신의 함수를 정의하여 사용할 수 있습니다.<br>함수와 Mixins은 거의 유사하지만 반환되는 내용이 다릅니다.</p><p>Mixin은 위에서 살펴본 대로 지정한 스타일(Style)을 반환하는 반면,<br>함수는 보통 연산된(Computed) 특정 <strong>값</strong>을 <code>@return</code> 지시어를 통해 반환합니다.</p><pre><code class="scss">// Mixins@mixin 믹스인이름($매개변수) {  스타일;}// Functions@function 함수이름($매개변수) {  @return 값}</code></pre><p>사용하는 방법에도 차이가 있습니다.<br>Mixin은 <code>@include</code> 지시어를 사용하는 반면,<br>함수는 함수이름으로 바로 사용합니다.</p><pre><code class="scss">// Mixin@include 믹스인이름(인수);// Functions함수이름(인수)</code></pre><p>SCSS:</p><pre><code class="scss">$max-width: 980px;@function columns($number: 1, $columns: 12) {  @return $max-width * ($number / $columns)}.box_group {  width: $max-width;  .box1 {    width: columns();  // 1  }  .box2 {    width: columns(8);  }  .box3 {    width: columns(3);  }}</code></pre><p>Compiled to:</p><pre><code class="css">.box_group {  /* 총 너비 */  width: 980px;}.box_group .box1 {  /* 총 너비의 약 8.3% */  width: 81.66667px;}.box_group .box2 {  /* 총 너비의 약 66.7% */  width: 653.33333px;}.box_group .box3 {  /* 총 너비의 25% */  width: 245px;}</code></pre><p>위와 같이 함수는 <code>@include</code> 같은 별도의 지시어 없이 사용하기 때문에 내가 지정한 함수와 내장 함수(Built-in Functions)의 이름이 충돌할 수 있습니다.<br>따라서 내가 지정한 함수에는 별도의 접두어를 붙여주는 것이 좋습니다.</p><blockquote><p>내장 함수란, 응용 프로그램에 내장되어 있으며 최종 사용자가 액세스 할 수 있는 기능입니다.<br>예를 들어, 대부분의 스프레드 시트 응용 프로그램은 행이나 열의 모든 셀을 추가하는 내장 SUM 함수를 지원합니다.</p></blockquote><p>예를 들어, 색의 빨강 성분을 가져오는 내장 함수로 이미 <code>red()</code>가 있습니다.<br>같은 이름을 사용하여 함수를 정의하면 이름이 충돌하기 때문에 별도의 접두어를 붙여 <code>extract-red()</code> 같은 이름을 만들 수 있습니다.</p><pre><code class="scss">// 내가 정의한 함수@function extract-red($color) {  // 내장 함수  @return rgb(red($color), 0, 0);}div {  color: extract-red(#D55A93);}</code></pre><p>혹은 모든 내장 함수의 이름을 다 알고 있을 수 없기 때문에 특별한 이름을 접두어로 사용할 수도 있습니다.<br><code>my-custom-func-red()</code></p><h2><span id="618fb25f-75d4-4c9d-9c79-e4c5d3b89bda">조건과 반복(Control Directives / Expressions)</span><a href="#618fb25f-75d4-4c9d-9c79-e4c5d3b89bda" class="header-anchor"></a></h2><h3><span id="1c4c5336-ae59-44cb-993a-4a9136e89fb8">if (함수)</span><a href="#1c4c5336-ae59-44cb-993a-4a9136e89fb8" class="header-anchor"></a></h3><p>조건의 값(<code>true</code>, <code>false</code>)에 따라 두 개의 표현식 중 하나만 반환합니다.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Conditional_Operator" target="_blank" rel="noopener">조건부 삼항 연산자(conditional ternary operator)</a>와 비슷합니다.</p><p>조건의 값이 <code>true</code>이면 <code>표현식1</code>을,<br>조건의 값이 <code>false</code>이면 <code>표현식2</code>를 실행합니다.</p><pre><code class="scss">if(조건, 표현식1, 표현식2)</code></pre><p>SCSS:</p><pre><code class="scss">$width: 555px;div {  width: if($width &gt; 300px, $width, null);}</code></pre><p>Compiled to:</p><pre><code class="css">div {  width: 555px;}</code></pre><h3><span id="8b99ec7c-eb79-4ae0-b9b2-a0a1711d2e54">@if (지시어)</span><a href="#8b99ec7c-eb79-4ae0-b9b2-a0a1711d2e54" class="header-anchor"></a></h3><p><code>@if</code> 지시어는 조건에 따른 분기 처리가 가능하며, <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/if...else" target="_blank" rel="noopener">if 문(if statements)</a>과 유사합니다.<br>같이 사용할 수 있는 지시어는 <code>@else</code>, <code>if</code>가 있습니다.<br>추가 지시어를 사용하면 좀 더 복잡한 조건문을 작성할 수 있습니다.</p><pre><code class="scss">// @if@if (조건) {  /* 조건이 참일 때 구문 */}// @if @else@if (조건) {  /* 조건이 참일 때 구문 */} @else {  /* 조건이 거짓일 때 구문 */}// @if @else if@if (조건1) {  /* 조건1이 참일 때 구문 */} @else if (조건2) {  /* 조건2가 참일 때 구문 */} @else {  /* 모두 거짓일 때 구문 */}</code></pre><p>조건에 <code>()</code>는 생략이 가능하기 때문에, <code>()</code> 없이 작성하는 방법이 좀 더 편리할 수 있습니다.</p><pre><code class="scss">$bg: true;div {  @if $bg {    background: url(&quot;/images/a.jpg&quot;);  }}</code></pre><p>SCSS:</p><pre><code class="scss">$color: orange;div {  @if $color == strawberry {    color: #FE2E2E;  } @else if $color == orange {    color: #FE9A2E;  } @else if $color == banana {    color: #FFFF00;  } @else {    color: #2A1B0A;  }}</code></pre><p>Compiled to:</p><pre><code class="css">div {  color: #FE9A2E;}</code></pre><p>조건에는 논리 연산자 <code>and</code>, <code>or</code>, <code>not</code>을 사용할 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">@function limitSize($size) {  @if $size &gt;= 0 and $size &lt;= 200px {    @return 200px;  } @else {    @return 800px;  }}div {  width: limitSize(180px);  height: limitSize(340px);}</code></pre><p>Compiled to:</p><pre><code class="css">div {  width: 200px;  height: 800px;}</code></pre><p>다른 예제도 살펴봅시다!<br>Sass의 내장 함수 <code>unitless()</code>는 숫자에 단위가 있는지 여부를 반환합니다.</p><p>SCSS:</p><pre><code class="scss">@mixin pCenter($w, $h, $p: absolute) {  @if    $p == absolute    or $p == fixed    // or not $p == relative    // or not $p == static  {    width: if(unitless($w), #{$w}px, $w);    height: if(unitless($h), #{$h}px, $h);    position: $p;    top: 0;    bottom: 0;    left: 0;    right: 0;    margin: auto;  }}.box1 {  @include pCenter(10px, 20px);}.box2 {  @include pCenter(50, 50, fixed);}.box3 {  @include pCenter(100, 200, relative);}</code></pre><p>Compiled to:</p><pre><code class="css">.box1 {  width: 10px;  height: 20px;  position: absolute;  top: 0;  bottom: 0;  left: 0;  right: 0;  margin: auto;}.box2 {  width: 50px;  height: 50px;  position: fixed;  top: 0;  bottom: 0;  left: 0;  right: 0;  margin: auto;}</code></pre><h3><span id="c5b450f6-c6f8-485b-be94-07befd6acfc7">@for</span><a href="#c5b450f6-c6f8-485b-be94-07befd6acfc7" class="header-anchor"></a></h3><p><code>@for</code>는 스타일을 반복적으로 출력합니다.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for" target="_blank" rel="noopener">for 문</a>과 유사합니다.</p><p><code>@for</code>는 <code>through</code>를 사용하는 형식과 <code>to</code>를 사용하는 형식으로 나뉩니다.<br>두 형식은 종료 조건이 해석되는 방식이 다릅니다.</p><pre><code class="scss">// through// 종료 만큼 반복@for $변수 from 시작 through 종료 {  // 반복 내용}// to// 종료 직전까지 반복@for $변수 from 시작 to 종료 {  // 반복 내용}</code></pre><p>차이점을 이해하기 위해 다음 예제를 살펴봅시다.<br>변수는 관례상 <code>$i</code>를 사용합니다.</p><p>SCSS:</p><pre><code class="scss">// 1부터 3번 반복@for $i from 1 through 3 {  .through:nth-child(#{$i}) {    width : 20px * $i  }}// 1부터 3 직전까지만 반복(2번 반복)@for $i from 1 to 3 {  .to:nth-child(#{$i}) {    width : 20px * $i  }}</code></pre><p>Compiled to:</p><pre><code class="css">.through:nth-child(1) { width: 20px; }.through:nth-child(2) { width: 40px; }.through:nth-child(3) { width: 60px; }.to:nth-child(1) { width: 20px; }.to:nth-child(2) { width: 40px; }</code></pre><p><code>to</code>는 주어진 값 직전까지만 반복해야할 경우 유용할 수 있습니다.<br>그러나 <code>:nth-child()</code>에서 특히 유용하게 사용되는 <code>@for</code>는 일반적으로 <code>through</code>를 사용하길 권장합니다.</p><h3><span id="b6450f51-6227-4f76-a8e4-ef3a33d1bc14">@each</span><a href="#b6450f51-6227-4f76-a8e4-ef3a33d1bc14" class="header-anchor"></a></h3><p><code>@each</code>는 List와 Map 데이터를 반복할 때 사용합니다.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for...in" target="_blank" rel="noopener">for in 문</a>과 유사합니다.</p><pre><code class="scss">@each $변수 in 데이터 {  // 반복 내용}</code></pre><p>List 데이터를 반복해 보겠습니다.</p><p>SCSS:</p><pre><code class="scss">// List Data$fruits: (apple, orange, banana, mango);.fruits {  @each $fruit in $fruits {    li.#{$fruit} {      background: url(&quot;/images/#{$fruit}.png&quot;);    }  }}</code></pre><p>Compiled to:</p><pre><code class="css">.fruits li.apple {  background: url(&quot;/images/apple.png&quot;);}.fruits li.orange {  background: url(&quot;/images/orange.png&quot;);}.fruits li.banana {  background: url(&quot;/images/banana.png&quot;);}.fruits li.mango {  background: url(&quot;/images/mango.png&quot;);}</code></pre><p>혹시 매번 반복마다 Index 값이 필요하다면 다음과 같이 <code>index()</code> 내장 함수를 사용할 수 있습니다.</p><p>SCSS:</p><pre><code class="scss">$fruits: (apple, orange, banana, mango);.fruits {  @each $fruit in $fruits {    $i: index($fruits, $fruit);    li:nth-child(#{$i}) {      left: 50px * $i;    }  }}</code></pre><p>Compiled to:</p><pre><code class="css">.fruits li:nth-child(1) {  left: 50px;}.fruits li:nth-child(2) {  left: 100px;}.fruits li:nth-child(3) {  left: 150px;}.fruits li:nth-child(4) {  left: 200px;}</code></pre><p>동시에 여러 개의 List 데이터를 반복 처리할 수도 있습니다.<br>단, 각 데이터의 Length가 같아야 합니다.</p><p>SCSS:</p><pre><code class="scss">$apple: (apple, korea);$orange: (orange, china);$banana: (banana, japan);@each $fruit, $country in $apple, $orange, $banana {  .box-#{$fruit} {    background: url(&quot;/images/#{$country}.png&quot;);  }}</code></pre><p>Compiled to:</p><pre><code class="css">.box-apple {  background: url(&quot;/images/korea.png&quot;);}.box-orange {  background: url(&quot;/images/china.png&quot;);}.box-banana {  background: url(&quot;/images/japan.png&quot;);}</code></pre><p>Map 데이터를 반복할 경우 하나의 데이터에 두 개의 변수가 필요합니다.</p><pre><code class="scss">@each $key변수, $value변수 in 데이터 {  // 반복 내용}</code></pre><pre><code class="scss">$fruits-data: (  apple: korea,  orange: china,  banana: japan);@each $fruit, $country in $fruits-data {  .box-#{$fruit} {    background: url(&quot;/images/#{$country}.png&quot;);  }}</code></pre><pre><code class="css">.box-apple {  background: url(&quot;/images/korea.png&quot;);}.box-orange {  background: url(&quot;/images/china.png&quot;);}.box-banana {  background: url(&quot;/images/japan.png&quot;);}</code></pre><h3><span id="ae8d3deb-89fb-4343-917a-a88069e589a6">@while</span><a href="#ae8d3deb-89fb-4343-917a-a88069e589a6" class="header-anchor"></a></h3><p><code>@while</code>은 조건이 <code>false</code>로 평가될 때까지 내용을 반복합니다.<br><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/while" target="_blank" rel="noopener">while 문</a>과 유사하게 잘못된 조건으로 인해 컴파일 중 무한 루프에 빠질 수 있습니다.<br>사용을 권장하지 않습니다.</p><pre><code class="scss">@while 조건 {  // 반복 내용}</code></pre><pre><code class="scss">$i: 6;@while $i &gt; 0 {  .item-#{$i} {    width: 2px * $i;  }  $i: $i - 2;}</code></pre><pre><code class="css">.item-6 { width: 12px; }.item-4 { width: 8px; }.item-2 { width: 4px; }</code></pre><h2><span id="99a45312-957d-49b0-b84c-3b8a7149be0e">내장 함수(Built-in Functions)</span><a href="#99a45312-957d-49b0-b84c-3b8a7149be0e" class="header-anchor"></a></h2><p>Sass에서 기본적으로 제공하는 내장 함수에는 많은 종류가 있습니다.<br>모두 소개하지 않고, 주관적 경험에 의거해 필요하거나 필요할 수 있는 함수만 정리했습니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html" target="_blank" rel="noopener">Sass Built-in Functions</a>에서 모든 내장 함수를 확인할 수 있습니다.</p><ul><li><code>[]</code>는 선택 가능한 인수(argument)입니다.</li><li><a href="https://en.wikipedia.org/wiki/Zero-based_numbering" target="_blank" rel="noopener">Zero-based numbering</a>을 사용하지 않습니다.</li></ul><h3><span id="c2ec257f-5aa6-4947-8855-cdcf4ce2d1ed">색상(RGB / HSL / Opacity) 함수</span><a href="#c2ec257f-5aa6-4947-8855-cdcf4ce2d1ed" class="header-anchor"></a></h3><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#mix-instance_method" target="_blank" rel="noopener">mix($color1, $color2)</a> : 두 개의 색을 섞습니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#lighten-instance_method" target="_blank" rel="noopener">lighten($color, $amount)</a> : 더 밝은색을 만듭니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#darken-instance_method" target="_blank" rel="noopener">darken($color, $amount)</a> : 더 어두운색을 만듭니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#saturate-instance_method" target="_blank" rel="noopener">saturate($color, $amount)</a> : 색상의 채도를 올립니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#desaturate-instance_method" target="_blank" rel="noopener">desaturate($color, $amount)</a> : 색상의 채도를 낮춥니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#grayscale-instance_method" target="_blank" rel="noopener">grayscale($color)</a> : 색상을 회색으로 변환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#invert-instance_method" target="_blank" rel="noopener">invert($color)</a> : 색상을 반전시킵니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#rgba-instance_method" target="_blank" rel="noopener">rgba($color, $alpha)</a> : 색상의 투명도를 변경합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#opacify-instance_method" target="_blank" rel="noopener">opacify($color, $amount) / fade-in($color, $amount)</a> : 색상을 더 불투명하게 만듭니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#transparentize-instance_method" target="_blank" rel="noopener">transparentize($color, $amount) / fade-out($color, $amount)</a> : 색상을 더 투명하게 만듭니다.</p><h3><span id="858f0629-890c-49c1-95b1-8204e8627aca">문자(String) 함수</span><a href="#858f0629-890c-49c1-95b1-8204e8627aca" class="header-anchor"></a></h3><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#unquote-instance_method" target="_blank" rel="noopener">unquote($string)</a> : 문자에서 따옴표를 제거합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#quote-instance_method" target="_blank" rel="noopener">quote($string)</a> : 문자에 따옴표를 추가합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#str_insert-instance_method" target="_blank" rel="noopener">str-insert($string, $insert, $index)</a> : 문자의 index번째에 특정 문자를 삽입합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#str_index-instance_method" target="_blank" rel="noopener">str-index($string, $substring)</a> : 문자에서 특정 문자의 첫 index를 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#str_slice-instance_method" target="_blank" rel="noopener">str-slice($string, $start-at, [$end-at])</a> : 문자에서 특정 문자(몇 번째 글자부터 몇 번째 글자까지)를 추출합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#to_upper_case-instance_method" target="_blank" rel="noopener">to-upper-case($string)</a> : 문자를 대문자를 변환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#to_lower_case-instance_method" target="_blank" rel="noopener">to-lower-case($string)</a> : 문자를 소문자로 변환합니다.</p><h3><span id="ff901358-135c-4c56-a3e3-f98967c98e63">숫자(Number) 함수</span><a href="#ff901358-135c-4c56-a3e3-f98967c98e63" class="header-anchor"></a></h3><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#percentage-instance_method" target="_blank" rel="noopener">percentage($number)</a> : 숫자(단위 무시)를 백분율로 변환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#round-instance_method" target="_blank" rel="noopener">round($number)</a> : 정수로 반올림합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#ceil-instance_method" target="_blank" rel="noopener">ceil($number)</a> : 정수로 올림합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#floor-instance_method" target="_blank" rel="noopener">floor($number)</a> : 정수로 내림(버림)합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#abs-instance_method" target="_blank" rel="noopener">abs($number)</a> : 숫자의 절대 값을 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#min-instance_method" target="_blank" rel="noopener">min($numbers…)</a> : 숫자 중 최소 값을 찾습니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#max-instance_method" target="_blank" rel="noopener">max($numbers…)</a> : 숫자 중 최대 값을 찾습니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#random-instance_method" target="_blank" rel="noopener">random()</a> : <code>0</code> 부터 <code>1</code> 사이의 난수를 반환합니다.</p><h3><span id="3225a970-20d9-4d91-b3eb-6208c4aa978d">List 함수</span><a href="#3225a970-20d9-4d91-b3eb-6208c4aa978d" class="header-anchor"></a></h3><p>모든 List 내장 함수는 기존 List 데이터를 갱신하지 않고 새 List 데이터를 반환합니다.<br>모든 List 내장 함수는 Map 데이터에서도 사용할 수 있습니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#length-instance_method" target="_blank" rel="noopener">length($list)</a> : List의 개수를 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#nth-instance_method" target="_blank" rel="noopener">nth($list, $n)</a> : List에서 n번째 값을 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#set_nth-instance_method" target="_blank" rel="noopener">set-nth($list, $n, $value)</a> : List에서 n번째 값을 다른 값으로 변경합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#join-instance_method" target="_blank" rel="noopener">join($list1, $list2, [$separator])</a> : 두 개의 List를 하나로 결합합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#zip-instance_method" target="_blank" rel="noopener">zip($lists…)</a> : 여러 List들을 하나의 다차원 List로 결합합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#index-instance_method" target="_blank" rel="noopener">index($list, $value)</a> : List에서 특정 값의 index를 반환합니다.</p><h3><span id="f47f1fb5-4e79-4e6f-8e0a-ecd24e1ec2b1">Map 함수</span><a href="#f47f1fb5-4e79-4e6f-8e0a-ecd24e1ec2b1" class="header-anchor"></a></h3><p>모든 Map 내장 함수는 기존 Map 데이터를 갱신하지 않고 새 Map 데이터를 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#map_get-instance_method" target="_blank" rel="noopener">map-get($map, $key)</a> : Map에서 특정 key의 value를 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#map_merge-instance_method" target="_blank" rel="noopener">map-merge($map1, $map2)</a> : 두 개의 Map을 병합하여 새로운 Map를 만듭니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#map_keys-instance_method" target="_blank" rel="noopener">map-keys($map)</a> : Map에서 모든 key를 List로 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#map_values-instance_method" target="_blank" rel="noopener">map-values($map)</a> : Map에서 모든 value를 List로 반환합니다.</p><h3><span id="b708fdec-6afc-44a0-bbfa-b4f58bdad505">관리(Introspection) 함수</span><a href="#b708fdec-6afc-44a0-bbfa-b4f58bdad505" class="header-anchor"></a></h3><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#variable_exists-instance_method" target="_blank" rel="noopener">variable-exists(name)</a> : 변수가 현재 범위에 존재하는지 여부를 반환합니다.(인수는 <code>$</code>없이 변수의 이름만 사용합니다.)</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#unit-instance_method" target="_blank" rel="noopener">unit($number)</a> : 숫자의 단위를 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#unitless-instance_method" target="_blank" rel="noopener">unitless($number)</a> : 숫자에 단위가 있는지 여부를 반환합니다.</p><p><a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#comparable-instance_method" target="_blank" rel="noopener">comparable($number1, $number2)</a> : 두 개의 숫자가 연산 가능한지 여부를 반환합니다.</p><h1><span id="0c028d61-4972-493e-a7a3-70d64afe42da">참고 자료(References)</span><a href="#0c028d61-4972-493e-a7a3-70d64afe42da" class="header-anchor"></a></h1><p><a href="http://sass-lang.com/documentation" target="_blank" rel="noopener">http://sass-lang.com/documentation</a><br><a href="https://www.sitepoint.com/sass-basics-operators/" target="_blank" rel="noopener">https://www.sitepoint.com/sass-basics-operators/</a><br><a href="https://sass-guidelin.es/ko/" target="_blank" rel="noopener">https://sass-guidelin.es/ko/</a><br><a href="http://www.thesassway.com/" target="_blank" rel="noopener">http://www.thesassway.com/</a></p>]]></content>
    
    <summary type="html">
    
      Style(CSS) 작업 시 필수가 되어버린 CSS Preprocessor(전처리기) Sass(SCSS)에 대해서 이해하고, CSS로 컴파일하는 방법부터 자세한 SCSS 문법까지 살펴봅니다.
    
    </summary>
    
    
      <category term="css" scheme="https://heropy.blog/tags/css/"/>
    
      <category term="sass" scheme="https://heropy.blog/tags/sass/"/>
    
      <category term="scss" scheme="https://heropy.blog/tags/scss/"/>
    
  </entry>
  
  <entry>
    <title>Parcel - 쉽고 빠르고 강력한 웹앱 번들러 - SASS / PostCSS / Babel / Production</title>
    <link href="https://heropy.blog/2018/01/20/parcel-1-start/"/>
    <id>https://heropy.blog/2018/01/20/parcel-1-start/</id>
    <published>2018-01-20T02:37:35.000Z</published>
    <updated>2018-02-21T03:47:40.000Z</updated>
    
    <content type="html"><![CDATA[<p>2017년 여름에 등장한 <a href="https://parceljs.org/" target="_blank" rel="noopener">Parcel.js</a>는 새로운 웹 애플리케이션 번들러 입니다.<br>기존에 많이 사용하던 <a href="https://webpack.js.org/" target="_blank" rel="noopener">Webpack</a>이나 <a href="https://gulpjs.com/" target="_blank" rel="noopener">Gulp</a>와 다르게 별도 설정 없어도 빠르게 빌드가 가능합니다.<br>빌드를 위해 번들러를 학습하는 시간을 많이 줄일 수 있습니다.</p><div class="toc"><ul><li><a href="b6786df6-9a9b-4ce9-8d7f-4a36ee9d8ed3">특징</a><ul><li><a href="a995522f-7a0e-4125-a7dc-13d3e9741ebd">빠른 빌드 실행</a></li><li><a href="30f2d35c-11f8-4cc7-b069-ac46a5bc2b98">빠른 번들 속도</a></li><li><a href="3890b0eb-1a00-42c3-aa0a-803481308318">Assets(애셋) 기반 번들링</a></li><li><a href="1d2092f6-7966-40dc-836d-ea94d2a38917">자동 변환</a></li><li><a href="61407977-377c-4f6d-bbe6-1572e96a23bd">HMR(Hot Module Replacement)</a></li><li><a href="468b6dc7-c2bb-4866-ad84-5e8d683987c2">설정 없이 코드 분할(Splitting)</a></li><li><a href="07399fdd-c16b-4157-9dad-050188670618">제품화(Production)</a></li></ul></li><li><a href="ebdde711-9446-464a-81dc-a5291263201f">설치</a></li><li><a href="f7a5b0ff-6fe1-43df-afe3-0ed99bcb30ea">프로젝트 시작하기</a><ul><li><a href="41c4cd6f-d1b3-4e7d-bc67-67dded7ca332">SCSS(SASS) 사용하기</a></li><li><a href="c5584684-17e2-4398-92b6-ddd30bafc0ea">PostCSS 사용하기</a></li><li><a href="0607172e-0be1-468e-a45c-a93017003d17">Babel 사용하기</a></li></ul></li></ul></div><h1><span id="b6786df6-9a9b-4ce9-8d7f-4a36ee9d8ed3">특징</span><a href="#b6786df6-9a9b-4ce9-8d7f-4a36ee9d8ed3" class="header-anchor"></a></h1><h2><span id="a995522f-7a0e-4125-a7dc-13d3e9741ebd">빠른 빌드 실행</span><a href="#a995522f-7a0e-4125-a7dc-13d3e9741ebd" class="header-anchor"></a></h2><p>별도의 설정 없이 진입 파일(Entry File)만 지정하면 바로 빌드(Build) 합니다.</p><pre><code class="bash">$ parcel index.html</code></pre><h2><span id="30f2d35c-11f8-4cc7-b069-ac46a5bc2b98">빠른 번들 속도</span><a href="#30f2d35c-11f8-4cc7-b069-ac46a5bc2b98" class="header-anchor"></a></h2><p>멀티코어 컴파일이 가능하고, 재시작을 하더라도 캐시를 이용하여 빠르게 리빌드(Rebuild)를 할 수 있습니다.</p><p>첫번째 빌드 속도: 4.21초<br><img src="/images/screenshot/parcel_build_speed_before.jpg" alt="Parcel Build"></p><p>재시작 후 빌드 속도: 0.769초<br><img src="/images/screenshot/parcel_build_speed_after.jpg" alt="Parcel Rebuild"></p><h2><span id="3890b0eb-1a00-42c3-aa0a-803481308318">Assets(애셋) 기반 번들링</span><a href="#3890b0eb-1a00-42c3-aa0a-803481308318" class="header-anchor"></a></h2><p>HTML, CSS, JavaScript 같은 특정 유형의 애셋을 지원합니다.<br>비슷한 유형의 애셋은 같은 번들로 출력하고 다른 유형의 애셋은 자식 번들로 만들어 부모 번들에 참조합니다.<br>예를들어 <code>main.js</code>파일에서 SCSS 파일을 가져오기(<code>import &#39;./scss/main.scss&#39;</code>) 했다면 다른 번들(<code>.js</code>파일과 <code>.css</code>파일)로 만들어지고 참조를 남깁니다.</p><h2><span id="1d2092f6-7966-40dc-836d-ea94d2a38917">자동 변환</span><a href="#1d2092f6-7966-40dc-836d-ea94d2a38917" class="header-anchor"></a></h2><p>가장 많이 사용하는 <a href="https://babeljs.io/" target="_blank" rel="noopener">Babel</a>, <a href="https://github.com/postcss/postcss" target="_blank" rel="noopener">PostCSS</a>(특히 <a href="https://github.com/postcss/autoprefixer" target="_blank" rel="noopener">Autoprefixer</a>) 같은 트랜스파일러들을 <strong>내장</strong> 하여 지원합니다.<br>모듈 안에 <code>.babelrc</code>, <code>.postcssrc</code> 같은 설정 파일들을 발견하면 자동으로 변환합니다.</p><h2><span id="61407977-377c-4f6d-bbe6-1572e96a23bd">HMR(Hot Module Replacement)</span><a href="#61407977-377c-4f6d-bbe6-1572e96a23bd" class="header-anchor"></a></h2><p>런타임 중 페이지를 새로고침 하지 않고도 모듈을 자동 업데이트하는 HMR(Hot Module Replacement)이 내장되어 있습니다.<br>(자동 업데이트가 실패하면 새로고침 합니다)</p><h2><span id="468b6dc7-c2bb-4866-ad84-5e8d683987c2">설정 없이 코드 분할(Splitting)</span><a href="#468b6dc7-c2bb-4866-ad84-5e8d683987c2" class="header-anchor"></a></h2><p><a href="https://github.com/tc39/proposal-dynamic-import" target="_blank" rel="noopener">Dynamic import() function syntax proposal</a></p><p>Dynamic import 함수 <code>import()</code>를 사용하면 코드를 분할(Splitting)하여 빌드합니다.<br>이는 <code>require()</code>, <code>import</code>와 비슷하게 사용하며 Promise를 반환합니다.<br>즉 모듈을 비동기 로드할 수 있습니다.<br>아래에서 살펴볼 예제들 중 하나를 응용하여 Dynamic import 함수를 사용해 보겠습니다.</p><pre><code class="js">// msg.jsexport const msg = {  hello: &#39;Hello Parcel Dynamic import!!&#39;};</code></pre><pre><code class="js">// main.jsimport(&#39;./msg&#39;)  .then(function (module) {    console.log(module.msg.hello);  // &#39;Hello Parcel Dynamic import!!&#39;  });</code></pre><p>위와 같이 Dynamic import 함수를 사용하면 아래와 같이 파일이 분할 됩니다.<br>하나는 <code>main.js</code>의 번들이고, 나머지 하나는 <code>msg.js</code>의 번들입니다.</p><p><img src="/images/screenshot/parcel_splitting_js_file.jpg" alt="File Splitting"></p><h2><span id="07399fdd-c16b-4157-9dad-050188670618">제품화(Production)</span><a href="#07399fdd-c16b-4157-9dad-050188670618" class="header-anchor"></a></h2><p>개발이 완료되고 배포용 빌드(제품화)를 하게되면 <strong>자동</strong> 으로 Minify(압축)와 Uglify(난독화)를 활성화하여 실행합니다.<br>물론 다음과 같이 비활성화도 가능합니다.</p><pre><code class="bash">$ parcel build entry.js --no-minify</code></pre><h1><span id="ebdde711-9446-464a-81dc-a5291263201f">설치</span><a href="#ebdde711-9446-464a-81dc-a5291263201f" class="header-anchor"></a></h1><p>특징들을 알아봤으니 이제 직접 설치하여 사용하겠습니다.<br>Global로 설치합니다.</p><p>NPM 설치</p><pre><code class="bash">$ npm install -g parcel-bundler</code></pre><p>Yarn 설치</p><pre><code class="bash">$ yarn global add parcel-bundler</code></pre><h1><span id="f7a5b0ff-6fe1-43df-afe3-0ed99bcb30ea">프로젝트 시작하기</span><a href="#f7a5b0ff-6fe1-43df-afe3-0ed99bcb30ea" class="header-anchor"></a></h1><p>간단한 Parcel 앱을 만들어 보겠습니다.<br><code>Parcel-App</code>이라는 폴더를 만들고 <code>package.json</code>를 만들어 줍니다.</p><pre><code class="bash">$ mkdir Parcel-App$ cd Parcel-App$ npm init</code></pre><p>다음의 구조를 만들어 주세요.</p><pre><code class="bash">Parcel-App  ├─src  │  ├─main.css  │  └─main.js  ├─index.html  └─package.json</code></pre><pre><code class="html">&lt;!-- index.html --&gt;&lt;!doctype html&gt;&lt;html lang=&quot;en&quot;&gt;&lt;head&gt;  &lt;meta charset=&quot;UTF-8&quot;&gt;  &lt;meta name=&quot;viewport&quot;        content=&quot;width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0&quot;&gt;  &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;ie=edge&quot;&gt;  &lt;title&gt;Document&lt;/title&gt;&lt;/head&gt;&lt;body&gt;  &lt;h1&gt;Hello Parcel.js!&lt;/h1&gt;  &lt;script src=&quot;./src/main.js&quot;&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><pre><code class="css">/*main.css*/h1 {  color: tomato;}</code></pre><pre><code class="js">// main.jsrequire(&#39;./main.css&#39;);console.log(&#39;Hello Parcel!&#39;);</code></pre><p>Parcel로 빌드하기 위해서 단지 진입 파일을 선택하면 됩니다.</p><pre><code class="bash">$ parcel index.html</code></pre><p>브라우저에서 <a href="http://localhost:1234" target="_blank" rel="noopener">http://localhost:1234</a> 에 접속하면 빌드된 페이지를 볼 수 있습니다.<br>만약 포트번호를 변경하고 싶다면 <code>-p &lt;port number&gt;</code> 옵션으로 덮어쓸 수 있습니다.</p><pre><code class="bash">$ parcel index.html -p 9876</code></pre><p><a href="http://localhost:9876" target="_blank" rel="noopener">http://localhost:9876</a> 으로 접속할 수 있습니다.</p><h2><span id="41c4cd6f-d1b3-4e7d-bc67-67dded7ca332">SCSS(SASS) 사용하기</span><a href="#41c4cd6f-d1b3-4e7d-bc67-67dded7ca332" class="header-anchor"></a></h2><p>SCSS를 컴파일 하기 위해 <code>node-sass</code> 모듈을 설치하겠습니다.</p><pre><code class="bash">$ npm install --save node-sass</code></pre><p>설치가 완료되었다면 모듈이 제대로 동작하는지 확인해 보겠습니다.<br>프로젝트 구조를 살짝 바꿔봅시다.</p><pre><code class="bash">Parcel-App  ├─src  │  ├─scss  │  │  ├─main.scss  │  │  └─_config.scss  │  └─main.js  ├─index.html  └─package.json</code></pre><pre><code class="scss">/*_config.scss*/$color-primary: royalblue;</code></pre><pre><code class="scss">/*main.scss*/@import &#39;config&#39;;h1 {  color: $color-primary;}</code></pre><p><code>scss</code> 디렉토리를 생성했으니 가져오기 경로를 변경하세요.</p><pre><code class="js">// main.jsrequire(&#39;./scss/main.scss&#39;);console.log(&#39;Hello Parcel!&#39;);</code></pre><pre><code class="bash">$ parcel index.html</code></pre><p>잘 동작하나요?<br>별도 설정이 없어도 모듈만 설치하면 바로 사용할 수 있습니다.</p><h2><span id="c5584684-17e2-4398-92b6-ddd30bafc0ea">PostCSS 사용하기</span><a href="#c5584684-17e2-4398-92b6-ddd30bafc0ea" class="header-anchor"></a></h2><p>이번엔 <a href="https://github.com/postcss/postcss" target="_blank" rel="noopener">PostCSS</a>로 CSS를 변환하여 사용하겠습니다.<br>PostCSS plugin 중에서는 Vendor Prefix(공급 업체 접두사)를 제공하는 <a href="https://github.com/postcss/autoprefixer" target="_blank" rel="noopener">autoprefixer</a>를 추가합니다.</p><pre><code class="bash">$ npm install --save autoprefixer</code></pre><p>편집기가 파일을 올바르게 해석할 수 있도록 <code>.postcssrc</code> 파일을 생성합니다.</p><pre><code class="bash">Parcel-App  # ...  │  └─main.js  ├─.postcssrc  ├─index.html  └─package.json</code></pre><pre><code class="json">/*.postcssrc*/{  &quot;plugins&quot;: {    &quot;autoprefixer&quot;: true  }}</code></pre><p>Vendor Prefix가 잘 적용되는지 확인해 보겠습니다.<br>최근에 많이 사용하는 FlexBox 기능을 적용하겠습니다.</p><pre><code class="scss">/*main.scss*/@import &#39;config&#39;;h1 {  color: $color-primary;  display: flex;  justify-content: center;  align-items: center;}</code></pre><pre><code class="bash">$ parcel index.html</code></pre><p>Vendor Prefix 적용 유무를 확인하기 위해 <code>dist</code> 디렉토리에 있는 빌드된 CSS 파일을 열어보겠습니다.</p><pre><code class="css">/*dist/a9b13cfdff4b53240fb7925101bb19f2.css*/h1 {  color: royalblue;  display: -webkit-box;  display: -ms-flexbox;  display: flex;  -webkit-box-pack: center;      -ms-flex-pack: center;          justify-content: center;  -webkit-box-align: center;      -ms-flex-align: center;          align-items: center; }</code></pre><p><code>-webkit-</code>, <code>-ms-</code> 같은 Vendor Prefix가 적용되었습니다.</p><h2><span id="0607172e-0be1-468e-a45c-a93017003d17">Babel 사용하기</span><a href="#0607172e-0be1-468e-a45c-a93017003d17" class="header-anchor"></a></h2><p>Babel을 설치하여 ES6 문법을 사용합시다.</p><pre><code class="bash">$ npm install --save babel-preset-env</code></pre><pre><code class="bash">Parcel-App  # ...  │  └─main.js  ├─.babelrc  ├─.postcssrc  ├─index.html  └─package.json</code></pre><p>프로젝트 루트에 <code>.babelrc</code> 설정 파일을 만들고, ES2015+ 사용을 위해 preset 활성화를 해줍니다.</p><pre><code class="json">/*.babelrc*/{  &quot;presets&quot;: [&quot;env&quot;]}</code></pre><p>역시 구조를 약간 수정하겠습니다.</p><pre><code class="bash">Parcel-App  ├─src  │  ├─js  │  │  ├─main.js  │  │  └─msg.js  │  └─scss  │     ├─main.scss  │     └─_config.scss  ├─.babelrc  # ...</code></pre><pre><code class="js">// msg.jsexport const msg = {  hello: &#39;Hello Parcel with Babel!!&#39;};</code></pre><pre><code class="js">// main.jsimport &#39;../scss/main.scss&#39;;import { msg } from &#39;./msg&#39;;console.log(msg.hello);</code></pre><p>변경된 구조에 맞게 호출 경로도 같이 수정합니다.</p><pre><code class="html">&lt;!-- index.html --&gt;&lt;!-- ... --&gt;  &lt;h1&gt;Hello Parcel.js!&lt;/h1&gt;  &lt;script src=&quot;src/js/main.js&quot;&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><pre><code class="bash">$ parcel index.html</code></pre><p>개발자 도구 Console에 <code>&#39;Hello Parcel with Babel!!&#39;</code> 메시지가 잘 보이나요?</p><p>만약 개발이 완료되고 배포용 빌드(제품화)를 원한다면 다음과 같이 입력합니다.<br>코드의 Minify(압축)와 Uglify(난독화)를 자동으로 실행합니다.</p><pre><code class="bash">$ parcel build index.html</code></pre>]]></content>
    
    <summary type="html">
    
      Parcel는 2017년 여름에 등장한 새로운 웹 애플리케이션 번들러로, 별도의 설정 파일이 없고 대부분의 애셋을 지원하는 등 강력한 기능들을 가지고 있습니다.
    
    </summary>
    
    
      <category term="parcel" scheme="https://heropy.blog/tags/parcel/"/>
    
  </entry>
  
  <entry>
    <title>GitHub와 Netlify를 이용한 쉽고 빠른 HTTPS 무료 호스팅</title>
    <link href="https://heropy.blog/2018/01/10/netlify/"/>
    <id>https://heropy.blog/2018/01/10/netlify/</id>
    <published>2018-01-10T05:33:14.000Z</published>
    <updated>2018-02-20T05:26:11.000Z</updated>
    
    <content type="html"><![CDATA[<p>많은 사람들이 대표적인 <a href="https://ko.wikipedia.org/wiki/PaaS" target="_blank" rel="noopener">PaaS</a>로 <a href="https://www.heroku.com/" target="_blank" rel="noopener">Heroku</a>를 꼽습니다.<br>Node.js를 사용하는 저로서는 GitHub 나 Dropbox 를 기반으로 쉽고 빠르게 호스팅할 수 있는 아주 좋은 서비스죠.<br>하지만 Heroku는 일반적으로 백엔드 호스팅을 위해 사용하는데, 단순한 정적(Static) 웹사이트를 호스팅하기에는 약간 군더더기가 있다고 느끼고 있었죠.<br>그 와중에 <a href="https://www.netlify.com/" target="_blank" rel="noopener">Netlify</a>를 알게되어 사용하게 되었습니다.<br>Heroku 보다 더욱 쉽고 빠르게 정적 웹사이트를 호스팅할 수 있습니다.</p><div class="toc"><ul><li><a href="2f2fe8a7-f052-4a76-ae90-16e7442ec1ff">Netlify 란?</a></li><li><a href="8cac01b9-1f2a-41ff-b83f-e93832778e87">Netlify 사용법</a><ul><li><a href="1938dab9-5ab4-45fe-8a47-5be31b60aa14">GitHub에 프로젝트 푸쉬</a></li><li><a href="be45b4b1-81c2-4426-ab48-32e31ff2bc63">Netlify에서 새로운 사이트 생성</a><ul><li><a href="e8e45356-a81f-4ce1-8df7-f55d9ddfc700">액세스 제한</a></li><li><a href="c0931d78-1cef-4305-a142-b50b4ba72d85">승인 요청</a></li><li><a href="882c0f98-4833-4f42-bcef-b3b1e965f0ed">저장소 선택</a></li><li><a href="7bfae09f-b48a-4792-bd8c-a2772dcd683c">기본 빌드 설정</a></li><li><a href="f1c538be-6e12-4f50-93d9-360474abbc9a">사이트 이름(도메인) 설정</a></li></ul></li><li><a href="9be11d6f-7f76-4879-8164-fd2d39cd8aff">사용자 정의 도메인(Custom Domain) 설정</a><ul><li><a href="a0d145b0-47fb-478c-8332-27e32030c5ba">DNS 설정</a></li><li><a href="7f82a4bb-413d-44fd-a64e-243e9d2dccaa">네임서버 변경</a></li></ul></li><li><a href="eac4f850-e3f6-4656-9a3a-65dfb773a24e">무료 TLS 인증서로 HTTPS 보안 및 암호화를 추가</a><ul><li><a href="c1e7849b-380f-4a99-8d0f-947cbb0bd250">DNS 구성 확인</a></li><li><a href="12c38ac8-429f-421f-95f0-c7d41a216523">Let’s Encrypt 인증서 프로비저닝</a></li><li><a href="1e92bd39-20e0-4096-8310-442599c1b3ba">프로젝트 업데이트</a></li></ul></li></ul></li></ul></div><h1><span id="2f2fe8a7-f052-4a76-ae90-16e7442ec1ff">Netlify 란?</span><a href="#2f2fe8a7-f052-4a76-ae90-16e7442ec1ff" class="header-anchor"></a></h1><p><a href="https://www.netlify.com/" target="_blank" rel="noopener">Netlity</a>는 GitHub, GitLab 등과 계정 연동 및 쉬운 호스팅을 제공하며,<br>CDN, <a href="https://ko.wikipedia.org/wiki/%EC%A7%80%EC%86%8D%EC%A0%81_%EB%B0%B0%ED%8F%AC" target="_blank" rel="noopener">Continuous Deployment(지속적 배포)</a>, One-Click HTTPS 제공 등 고성능 사이트 / 웹 응용 프로그램을 제작하는데 필요한 쉽고 빠른 다양한 서비스들을 제공합니다.</p><h1><span id="8cac01b9-1f2a-41ff-b83f-e93832778e87">Netlify 사용법</span><a href="#8cac01b9-1f2a-41ff-b83f-e93832778e87" class="header-anchor"></a></h1><p>간단하지만 헷갈릴 수 있는 부분들을 하나씩 진행하면서 살펴보겠습니다.</p><h2><span id="1938dab9-5ab4-45fe-8a47-5be31b60aa14">GitHub에 프로젝트 푸쉬</span><a href="#1938dab9-5ab4-45fe-8a47-5be31b60aa14" class="header-anchor"></a></h2><p>정적(Static) 웹사이트를 완성했다면 GitHub에 Push합니다.<br>보통 <code>public/</code>이나 <code>dist/</code> 정도가 정적 디렉토리(Public directory)가 됩니다.<br>혹시 다른 이름으로 되어 있다면 Netlify에서 설정할 수 있습니다.</p><blockquote><p>혹시 Netlify에서 반복되는 <code>Deploy Failed</code> 메세지로 스트레스를 받고 싶지 않다면, Public directory 안에는 <code>index.html</code>을 두는 것을 권장합니다.<br>혹시 EJS나 PUG 같은 템플릿엔진을 사용한다면 컴파일(빌드) 후 푸쉬하시고, 마찬가지로 SASS나 Stylus 같은 CSS preprocessor를 사용할 때도 컴파일 후 사용하세요.</p></blockquote><h2><span id="be45b4b1-81c2-4426-ab48-32e31ff2bc63">Netlify에서 새로운 사이트 생성</span><a href="#be45b4b1-81c2-4426-ab48-32e31ff2bc63" class="header-anchor"></a></h2><p>Netlify로 이동하여 Sign-up(회원가입) 합니다.<br>원하는 방식으로 회원 가입 후 접속합니다.</p><p><img src="/images/screenshot/netlify_1_login.jpg" alt="Netlify Login"></p><p>접속 후 바로 ‘New site from git’를 선택할 수 있습니다.<br>그러면 Git 솔루션를 선택할 수 있습니다.</p><p><img src="/images/screenshot/netlify_1_new_site.jpg" alt="Netlify Create a new site"></p><p>다음과 같은 Git 솔루션을 지원합니다.</p><ul><li><a href="https://github.com/" target="_blank" rel="noopener">GitHub</a></li><li><a href="https://about.gitlab.com/" target="_blank" rel="noopener">GitLab</a></li><li><a href="https://bitbucket.org/" target="_blank" rel="noopener">Bitbucket</a></li></ul><h3><span id="e8e45356-a81f-4ce1-8df7-f55d9ddfc700">액세스 제한</span><a href="#e8e45356-a81f-4ce1-8df7-f55d9ddfc700" class="header-anchor"></a></h3><p>업체를 선택하기 전에 <code>Limit GitHub access to public repositories.</code>를 체크할 수 있습니다.</p><p><img src="/images/screenshot/netlify_1_limit_github_access.jpg" alt="Netlify Limit GitHub Access"></p><p>민감한 저장소에 대한 액세스 권한을 부여하는 것에 대해 걱정이된다면 GitHub를 사용하여 응용 프로그램 액세스를 제한 할 수 있습니다.<br>자세한 내용은 <a href="https://www.netlify.com/docs/github-permissions/" target="_blank" rel="noopener">Netlify - GitHub Permissions</a>에서 확인할 수 있습니다.<br>원할 경우 체크합니다.</p><p>전 단계에서 프로젝트를 GitHub에 푸쉬했으니 업체 중 ‘GitHub’를 선택하여 진행합니다.</p><h3><span id="c0931d78-1cef-4305-a142-b50b4ba72d85">승인 요청</span><a href="#c0931d78-1cef-4305-a142-b50b4ba72d85" class="header-anchor"></a></h3><p>‘Limit GitHub access to public repositories. What’s this?’를 체크하고 넘어왔다면 다음과 같은 승인 화면을 볼 수 있습니다.<br>‘Authorize netlify’를 선택하여 승인합니다.</p><p><img src="/images/screenshot/netlify_1_authorize_netlify.jpg" alt="Netlify Authorize"></p><p>권한 설정을 위해 GitHub 비밀번호를 입력합니다.</p><p><img src="/images/screenshot/netlify_1_authorize_netlify_password.jpg" alt="Netlify Authorize Confirm Password"></p><h3><span id="882c0f98-4833-4f42-bcef-b3b1e965f0ed">저장소 선택</span><a href="#882c0f98-4833-4f42-bcef-b3b1e965f0ed" class="header-anchor"></a></h3><p>나의 GitHub 저장소(repository) 목록이 나타납니다.<br>여기서 상위 단계에서 푸쉬했던 저장소를 찾아 선택합니다.</p><p><img src="/images/screenshot/netlify_1_select_repository.jpg" alt="Netlify Select repository"></p><h3><span id="7bfae09f-b48a-4792-bd8c-a2772dcd683c">기본 빌드 설정</span><a href="#7bfae09f-b48a-4792-bd8c-a2772dcd683c" class="header-anchor"></a></h3><p><img src="/images/screenshot/netlify_1_deploy_setting.jpg" alt="Netlify Deploy Setting"></p><p>빌드 커맨드(Build Command)가 있다면 설정합니다.<br>예를들면 <code>npm start</code>, <code>npm run prod</code>, <code>grunt</code> 등의 빌드용 명령을 작성할 수 있습니다.<br>필요하지 않다면 값을 적지 않아도 됩니다.</p><p>다음으로 정적 디렉토리(Public Directory)를 설정합니다.<br>보통 <code>public/</code>이나 <code>dist/</code> 정도가 정적 디렉토리(Public directory)가 됩니다.<br>설정하지 않는다면 루트(root, <code>/</code>)에서 검색합니다.</p><p>마지막으로 <code>Deploy site</code> 버튼을 눌러 새로운 사이트를 배포합니다.<br>약간의 로딩이 끝나고 잘 배포되었다면 <code>Your site is deployed</code> 메시지를 볼 수 있습니다.</p><p><img src="/images/screenshot/netlify_1_deploying_your_site.jpg" alt="Netlify Deploying Your Site"><br><img src="/images/screenshot/netlify_1_deploy_log.jpg" alt="Netlify Deploy Log"></p><p>만약 <code>Deploy Failed</code> 메시지가 뜬다면 ‘Deploy Log’를 잘 확인하시고 프로젝트 구조를 Netlify에 맞게 변경하시길 권장합니다.<br>이제 1단계의 설정이 끝났습니다.</p><h3><span id="f1c538be-6e12-4f50-93d9-360474abbc9a">사이트 이름(도메인) 설정</span><a href="#f1c538be-6e12-4f50-93d9-360474abbc9a" class="header-anchor"></a></h3><p>우리의 사이트 배포는 끝났고 당장 사용할 수 있습니다.<br>하지만 당장은 Netlify에서 제공하는 다음과 같은 (예쁘지 않은) 도메인을 사용해야 합니다.</p><pre><code class="domain">https://happy-noether-c27ffa.netlify.com/</code></pre><p>2단계로 커스텀(Custom) 도메인을 사용할 수 있습니다만 그 전에 Netlify에서 제공하는 도메인을 (조금 더 예쁘게) 수정해서 사용할 수 있습니다.</p><p><code>Site setting</code> 버튼을 선택합니다.</p><p><img src="/images/screenshot/netlify_1_site_setting_btn.jpg" alt="Netlify Site Setting Button"></p><p>‘Site information &gt; Site name:’ 을 클릭하여 이름을 수정할 수 있습니다.</p><p><img src="/images/screenshot/netlify_1_site_naming.jpg" alt="Netlify Site Naming"></p><p>좀 더 나은 <strong>고유 이름</strong> 을 선택할 수 있습니다.<br>당장 나의 사용자 정의 도메인이 없을 경우 유용합니다.</p><h2><span id="9be11d6f-7f76-4879-8164-fd2d39cd8aff">사용자 정의 도메인(Custom Domain) 설정</span><a href="#9be11d6f-7f76-4879-8164-fd2d39cd8aff" class="header-anchor"></a></h2><p>이제 별도의 사용자 정의 도메인을 설정하겠습니다.<br>2단계에서 ‘Set up a custom domain’을 선택합니다.</p><p><img src="/images/screenshot/netlify_2_set_custom_domain.jpg" alt="Netlify Set up a custom domain"></p><p>‘Add custom domain’ 버튼을 눌러 보유하고 있는 나의 도메인을 추가합니다.</p><p><img src="/images/screenshot/netlify_2_add_custom_domain.jpg" alt="Netlify Add custom domain"><br><img src="/images/screenshot/netlify_2_add_custom_domain_naming.jpg" alt="Netlify Add custom domain naming"></p><h3><span id="a0d145b0-47fb-478c-8332-27e32030c5ba">DNS 설정</span><a href="#a0d145b0-47fb-478c-8332-27e32030c5ba" class="header-anchor"></a></h3><p>다음과 같이 도메인이 등록되었고 Netlify DNS를 설정(‘Set up Netlify DNS’)해야 합니다.</p><p><img src="/images/screenshot/netlify_2_set_up_netlify_dns.jpg" alt="Netlify Set up Netlify DNS"></p><p>사용자 정의 도메인에 대한 DNS 레코드가 포함될 DNS 영역을 만들어야 합니다. 그러면 사이트의 필수 DNS 레코드가 자동으로 생성됩니다.</p><p><img src="/images/screenshot/netlify_2_create_dns_zone.jpg" alt="Netlify Create DNS Zone"><br><img src="/images/screenshot/netlify_2_add_dns_records.jpg" alt="Netlify Add DNS Records"><br><img src="/images/screenshot/netlify_2_hostnames.jpg" alt="Netlify Hostnames"></p><h3><span id="7f82a4bb-413d-44fd-a64e-243e9d2dccaa">네임서버 변경</span><a href="#7f82a4bb-413d-44fd-a64e-243e9d2dccaa" class="header-anchor"></a></h3><p>마지막 단계로, 도메인 제공 업체(<a href="https://kr.godaddy.com" target="_blank" rel="noopener">GoDaddy</a>, <a href="https://www.cafe24.com/?controller=domain_main" target="_blank" rel="noopener">Cafe24</a>, <a href="https://www.gabia.com" target="_blank" rel="noopener">Gabia</a> 등..)의 계정에 로그인하고 제공되는 호스트 이름으로 네임 서버를 변경합니다.<br>참고로 저는 <a href="https://kr.godaddy.com" target="_blank" rel="noopener">GoDaddy</a>에서 도메인을 구입했습니다.</p><p><img src="/images/screenshot/netlify_2_update_nameserver_enter.jpg" alt="Netlify Update Nameserver Enter"></p><p>도메인의 네임서버를 변경 후 잠시(5~10분) 기다리면 완료됩니다.<br>이제 사용자 정의 도메인을 사용할 수 있습니다.</p><h2><span id="eac4f850-e3f6-4656-9a3a-65dfb773a24e">무료 TLS 인증서로 HTTPS 보안 및 암호화를 추가</span><a href="#eac4f850-e3f6-4656-9a3a-65dfb773a24e" class="header-anchor"></a></h2><p>3단계로 HTTPS 보안 및 암호화를 추가하겠습니다.<br>Netlify의 최대 장점은 <strong>무료 SSL 자동 발급</strong> 이라 할 수 있습니다.<br>정말 간단한 절차로 HTTPS를 사용할 수 있습니다.</p><h3><span id="c1e7849b-380f-4a99-8d0f-947cbb0bd250">DNS 구성 확인</span><a href="#c1e7849b-380f-4a99-8d0f-947cbb0bd250" class="header-anchor"></a></h3><p>3단계의 ‘Secure your site with HTTPS’ 버튼을 선택합니다.<br>페이지 중간에 ‘HTTPS’ 영역에서 ‘Verify DNS configuration’ 버튼을 선택합니다.</p><p><img src="/images/screenshot/netlify_3_verify_dns_configuration.jpg" alt="Netlify Verify DNS configuration"></p><p>DNS 변경 사항이 전파될 때까지 1시간 정도 기다립니다.</p><h3><span id="12c38ac8-429f-421f-95f0-c7d41a216523">Let’s Encrypt 인증서 프로비저닝</span><a href="#12c38ac8-429f-421f-95f0-c7d41a216523" class="header-anchor"></a></h3><p>1시간 정도 기다린 후 HTTPS를 사용할 준비가 되면 ‘Let’s Encrypt certificate’ 버튼이 생성됩니다.<br>버튼을 누르면, <a href="https://letsencrypt.org/" target="_blank" rel="noopener">Let ‘s Encrypt</a>에서 TLS 인증서를 제공하고 CDN에 설치합니다.</p><p><img src="/images/screenshot/netlify_3_encrypt_certificate.jpg" alt="Netlify Let&#39;s Encrypt certificate"><br><img src="/images/screenshot/netlify_3_automatic_tls_setup.jpg" alt="Netlify Automatic TLS setup"><br><img src="/images/screenshot/netlify_3_provisioning.jpg" alt="Netlify Provisioning"></p><p>역시 몇 분 기다리면 무료 인증서의 <a href="https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D" target="_blank" rel="noopener">프로비저닝</a>이 완료됩니다.<br>만약 사용에 대한 대가를 지불하고 싶다면 <a href="https://letsencrypt.org/donate/" target="_blank" rel="noopener">Donate to Let’s Encrypt</a>를 통해 기부할 수 있습니다.<br>Let’s Encrypt에 대해 좀 더 알고 싶다면 <a href="https://blog.outsider.ne.kr/1178" target="_blank" rel="noopener">&gt; Lets’ Encrypt로 무료로 HTTPS 지원하기</a>를 참고하세요.</p><p><img src="/images/screenshot/netlify_3_https_enabled.jpg" alt="Netlify HTTPS enabled"></p><p>이제 HTTPS를 사용할 수 있습니다.</p><p><img src="/images/screenshot/netlify_3_https_enabled_href.jpg" alt="Netlify HTTPS enabled href"></p><h3><span id="1e92bd39-20e0-4096-8310-442599c1b3ba">프로젝트 업데이트</span><a href="#1e92bd39-20e0-4096-8310-442599c1b3ba" class="header-anchor"></a></h3><p>프로젝트를 수정하여 GitHub에 푸쉬하면 Netlify에서 자동으로 빌드하여 배포합니다.<br>푸쉬 후 자동 배포까지 대략 1~5분이 소요됩니다.</p><p>지금까지 Netlify를 이용하여 쉽고 빠르게 GitHub를 기반으로 하는 HTTPS 웹사이트를 호스팅하였습니다.<br>무료 비공개 저장소를 원한다면 <a href="https://about.gitlab.com/" target="_blank" rel="noopener">GitLab</a>도 좋은 선택이 될 것입니다.</p>]]></content>
    
    <summary type="html">
    
      GitHub, GitLab 등과 계정 연동 및 쉬운 호스팅을 제공하는 PaaS 서비스로, Continuous Deployment, One-Click HTTPS 제공 등 고성능 사이트 / 웹 응용 프로그램을 제작하는데 필요한 쉽고 빠른 다양한 서비스들을 제공합니다.
    
    </summary>
    
    
      <category term="netlify" scheme="https://heropy.blog/tags/netlify/"/>
    
      <category term="hosting" scheme="https://heropy.blog/tags/hosting/"/>
    
      <category term="https" scheme="https://heropy.blog/tags/https/"/>
    
  </entry>
  
  <entry>
    <title>Vuex - 6 - Helpers(Mapping)</title>
    <link href="https://heropy.blog/2018/01/04/vuex-6-mapping/"/>
    <id>https://heropy.blog/2018/01/04/vuex-6-mapping/</id>
    <published>2018-01-04T01:04:15.000Z</published>
    <updated>2018-02-12T16:04:04.000Z</updated>
    
    <content type="html"><![CDATA[<p>기존에 작성한 코드를 조금 수정해서 Vuex Helpers를 사용해 보겠습니다.<br>Helpers는 컴포넌트의 <code>computed</code>나 <code>methods</code> 속성에 Vuex 저장소(Store) 내용을 바인딩하여 좀 더 직관적으로 사용할 수 있게 도와줍니다.</p><p>Vuex에서 사용하는 상태(State), 게터(Getters), 변이(Mutations), 액션(Actions)을 맵핑할 수 있습니다.</p><ul><li>mapState</li><li>mapGetters</li><li>mapMutations</li><li>mapActions</li></ul><p>먼저, <code>FruitsList.vue</code>를 수정하겠습니다.</p><pre><code class="vue">&lt;!--FruitsList.vue--&gt;&lt;!-- ... --&gt;&lt;script&gt;  import { mapState, mapGetters } from &#39;vuex&#39;;  export default {    computed: {      ...mapState([  // ES6 - Spread        &#39;fruits&#39;      ]),      ...mapGetters([  // ES6 - Spread        &#39;upperCaseFruits&#39;      ])    }  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre><p>상태(State)를 바인딩하기 위해서 <code>mapState</code>를 사용했고,<br>게터(Getters)를 위해 <code>mapGetters</code>를 사용했습니다.</p><p>문법적으로 객체의 속성이나 메소드 자리에서는 함수 실행을 할 수 없는데, 함수 앞에 전개 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_operator" target="_blank" rel="noopener">연산자(Spread / <code>...</code>)</a>를 사용하면 객체의 일부로 확장해서 사용할 수 있습니다.<br>Helpers는 이 방식을 사용하여 작성합니다.</p><p>우선 문제가 없는지 결과를 확인하세요.<br>혹시 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_operator" target="_blank" rel="noopener">전개 연산자</a> 때문에 문제가 있다면 ‘Babel Preset’이 필요할 수 있습니다.</p><pre><code class="bash">$ npm install --save-dev babel-preset-es2015</code></pre><pre><code class="json">// .babelrc{  &quot;presets&quot;: [&quot;es2015&quot;]}</code></pre><p>잘 나오는지 확인하셨다면 다음 컴포넌트들도 수정하겠습니다.</p><pre><code class="vue">&lt;!--FruitsPrice.vue--&gt;&lt;!-- ... --&gt;&lt;script&gt;  import { mapState } from &#39;vuex&#39;;  export default {    computed: {      ...mapState([&#39;fruits&#39;])    }  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre><p><code>BtnDiscount.vue</code>의 경우 <code>mapActions</code>를 사용합니다.</p><p>기존 코드에서는 <code>dispatch</code> 메소드에 Payload 인자로 할인율을 전달해서 사용했습니다.</p><pre><code class="js">discountPrice() {  this.$store.dispatch(&#39;discountPrice&#39;, {    discountRate: 20  });}</code></pre><p>하지만 <code>mapActions</code>로 맵핑하면 Payload 인자를 직접 사용할 수 없기 때문에, 별도 데이터를 만들어서 클릭 이벤트가 발생할 때 인자로 사용합니다.</p><pre><code class="vue">&lt;!--BtnDiscount.vue--&gt;&lt;template&gt;  &lt;div class=&quot;btn&quot; @click=&quot;discountPrice(discountData)&quot;&gt;DISCOUNT PRICE&lt;/div&gt;&lt;/template&gt;&lt;script&gt;  import { mapActions } from &#39;vuex&#39;;  export default {    data() {      return {        discountData: {          rate: 20        }      }    },    methods: {      ...mapActions([        &#39;discountPrice&#39;      ])    }  }&lt;/script&gt;&lt;!-- ... --&gt;</code></pre><p>이벤트 발생의 전달 인자로 <code>discountData</code>라는 객체를 만듭니다.<br><code>discountData</code> 객체는 <code>rate</code> 속성을 가지는데 저장소(Store)에는 Payload가 <code>discountRate</code> 속성을 사용하도록 작성되어 있습니다.<br>이름을 수정하겠습니다.</p><pre><code class="js">// store.js// ...export const store = new Vuex.Store({  state: {    // ...  },  getters: {    // ...  },  mutations: {    discountPrice(state, payload) {      state.fruits.forEach(fruit =&gt; {        fruit.price *= (100 - payload.rate) / 100;  // discountRate &gt;&gt; rate      });    }  },  actions: {    // ...  }});</code></pre><p>Helpers를 사용해서 모두 수정했습니다.<br>잘 동작하는지 확인하세요.</p><p>혹시 매핑한 이름이 최적화가 아니거나 적당하지 않다면, 다음과 같이 컴포넌트 안에서 맵핑하는 메소드의 이름을 변경해 사용할 수 있습니다.</p><pre><code class="vue">&lt;!--BtnDiscount.vue--&gt;&lt;template&gt;  &lt;div class=&quot;btn&quot; @click=&quot;price(discountData)&quot;&gt;DISCOUNT PRICE&lt;/div&gt;&lt;/template&gt;&lt;script&gt;  import { mapActions } from &#39;vuex&#39;;  export default {    data() {      return {        discountData: {          rate: 20        }      }    },    methods: {      ...mapActions({        price: &#39;discountPrice&#39;      })    }  }&lt;/script&gt;&lt;!-- ... --&gt;</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;기존에 작성한 코드를 조금 수정해서 Vuex Helpers를 사용해 보겠습니다.&lt;br&gt;Helpers는 컴포넌트의 &lt;code&gt;computed&lt;/code&gt;나 &lt;code&gt;methods&lt;/code&gt; 속성에 Vuex 저장소(Store) 내용을 바인딩하여 
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="vue" scheme="https://heropy.blog/tags/vue/"/>
    
      <category term="vuex" scheme="https://heropy.blog/tags/vuex/"/>
    
  </entry>
  
  <entry>
    <title>Vuex - 5 - Actions / Context / Vue.js Devtools</title>
    <link href="https://heropy.blog/2018/01/01/vuex-5-actions/"/>
    <id>https://heropy.blog/2018/01/01/vuex-5-actions/</id>
    <published>2018-01-01T07:54:43.000Z</published>
    <updated>2018-02-12T07:33:46.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Vuex-Actions"><a href="#Vuex-Actions" class="headerlink" title="Vuex Actions"></a>Vuex Actions</h1><p>Vuex의 액션(Actions)은 변이(Mutations)와 많이 비슷합니다.<br>변이(Mutations)는 <strong>동기적</strong>, 액션(Actions)은 <strong>비동기적</strong> 으로 작업이 처리되고 그렇게 구분해야 합니다.</p><p>변이(Mutations)는 상태(State)의 관리에 초점을 두고 있는데, 비동기 작업이 포함되면 각 컴포넌트에서 변이가 작동할 때 프로그램 흐름을 추적하기가 굉장히 어려워 집니다.<br>따라서 액션(Actions)을 만들어 비동기 작업 등을 구분해야 합니다.</p><p>하지만 동기와 비동기 처리를 구분해야 하는 상황이 아니라면 변이와 액션의 차이 필요성을 느끼지 못할 수 있습니다.<br>일단 이해를 위해 단순한 비동기 처리 할인 버튼을 만들어 보겠습니다.</p><p>먼저 변이와 액션의 차이점을 좀 더 쉽게 이해하기 위해서 <a href="https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?utm_source=chrome-ntp-icon" target="_blank" rel="noopener">Vue.js devtools</a>를 설치하겠습니다.</p><p><img src="/images/screenshot/vuex_vuejs_devtools_screenshot.jpg" alt="Vue.js devtools"></p><p><a href="https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?utm_source=chrome-ntp-icon" target="_blank" rel="noopener">Vue.js devtools</a>는 Vue.js를 사용하는 응용 프로그램 디버깅을 위한 Chrome 및 Firefox 확장 프로그램입니다.</p><p>설치가 완료된 후 앱을 한 번 리부트합니다.</p><pre><code class="bash">$ npm run dev</code></pre><p>개발자 도구를 켜면 상단에 다음과 같이 ‘Vue’ 탭이 생기고 디버깅이 가능합니다.</p><p>현재 앱에서 사용하는 컴포넌트들을 확인할 수 있는 ‘Switch to Components’ 탭이 있고,</p><p><img src="/images/screenshot/vuex_vue_devtools_components_panel_screenshot.jpg" alt="Vue.js devtools"></p><p>Vuex에서 발생하는 동작들을 확인할 수 있는 ‘Switch to Vuex’ 탭도 있습니다.</p><p><img src="/images/screenshot/vuex_vue_devtools_vuex_panel_screenshot.jpg" alt="Vue.js devtools"></p><p>설치가 완료되었다면, 비동기 처리를 위해 할인(Discount Price) 버튼을 클릭하여 2초 후 할인이 적용되도록 코드를 수정하겠습니다.</p><pre><code class="js">// store.js// ...export const store = new Vuex.Store({  state: {    // ...  },  getters: {    // ...  },  mutations: {    discountPrice(state, payload) {      setTimeout(() =&gt; {        state.fruits.forEach(fruit =&gt; {          fruit.price *= (100 - payload.discountRate) / 100;        });      }, 2000);    }  }});</code></pre><p>이제 버튼을 눌러보세요.<br>버튼을 누를 때마다 개발자 도구 ‘Vue.js devtools’ 패널에 <code>discountPrice</code> 변이가 즉시 기록되지만, 실제로 화면에 할인된 가격이 적용되는 변화는 2초 후 일어납니다.<br>이렇게 변이(Mutations)에서 비동기 처리를 이용하면 추적이 어렵습니다.</p><p><img src="/images/screenshot/vuex_vue_devtools_vuex_panel_async_mutations_screenshot.jpg" alt="Async Mutations"></p><p>그래서 비동기 처리는 액션(Actions)으로 작성해야 합니다.<br>다음과 같이 <code>actions</code> 속성을 추가하고 <code>discountPrice</code> 액션을 만들어 줍니다.<br>비동기 처리를 위해 <code>setTimeout</code> 함수를 실행하고 내부에서 <code>commit</code>으로 변이를 호출합니다.<br>당연히 <code>discountPrice</code> 변이에서는 비동기 처리를 제거합니다.</p><pre><code class="js">// store.js// ...export const store = new Vuex.Store({  state: {    // ...  },  getters: {    // ...  },  mutations: {    discountPrice(state, payload) {      state.fruits.forEach(fruit =&gt; {        fruit.price *= (100 - payload.discountRate) / 100;      });    }  },  actions: {    discountPrice(context, payload) {      setTimeout(() =&gt; {        context.commit(&#39;discountPrice&#39;, payload);      }, 2000);    }  }});</code></pre><p>변이(Mutations)를 호출하기 위해서는 <code>commit</code> 메소드를 사용했습니다.<br>하지만 액션(Actions)을 호출하기 위해서는 <code>dispatch</code> 메소드를 사용해야 합니다.</p><pre><code class="vue">&lt;!--BtnDiscount.vue--&gt;&lt;!-- ... --&gt;&lt;script&gt;  export default {    methods: {      discountPrice() {        // 액션 호출        this.$store.dispatch(&#39;discountPrice&#39;, {          discountRate: 20        });      }    }  }&lt;/script&gt;&lt;!-- ... --&gt;</code></pre><p>자 이제 비동기 처리를 변이에서 액션으로 옮겼으니 실제로 동작시켜 봅시다.<br>버튼을 눌러보면 ‘Vue.js devtools’ 패널에 <code>discountPrice</code> 변이가 즉시 표시되지 않고, 2초 후 표시됩니다.<br>즉, 비동기 처리는 액션(Actions)으로 관리해야 추적이 가능하단 부분이죠.</p><p>추가로 <code>actions</code>의 메소드는 <code>context</code>라는 인자를 갖는데, 이는 저장소(Store)의 <code>state</code>, <code>getters</code>, <code>commit</code> 등에 접근할 수 있습니다.<br><code>context</code>를 콘솔에서 확인해 보면 다음과 같습니다.</p><p><img src="/images/screenshot/vuex_actions_context_object_screenshot.jpg" alt="Context in Actions"></p><p>특히 <code>commit</code>을 여러 번 호출할 경우 다음과 같이 수정할 수 있습니다.</p><pre><code class="js">// store.js// ...actions: {  discountPrice({ commit }, payload) {  // ES6 - Destructuring    setTimeout(() =&gt; {      commit(&#39;discountPrice&#39;, payload);    }, 2000);  }}// ...</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Vuex-Actions&quot;&gt;&lt;a href=&quot;#Vuex-Actions&quot; class=&quot;headerlink&quot; title=&quot;Vuex Actions&quot;&gt;&lt;/a&gt;Vuex Actions&lt;/h1&gt;&lt;p&gt;Vuex의 액션(Actions)은 변이(Mutation
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="vue" scheme="https://heropy.blog/tags/vue/"/>
    
      <category term="vuex" scheme="https://heropy.blog/tags/vuex/"/>
    
  </entry>
  
  <entry>
    <title>Vuex - 4 - Mutations / Payload</title>
    <link href="https://heropy.blog/2017/12/31/vuex-4-mutations/"/>
    <id>https://heropy.blog/2017/12/31/vuex-4-mutations/</id>
    <published>2017-12-31T04:33:00.000Z</published>
    <updated>2018-02-12T07:33:46.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Vuex-Mutations"><a href="#Vuex-Mutations" class="headerlink" title="Vuex Mutations"></a>Vuex Mutations</h1><p>Vuex 변이(Mutations)는 저장소의 상태(State)를 변경하는 방법입니다.</p><p>과일의 가격을 할인하는 버튼을 추가하여 변이(Mutations)를 적용해 보겠습니다.<br>버튼을 사용하기 위해 <code>src/components</code> 디렉토리에 <code>BtnDiscount.vue</code> 컴포넌트를 추가합니다.</p><pre><code class="bash">vue-simple-app  #...  ├─src  │  ├─assets  │  ├─components  │  │  ├─BtnDiscount.vue  │  │  ├─FruitsList.vue  │  │  └─FruitsPrice.vue  │  ├─css  │  └─store  #...</code></pre><p>약간의 스타일이 추가된 버튼(<code>BtnDiscount.vue</code>) 컴포넌트를 만들어 줍니다.</p><pre><code class="vue">&lt;!--BtnDiscount.vue--&gt;&lt;template&gt;  &lt;div class=&quot;btn&quot;&gt;DISCOUNT PRICE&lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {  }&lt;/script&gt;&lt;style scoped&gt;  .btn {    max-width: 700px;    margin: 10px auto;    padding: 10px 0;    color: white;    text-align: center;    background: salmon;    border-radius: 6px;    cursor: pointer;  }&lt;/style&gt;</code></pre><p><code>App.vue</code>에서 버튼 컴포넌트를 사용합니다.</p><pre><code class="vue">&lt;!--App.vue--&gt;&lt;template&gt;  &lt;div id=&quot;app&quot;&gt;    &lt;h1 id=&quot;app-title&quot;&gt;Fruits List&lt;/h1&gt;    &lt;div id=&quot;fruits-table&quot;&gt;      &lt;fruits-list&gt;&lt;/fruits-list&gt;      &lt;fruits-price&gt;&lt;/fruits-price&gt;    &lt;/div&gt;    &lt;btn-discount&gt;&lt;/btn-discount&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;import FruitsList from &#39;./components/FruitsList.vue&#39;;import FruitsPrice from &#39;./components/FruitsPrice.vue&#39;;import BtnDiscount from &#39;./components/BtnDiscount.vue&#39;;export default {  // ...  components: {    &#39;fruits-list&#39;: FruitsList,    &#39;fruits-price&#39;: FruitsPrice,    &#39;btn-discount&#39;: BtnDiscount  }}&lt;/script&gt;&lt;!-- ... --&gt;</code></pre><p>화면에 ‘Discount Price’ 버튼이 추가되었습니다.</p><p><img src="/images/screenshot/vuex_mutations_btn_screenshot.jpg" alt="버튼 컴포넌트가 적용된 상태"></p><p>저장소 상태를 변형하기 위해 <code>mutations</code> 속성에 현재 과일 가격의 ‘10%’를 할인하는 <code>discountPrice</code> 함수를 추가합니다.</p><pre><code class="js">// store.js// ...export const store = new Vuex.Store({  state: {    // ...  },  getters: {    // ...  },  mutations: {    discountPrice(state) {      state.fruits.forEach(fruit =&gt; {         fruit.price *= .9;      });    }  }});</code></pre><p><code>discountPrice</code> 변이를 적용할 수 있도록 버튼 컴포넌트(<code>BtnDiscount.vue</code>)에 클릭 이벤트를 추가하겠습니다.<br>변이 핸들러는 직접 호출할 수 없으므로 아래와 같이 <code>commit</code> 메소드를 이용하여 호출해야 합니다.</p><pre><code class="vue">&lt;!--BtnDiscount.vue--&gt;&lt;template&gt;  &lt;div class=&quot;btn&quot; @click=&quot;discountPrice&quot;&gt;DISCOUNT PRICE&lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {    methods: {      discountPrice() {        this.$store.commit(&#39;discountPrice&#39;);      }    }  }&lt;/script&gt;&lt;!-- ... --&gt;</code></pre><p>‘DISCOUNT PRICE’ 버튼을 누르면 현재 가격의 ‘10%’ 할인된 가격이 표시됩니다.</p><p><img src="/images/screenshot/vuex_mutations_discounted_screenshot.jpg" alt="할인 버튼을 눌렀을 때"></p><h1 id="Payload"><a href="#Payload" class="headerlink" title="Payload"></a>Payload</h1><p>이번에는 커밋(Commit)에서 할인율을 입력할 수 있도록 페이로드(Payload)라고 하는 인자를 사용하여 호출하겠습니다.<br>할인율을 적용하면서 호출하도록 코드를 수정하겠습니다.<br>‘20%’를 할인하도록 적용하겠습니다.</p><pre><code class="vue">&lt;!--BtnDiscount.vue--&gt;&lt;template&gt;  &lt;div class=&quot;btn&quot; @click=&quot;discountPrice&quot;&gt;DISCOUNT PRICE&lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {    methods: {      discountPrice() {        this.$store.commit(&#39;discountPrice&#39;, {          discountRate: 20        });      }    }  }&lt;/script&gt;&lt;!-- ... --&gt;</code></pre><p>값이 어떤 의미를 가지고 페이로드로써 전달되는지 정확하게 나타내기 위해 페이로드를 객체로 작성합니다.</p><blockquote><p>대부분의 경우 페이로드는 여러 필드를 포함할 수 있는 객체여야 하며 기록된 변이는 더 이해하기 쉽습니다.</p></blockquote><p>이제 저장소의 변이가 인자를 받도록 수정합니다.</p><pre><code class="js">// store.js// ...export const store = new Vuex.Store({  state: {    // ...  },  getters: {    // ...  },  mutations: {    discountPrice(state, payload) {      state.fruits.forEach(fruit =&gt; {         fruit.price *= (100 - payload.discountRate) / 100;      });    }  }});</code></pre><p>잘 적용되었다면 버튼을 클릭할 때마다 ‘20%’의 할인이 적용된 가격이 표시됩니다.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Vuex-Mutations&quot;&gt;&lt;a href=&quot;#Vuex-Mutations&quot; class=&quot;headerlink&quot; title=&quot;Vuex Mutations&quot;&gt;&lt;/a&gt;Vuex Mutations&lt;/h1&gt;&lt;p&gt;Vuex 변이(Mutations)는 저장
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="vue" scheme="https://heropy.blog/tags/vue/"/>
    
      <category term="vuex" scheme="https://heropy.blog/tags/vuex/"/>
    
  </entry>
  
  <entry>
    <title>Vuex - 3 - Getters</title>
    <link href="https://heropy.blog/2017/12/31/vuex-3-getters/"/>
    <id>https://heropy.blog/2017/12/31/vuex-3-getters/</id>
    <published>2017-12-31T03:29:58.000Z</published>
    <updated>2018-02-26T07:50:03.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Vuex-Getters"><a href="#Vuex-Getters" class="headerlink" title="Vuex Getters"></a>Vuex Getters</h1><p>저장소 상태(State)를 계산된 상태로 사용할 경우 Getters를 사용할 수 있습니다.</p><blockquote><p><code>getters</code> 속성은 Vue의 <code>computed</code> 속성과 비슷합니다.</p></blockquote><p>모든 과일 이름을 대문자로 표시하고자 합니다.<br><code>fruits</code> 데이터의 <code>name</code> 속성 정보를 대문자로 표시하도록(‘- 과일이름’) 다음과 같이 <code>store.js</code>을 수정합니다.</p><p><code>state</code>의 <code>fruits</code> (배열)데이터를 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map" target="_blank" rel="noopener">map 메소드</a>로 반복 처리합니다.<br><code>upperCaseFruits</code> 속성의 인자로 <code>state</code>를 전달하여 내부에서 사용할 수 있습니다.</p><pre><code class="js">// store.js// ...export const store = new Vuex.Store({  state: {    fruits: [      // ...    ]  },  getters: {    upperCaseFruits: state =&gt; {      return state.fruits.map(fruit =&gt; {        return {          name: `- ${fruit.name.toUpperCase()}`  // ES6 - Template Strings        }      });    }  }});</code></pre><p><code>getters</code>에서 설정한 <code>upperCaseFruits</code>를 과일 이름 목록 컴포넌트(<code>FruitsList.vue</code>)에서 사용합니다.</p><pre><code class="vue">&lt;!--FruitsList.vue--&gt;&lt;template&gt;  &lt;div id=&quot;fruits-list&quot;&gt;    &lt;h1&gt;Fruits Name&lt;/h1&gt;    &lt;ul&gt;      &lt;li v-for=&quot;fruit in upperCaseFruits&quot;&gt;        {{ fruit.name }}      &lt;/li&gt;    &lt;/ul&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {    computed: {      fruits() {        return this.$store.state.fruits;      },      upperCaseFruits() {        return this.$store.getters.upperCaseFruits;      }    }  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre><p>과일 이름 목록이 대문자로 바뀌고 각 이름 앞에 <code>-</code> 기호가 추가되었습니다.</p><p><img src="/images/screenshot/vuex_getters_screenshot.jpg" alt="Vuex Getters 적용"></p><p>다른 컴포넌트에서의 활용 등 최적화된 저장소 사용을 위해 다음과 같이 저장소 상태(State)를 <code>computed</code>에서 직접 계산하지 않도록 주의하세요!</p><pre><code class="vue">&lt;!--FruitsList.vue--&gt;&lt;template&gt;  &lt;div id=&quot;fruits-list&quot;&gt;    &lt;h1&gt;Fruits Name&lt;/h1&gt;    &lt;ul&gt;      &lt;li v-for=&quot;fruit in upperCaseFruits&quot;&gt;        {{ fruit.name }}      &lt;/li&gt;    &lt;/ul&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {    computed: {      fruits() {        return this.$store.state.fruits;      },      // Bad case!      upperCaseFruits() {        return this.$store.state.fruits.map(fruit =&gt; {          return {            name: `- ${fruit.name.toUpperCase()}`          }        });      }    }  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Vuex-Getters&quot;&gt;&lt;a href=&quot;#Vuex-Getters&quot; class=&quot;headerlink&quot; title=&quot;Vuex Getters&quot;&gt;&lt;/a&gt;Vuex Getters&lt;/h1&gt;&lt;p&gt;저장소 상태(State)를 계산된 상태로 사용할 경우 
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="vue" scheme="https://heropy.blog/tags/vue/"/>
    
      <category term="vuex" scheme="https://heropy.blog/tags/vuex/"/>
    
  </entry>
  
  <entry>
    <title>Vuex - 2 - Store / State</title>
    <link href="https://heropy.blog/2017/12/30/vuex-2-store/"/>
    <id>https://heropy.blog/2017/12/30/vuex-2-store/</id>
    <published>2017-12-30T03:12:33.000Z</published>
    <updated>2018-02-26T07:50:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Vuex-Central-Store"><a href="#Vuex-Central-Store" class="headerlink" title="Vuex Central Store"></a>Vuex Central Store</h1><p>이전 페이지에서 Vuex를 설치했습니다.</p><p>Vuex를 이용하여 데이터 저장소(Store)를 만들겠습니다.<br><code>src/store</code>라는 디렉토리를 추가하여 <code>store.js</code>를 생성합니다.</p><pre><code class="bash">vue-simple-app  #...  ├─src  │  ├─assets  │  ├─components  │  ├─css  │  ├─store  │  │  └─store.js  #...</code></pre><p>이제 <code>App.vue</code>에서 관리하던 <code>fruits</code> 데이터를 <code>store.js</code>에서 옮겨서 관리하겠습니다.<br>데이터를 가져와서 <code>state</code>(상태) 속성에 추가합니다.</p><blockquote><p><code>state</code> 속성은 Vue에서의 <code>data</code> 속성과 비슷합니다.</p></blockquote><p><code>Vue.use</code> 메소드를 사용하여 <a href="https://kr.vuejs.org/v2/guide/plugins.html" target="_blank" rel="noopener">Vuex 플러그인 사용</a>을 등록합니다.<br>그리고 Store를 앱에서 사용하기 위해 저장소 정보를 상수 <code>store</code>에 할당한 뒤 <code>export</code> 합니다.</p><pre><code class="js">// store.jsimport Vue from &#39;vue&#39;;import Vuex from &#39;vuex&#39;;Vue.use(Vuex);export const store = new Vuex.Store({  // 상태  state: {    fruits: [      { name: &#39;Apple&#39;, price: 30 },      { name: &#39;Banana&#39;, price: 40 },      { name: &#39;Mango&#39;, price: 50 },      { name: &#39;Orange&#39;, price: 60 },      { name: &#39;Tomato&#39;, price: 70 },      { name: &#39;Pineapple&#39;, price: 80 }    ]  }});</code></pre><p>이렇게 내보낸 정보는 <code>main.js</code>에서 받아 사용합니다.<br>Store에 작성한 <code>Vue.use(Vuex)</code>에 의해서 Vue에서 <code>store</code> 속성을 사용할 수 있습니다.</p><pre><code class="js">// main.jsimport Vue from &#39;vue&#39;import App from &#39;./App.vue&#39;import { store } from &#39;./store/store&#39;new Vue({  store,  // store: store,  // ES6  el: &#39;#app&#39;,  render: h =&gt; h(App)})</code></pre><p>이제 중앙 집중식 저장소를 만들었으니 각 컴포넌트가 실행될 때 <code>props</code>를 사용하지 않아도 됩니다.<br>아래와 같이 각 컴포넌트에서 <code>props</code>를 제거하고 <code>computed</code>(계산된) 속성을 추가하여 Store에서 데이터를 가져와 바인딩합니다.<br>저장소에서 State(상태)가 변경되면 <code>computed</code>가 변경되고 결국 DOM 업데이트가 트리거됩니다.</p><pre><code class="vue">&lt;!--App.vue--&gt;&lt;template&gt;  &lt;div id=&quot;app&quot;&gt;    &lt;h1 id=&quot;app-title&quot;&gt;Fruits List&lt;/h1&gt;    &lt;div id=&quot;fruits-table&quot;&gt;      &lt;fruits-list&gt;&lt;/fruits-list&gt;      &lt;fruits-price&gt;&lt;/fruits-price&gt;    &lt;/div&gt;  &lt;/div&gt;&lt;/template&gt;&lt;!-- ... --&gt;</code></pre><pre><code class="vue">&lt;!--FruitsList.vue--&gt;&lt;!-- ... --&gt;&lt;script&gt;  export default {    // Removed Props    computed: {      fruits() {        return this.$store.state.fruits      }    }  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre><pre><code class="vue">&lt;!--FruitsPrice.vue--&gt;&lt;!-- ... --&gt;&lt;script&gt;  export default {    // Removed props    computed: {      fruits() {        return this.$store.state.fruits      }    }  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre><p>정리가 되었다면 잘 나오는지 확인합니다.</p><pre><code class="bash">$ npm run dev</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Vuex-Central-Store&quot;&gt;&lt;a href=&quot;#Vuex-Central-Store&quot; class=&quot;headerlink&quot; title=&quot;Vuex Central Store&quot;&gt;&lt;/a&gt;Vuex Central Store&lt;/h1&gt;&lt;p&gt;이전 페이지
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="vue" scheme="https://heropy.blog/tags/vue/"/>
    
      <category term="vuex" scheme="https://heropy.blog/tags/vuex/"/>
    
  </entry>
  
  <entry>
    <title>Vuex - 1 - 시작하기 / Vuex 설치</title>
    <link href="https://heropy.blog/2017/12/28/vuex-1-start/"/>
    <id>https://heropy.blog/2017/12/28/vuex-1-start/</id>
    <published>2017-12-27T15:25:59.000Z</published>
    <updated>2018-02-26T07:50:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Vuex-란"><a href="#Vuex-란" class="headerlink" title="Vuex 란?"></a>Vuex 란?</h1><p><a href="https://vuex.vuejs.org/kr/intro.html" target="_blank" rel="noopener">Vuex</a>는 <a href="https://kr.vuejs.org/" target="_blank" rel="noopener">Vue</a>와 함께 사용하기 위한 ‘상태 관리 패턴’ 라이브러리입니다.<br>앱의 모든 구성 요소에서 액세스할 수 있는 중앙 집중식 데이터 저장소를 만들 수 있습니다.<br><br><br>Vue.js만 사용할 경우 데이터를 주고 받기 위해서 상위 컴포넌트와 하위 컴포넌트가 지속적으로 <code>props</code>과 <code>event</code> 등을 공유해야 하며, 앱의 규모가 커지며 컴포넌트의 단계가 많아지게 되면 급격하게 관리가 어려워집니다.</p><p><img src="/images/screenshot/vuex_only_vuejs.jpg" alt="Using only Vue.js"></p><p>아래 그림 처럼 각 컴포넌트가 데이터를 중앙 집중 저장소에서 공유할 수 있게 된다면 컴포넌트의 단계가 많아지더라도 훨씬 쉽고 효율적으로 데이터를 활용할 수 있습니다.</p><p><img src="/images/screenshot/vuex_using_vuex.jpg" alt="Using Vuex"></p><h1 id="Vuex-실습-준비"><a href="#Vuex-실습-준비" class="headerlink" title="Vuex 실습 준비"></a>Vuex 실습 준비</h1><h2 id="VUE-CLI-프로젝트-설정"><a href="#VUE-CLI-프로젝트-설정" class="headerlink" title="VUE-CLI 프로젝트 설정"></a>VUE-CLI 프로젝트 설정</h2><p>우선 <a href="https://github.com/vuejs/vue-cli" target="_blank" rel="noopener">vue-cli</a>를 설치하세요.</p><pre><code class="bash">$ npm install -g vue-cli</code></pre><p>빠른 사용을 위한 <a href="https://webpack.github.io/" target="_blank" rel="noopener">Webpack</a> + <a href="https://vue-loader.vuejs.org/kr/" target="_blank" rel="noopener">Vue-loader</a> 프로젝트를 생성합니다.</p><pre><code class="bash">$ vue init webpack-simple vuex-simple-app</code></pre><p>의존성 모듈을 설치합니다.</p><pre><code class="bash">$ npm install</code></pre><h2 id="Vuex-설치"><a href="#Vuex-설치" class="headerlink" title="Vuex 설치"></a>Vuex 설치</h2><pre><code class="bash">npm install --save vuex</code></pre><p>약간의 스타일을 추가한 예제를 만들어보려고 합니다.<br>Vuex에 더욱 집중할 수 있도록 아래의 과정을 생략하고 싶다면 <a href="https://github.com/ParkYoungWoong/vuex-simple-app-fruits-list/tree/Start_Project" target="_blank" rel="noopener">vuex-simple-app-fruits-list</a>을 사용할 수 있습니다.</p><h2 id="Component-amp-CSS-구성"><a href="#Component-amp-CSS-구성" class="headerlink" title="Component &amp; CSS 구성"></a>Component &amp; CSS 구성</h2><p>과일의 이름과 가격 데이터를 화면에 출력하고자 합니다.<br>과일 이름의 목록과 가격을 각 컴포넌트로 구성합니다.</p><p><code>components</code> 디렉토리 안에 <code>FruitsList.vue</code>와 <code>FruitsPrice.vue</code>를 생성하세요..<br><code>css</code> 디렉토리 안에 <code>app.css</code>을 생성하세요..<br><code>app.css</code>는 reset-css 모듈에 의존성이 있으니 설치합니다.</p><pre><code class="bash">$ npm install --save-dev reset-css</code></pre><p>프로젝트의 src 디렉토리 구조는 다음과 같습니다.</p><pre><code class="bash">vue-simple-app  #...  ├─src  │  ├─assets  │  ├─components  │  │  ├─FruitsList.vue  │  │  └─FruitsPrice.vue  │  ├─css  │  │  └─app.css  │  ├─App.vue  │  └─main.js  #...</code></pre><p>App.vue 구성</p><p><code>data</code> 메소드로 데이터를 정의하고 각 컴포넌트를 가져와 <code>fruits</code>속성에 <code>fruits</code>데이터를 바인딩합니다.</p><pre><code class="vue">&lt;!--App.vue--&gt;&lt;template&gt;  &lt;div id=&quot;app&quot;&gt;    &lt;h1 id=&quot;app-title&quot;&gt;Fruits List&lt;/h1&gt;    &lt;div id=&quot;fruits-table&quot;&gt;      &lt;fruits-list :fruits=&quot;fruits&quot;&gt;&lt;/fruits-list&gt;      &lt;fruits-price :fruits=&quot;fruits&quot;&gt;&lt;/fruits-price&gt;    &lt;/div&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;import FruitsList from &#39;./components/FruitsList.vue&#39;;import FruitsPrice from &#39;./components/FruitsPrice.vue&#39;;export default {  name: &#39;app&#39;,  data () {    return {      fruits: [        { name: &#39;Apple&#39;, price: 30 },        { name: &#39;Banana&#39;, price: 40 },        { name: &#39;Mango&#39;, price: 50 },        { name: &#39;Orange&#39;, price: 60 },        { name: &#39;Tomato&#39;, price: 70 },        { name: &#39;Pineapple&#39;, price: 80 },      ]    }  },  components: {    &#39;fruits-list&#39;: FruitsList,    &#39;fruits-price&#39;: FruitsPrice  }}&lt;/script&gt;&lt;style&gt;  @import url(&quot;css/app.css&quot;);&lt;/style&gt;</code></pre><p><code>App.vue</code>에서 바인딩한 데이터를 각 컴포넌트의 <code>props</code>로 받아 사용합니다.</p><p>FruitsList.vue 구성</p><pre><code class="vue">&lt;!--FruitsList.vue--&gt;&lt;template&gt;  &lt;div id=&quot;fruits-list&quot;&gt;    &lt;h1&gt;Fruits Name&lt;/h1&gt;    &lt;ul&gt;      &lt;li v-for=&quot;fruit in fruits&quot;&gt;        {{ fruit.name }}      &lt;/li&gt;    &lt;/ul&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {    props: [&#39;fruits&#39;]  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre><p>FruitsPrice.vue 구성</p><pre><code class="vue">&lt;!--FruitsPrice.vue--&gt;&lt;template&gt;  &lt;div id=&quot;fruits-price&quot;&gt;    &lt;h1&gt;Fruits Price&lt;/h1&gt;    &lt;ul&gt;      &lt;li v-for=&quot;fruit in fruits&quot;&gt;        $ {{ fruit.price }}      &lt;/li&gt;    &lt;/ul&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;  export default {    props: [&#39;fruits&#39;]  }&lt;/script&gt;&lt;style&gt;&lt;/style&gt;</code></pre><p>app.css 구성</p><p>약간의 스타일을 추가합니다.</p><pre><code class="css">/*app.css*/@import url(&quot;~reset-css/reset.css&quot;);#app-title {  color: orangered;  font-size: 40px;  font-weight: bold;  text-align: center;  padding: 40px 0 20px 0;}#fruits-table {  max-width: 700px;  margin: 0 auto;  display: flex;  text-align: center;  padding: 0 20px;}#fruits-table li {  padding: 20px;  border-bottom: 1px dashed grey;  color: white;}#fruits-table li:last-child { border-bottom: none; }#fruits-table li:hover { background: rgba(255,255,255,.2); }#fruits-list {  background: orange;  flex: 1;}#fruits-price {  background: tomato;  flex: 1;}#fruits-list h1,#fruits-price h1 {  font-weight: bold;  font-size: 20px;  padding: 20px;  border-bottom: 1px solid;}</code></pre><p>준비가 끝났으니, 화면에 출력해 보겠습니다.</p><pre><code class="bash">$ npm run dev</code></pre><p><img src="/images/screenshot/vuex_start_project_screenshot.jpg" alt="출력"></p><p>위와 같은 화면이 나온다면 제대로 완성된 상태입니다.</p><p>이제 Vuex 를 설치하죠.</p><pre><code class="bash">$ npm install --save vuex</code></pre><p>설치가 되었다면 다음 장에서 Vuex를 사용하여 ‘중앙 집중식 데이터 저장소’를 만들어 봅시다.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Vuex-란&quot;&gt;&lt;a href=&quot;#Vuex-란&quot; class=&quot;headerlink&quot; title=&quot;Vuex 란?&quot;&gt;&lt;/a&gt;Vuex 란?&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://vuex.vuejs.org/kr/intro.html&quot; targe
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="vue" scheme="https://heropy.blog/tags/vue/"/>
    
      <category term="vuex" scheme="https://heropy.blog/tags/vuex/"/>
    
  </entry>
  
  <entry>
    <title>macOS 업데이트 후 git 에러</title>
    <link href="https://heropy.blog/2017/12/07/git-error/"/>
    <id>https://heropy.blog/2017/12/07/git-error/</id>
    <published>2017-12-07T14:40:33.000Z</published>
    <updated>2018-02-26T07:49:46.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="macOS-업데이트-후-git-에러"><a href="#macOS-업데이트-후-git-에러" class="headerlink" title="macOS 업데이트 후 git 에러"></a>macOS 업데이트 후 git 에러</h1><p>macOS 업데이트를 하면 항상 이런저런 프로그램에서 에러가 발생하네요.<br>이번에 ‘High Sierra’로 업데이트 후 <code>git</code> 명령이 동작하지 않아 적잖이 당황했습니다.<br><code>git --version</code>을 입력하면 아래와 같이 나옵니다.</p><pre><code class="bash">xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun</code></pre><p>이럴 경우 ‘XCode’를 재설치하거나, 아래와 같이 ‘XCode’s Command line tools’만 재설치 시 문제가 해결됩니다.</p><pre><code class="bash">$ xcode-select --install</code></pre><blockquote><p>Remember, in MAC git is attached to XCode’s Com­mand line tools.</p></blockquote><h1 id="참고-문서"><a href="#참고-문서" class="headerlink" title="참고 문서"></a>참고 문서</h1><p><a href="http://tips.tutorialhorizon.com/2015/10/01/xcrun-error-invalid-active-developer-path-library-developer-commandline-tools-missing-xcrun/" target="_blank" rel="noopener">xcrun: error - Tips &amp; Tricks</a></p><p><a href="https://stackoverflow.com/questions/32893412/command-line-tools-not-working-os-x-el-capitan-macos-sierra-macos-high-sierra" target="_blank" rel="noopener">Command Line Tools not working - Stack Overflow</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;macOS-업데이트-후-git-에러&quot;&gt;&lt;a href=&quot;#macOS-업데이트-후-git-에러&quot; class=&quot;headerlink&quot; title=&quot;macOS 업데이트 후 git 에러&quot;&gt;&lt;/a&gt;macOS 업데이트 후 git 에러&lt;/h1&gt;&lt;p&gt;ma
      
    
    </summary>
    
    
      <category term="git" scheme="https://heropy.blog/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>Yarn 설치 및 사용법</title>
    <link href="https://heropy.blog/2017/11/25/yarn/"/>
    <id>https://heropy.blog/2017/11/25/yarn/</id>
    <published>2017-11-25T02:15:55.000Z</published>
    <updated>2018-02-12T07:33:46.000Z</updated>
    
    <content type="html"><![CDATA[<p>Facebook에서 만든 자바스크립트 패키지 매니저인 Yarn을 사용해 봅시다.</p><h1 id="Yarn-설치"><a href="#Yarn-설치" class="headerlink" title="Yarn 설치"></a>Yarn 설치</h1><p>Yarn은 다양한 OS의 설치를 지원합니다.</p><h2 id="macOS"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h2><p><a href="https://brew.sh/index_ko.html" target="_blank" rel="noopener">Homebrew</a>를 사용하는 설치</p><pre><code class="bash">$ brew install yarn</code></pre><p>NVM 같은 버전 관리 툴을 사용해야 한다면 Node.js의 설치를 제외하도록 합니다.</p><pre><code class="bash">$ brew install yarn --without-node</code></pre><h2 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h2><p><a href="https://chocolatey.org/" target="_blank" rel="noopener">Chocolatey</a>를 사용하는 설치</p><pre><code class="bash">$ choco install yarn</code></pre><p><a href="http://scoop.sh/" target="_blank" rel="noopener">Scoop</a>를 사용하는 설치</p><pre><code class="bash">$ scoop install yarn</code></pre><h2 id="NPM"><a href="#NPM" class="headerlink" title="NPM"></a>NPM</h2><p>NPM으로 설치할 수도 있습니다.</p><pre><code class="bash">$ npm install -g yarn</code></pre><p>설치가 잘 되었는지 확인합니다.</p><pre><code class="bash">$ yarn --version</code></pre><p>설치 후 전역 사용에 문제가 있다면 Path 설정을 해줘야 합니다.</p><p><code>.profile</code>, <code>.bash_profile</code>, <code>.bashrc</code>, <code>.zshrc</code> 등…</p><pre><code class="bash">$ export PATH=&quot;$PATH:/opt/yarn-[version]/bin&quot;</code></pre><h1 id="Yarn-사용법"><a href="#Yarn-사용법" class="headerlink" title="Yarn 사용법"></a>Yarn 사용법</h1><p>NPM을 사용한다면 어렵지 않습니다.</p><p>프로젝트를 시작할 때 초기화를 하려면(<code>package.json</code>을 생성합니다.)</p><pre><code class="bash">$ yarn init</code></pre><p><code>package.json</code>으로부터 의존성 모듈을 설치하려면</p><pre><code class="bash">$ yarn# or$ yarn install</code></pre><p>의존성 모듈을 설치하려면</p><pre><code class="bash">$ yarn add [package]$ yarn add [package]@[version]$ yarn add [package]@[tag]</code></pre><p><code>devDependencies</code>, <code>peerDependencies</code>, <code>optionalDependencies</code>와 같은 다른 범주의 의존성을 추가하려면</p><pre><code class="bash">$ yarn add [package] --dev$ yarn add [package] --peer$ yarn add [package] --optional</code></pre><p>의존성 모듈을 업그레이드하려면</p><pre><code class="bash">$ yarn upgrade [package]$ yarn upgrade [package]@[version]$ yarn upgrade [package]@[tag]</code></pre><p>의존성 모듈을 제거하려면</p><pre><code class="bash">$ yarn remove [package]</code></pre><h1 id="yarn-lock"><a href="#yarn-lock" class="headerlink" title="yarn.lock"></a>yarn.lock</h1><p><code>Yarn.lock</code> 파일은 설치된 모듈의 버전을 저장해 어디서나 같은 버전과 구조의 의존성을 가지게 합니다.<br>Yarn에서는 자동으로 <code>yarn install</code> 때 마다 yarn.lock이 생성됩니다.<br><code>package-lock.json</code>와 비슷한 기능을 한다고 생각하면 됩니다.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Facebook에서 만든 자바스크립트 패키지 매니저인 Yarn을 사용해 봅시다.&lt;/p&gt;&lt;h1 id=&quot;Yarn-설치&quot;&gt;&lt;a href=&quot;#Yarn-설치&quot; class=&quot;headerlink&quot; title=&quot;Yarn 설치&quot;&gt;&lt;/a&gt;Yarn 설치&lt;/h1&gt;&lt;p
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
  </entry>
  
  <entry>
    <title>Closure(함수 클로저)</title>
    <link href="https://heropy.blog/2017/11/10/closure/"/>
    <id>https://heropy.blog/2017/11/10/closure/</id>
    <published>2017-11-10T13:13:02.000Z</published>
    <updated>2018-02-12T07:33:46.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="클로저란"><a href="#클로저란" class="headerlink" title="클로저란?"></a>클로저란?</h1><blockquote><p>클로저(Closure)는 일급 객체 함수(first-class functions)의 개념을 이용하여 유효범위(scope)에 묶인 변수를 바인딩 하기 위한 일종의 기술이다. 기능상으로, 클로저는 함수를 저장한 레코드(record)이며, 스코프(scope)의 인수(Factor)들은 클로저가 만들어질 때 정의(define)되며, 스코프 내의 영역이 소멸(remove)되었어도 그에 대한 접근(access)은 독립된 복사본인 클로저를 통해 이루어질 수 있다.</p></blockquote><p>자바스크립트 클로저(Closure)는 독립적인 변수(로컬로 사용되지만 둘러싼 범위에서 정의 된 변수)를 참조하는 함수입니다.<br>이 함수들은 생성될 당시의 환경을 <strong>기억</strong> 합니다.</p><p>이 방식으로 종료된 함수 내 특정 지역변수를 사용할 수 있습니다.<br>우선 간단한 예제를 살펴봅시다.</p><pre><code class="js">function plus() {  var a = 0;  return function () {    return ++a;  }}var p = plus();console.log(p());  // 1console.log(p());  // 2console.log(p());  // 3</code></pre><p>함수 <code>plus</code>는 변수 <code>p</code>에 할당되며 실행이 종료되었지만, <code>p</code>를 실행하면 <code>plus</code>함수 내에 선언된 변수 <code>a</code>를 사용할 수 있습니다.</p><p><img src="/images/screenshot/closure_plus_function.png" alt="Closure"></p><p>변수 <code>a</code>가 사용되는 동안에는 <a href="https://namu.wiki/w/%EC%93%B0%EB%A0%88%EA%B8%B0%20%EC%88%98%EC%A7%91" title="Garbage Collection" target="_blank" rel="noopener">GC</a>이 되지 않습니다.<br>따라서 상황에 맞게 변수 <code>a</code> 참조를 없애주는 것이 좋습니다.</p><pre><code class="js">// 참조 제거p = null;</code></pre><h1 id="반복문에서-클로저"><a href="#반복문에서-클로저" class="headerlink" title="반복문에서 클로저"></a>반복문에서 클로저</h1><p>배열 데이터에 값을 할당할 때 사용된 변수 <code>i</code>는 호출 시점에서 접근하기 때문에, 출력 시 반복문이 종료된 상태의 <code>i</code>의 값인 ‘5’만을 참조합니다.</p><pre><code class="js">var arr = [];for (var i = 0; i &lt; 5; i++) {  arr[i] = function () {    return i;  }  }arr.forEach(function (el) {  console.log(el());  // 5 / 5 / 5 / 5 / 5});</code></pre><p>즉시실행함수(IIFE)로 값을 할당하여, 호출과 참조 시점을 같게 해줍니다.</p><pre><code class="js">var arr = [];for (var i = 0; i &lt; 5; i++) {  arr[i] = (function () {    return i;  }());}arr.forEach(function (el) {  console.log(el);  // 0 / 1 / 2 / 3 / 4});</code></pre><p>좀 더 실용적인 예제를 살펴봅시다.<br>목록을 클릭하여 목록에 해당하는 숫자가 콘솔에 출력되도록 하는 예제를 만들겠습니다.</p><pre><code class="html">&lt;ul class=&quot;click&quot;&gt;  &lt;li&gt;CLICK 1&lt;/li&gt;  &lt;li&gt;CLICK 2&lt;/li&gt;  &lt;li&gt;CLICK 3&lt;/li&gt;  &lt;li&gt;CLICK 4&lt;/li&gt;&lt;/ul&gt;</code></pre><pre><code class="js">var items = document.querySelectorAll(&#39;.click li&#39;);for (var i = 0; i &lt; items.length; i++) {  items[i].onclick = function () {    console.log(&#39;click&#39; + i);  // &#39;click4&#39;  }}</code></pre><p>콘솔에는 <code>&#39;click4&#39;</code>만 출력됩니다.<br><code>onclick</code>에 할당되는 핸들러(함수)가 실행되는 시점과, 변수 <code>i</code>를 참조하는 시점이 다르기 때문에 정상적으로 숫자가 출력되지 않습니다.<br>그렇다면 잘 동작할 수 있도록 아래와 같이 변경합니다.</p><pre><code class="js">var items = document.querySelectorAll(&#39;.click li&#39;);for (var i = 0; i &lt; items.length; i++) {  items[i].onclick = (function (j) {    return function () {      console.log(&#39;click&#39; + j);  // &#39;click0&#39; / &#39;click1&#39; / &#39;click2&#39; / &#39;click3&#39;    }  }(i))}</code></pre><p>핸들러를 반환하는 즉시실행함수(외부함수)를 생성하고, 지역변수(매개변수)를 만들어 핸들러가 값을 참조 가능하도록 합니다.<br>이제 핸들러는 반복문의 <code>i</code>를 참조하지 않고 외부함수의 매개변수인 <code>j</code>를 참조합니다.<br>콘솔을 확인하면 순서대로 숫자가 잘 출력됩니다.</p><p>ES2015(ES6)의 <code>let</code> 키워드를 사용하면 좀 더 쉽게 구현할 수 있습니다.</p><pre><code class="js">var items = document.querySelectorAll(&#39;.click li&#39;);for (let i = 0; i &lt; items.length; i++) {  items[i].onclick = function () {    console.log(&#39;click&#39; + i);  // &#39;click0&#39; / &#39;click1&#39; / &#39;click2&#39; / &#39;click3&#39;  }}</code></pre><h1 id="클로저의-캡슐화-은닉화"><a href="#클로저의-캡슐화-은닉화" class="headerlink" title="클로저의 캡슐화, 은닉화"></a>클로저의 캡슐화, 은닉화</h1><p>자바스크립트에서 일반적인 객체지향 프로그래밍 방식으로 <code>prototype</code>를 사용하는데 객체 안에서 사용할 속성을 생성할 때 <code>this</code> 키워드를 사용하게 됩니다.<br>아래 예제에서 속성 <code>this._name</code>은 <code>_</code>를 앞에 붙여줌으로 네이밍 컨벤션을 기준으로 외부 접근 불가(Private)의 의미를 가집니다.<br>그러나 자바스크립트에서는 <code>private</code>, <code>public</code>, <code>protected</code> 같은 ‘접근 수정자’를 제공하지 않기 때문에, 예제에서의 속성 <code>_name</code>은 ‘접근 불가’라는 의미만 가질 뿐 실제론 외부에서 접근이 가능합니다.<br>따라서 <code>a._name</code>으로 콘솔에 출력하면 값을 확인할 수 있습니다.</p><pre><code class="js">function Hello(name) {  this._name = name;}Hello.prototype = {  getName: function () {    return this._name;  },  setName: function (name) {    this._name = name;  }}var a = new Hello(&#39;good&#39;);console.log(a._name);  // &#39;good&#39;console.log(a.getName());  // &#39;good&#39;a.setName(&#39;nice&#39;);console.log(a._name);  // &#39;nice&#39;console.log(a.getName());  // &#39;nice&#39;</code></pre><p>이제 외부에서 접근할 수 없도록 ‘캡슐화(Encapsulation)’ 해봅시다.<br>즉시실행함수를 활용합니다.<br>즉시실행함수 내부에 별도의 객체를 만들고 객체 자체를 반환하는 형태로 <code>Hello</code> 생성자를 만들어 줍니다.<br>이제 ‘은닉화(Hiding)’된 <code>_name</code>과 <code>name</code>은 외부에서 접근할 수 없습니다.</p><pre><code class="js">var Hello = (function () {    var _name;  function _Hello(name) {    _name = name;  }  _Hello.prototype = {    getName: function () {      return _name;    },    setName: function (name) {      _name = name;    }  }  return _Hello;}());var a = new Hello(&#39;good&#39;);console.log(a._name);  // undefinedconsole.log(a.getName());  // &#39;good&#39;a.setName(&#39;nice&#39;);console.log(a._name);  // undefinedconsole.log(a.getName());  // &#39;nice&#39;</code></pre><p>같은 예제라고 할 수는 없지만 좀 더 쉬운 이해를 위해서 위의 코드를 아래와 같이 단순화할 수 있습니다.</p><pre><code class="js">function hello(name) {  var _name = name;  return {    getName: function () {      return _name;    },    setName: function (name) {      _name = name;    }  };}</code></pre><h1 id="클로저와-this"><a href="#클로저와-this" class="headerlink" title="클로저와 this"></a>클로저와 this</h1><p>아래 예제의 경우 <code>this.name</code> 키워드가 외부 함수의 <code>this</code>가 아닌 글로벌 <code>this</code>를 참조하고 있습니다.<br>비상식적으로 동작하지만 실제로 많은 부분에서 발생하는 문제입니다.</p><pre><code class="js">window.name = &#39;window&#39;;var object = {  name: &#39;object&#39;,  getName: function () {    return (function () {      return this.name;    }());  }}console.log(object.getName());  // &#39;window&#39;</code></pre><p>이럴 경우 <code>this</code>가 참조 가능한 별도의 변수 <code>_this</code>를 생성할 수 있습니다.</p><pre><code class="js">window.name = &#39;window&#39;;var object = {  name: &#39;object&#39;,  getName: function () {    var _this = this;    return (function () {      return _this.name;    }());  }}console.log(object.getName());  // &#39;object&#39;</code></pre><h1 id="참고-문서"><a href="#참고-문서" class="headerlink" title="참고 문서"></a>참고 문서</h1><p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures" target="_blank" rel="noopener">클로저 - MDN</a></p><p><a href="http://blog.javarouka.me/2012/01/javascripts-closure.html" target="_blank" rel="noopener">자바스크립트의 클로저 (JavaScript’s Closure)</a></p><p><a href="https://opentutorials.org/course/743/6544" target="_blank" rel="noopener">클로저 - 생활코딩</a></p><p><a href="https://hyunseob.github.io/2016/08/30/javascript-closure/" target="_blank" rel="noopener">JavaScript 클로저(Closure)</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;클로저란&quot;&gt;&lt;a href=&quot;#클로저란&quot; class=&quot;headerlink&quot; title=&quot;클로저란?&quot;&gt;&lt;/a&gt;클로저란?&lt;/h1&gt;&lt;blockquote&gt;&lt;p&gt;클로저(Closure)는 일급 객체 함수(first-class functions)의 개
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
  </entry>
  
  <entry>
    <title>Webpack - 3 - 용어 정리</title>
    <link href="https://heropy.blog/2017/10/28/webpack_term/"/>
    <id>https://heropy.blog/2017/10/28/webpack_term/</id>
    <published>2017-10-28T03:40:33.000Z</published>
    <updated>2018-02-12T07:33:46.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Webpack-Config"><a href="#Webpack-Config" class="headerlink" title="Webpack Config"></a>Webpack Config</h1><p>‘Webpack 기초 따라하기’를 진행하면서 <code>webpack.config.js</code> 파일을 구성하면 다음과 속성들이 등장합니다.<br>각 속성들이 어떤 의미를 가지고 있는지 알아봅시다.</p><h2 id="entry"><a href="#entry" class="headerlink" title="entry"></a>entry</h2><p>‘Webpack’이 파일을 읽어들이기 시작하는 부분(진입점).<br><code>require</code>, <code>@import</code> 등 모듈간의 의존성을 해석하며 의존성 트리를 생성, 하나 이상의 진입 포인트를 설정할 수 있습니다.</p><pre><code class="js">{  entry: &#39;./src/app.js&#39;}</code></pre><pre><code class="js">// 하나 이상의 진입 포인트 설정{  entry: [    &#39;./src/app.js&#39;,    &#39;./src/main.js&#39;  ]}</code></pre><pre><code class="js">// 속성(키)을 사용하여 하나 이상의 진입 포인트를 설정 가능{  entry: {    app: &#39;./src/app.js&#39;  }}</code></pre><h2 id="output"><a href="#output" class="headerlink" title="output"></a>output</h2><p><code>entry</code>에서 설정한 의존성 트리를 바탕으로 결과물(번들)을 반환하는 설정.</p><p><code>output.path</code>: 결과물이 저장될 경로 지정<br><code>output.filename</code>: 결과물의 파일 이름 지정</p><h3 id="매개변수-parameter-설정"><a href="#매개변수-parameter-설정" class="headerlink" title="매개변수(parameter) 설정"></a>매개변수(parameter) 설정</h3><p><code>[name]</code>: <code>entry</code>의 ‘key’ 이름</p><pre><code class="js">{  entry: &#39;./src/app.js&#39;,  output: {    path: &#39;/dist&#39;,    filename: &#39;app.bundle.js&#39;  }}</code></pre><pre><code class="js">// 매개변수를 활용하여 번들의 이름 설정{  entry: {    app: &#39;./src/app.js&#39;  },  output: {    path: &#39;/dist&#39;,    filename: &#39;[name].bundle.js&#39;  }}</code></pre><h2 id="resolve"><a href="#resolve" class="headerlink" title="resolve"></a>resolve</h2><p><code>require()</code>로 호출하는 모듈의 해석 방식 설정</p><p><code>resolve.root</code>: 모듈 탐색을 시작할 경로 지정 (default: <code>&#39;node_modules/&#39;</code>)<br><code>extensions</code>: 탐색할 모듈의 확장자를 지정</p><pre><code class="js">{  resolve: {    extensions: [&#39;&#39;, &#39;.js&#39;, &#39;.css&#39;]  }}</code></pre><h2 id="module"><a href="#module" class="headerlink" title="module"></a>module</h2><p>의존성 트리 내의 모듈 처리 방식을 설정.</p><p><code>test</code>: 정규표현식으로 적용되는 파일들을 설정<br><code>use</code>: 지정된 ‘loader’가 <code>test</code>에서 적용한 파일을 컴파일<br><code>exclude</code>: 컴파일하지 않을 폴더나 파일을 제외<br><code>include</code>: 컴파일할 폴더나 파일을 별도 지정</p><pre><code class="js">{  module: {    rules: [      {        // Sass Loader        test: /\.sass$/,        use: ExtractTextPlugin.extract({          fallback: &#39;style-loader&#39;,          use: [            &#39;css-loader&#39;,            &#39;sass-loader&#39;          ],          publicPath: &#39;/dist&#39;        })      },      {        // Babel Loader        test: /\.js$/,        exclude: /node_modules/,        use: &quot;babel-loader&quot;      }    ]  }}</code></pre><h2 id="plugins"><a href="#plugins" class="headerlink" title="plugins"></a>plugins</h2><p>번들링 후 결과물의 처리 방식 등을 처리할 다양한 플러그인들을 설정</p><pre><code class="js">{  plugins: [    // Use EJS Template    new HtmlWebpackPlugin({      title: &#39;Hello Webpack Project!&#39;,      minify: {        collapseWhitespace: true      },      hash: true,      template: path.join(__dirname, &#39;/src/index.ejs&#39;)    }),    // Extracting to single file`    new ExtractTextPlugin(&#39;app.css&#39;)  ]}</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Webpack-Config&quot;&gt;&lt;a href=&quot;#Webpack-Config&quot; class=&quot;headerlink&quot; title=&quot;Webpack Config&quot;&gt;&lt;/a&gt;Webpack Config&lt;/h1&gt;&lt;p&gt;‘Webpack 기초 따라하기’를 진행하
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="webpack" scheme="https://heropy.blog/tags/webpack/"/>
    
  </entry>
  
  <entry>
    <title>Webpack - 2 - Dev Server / Babel</title>
    <link href="https://heropy.blog/2017/10/23/webpack_2_devServer_babel/"/>
    <id>https://heropy.blog/2017/10/23/webpack_2_devServer_babel/</id>
    <published>2017-10-22T18:19:01.000Z</published>
    <updated>2018-02-12T07:33:46.000Z</updated>
    
    <content type="html"><![CDATA[<p>이번에는 <code>Webpack Dev Server</code>에 대해서 알아보고,<br>ES6 이상의 문법을 사용하기 위해 <code>Babel</code>을 구성하겠습니다.</p><h1 id="프로젝트-구성하기"><a href="#프로젝트-구성하기" class="headerlink" title="프로젝트 구성하기"></a>프로젝트 구성하기</h1><pre><code class="bash">$ npm init -y</code></pre><p>아래와 같이 디렉토리를 구성하고 파일을 생성하세요.</p><pre><code class="bash">webpack-test  ├─src  │  ├─js  │  │  ├─app.js  │  │  └─hello.js  │  ├─scss  │  │  ├─app.scss  │  │  └─hello.scss  │  └─index.ejs  ├─package.json  └─webpack.config.js</code></pre><pre><code class="json">// package.json{  &quot;name&quot;: &quot;webpack-test&quot;,  &quot;version&quot;: &quot;1.0.0&quot;,  &quot;description&quot;: &quot;&quot;,  &quot;main&quot;: &quot;index.js&quot;,  &quot;scripts&quot;: {    &quot;dev&quot;: &quot;webpack -d --watch&quot;,    &quot;prod&quot;: &quot;webpack -p&quot;  },  &quot;keywords&quot;: [],  &quot;author&quot;: &quot;&quot;,  &quot;license&quot;: &quot;ISC&quot;,  &quot;devDependencies&quot;: {    &quot;css-loader&quot;: &quot;^0.28.7&quot;,    &quot;extract-text-webpack-plugin&quot;: &quot;^3.0.1&quot;,    &quot;html-webpack-plugin&quot;: &quot;^2.30.1&quot;,    &quot;node-sass&quot;: &quot;^4.5.3&quot;,    &quot;sass-loader&quot;: &quot;^6.0.6&quot;,    &quot;style-loader&quot;: &quot;^0.19.0&quot;,    &quot;webpack&quot;: &quot;^3.8.1&quot;  }}</code></pre><pre><code class="js">// webpack.config.jsvar HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;);var ExtractTextPlugin = require(&#39;extract-text-webpack-plugin&#39;);var path = require(&#39;path&#39;);module.exports = {  entry: path.join(__dirname, &#39;/src/js/app.js&#39;),  output: {    path: path.join(__dirname, &#39;/dist&#39;),    filename: &#39;app.bundle.js&#39;  },  resolve: {    extensions: [&#39;.js&#39;]  },  module: {    rules: [      {        test: /\.scss$/,        use: ExtractTextPlugin.extract({          fallback: &#39;style-loader&#39;,          use: [            &#39;css-loader&#39;,            &#39;sass-loader&#39;          ],          publicPath: &#39;/dist&#39;        })      }    ]  },  plugins: [    new HtmlWebpackPlugin({      title: &#39;Hello Webpack Project!&#39;,      minify: {        collapseWhitespace: true      },      hash: true,      template: path.join(__dirname, &#39;/src/index.ejs&#39;)    }),    new ExtractTextPlugin(&#39;app.css&#39;)  ]};</code></pre><pre><code class="ejs">&lt;!-- index.ejs --&gt;&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot;&gt;&lt;head&gt;    &lt;meta charset=&quot;UTF-8&quot;&gt;    &lt;title&gt;&lt;%= htmlWebpackPlugin.options.title %&gt;&lt;/title&gt;&lt;/head&gt;&lt;body&gt;    &lt;div class=&quot;container&quot;&gt;        &lt;h1&gt;Hello Webpack!&lt;/h1&gt;    &lt;/div&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><pre><code class="js">// app.jsrequire(&#39;../scss/app.scss&#39;);require(&#39;./hello&#39;);</code></pre><pre><code class="js">// hello.jsconsole.log(&#39;Hello Webpack!&#39;);</code></pre><pre><code class="scss">/* app.scss */body {  background: tomato;}@import &#39;hello.scss&#39;;</code></pre><pre><code class="scss">/* hello.scss */.container {  h1 {    color: white;  }}</code></pre><p>구성이 완료되었다면 Package를 설치합니다.</p><pre><code class="bash">$ npm i</code></pre><h1 id="webpack-dev-server-설치하기"><a href="#webpack-dev-server-설치하기" class="headerlink" title="webpack-dev-server 설치하기"></a>webpack-dev-server 설치하기</h1><p><code>webpack-dev-server</code>는 로컬에서 사용할 개발용 서버를 제공합니다.<br>이 기능을 사용하여 소스 파일을 감시하고 내용이 변경될 마다 번들을 다시 컴파일 합니다.<br>수시로 새로고침을 하지 않아 편리합니다.</p><pre><code class="bash">$ npm i webpack-dev-server -D</code></pre><p><code>dev</code> 명령이 <code>&quot;webpack-dev-server&quot;</code>를 실행하도록 수정하세요.</p><pre><code class="json">// package.json&quot;scripts&quot;: {  &quot;dev&quot;: &quot;webpack-dev-server&quot;,  &quot;prod&quot;: &quot;webpack -p&quot;},</code></pre><p><code>webpack.config.js</code>의 내용으로 <code>devServer</code>속성을 추가합니다.</p><pre><code class="js">// webpack.config.jsmodule: {  // ...},devServer: {  contentBase: path.join(__dirname, &#39;dist&#39;),  compress: true,  port: 9000},plugins: [  // ...]</code></pre><p><code>contentBase</code>: 정적 파일을 제공할 디렉토리 설정, 소스 파일을 감시하고 변경 될 때마다 번들을 다시 컴파일합니다.<br><code>compress</code>: <code>gzip</code>압축 사용 유무<br><code>port</code>: 포트 설정</p><h1 id="Babel-설치하기"><a href="#Babel-설치하기" class="headerlink" title="Babel 설치하기"></a>Babel 설치하기</h1><pre><code class="bash">$ npm i babel-loader babel-core -D</code></pre><pre><code class="js">// webpack.config.jsmodule: {  rules: [    {      // ...    },    {      test: /\.js$/,      exclude: /node_modules/,      use: &quot;babel-loader&quot;    }  ]},</code></pre><p><code>exclude</code>: 제외할 조건에 대해서 설정</p><h2 id="babelrc-생성"><a href="#babelrc-생성" class="headerlink" title=".babelrc 생성"></a>.babelrc 생성</h2><p>프로젝트 루트에 <code>.babelrc</code> 파일을 생성하여 설정을 만들고 일부 플러그인을 활성화 합니다.</p><pre><code class="bash">webpack-test  ├─src  │  ├─js  │  │  ├─app.js  │  │  └─hello.js  │  ├─scss  │  │  ├─app.scss  │  │  └─hello.scss  │  └─index.ejs  ├─.babelrc  ├─package.json  └─webpack.config.js</code></pre><p>‘<a href="https://babeljs.io/docs/plugins/preset-env/" target="_blank" rel="noopener">Env preset</a>‘ 는 지원되는 환경을 기반으로 필요한 Babel 플러그인들을 자동으로 설정합니다.</p><pre><code class="bash">npm i babel-preset-env -D</code></pre><p><code>&quot;presets&quot;</code>에 구성 옵션이 없으면(<code>&quot;env&quot;</code>) <code>babel-preset-latest</code>(<code>babel-preset-es2015</code>, <code>babel-preset-es2016</code>, <code>babel-preset-es2017</code>)와 동일하게 작동합니다.</p><pre><code class="json">// .babelrc{  &quot;presets&quot;: [&quot;env&quot;]}</code></pre><p>이제 설정이 끝났으니 ES2015+ 문법을 사용합니다.</p><pre><code class="js">// app.jsimport &#39;../scss/app.scss&#39;;import &#39;./hello&#39;;</code></pre><pre><code class="bash">$ npm run dev</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;이번에는 &lt;code&gt;Webpack Dev Server&lt;/code&gt;에 대해서 알아보고,&lt;br&gt;ES6 이상의 문법을 사용하기 위해 &lt;code&gt;Babel&lt;/code&gt;을 구성하겠습니다.&lt;/p&gt;&lt;h1 id=&quot;프로젝트-구성하기&quot;&gt;&lt;a href=&quot;#프로젝트-
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="webpack" scheme="https://heropy.blog/tags/webpack/"/>
    
      <category term="bebel" scheme="https://heropy.blog/tags/bebel/"/>
    
  </entry>
  
  <entry>
    <title>Webpack - 1 - 시작하기 / EJS / SASS(SCSS)</title>
    <link href="https://heropy.blog/2017/10/18/webpack_1_start_ejs_sass/"/>
    <id>https://heropy.blog/2017/10/18/webpack_1_start_ejs_sass/</id>
    <published>2017-10-18T13:07:58.000Z</published>
    <updated>2018-03-06T08:09:04.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="시작하기"><a href="#시작하기" class="headerlink" title="시작하기"></a>시작하기</h1><p>빈 프로젝트 디렉토리를 생성한 후 터미널을 이용하여, <code>package.json</code>을 생성하세요.</p><pre><code class="bash">$ npm init -y</code></pre><p>그리고 <code>webpack</code>을 설치하세요.</p><pre><code class="bash">$ npm i -D webpack</code></pre><p><code>webpack.config.js</code> 파일을 생성한 후 아래와 같이 작성합니다.</p><pre><code class="js">// webpack.config.jsmodule.exports = {  entry: &#39;./src/app.js&#39;,  output: {    filename: &#39;./dist/app.bundle.js&#39;  }};</code></pre><p><code>src</code> 디렉토리를 만들어 <code>app.js</code> 파일을 추가하고,</p><pre><code class="js">// app.jsconsole.log(&#39;Hello Webpack!&#39;);</code></pre><p><code>dist</code> 디렉토리를 만들어 <code>index.html</code> 파일을 추가하세요.</p><pre><code class="html">&lt;!-- index.html --&gt;&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot;&gt;&lt;head&gt;  &lt;meta charset=&quot;UTF-8&quot;&gt;  &lt;title&gt;Webpack Test&lt;/title&gt;&lt;/head&gt;&lt;body&gt;  &lt;script src=&quot;app.bundle.js&quot;&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><p>아래와 같은 디렉토리 구조를 만드세요.</p><pre><code class="bash">webpack-test  ├─dist  │  └─index.html  ├─src  │  └─app.js  ├─package.json  └─webpack.config.js</code></pre><p><code>webpack</code>으로 동작시키기 위해 <code>package.json</code> 파일의 <code>&quot;scripts&quot;</code> 내용을 아래와 같이 수정하세요.</p><pre><code class="json">// package.json&quot;scripts&quot;: {  &quot;dev&quot;: &quot;webpack -d --watch&quot;,  // 개발용 (development)  &quot;prod&quot;: &quot;webpack -p&quot;  // 제품용 (production)}</code></pre><p>실행</p><pre><code class="bash"># 개발용$ npm run dev# 제품용$ npm run prod</code></pre><p><code>dist</code> 디렉토리에 <code>app.bundle.js</code> 파일이 생성되면,<br><code>index.html</code>파일을 웹브라우저로 열어 개발자 도구로 콘솔 창을 확인하세요.</p><pre><code class="console">&#39;Hello Webpack!&#39;</code></pre><h1 id="EJS-템플릿-사용하기"><a href="#EJS-템플릿-사용하기" class="headerlink" title="EJS 템플릿 사용하기"></a>EJS 템플릿 사용하기</h1><p>EJS 템플릿(template)을 사용해 보겠습니다.</p><p>이제 테스트는 끝났으니 <code>dist</code> 디렉토리를 삭제하고,<br><code>src</code> 디렉토리에 <code>index.ejs</code> 파일을 생성하고 아래와 같이 작성합니다.</p><pre><code class="ejs">&lt;!-- index.ejs --&gt;&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot;&gt;&lt;head&gt;    &lt;meta charset=&quot;UTF-8&quot;&gt;    &lt;title&gt;&lt;%= htmlWebpackPlugin.options.title %&gt;&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><p>디텍토리 구조을 아래와 같이 만드시면 됩니다.</p><pre><code class="bash">webpack-test  ├─src  │  ├─app.js  │  └─index.ejs  ├─package.json  └─webpack.config.js</code></pre><p>그리고 EJS 템플릿을 사용하기 위한 아래의 플러그인을 설치합니다.</p><p><a href="https://github.com/jantimon/html-webpack-plugin" target="_blank" rel="noopener">HTML Webpack Plugin</a></p><pre><code class="bash">$ npm i html-webpack-plugin -D</code></pre><p><code>webpack.config.js</code> 파일의 내용을 아래와 같이 변경합니다.</p><pre><code class="js">// webpack.config.jsvar HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;);module.exports = {  entry: &#39;./src/app.js&#39;,  output: {    path: __dirname + &#39;/dist&#39;,    filename: &#39;app.bundle.js&#39;  },  plugins: [    new HtmlWebpackPlugin({      title: &#39;Hello Webpack Project!&#39;,      template: &#39;./src/index.ejs&#39;    })  ]};</code></pre><p>실행하고 확인해 보세요.</p><pre><code class="bash">$ npm run dev</code></pre><p>디텍토리의 구조가 아래 처럼 변경됩니다.</p><pre><code class="bash">webpack-test  ├─dist  │  ├─app.bundle.js  │  └─index.html  ├─src  │  ├─app.js  │  └─index.ejs  ├─package.json  └─webpack.config.js</code></pre><p>마지막으로 플러그인의 몇가지 옵션을 적용해 봅시다.</p><pre><code class="js">// webpack.config.jsnew HtmlWebpackPlugin({  title: &#39;Hello Webpack Project!&#39;,  minify: {    collapseWhitespace: true  },  hash: true,  template: &#39;./src/index.ejs&#39;})</code></pre><p><code>collapseWhitespace</code>: 문서의 텍스트 노드에서 공백을 제거합니다.<br><code>hash</code>: 모든 CSS 혹은 JS 파일에 고유한 웹팩 컴파일 해시를 추가합니다. 캐시 무효화에 유용하다고 하네요.</p><h1 id="Style-CSS-그리고-Sass-Loaders-사용하기"><a href="#Style-CSS-그리고-Sass-Loaders-사용하기" class="headerlink" title="Style, CSS 그리고 Sass Loaders 사용하기"></a>Style, CSS 그리고 Sass Loaders 사용하기</h1><h2 id="Loaders-란"><a href="#Loaders-란" class="headerlink" title="Loaders 란?"></a>Loaders 란?</h2><blockquote><p>파일을 가져 오거나 ‘Load’할 때 파일을 사전 처리 할 수 ​​있습니다. ‘Gulp’나 ‘Grunt’ 같은 다른 빌드 도구에서의 “Task”과 유사하며 프론트엔드 빌드 단계를 처리하는 강력한 방법을 제공합니다. 파일을 ‘TypeScript’ 같은 다른 언어에서 ‘JavaScript’로 변환하거나 인라인 이미지를 데이터 URL로 변형할 수 있습니다. 자바스크립트 모듈에서 직접 CSS 파일을 가져 오는 것과 같은 일을 할 수도 있습니다.</p></blockquote><h2 id="style-loader-와-css-loader-사용"><a href="#style-loader-와-css-loader-사용" class="headerlink" title="style-loader 와 css-loader 사용"></a>style-loader 와 css-loader 사용</h2><p>CSS를 처리하기 위해 먼저 <code>style-loader</code>와 <code>css-loader</code>를 설치합니다.</p><p><code>style-loader</code>: CSS를 <code>&lt;style&gt;</code> 태그로 출력합니다.<br><code>css-loader</code>: 자바스크립트 안에 CSS를 해석하고, 모든 의존성을 해결 합니다.</p><pre><code class="bash">$ npm i style-loader css-loader -D</code></pre><p>그리고 <code>webpack.config.js</code>에 <code>module</code>을 추가합니다.</p><pre><code class="js">// webpack.config.jsvar HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;);module.exports = {  entry: &#39;./src/app.js&#39;,  output: {    path: __dirname + &#39;/dist&#39;,    filename: &#39;app.bundle.js&#39;  },  module: {    rules: [      {        test: /\.css$/,        use: [          &#39;style-loader&#39;,          &#39;css-loader&#39;        ]      }    ]  },  plugins: [    new HtmlWebpackPlugin({      title: &#39;Hello Webpack Project!&#39;,      template: &#39;./src/index.ejs&#39;    })  ]};</code></pre><p><code>app.css</code> 파일을 생성하고 간단한 css 코드를 추가합니다.</p><pre><code class="css">/* app.css */body {  background: tomato;}</code></pre><p><code>app.js</code> 파일로 가져옵니다.</p><pre><code class="js">// app.jsvar css = require(&#39;./app.css&#39;);</code></pre><p>실행하여 <code>dist/index.html</code> 파일을 실행하면 배경 색상이 변경된 것을 볼 수 있습니다.</p><pre><code class="bash">$ npm dev run</code></pre><h2 id="sass-loader-사용"><a href="#sass-loader-사용" class="headerlink" title="sass-loader 사용"></a>sass-loader 사용</h2><p><a href="https://www.npmjs.com/package/sass-loader" target="_blank" rel="noopener">sass-loader</a>를 설치합니다.</p><pre><code class="bash">$ npm i sass-loader node-sass -D</code></pre><p>설치가 끝나면 <code>css</code>로 작성된 부분들을 <code>scss</code>로 수정합니다.</p><pre><code class="js">// webpack.config.jstest: /\.scss$/,use: [  &#39;style-loader&#39;,  &#39;css-loader&#39;,  &#39;sass-loader&#39;]</code></pre><pre><code class="js">// app.jsvar css = require(&#39;./app.scss&#39;);</code></pre><p>CSS 파일도 확장자를 수정하고, 내용을 Sass(Scss) 문법으로 바꿔보죠.</p><pre><code class="scss">/* app.sass */body {  background: tomato;  p {    color: white;  }}</code></pre><p>그에 맞게 <code>index.ejs</code> 파일도 수정합니다.</p><pre><code class="ejs">&lt;!-- index.ejs --&gt;&lt;body&gt;  &lt;p&gt;Hello Sass!&lt;/p&gt;&lt;/body&gt;</code></pre><p>실행합니다.</p><pre><code class="bash">$ npm run dev</code></pre><h2 id="단일-파일로-추출하기"><a href="#단일-파일로-추출하기" class="headerlink" title="단일 파일로 추출하기"></a>단일 파일로 추출하기</h2><p>우리는 <code>app.js</code>파일에 <code>require(&#39;./app.css&#39;)</code>코드를 사용하여 <code>app.bundle.js</code>파일로 모두 병합했습니다.<br>그러나 css 파일의 규모가 커지면 번들 파일 내에서 인라인으로 많은 양의 코드가 읽히기 때문에, 구분하여 호출하는 것이 병렬 방식으로 로딩할 수 있어 더 빠르게 동작할 것입니다.</p><p><a href="https://github.com/webpack-contrib/extract-text-webpack-plugin" target="_blank" rel="noopener">extract text plugin for webpack</a>을 설치합니다.</p><pre><code class="bash">$ npm i extract-text-webpack-plugin -D</code></pre><p><code>webpac.config.js</code>파일의 내용을 아래와 같이 수정하세요.</p><pre><code class="js">// webpack.config.jsvar ExtractTextPlugin = require(&#39;extract-text-webpack-plugin&#39;);// ...{  test: /\.scss$/,  use: ExtractTextPlugin.extract({    fallback: &#39;style-loader&#39;,    use: [      &#39;css-loader&#39;,      &#39;sass-loader&#39;    ],    publicPath: &#39;/dist/&#39;  // &#39;../images/&#39;  })}// ...</code></pre><p><code>fallback</code>: CSS가 추출되지 않을 때 사용될 로더<br><code>use</code>: CSS 내보내기 모듈로 변환하는데 사용할 로더<br><code>publicPath</code>: 경로 재설정</p><pre><code class="js">// webpack.config.js// ...{  plugins: [    new HtmlWebpackPlugin({      // ...    }),    new ExtractTextPlugin(&#39;app.css&#39;)  ]}// ...</code></pre><p>다음으로 로컬에서 사용할 개발용 서버 구성(<code>webpack-dev-server</code>)에 대해서 알아볼께요.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;시작하기&quot;&gt;&lt;a href=&quot;#시작하기&quot; class=&quot;headerlink&quot; title=&quot;시작하기&quot;&gt;&lt;/a&gt;시작하기&lt;/h1&gt;&lt;p&gt;빈 프로젝트 디렉토리를 생성한 후 터미널을 이용하여, &lt;code&gt;package.json&lt;/code&gt;을 생성하세요
      
    
    </summary>
    
    
      <category term="js" scheme="https://heropy.blog/tags/js/"/>
    
      <category term="webpack" scheme="https://heropy.blog/tags/webpack/"/>
    
  </entry>
  
  <entry>
    <title>MarkDown 사용법 총정리</title>
    <link href="https://heropy.blog/2017/09/30/markdown/"/>
    <id>https://heropy.blog/2017/09/30/markdown/</id>
    <published>2017-09-30T07:03:22.000Z</published>
    <updated>2018-02-26T07:49:48.000Z</updated>
    
    <content type="html"><![CDATA[<p>마크다운(MarkDown)에 대해서 알고 계신가요?<br>파일 확장자가 <code>.md</code>로 된 파일을 보셨나요?<br>웹 개발을 하면서 아마 <code>README.md</code>라는 이름의 파일을 한 번은 보셨을텐데, 이 파일이 마크다운 문법으로 작성된 파일 입니다.</p><p>사용법이 매우 쉽고, 빠르게 문서를 정리할 수 있습니다.<br>물론 모든 HTML 마크업을 대신할 수 없기 때문에 지나친 의존보다는 쉽고 빠르게 작성하는 용도로 사용하세요.<br>마크다운과 비슷한 형태로 문법이 좀 더 복잡하지만 확장자가 <code>.adoc</code>인 <a href="http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#tables" target="_blank" rel="noopener">AsciiDoc</a> 문법도 있습니다. 좀 더 다양한 형태의 문서를 만들 수 있지만, 문법이 좀 더 복잡하고 지원 플랫폼이 적습니다.</p><p>우선 문법이 쉽고 다양한 플랫폼을 지원하는 마크다운 문법을 배우세요.<br>30분이면 충분합니다.</p><div class="toc"><ul><li><a href="77167ee6-a9c4-4d00-9601-430354d40ceb">마크다운(MarkDown)에 대해서</a><ul><li><a href="b60ca103-adf6-4122-a972-f84d97b8ed8a">마크다운의 장점</a></li><li><a href="2700bcf2-f0d8-4bd9-b1d4-c22927a6deb7">마크다운의 단점</a></li><li><a href="93d165c7-3e9c-49b0-b938-fb7fd5d5a6f5">마크다운의 사용</a></li></ul></li><li><a href="1e9bf8c5-168b-4482-9d75-79c5e3373c57">마크다운 문법(syntax)</a><ul><li><a href="537a7138-f45b-4be7-b051-3b0274596eda">제목(Header)</a></li><li><a href="c3bd817c-a9a4-4adb-a96f-d7f666ba215c">강조(Emphasis)</a></li><li><a href="cfa160f7-cd5c-402b-a84b-fb65d7bbee5d">목록(List)</a></li><li><a href="fe43e83d-2727-455d-8c4e-427554ad3cfc">링크(Links)</a></li><li><a href="675ccdfa-2d84-40ac-a8af-93cd5a589cc2">이미지(Images)</a><ul><li><a href="2ff766d8-7050-4fc9-b04b-2dec8f17f4c2">이미지에 링크</a></li></ul></li><li><a href="98c0b801-3c33-4f23-b8ad-0f07d05d68eb">코드(Code) 강조</a><ul><li><a href="a97ffacb-1858-4202-8f82-2eec23d69f4a">인라인(inline) 코드 강조</a></li><li><a href="9aa394f2-330e-4f3b-865b-05fd436b5f42">블록(block) 코드 강조</a></li></ul></li><li><a href="b81aadaa-6b64-4904-8b3b-41d52d93d6a6">표(Table)</a></li><li><a href="98e913f1-42b6-4ca4-8e98-9fd148a24ae5">인용문(BlockQuote)</a></li><li><a href="25fc88de-fd8b-4b70-be95-90741989fb9f">원시 HTML(Raw HTML)</a></li><li><a href="210835c3-76dd-4679-b365-b7702837b231">수평선(Horizontal Rule)</a></li><li><a href="adaafdd3-62e3-487e-86e4-cbe65a2b05cd">줄바꿈(Line Breaks)</a></li></ul></li></ul></div><h1><span id="77167ee6-a9c4-4d00-9601-430354d40ceb">마크다운(MarkDown)에 대해서</span><a href="#77167ee6-a9c4-4d00-9601-430354d40ceb" class="header-anchor"></a></h1><h2><span id="b60ca103-adf6-4122-a972-f84d97b8ed8a">마크다운의 장점</span><a href="#b60ca103-adf6-4122-a972-f84d97b8ed8a" class="header-anchor"></a></h2><ol><li>문법이 쉽다.</li><li>관리가 쉽다.</li><li>지원 가능한 플랫폼과 프로그램이 다양하다.</li></ol><h2><span id="2700bcf2-f0d8-4bd9-b1d4-c22927a6deb7">마크다운의 단점</span><a href="#2700bcf2-f0d8-4bd9-b1d4-c22927a6deb7" class="header-anchor"></a></h2><ol><li>표준이 없어 사용자마다 문법이 상이할 수 있다.</li><li>모든 HTML 마크업을 대신하지 못한다.</li></ol><h2><span id="93d165c7-3e9c-49b0-b938-fb7fd5d5a6f5">마크다운의 사용</span><a href="#93d165c7-3e9c-49b0-b938-fb7fd5d5a6f5" class="header-anchor"></a></h2><p>메모장부터 전용 에디터까지 많은 곳에서 활용할 수 있습니다.<br>문법이 쉽기 때문에 꼭 전용 에디터를 사용할 필요는 없습니다만, 마크다운 코드의 하이라이트 효과를 원한다면 전용 에디터가 좋은 선택이 될 것 같네요.<br>저는 평소 <a href="https://atom.io" target="_blank" rel="noopener">Atom</a>을 사용하고 있습니다.<br>혹은 마크다운 문법을 지원하는 모든 곳에서 사용할 수 있으며, 일반 블로그나 워드프레스 외 <a href="https://slack.com/" target="_blank" rel="noopener">Slack</a>이나 <a href="https://trello.com/" target="_blank" rel="noopener">Trello</a> 같은 서비스에서 메세지를 작성하듯 사용할 수도 있습니다.<br>화면에 표현되는 스타일(CSS)은 설정에 따라 달라집니다.<br>HTML과 마찬가지로 눈에 보이는 스타일은 무시하고 각 문법의 의미로 사용하세요.</p><h1><span id="1e9bf8c5-168b-4482-9d75-79c5e3373c57">마크다운 문법(syntax)</span><a href="#1e9bf8c5-168b-4482-9d75-79c5e3373c57" class="header-anchor"></a></h1><h2><span id="537a7138-f45b-4be7-b051-3b0274596eda">제목(Header)</span><a href="#537a7138-f45b-4be7-b051-3b0274596eda" class="header-anchor"></a></h2><p><code>&lt;h1&gt;</code>부터 <code>&lt;h6&gt;</code>까지 제목을 표현할 수 있습니다.</p><pre><code class="markdown"># 제목 1## 제목 2### 제목 3#### 제목 4##### 제목 5###### 제목 6</code></pre><p>제목1(h1)과 제목2(h2)는 다음과 같이 표현할 수 있습니다.</p><pre><code class="markdown">제목 1======제목 2------</code></pre><h2><span id="c3bd817c-a9a4-4adb-a96f-d7f666ba215c">강조(Emphasis)</span><a href="#c3bd817c-a9a4-4adb-a96f-d7f666ba215c" class="header-anchor"></a></h2><p>각각 <code>&lt;em&gt;</code>, <code>&lt;strong&gt;</code>, <code>&lt;del&gt;</code> 태그로 변환됩니다.</p><p>밑줄을 입력하고 싶다면 <code>&lt;u&gt;&lt;/u&gt;</code> 태그를 사용하세요.</p><pre><code class="markdown">이텔릭체는 *별표(asterisks)* 혹은 _언더바(underscore)_를 사용하세요.두껍게는 **별표(asterisks)** 혹은 __언더바(underscore)__를 사용하세요.**_이텔릭체_와 두껍게**를 같이 사용할 수 있습니다.취소선은 ~~물결표시(tilde)~~를 사용하세요.&lt;u&gt;밑줄&lt;/u&gt;은 `&lt;u&gt;&lt;/u&gt;`를 사용하세요.</code></pre><p>이텔릭체는 <em>별표(asterisks)</em> 혹은 <em>언더바(underscore)</em>를 사용하세요.<br>두껍게는 <strong>별표(asterisks)</strong> 혹은 <strong>언더바(underscore)</strong>를 사용하세요.<br><strong><em>이텔릭체</em>와 두껍게</strong>를 같이 사용할 수 있습니다.<br>취소선은 <del>물결표시(tilde)</del>를 사용하세요.<br><u>밑줄</u>은 <code>&lt;u&gt;&lt;/u&gt;</code>를 사용하세요.</p><h2><span id="cfa160f7-cd5c-402b-a84b-fb65d7bbee5d">목록(List)</span><a href="#cfa160f7-cd5c-402b-a84b-fb65d7bbee5d" class="header-anchor"></a></h2><p><code>&lt;ol&gt;</code>, <code>&lt;ul&gt;</code> 목록 태그로 변환됩니다.</p><pre><code class="markdown">1. 순서가 필요한 목록1. 순서가 필요한 목록  - 순서가 필요하지 않은 목록(서브)   - 순서가 필요하지 않은 목록(서브) 1. 순서가 필요한 목록  1. 순서가 필요한 목록(서브)  1. 순서가 필요한 목록(서브)1. 순서가 필요한 목록- 순서가 필요하지 않은 목록에 사용 가능한 기호  - 대쉬(hyphen)  * 별표(asterisks)  + 더하기(plus sign)</code></pre><ol><li>순서가 필요한 목록</li><li>순서가 필요한 목록<ul><li>순서가 필요하지 않은 목록(서브)</li><li>순서가 필요하지 않은 목록(서브)</li></ul></li><li>순서가 필요한 목록<ol><li>순서가 필요한 목록(서브)</li><li>순서가 필요한 목록(서브)</li></ol></li><li>순서가 필요한 목록</li></ol><ul><li>순서가 필요하지 않은 목록에 사용 가능한 기호<ul><li>대쉬(hyphen)</li></ul><ul><li>별표(asterisks)</li></ul><ul><li>더하기(plus sign)</li></ul></li></ul><h2><span id="fe43e83d-2727-455d-8c4e-427554ad3cfc">링크(Links)</span><a href="#fe43e83d-2727-455d-8c4e-427554ad3cfc" class="header-anchor"></a></h2><p><code>&lt;a&gt;</code>로 변환됩니다.</p><pre><code class="markdown">[GOOGLE](https://google.com)[NAVER](https://naver.com &quot;링크 설명(title)을 작성하세요.&quot;)[상대적 참조](../users/login)[Dribbble][Dribbble link][GitHub][1]문서 안에서 [참조 링크]를 그대로 사용할 수도 있습니다.다음과 같이 문서 내 일반 URL이나 꺾쇠 괄호(`&lt; &gt;`, Angle Brackets)안의 URL은 자동으로 링크를 사용합니다.구글 홈페이지: https://google.com네이버 홈페이지: &lt;https://naver.com&gt;[Dribbble link]: https://dribbble.com[1]: https://github.com[참조 링크]: https://naver.com &quot;네이버로 이동합니다!&quot;</code></pre><p><a href="https://google.com" target="_blank" rel="noopener">GOOGLE</a></p><p><a href="https://naver.com" title="링크 설명(title)을 작성하세요." target="_blank" rel="noopener">NAVER</a></p><p><a href="../users/login">상대적 참조</a></p><p><a href="https://dribbble.com" target="_blank" rel="noopener">Dribbble</a></p><p><a href="https://github.com" target="_blank" rel="noopener">GitHub</a></p><p>문서 안에서 <a href="https://naver.com" title="네이버로 이동합니다!" target="_blank" rel="noopener">참조 링크</a>를 그대로 사용할 수도 있습니다.</p><p>다음과 같이 문서 내 일반 URL이나 꺾쇠 괄호(<code>&lt; &gt;</code>, Angle Brackets)안의 URL은 자동으로 링크를 사용합니다.</p><p>구글 홈페이지: <a href="https://google.com" target="_blank" rel="noopener">https://google.com</a><br>네이버 홈페이지: <a href="https://naver.com" target="_blank" rel="noopener">https://naver.com</a></p><h2><span id="675ccdfa-2d84-40ac-a8af-93cd5a589cc2">이미지(Images)</span><a href="#675ccdfa-2d84-40ac-a8af-93cd5a589cc2" class="header-anchor"></a></h2><p><code>&lt;img&gt;</code>로 변환됩니다.<br>링크과 비슷하지만 앞에 <code>!</code>가 붙습니다.</p><pre><code class="markdown">![대체 텍스트(alternative text)를 입력하세요!](http://www.gstatic.com/webp/gallery/5.jpg &quot;링크 설명(title)을 작성하세요.&quot;)![Kayak][logo][logo]: http://www.gstatic.com/webp/gallery/2.jpg &quot;To go kayaking.&quot;</code></pre><p><img src="http://www.gstatic.com/webp/gallery/5.jpg" alt="대체 텍스트(alternative text)를 입력하세요!" title="링크 설명(title)을 작성하세요."></p><p><img src="http://www.gstatic.com/webp/gallery/2.jpg" alt="Kayak" title="To go kayaking."></p><h3><span id="2ff766d8-7050-4fc9-b04b-2dec8f17f4c2">이미지에 링크</span><a href="#2ff766d8-7050-4fc9-b04b-2dec8f17f4c2" class="header-anchor"></a></h3><p>마크다운 이미지 코드를 링크 코드로 묶어 줍니다.</p><pre><code class="markdown">[![Vue](/images/vue.png)](https://kr.vuejs.org/)</code></pre><p><a href="https://kr.vuejs.org/" target="_blank" rel="noopener"><img src="/images/vue.png" alt="Vue"></a></p><h2><span id="98c0b801-3c33-4f23-b8ad-0f07d05d68eb">코드(Code) 강조</span><a href="#98c0b801-3c33-4f23-b8ad-0f07d05d68eb" class="header-anchor"></a></h2><p><code>&lt;pre&gt;</code>, <code>&lt;code&gt;</code>로 변환됩니다.<br>숫자 1번 키 왼쪽에 있는 <code>`</code>(Grave)를 입력하세요</p><h3><span id="a97ffacb-1858-4202-8f82-2eec23d69f4a">인라인(inline) 코드 강조</span><a href="#a97ffacb-1858-4202-8f82-2eec23d69f4a" class="header-anchor"></a></h3><pre><code class="markdown">`background`혹은 `background-image` 속성으로 요소에 배경 이미지를 삽입할 수 있습니다.</code></pre><p><code>background</code>혹은 <code>background-image</code> 속성으로 요소에 배경 이미지를 삽입할 수 있습니다.</p><h3><span id="9aa394f2-330e-4f3b-865b-05fd436b5f42">블록(block) 코드 강조</span><a href="#9aa394f2-330e-4f3b-865b-05fd436b5f42" class="header-anchor"></a></h3><p><code>`</code>를 3번 이상 입력하고 코드 종류도 적습니다.</p><pre><code class="markdown">```html&lt;a href="https://www.google.co.kr/" target="_blank"&gt;GOOGLE&lt;/a&gt;``````css.list > li {  position: absolute;  top: 40px;}``````javascriptfunction func() {  var a = 'AAA';  return a;}``````bash$ vim ./~zshrc``````pythons = "Python syntax highlighting"print s``````No language indicated, so no syntax highlighting. But let's throw in a <b>tag</b>.```</code></pre><pre><code class="html">&lt;a href=&quot;https://www.google.co.kr/&quot; target=&quot;_blank&quot;&gt;GOOGLE&lt;/a&gt;</code></pre><pre><code class="css">.list &gt; li {  position: absolute;  top: 40px;}</code></pre><pre><code class="javascript">function func() {  var a = &#39;AAA&#39;;  return a;}</code></pre><pre><code class="bash">$ vim ./~zshrc</code></pre><pre><code class="python">s = &quot;Python syntax highlighting&quot;print s</code></pre><pre><code>No language indicated, so no syntax highlighting. But let&#39;s throw in a &lt;b&gt;tag&lt;/b&gt;.</code></pre><h2><span id="b81aadaa-6b64-4904-8b3b-41d52d93d6a6">표(Table)</span><a href="#b81aadaa-6b64-4904-8b3b-41d52d93d6a6" class="header-anchor"></a></h2><p><code>&lt;table&gt;</code> 태그로 변환됩니다.<br>헤더 셀을 구분할 때 3개 이상의 <code>-</code>(hyphen/dash) 기호가 필요합니다.<br>헤더 셀을 구분하면서 <code>:</code>(Colons) 기호로 셀(열/칸) 안에 내용을 정렬할 수 있습니다.<br>가장 좌측과 가장 우측에 있는 <code>|</code>(vertical bar) 기호는 생략 가능합니다.</p><pre><code class="markdown">| 값 | 의미 | 기본값 ||---|:---:|---:|| `static` | 유형(기준) 없음 / 배치 불가능 | `static` || `relative` | 요소 자신을 기준으로 배치 |  || `absolute` | 위치 상 부모(조상)요소를 기준으로 배치 |  || `fixed` | 브라우저 창을 기준으로 배치 |  |값 | 의미 | 기본값---|:---:|---:`static` | 유형(기준) 없음 / 배치 불가능 | `static``relative` | 요소 **자신**을 기준으로 배치 |`absolute` | 위치 상 **_부모_(조상)요소**를 기준으로 배치 |`fixed` | **브라우저 창**을 기준으로 배치 |</code></pre><table><thead><tr><th>값</th><th style="text-align:center">의미</th><th style="text-align:right">기본값</th></tr></thead><tbody><tr><td><code>static</code></td><td style="text-align:center">유형(기준) 없음 / 배치 불가능</td><td style="text-align:right"><code>static</code></td></tr><tr><td><code>relative</code></td><td style="text-align:center">요소 <strong>자신</strong>을 기준으로 배치</td><td style="text-align:right"></td></tr><tr><td><code>absolute</code></td><td style="text-align:center">위치 상 <strong><em>부모</em>(조상)요소</strong>를 기준으로 배치</td><td style="text-align:right"></td></tr><tr><td><code>fixed</code></td><td style="text-align:center"><strong>브라우저 창</strong>을 기준으로 배치</td><td style="text-align:right"></td></tr></tbody></table><h2><span id="98e913f1-42b6-4ca4-8e98-9fd148a24ae5">인용문(BlockQuote)</span><a href="#98e913f1-42b6-4ca4-8e98-9fd148a24ae5" class="header-anchor"></a></h2><p><code>&lt;blockquote&gt;</code> 태그로 변환됩니다.</p><pre><code class="markdown">인용문(blockQuote)&gt; 남의 말이나 글에서 직접 또는 간접으로 따온 문장.&gt; _(네이버 국어 사전)_BREAK!&gt; 인용문을 작성하세요!&gt;&gt; 중첩된 인용문(nested blockquote)을 만들 수 있습니다.&gt;&gt;&gt; 중중첩된 인용문 1&gt;&gt;&gt; 중중첩된 인용문 2&gt;&gt;&gt; 중중첩된 인용문 3</code></pre><p>인용문(blockQuote)</p><blockquote><p>남의 말이나 글에서 직접 또는 간접으로 따온 문장.<br><em>(네이버 국어 사전)</em></p></blockquote><p>BREAK!</p><blockquote><p>인용문을 작성하세요!</p><blockquote><p>중첩된 인용문(nested blockquote)을 만들 수 있습니다.</p><blockquote><p>중중첩된 인용문 1<br>중중첩된 인용문 2<br>중중첩된 인용문 3</p></blockquote></blockquote></blockquote><h2><span id="25fc88de-fd8b-4b70-be95-90741989fb9f">원시 HTML(Raw HTML)</span><a href="#25fc88de-fd8b-4b70-be95-90741989fb9f" class="header-anchor"></a></h2><p>마크다운 문법이 아닌 원시 HTML 문법을 사용할 수 있습니다.</p><pre><code class="markdown">&lt;u&gt;마크다운에서 지원하지 않는 기능&lt;/u&gt;을 사용할 때 유용하며 대부분 잘 동작합니다.&lt;img width=&quot;150&quot; src=&quot;http://www.gstatic.com/webp/gallery/4.jpg&quot; alt=&quot;Prunus&quot; title=&quot;A Wild Cherry (Prunus avium) in flower&quot;&gt;![Prunus](http://www.gstatic.com/webp/gallery/4.jpg)</code></pre><p><u>마크다운에서 지원하지 않는 기능</u>을 사용할 때 유용하며 대부분 잘 동작합니다.</p><p><img width="150" src="http://www.gstatic.com/webp/gallery/4.jpg" alt="Prunus" title="A Wild Cherry (Prunus avium) in flower"></p><p><img src="http://www.gstatic.com/webp/gallery/4.jpg" alt="Prunus"></p><h2><span id="210835c3-76dd-4679-b365-b7702837b231">수평선(Horizontal Rule)</span><a href="#210835c3-76dd-4679-b365-b7702837b231" class="header-anchor"></a></h2><p>각 기호를 3개 이상 입력하세요.</p><pre><code class="markdown">---(Hyphens)***(Asterisks)___(Underscores)</code></pre><hr><p>(Hyphens)</p><hr><p>(Asterisks)</p><hr><p>(Underscores)</p><h2><span id="adaafdd3-62e3-487e-86e4-cbe65a2b05cd">줄바꿈(Line Breaks)</span><a href="#adaafdd3-62e3-487e-86e4-cbe65a2b05cd" class="header-anchor"></a></h2><pre><code class="markdown">동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세   &lt;!--띄어쓰기 2번--&gt;무궁화 삼천리 화려 강산&lt;br&gt;대한 사람 대한으로 길이 보전하세</code></pre><p>동해물과 백두산이 마르고 닳도록<br>하느님이 보우하사 우리나라 만세<br>무궁화 삼천리 화려 강산<br>대한 사람 대한으로 길이 보전하세</p><blockquote><p>일반 줄비꿈이 동작하지 않는 환경(설정 및 버전에 따라)의 경우, ‘2번의 띄어쓰기’나 <code>&lt;br&gt;</code>를 활용할 수 있습니다.</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;마크다운(MarkDown)에 대해서 알고 계신가요?&lt;br&gt;파일 확장자가 &lt;code&gt;.md&lt;/code&gt;로 된 파일을 보셨나요?&lt;br&gt;웹 개발을 하면서 아마 &lt;code&gt;README.md&lt;/code&gt;라는 이름의 파일을 한 번은 보셨을텐데, 이 파일이 
      
    
    </summary>
    
    
      <category term="asciidoc" scheme="https://heropy.blog/tags/asciidoc/"/>
    
      <category term="md" scheme="https://heropy.blog/tags/md/"/>
    
      <category term="markdown" scheme="https://heropy.blog/tags/markdown/"/>
    
      <category term="html" scheme="https://heropy.blog/tags/html/"/>
    
  </entry>
  
</feed>
