<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개탕 IT FACTORY</title>
    <link>https://rendaritfactory.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 05:59:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>rendar02</managingEditor>
    <item>
      <title>나의 전용 템플릿 엔진(?) 제작기 - react-template</title>
      <link>https://rendaritfactory.tistory.com/48</link>
      <description>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;이번엔 React 템플릿을 만들어볼려고한다.&lt;/p&gt;
&lt;p&gt;기본적인 구조는 리액트 프로젝트를 따라갈건데 기초 패키지는 정하고 템플릿을 만들예정이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TanStack Query (데이터 페칭)&lt;/li&gt;
&lt;li&gt;Zustand (상태 관리)&lt;/li&gt;
&lt;li&gt;Axios(API통신)&lt;/li&gt;
&lt;li&gt;React Router V7&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;기본적으로 위 패키지는 기본장착을 할 예정이다&lt;br&gt;스타일 부분은 tailwind, emotion, vanilla-extract 등이 고민되지만 (대세는 tailwind이긴하나…)&lt;br&gt;사실 각 css 프레임워크 별로 나눠서 템플릿을 분기처리할까 생각중이다.&lt;/p&gt;
&lt;p&gt;또 테스트 라이브러리도 cypress, playwright 고민을 하엿으나, 대세는 playwright라고 하였다.&lt;br&gt;(무엇보다 다양한 언어에 지원되는게 좋았던것같다.)&lt;/p&gt;
&lt;h2&gt;환경구성&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;my-monorepo-template/
├── packages/
│   ├── eslint-config/     # ESLint 설정 패키지
│   ├── prettier-config/   # Prettier 설정 패키지
│   ├── tsconfig/          # TypeScript 설정 패키지
│   ├── **react-template**/    # React 템플릿 패키지
│   ├── **create-react**/      # React 프로젝트 생성 도구
│   └── nextjs-template/   # (예정) Next.js 템플릿
├── package.json
└── pnpm-workspace.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;초기설정&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 패키지 디렉토리 생성
mkdir -p packages/react-template
cd packages/react-template

# 패키지 초기화
pnpm init

# 기본 의존성 설치
pnpm add react-router-dom@^7.4.1
pnpm add -D @vitejs/plugin-react@^4.3.4 vite@^6.0.7

# 모노레포 내 다른 패키지 의존성 추가
pnpm add -D @rendardev/eslint-config@workspace:* @rendardev/prettier-config@workspace:* @rendardev/tsconfig@workspace:*
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;프로젝트 환경 설정&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;: 패키지 정보 및 스크립트 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;: TypeScript 설정 (@rendardev/tsconfig/react.json 상속)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.eslintrc.js&lt;/code&gt;: ESLint 설정 (@rendardev/eslint-config/react 상속)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.prettierrc&lt;/code&gt;: Prettier 설정 (@rendardev/prettier-config 상속)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vite.config.ts&lt;/code&gt;: Vite 빌드 도구 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;playwright.config.ts&lt;/code&gt;: Playwright 테스트 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;프로젝트 코드&lt;/h2&gt;
&lt;h3&gt;1. package.json&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;@rendardev/react-template&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.4&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;React + TypeScript + Vite 템플릿&amp;quot;,
  &amp;quot;private&amp;quot;: false,
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;,
  &amp;quot;files&amp;quot;: [
    &amp;quot;src&amp;quot;,
    &amp;quot;index.html&amp;quot;,
    &amp;quot;vite.config.ts&amp;quot;,
    &amp;quot;tsconfig.json&amp;quot;,
    &amp;quot;.eslintrc.js&amp;quot;,
    &amp;quot;.prettierrc&amp;quot;,
    &amp;quot;.gitignore&amp;quot;,
    &amp;quot;README.md&amp;quot;,
    &amp;quot;playwright.config.ts&amp;quot;,
    &amp;quot;e2e&amp;quot;,
    &amp;quot;playwright.d.ts&amp;quot;
  ],
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;dev&amp;quot;: &amp;quot;vite&amp;quot;,
    &amp;quot;build&amp;quot;: &amp;quot;tsc &amp;amp;&amp;amp; vite build&amp;quot;,
    &amp;quot;preview&amp;quot;: &amp;quot;vite preview&amp;quot;,
    &amp;quot;lint&amp;quot;: &amp;quot;eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0&amp;quot;,
    &amp;quot;format&amp;quot;: &amp;quot;prettier --write \\&amp;quot;src/**/*.{ts,tsx,css,md}\\&amp;quot;&amp;quot;,
    &amp;quot;test&amp;quot;: &amp;quot;playwright test&amp;quot;,
    &amp;quot;test:ui&amp;quot;: &amp;quot;playwright test --ui&amp;quot;,
    &amp;quot;test:debug&amp;quot;: &amp;quot;playwright test --debug&amp;quot;,
    &amp;quot;test:report&amp;quot;: &amp;quot;playwright show-report&amp;quot;
  },
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;@tanstack/react-query&amp;quot;: &amp;quot;^5.24.1&amp;quot;,
    &amp;quot;@tanstack/react-query-devtools&amp;quot;: &amp;quot;^5.24.1&amp;quot;,
    &amp;quot;axios&amp;quot;: &amp;quot;^1.8.4&amp;quot;,
    &amp;quot;react-router-dom&amp;quot;: &amp;quot;^7.4.1&amp;quot;,
    &amp;quot;zustand&amp;quot;: &amp;quot;^4.5.1&amp;quot;
  },
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;@rendardev/eslint-config&amp;quot;: &amp;quot;workspace:*&amp;quot;,
    &amp;quot;@rendardev/prettier-config&amp;quot;: &amp;quot;workspace:*&amp;quot;,
    &amp;quot;@rendardev/tsconfig&amp;quot;: &amp;quot;workspace:*&amp;quot;,
    &amp;quot;@playwright/test&amp;quot;: &amp;quot;^1.42.1&amp;quot;,
    &amp;quot;@types/node&amp;quot;: &amp;quot;^20.11.30&amp;quot;,
    &amp;quot;@vitejs/plugin-react&amp;quot;: &amp;quot;^4.3.4&amp;quot;,
    &amp;quot;vite&amp;quot;: &amp;quot;^6.0.7&amp;quot;
  },
  &amp;quot;peerDependencies&amp;quot;: {
    &amp;quot;react&amp;quot;: &amp;quot;&amp;gt;=19.0.0&amp;quot;,
    &amp;quot;react-dom&amp;quot;: &amp;quot;&amp;gt;=19.0.0&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. tsconfig.json&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;extends&amp;quot;: &amp;quot;@rendardev/tsconfig/react.json&amp;quot;,
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;baseUrl&amp;quot;: &amp;quot;.&amp;quot;,
    &amp;quot;paths&amp;quot;: {
      &amp;quot;@/*&amp;quot;: [&amp;quot;./src/*&amp;quot;]
    },
    &amp;quot;esModuleInterop&amp;quot;: true,
    &amp;quot;jsx&amp;quot;: &amp;quot;react-jsx&amp;quot;,
    &amp;quot;types&amp;quot;: [&amp;quot;node&amp;quot;]
  },
  &amp;quot;include&amp;quot;: [&amp;quot;src&amp;quot;, &amp;quot;e2e&amp;quot;, &amp;quot;playwright.config.ts&amp;quot;],
  &amp;quot;exclude&amp;quot;: [&amp;quot;node_modules&amp;quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. .eslintrc.js&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;module.exports = {
  extends: [&amp;quot;@rendardev/eslint-config/react&amp;quot;],
  root: true
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. .prettierrc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;extends&amp;quot;: &amp;quot;@rendardev/prettier-config&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. vite.config.ts&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { defineConfig } from &amp;#39;vite&amp;#39;
import react from &amp;#39;@vitejs/plugin-react&amp;#39;
import path from &amp;#39;path&amp;#39;

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      &amp;#39;@&amp;#39;: path.resolve(__dirname, &amp;#39;./src&amp;#39;),
    },
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. src 디렉토리 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;src/
├── components/       # 재사용 가능한 UI 컴포넌트
│   ├── Header.tsx    # 헤더 컴포넌트
│   ├── Footer.tsx    # 푸터 컴포넌트
│   ├── Counter.tsx   # 카운터 예제 컴포넌트
│   └── Layout.tsx    # 레이아웃 컴포넌트
├── pages/            # 페이지 컴포넌트
│   ├── Home.tsx      # 홈 페이지
│   ├── About.tsx     # 소개 페이지
│   └── NotFound.tsx  # 404 페이지
├── routes/           # 라우팅 설정
│   └── index.tsx     # 라우터 구성
├── main.tsx          # 앱 진입점
└── index.css         # 전역 스타일
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;주요 기능&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;React 19 지원&lt;/strong&gt;: 최신 React 버전 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript 통합&lt;/strong&gt;: 타입 안정성을 위한 TypeScript 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vite 빌드 시스템&lt;/strong&gt;: 빠른 개발 환경과 효율적인 빌드&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React Router v7&lt;/strong&gt;: 최신 라우팅 라이브러리 통합&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESLint &amp;amp; Prettier&lt;/strong&gt;: 코드 품질과 일관성 유지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모듈화된 설정&lt;/strong&gt;: 설정 파일을 별도 패키지로 분리하여 재사용성 향상&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Playwright 테스트&lt;/strong&gt;: E2E 테스트 환경 제공&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TanStack Query&lt;/strong&gt;: 데이터 페칭 솔루션&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zustand&lt;/strong&gt;: 간결한 상태 관리 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Axios&lt;/strong&gt;: 강력한 HTTP 클라이언트&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;테스트 기능&lt;/h2&gt;
&lt;p&gt;테스트 기능도 추가하여 테스트도 가능하게 만들어두었다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;e2e/                     # 테스트 디렉토리
├── example.spec.ts      # 예제 테스트 파일
└── global.d.ts          # 타입 정의
playwright.config.ts     # Playwright 설정
playwright.d.ts          # 타입 선언
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/create-rendardev-react&quot;&gt;https://www.npmjs.com/package/create-rendardev-react&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;사실 배포까지 마무리되었다.&lt;br&gt;글쓰기전에 이미 템플릿 제작을 완료하여서 관련된 create 명령어까지 만들어두었다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# npm 사용
npm create rendardev-react my-app

# yarn 사용
yarn create rendardev-react my-app

# pnpm 사용
pnpm create rendardev-react my-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;@rendardev 패키지로 배포할려고하였으나 create명령어에는 create라는 예약 명령어가 name에 있어야된다구 오류를 뿜어대었다.&lt;/p&gt;
&lt;p&gt;이름은 아쉽지만 상당히 보람찬 부분이였다.&lt;br&gt;이제 해야될건 내부 템플릿제작이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;layout작업 (모바일,pc, 대시보드화면 등)&lt;/li&gt;
&lt;li&gt;Page작업 (로그인, main, 대시보드, admin)&lt;/li&gt;
&lt;li&gt;컴포넌트 → 사실 디자인시스템 구축해놓은거 쓸 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nextjs도 해야되지만… create-next-app도 충분히 좋으니 일단 나중으로 미루고,&lt;br&gt;react프로젝트나 계속 할 예정이다.&lt;/p&gt;</description>
      <category>Front-end</category>
      <category>Mono</category>
      <category>npm</category>
      <category>package</category>
      <category>pnpm</category>
      <category>react</category>
      <category>template</category>
      <category>모노레포</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/48</guid>
      <comments>https://rendaritfactory.tistory.com/48#entry48comment</comments>
      <pubDate>Tue, 15 Apr 2025 23:27:41 +0900</pubDate>
    </item>
    <item>
      <title>Array.prototype.map() 직접 구현하기</title>
      <link>https://rendaritfactory.tistory.com/47</link>
      <description>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;최근 네카라쿠배중에 운좋게 면접을 보게 되었는데 (떨어졌긴하지만…)&lt;br&gt;라이브 코딩테스트에서 자바스크립트 메소드를 직접 구현해보라는 내용이 나왔다.&lt;br&gt;사실 그동안 생각지도 못하고 그냥 단순히 구현된 메소드만 써봤지 내부 구현이나 어떻게 생겼는지 확인을 해본적은 없는 것 같았다. &lt;/p&gt;
&lt;p&gt;이번참에 디테일하게 공부할겸 메소드를 직접 구현해보고 브라우저에서는 어떻게 구현되어 있는지 확인하는 시간을 가져볼까한다.&lt;/p&gt;
&lt;p&gt;무엇보다 AI가 발전한 지금같은 시대에는 단순구현을 떠나서 AI에게 직접 코딩을 요청해서 코드 구현을 해볼까한다.&lt;/p&gt;
&lt;h2&gt;생각해보기&lt;/h2&gt;
&lt;p&gt;map함수는 정말 자주쓰이는 함수이다.&lt;br&gt;특히 데이터를 순회할때 많이 쓰이고 관련된 데이터 가공을 할때도 자주쓰이는데&lt;/p&gt;
&lt;p&gt;MDN문서를 통해서 한번 다시 생각해보는 시간을 가져볼까한다&lt;br&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;MDN문서&lt;/h3&gt;
&lt;p&gt;문서에 따르면 map함수는 아래와같이 정의하고있다.&lt;/p&gt;
&lt;aside&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array&quot;&gt;&lt;code&gt;Array&lt;/code&gt;&lt;/a&gt; 인스턴스의 &lt;strong&gt;&lt;code&gt;map()&lt;/code&gt;&lt;/strong&gt; 메서드는 호출한 배열의 모든 요소에 주어진 함수를 호출한 결과로 채운 새로운 배열을 생성합니다.&lt;/p&gt;
&lt;/aside&gt;

&lt;hr&gt;
&lt;p&gt;여기서 중요한건 &lt;code&gt;새로운 배열을 생성합니다&lt;/code&gt; 이 부분이다.&lt;br&gt;보통 면접질문에서도 map과 forEach의 차이점에 대해서 질문하면 많이 나오는 질문인데 가장중점적인 부분이 아닐까 싶다.&lt;/p&gt;
&lt;h3&gt;매개변수&lt;/h3&gt;
&lt;p&gt;map 함수에서 받는 매개변수는 총 2개&lt;strong&gt;(callback, thisArg)&lt;/strong&gt;이다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;callback(함수) - 배열의 각 요소에 대해 실행할 함수입니다. 반환 값은 새 배열에서 단일 요소로 추가됩니다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;currentValue&lt;/code&gt; - 배열 내에서 처리할 현재 요소.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index&lt;/code&gt; - 배열 내에서 처리할 현재 요소의 인덱스.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;array&lt;/code&gt; - &lt;code&gt;map()&lt;/code&gt;을 호출한 배열.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;thisArg - &lt;code&gt;callback&lt;/code&gt;을 실행할 때 &lt;code&gt;this&lt;/code&gt;로 사용되는 값.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;구현사항&lt;/h3&gt;
&lt;p&gt;어떻게 구현할까?&lt;br&gt;단순하게 생각해보자 배열내부를 순회해야되므로 가장 간단한 문법은 반복문을 써서 돌리면 가장 간단하다.&lt;/p&gt;
&lt;p&gt;반복문중에서도 for문을 써서 index와 함께 반환해버리면 단순한 구조로 구현이 가능할 것 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;prototype 상속으로 구현할것이므로 &lt;code&gt;Array.prototype.rdmap&lt;/code&gt; 으로 상속한다&lt;/li&gt;
&lt;li&gt;함수의 경우 익명함수로 값을 출력한다&lt;ol&gt;
&lt;li&gt;함수의 인자는 callback, thisArg로 통합한다 (기존 map함수와 동일하게)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;관련된 로직은 for문을 통해 구현한다&lt;ol&gt;
&lt;li&gt;변수 result 변수에 빈 배열을 선언한다&lt;/li&gt;
&lt;li&gt;for문을 통해 받은 함수의 length 만큼을 돌면서 result 배열안에 callback함수를 넣는다.&lt;/li&gt;
&lt;li&gt;결과값을 result 리턴한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;각 단계별로 구현을 하면 될 것이다 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// 1. prototype 상속으로 구현할것이므로 Array.prototype.rdmap 으로 상속한다
Array.prototype.rdmap = 
    // 2. 함수의 경우 익명함수로 값을 출력한다
    // 함수의 인자는 callback, thisArg로 통합한다 (기존 map함수와 동일하게)
    function(callback, thisArg) {
            // 관련된 로직은 for문을 통해 구현한다
            // 변수 result 변수에 빈 배열을 선언한다
            const result = [];
            // for문을 통해 받은 함수의 length 만큼을 돌면서 result 배열안에 callback함수를 넣는다.
            for(i = 0; i &amp;lt; this.length; i++) {
                result.push(callback(this[i], i, this));
            }
            // 결과값을 리턴한다.
            return result 
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDSQz9/btsM3Ty8iF2/6LNyncuBTOtABPGGvTwYv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDSQz9/btsM3Ty8iF2/6LNyncuBTOtABPGGvTwYv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDSQz9/btsM3Ty8iF2/6LNyncuBTOtABPGGvTwYv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDSQz9%2FbtsM3Ty8iF2%2F6LNyncuBTOtABPGGvTwYv0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;브라우저에서 테스트시 잘되는 것을 확인해볼수 있다.&lt;/p&gt;
&lt;p&gt;아마 브라우저 코드는 더 복잡하게 구현되어 있을테지만, 구현정도는 이정도로 할 수 있을 것 같다&lt;/p&gt;
&lt;p&gt;아래는 AI를 통해 만들어본 map함수다 (typescript적용)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;Array.prototype.myMap = function &amp;lt;T, U&amp;gt;(
  callback: (value: T, index: number, array: T[]) =&amp;gt; U,
  thisArg?: any
): U[] {
  const result: U[] = [];

  for (let i = 0; i &amp;lt; this.length; i++) {
    const boundCallback = callback.bind(thisArg);
    result.push(boundCallback(this[i], i, this));
  }

  return result;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;생각해보니 thisArg부분을 생각지도 못하였다&lt;/p&gt;
&lt;p&gt;gpt설명에 의하면 prefix값을 넣을때 예시를 알려주었는데 많이 사용하는지는 모르겠다 (아직 사용해본적 없음..)&lt;/p&gt;
&lt;h2&gt;실제 브라우저에서는 어떻게 구현되어있을까?&lt;/h2&gt;
&lt;p&gt;실제 V8엔진(크롬브라우저)에서는 어떻게 구현되어있을까? &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/v8/v8/blob/main/src/builtins/builtins-array-gen.cc&quot;&gt;https://github.com/v8/v8/blob/main/src/builtins/builtins-array-gen.cc&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;실제 V8엔진 코드의 경우 C++ 코드로 구성되어 있으나, 개발자에 의하면 다양한 언어로 개발되어 구성된다고 이야기하고있다.&lt;br&gt;각 단계별로 진행된다고 클로드한테 물어보니 대답해주었다 (정확한지는… C++ 공부를 딥하게 하지 않아서)&lt;/p&gt;
&lt;h3&gt;진입점&lt;/h3&gt;
&lt;p&gt;map함수가 호출되었을때 실행되는 함수 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;TF_BUILTIN(TypedArrayPrototypeMap, ArrayBuiltinsAssembler) {
  // 매개변수 추출 및 초기 설정
  TNode&amp;lt;IntPtrT&amp;gt; argc = ChangeInt32ToIntPtr(
      UncheckedParameter&amp;lt;Int32T&amp;gt;(Descriptor::kJSActualArgumentsCount));
  CodeStubArguments args(this, argc);
  auto context = Parameter&amp;lt;Context&amp;gt;(Descriptor::kContext);
  TNode&amp;lt;JSAny&amp;gt; receiver = args.GetReceiver();
  TNode&amp;lt;JSAny&amp;gt; callbackfn = args.GetOptionalArgumentValue(0);
  TNode&amp;lt;JSAny&amp;gt; this_arg = args.GetOptionalArgumentValue(1);

  // 배열 순회 함수 초기화
  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  // TypedArray 요소를 순회하며 map 연산 수행
  GenerateIteratingTypedArrayBuiltinBody(
      &amp;quot;%TypedArray%.prototype.map&amp;quot;,
      &amp;amp;ArrayBuiltinsAssembler::TypedArrayMapResultGenerator,
      &amp;amp;ArrayBuiltinsAssembler::TypedArrayMapProcessor);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;새로운 배열 생성&lt;/h3&gt;
&lt;p&gt;map 연산결과를 저장할 배열 생성&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void ArrayBuiltinsAssembler::TypedArrayMapResultGenerator() {
  // 원본 배열과 같은 타입의 새 TypedArray 생성
  TNode&amp;lt;JSTypedArray&amp;gt; original_array = CAST(o());
  const char* method_name = &amp;quot;%TypedArray%.prototype.map&amp;quot;;

  TNode&amp;lt;JSTypedArray&amp;gt; a = TypedArraySpeciesCreateByLength(
      context(), method_name, original_array, len());

  // 빠른 처리를 위한 요소 유형 비교
  fast_typed_array_target_ =
      Word32Equal(LoadElementsKind(original_array), LoadElementsKind(a));
  a_ = a;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;함수(map)처리&lt;/h3&gt;
&lt;p&gt;콜백함수를 처리하고, 새로운 배열을 리턴한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;TNode&amp;lt;JSAny&amp;gt; ArrayBuiltinsAssembler::TypedArrayMapProcessor(
    TNode&amp;lt;Object&amp;gt; k_value, TNode&amp;lt;UintPtrT&amp;gt; k) {
  // 콜백 함수 호출
  TNode&amp;lt;Number&amp;gt; k_number = ChangeUintPtrToTagged(k);
  TNode&amp;lt;JSAny&amp;gt; mapped_value =
      Call(context(), callbackfn(), this_arg(), k_value, k_number, o());

  // 결과 저장 경로 분기 (빠른 경로와 느린 경로)
  Label fast(this), slow(this), done(this), detached(this, Label::kDeferred);
  Branch(fast_typed_array_target_, &amp;amp;fast, &amp;amp;slow);

  BIND(&amp;amp;fast);
  // 타입 변환 및 새 배열에 저장 (타입에 따라 처리)
  TNode&amp;lt;Object&amp;gt; num_value;
  if (IsBigIntTypedArrayElementsKind(source_elements_kind_)) {
    num_value = ToBigInt(context(), mapped_value);
  } else {
    num_value = ToNumber_Inline(context(), mapped_value);
  }

  // 결과 저장
  EmitElementStore(CAST(a()), k_number, num_value, source_elements_kind_,
                   KeyedAccessStoreMode::kInBounds, &amp;amp;detached, context());
  Goto(&amp;amp;done);

  BIND(&amp;amp;slow);
  {
    // 느린 경로: 일반적인 속성 설정 사용
    SetPropertyStrict(context(), a(), k_number, mapped_value);
    Goto(&amp;amp;done);
  }

  BIND(&amp;amp;detached);
  // 버퍼가 분리된 경우 오류 발생
  ThrowTypeError(context_, MessageTemplate::kDetachedOperation, name_);

  BIND(&amp;amp;done);
  return a();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C++로 작성되어서 코드를 읽기 어려웠다 (대학 2학년때 배웠던 이후로 본적이…)&lt;br&gt;하지만 클로드를 통해서 해석을 할수 있어서 좋긴하였으나, 완벽하게 해석해준지는 모르겠다&lt;br&gt;하지만 기본적인 구조를 파악할수 있기 때문에 이런 구조로 구동된다를 확인할 수 있었다. &lt;/p&gt;
&lt;p&gt;내가 짠코드에 비해 거의 3배가량 코드가 많은거 같다. (아마 클래스나 모듈로 뺀부분이 많아서 그런 것 같다.)&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;브라우저 내부에서 구동되는 메소드를 직접 구현해보았다.&lt;br&gt;사실 브라우저의 경우 최적화 코드 및 다양한 언어로 개발되기 때문에 단순한 구조로 개발하는건 무리가 있다.&lt;br&gt;하지만 내부구조를 직접 개발하여서 구조도 알고, 왜 map은 새로운 배열을 뱉는 구조인지 알수 있어서 더욱 의미가 깊다고 생각된다 &lt;/p&gt;
&lt;p&gt;시리즈물로 계속 javascript 메소드를 연재해볼까 한다.&lt;/p&gt;
&lt;p&gt;다음은 map함수하면 따라오는 forEach에 대해서 다뤄볼까한다.&lt;/p&gt;</description>
      <category>Front-end</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/47</guid>
      <comments>https://rendaritfactory.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 31 Mar 2025 22:58:37 +0900</pubDate>
    </item>
    <item>
      <title>나의 전용 템플릿 엔진(?) 제작기 - tsconfig</title>
      <link>https://rendaritfactory.tistory.com/46</link>
      <description>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;점차 기본구성이 완성되고 있다.&lt;/p&gt;
&lt;p&gt;이번에는 config설정 마지막인 Typescript 설정 부분을 보겠다&lt;br&gt;이부분도 아마 굉장히 쉬울 것이다. 기본 타입 스크립트만 적용할 것이고, 그외에는 오버라이드 구조로 프로젝트에 위임할 예정이다 &lt;/p&gt;
&lt;h2&gt;환경구성&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;my-monorepo-template/      
├── packages/
│   ├── eslint-config/
│   ├── prettier-config/
│   ├── **tsconfig/**
│   ├── react-template/
│   └── nextjs-template/
├── package.json
└── pnpm-workspace.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;기존 구조에서 &lt;code&gt;packages/tsconfing&lt;/code&gt; 부분에 넣을예정이다&lt;/p&gt;
&lt;h3&gt;초기설정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;패키지내부에 폴더랑 파일을 추가해 주도록한다 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; mkdir -p packages/tsconfig
 cd packages/tsconfig

 # 패키지 초기화
 pnpm init&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;내부 파일을 만들어서 넣는다 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; # 다음 파일들 생성
 touch base.json next.json react.json&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;typescript 패키지를 설치한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; pnpm --filter **${본인 패키지 이름}**/tsconfig add typescript&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;strong&gt;프로젝트 환경 설정&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;base.json&lt;/code&gt;: 기본 설정 (모든 프로젝트 공통)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next.json&lt;/code&gt;: Next.js 전용 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react.json&lt;/code&gt;: React 전용 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;프로젝트 코드&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;package.json&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt; {
   &amp;quot;name&amp;quot;: &amp;quot;@rendardev/tsconfig&amp;quot;,
   &amp;quot;homepage&amp;quot;: &amp;quot;https://github.com/RendarCP/rendar-mono-template&amp;quot;,
   &amp;quot;repository&amp;quot;: {
     &amp;quot;type&amp;quot;: &amp;quot;git&amp;quot;,
     &amp;quot;url&amp;quot;: &amp;quot;git+https://github.com/RendarCP/rendar-mono-template.git&amp;quot;,
     &amp;quot;directory&amp;quot;: &amp;quot;packages/tsconfig&amp;quot;
   },
   &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
   &amp;quot;description&amp;quot;: &amp;quot;TSConfig configurations for Rendar Mono Template&amp;quot;,
   &amp;quot;private&amp;quot;: false,
   &amp;quot;files&amp;quot;: [
     &amp;quot;base.json&amp;quot;,
     &amp;quot;next.json&amp;quot;,
     &amp;quot;react.json&amp;quot;
   ],
   &amp;quot;publishConfig&amp;quot;: {
     &amp;quot;access&amp;quot;: &amp;quot;public&amp;quot;
   },
   &amp;quot;license&amp;quot;: &amp;quot;MIT&amp;quot;,
   &amp;quot;dependencies&amp;quot;: {
     &amp;quot;typescript&amp;quot;: &amp;quot;^5.7.3&amp;quot;
   }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;base.json&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt; {
   &amp;quot;$schema&amp;quot;: &amp;quot;https://json.schemastore.org/tsconfig&amp;quot;,
   &amp;quot;display&amp;quot;: &amp;quot;Default&amp;quot;,
   &amp;quot;compilerOptions&amp;quot;: {
     &amp;quot;target&amp;quot;: &amp;quot;ES2020&amp;quot;,
     &amp;quot;lib&amp;quot;: [&amp;quot;DOM&amp;quot;, &amp;quot;DOM.Iterable&amp;quot;, &amp;quot;ESNext&amp;quot;],
     &amp;quot;allowJs&amp;quot;: true,
     &amp;quot;skipLibCheck&amp;quot;: true,
     &amp;quot;esModuleInterop&amp;quot;: true,
     &amp;quot;allowSyntheticDefaultImports&amp;quot;: true,
     &amp;quot;strict&amp;quot;: true,
     &amp;quot;strictNullChecks&amp;quot;: true,
     &amp;quot;forceConsistentCasingInFileNames&amp;quot;: true,
     &amp;quot;noFallthroughCasesInSwitch&amp;quot;: true,
     &amp;quot;module&amp;quot;: &amp;quot;ESNext&amp;quot;,
     &amp;quot;moduleResolution&amp;quot;: &amp;quot;node&amp;quot;,
     &amp;quot;resolveJsonModule&amp;quot;: true,
     &amp;quot;isolatedModules&amp;quot;: true,
     &amp;quot;incremental&amp;quot;: true,
     &amp;quot;baseUrl&amp;quot;: &amp;quot;.&amp;quot;,
     &amp;quot;paths&amp;quot;: {
       &amp;quot;@/*&amp;quot;: [&amp;quot;./src/*&amp;quot;]
     }
   },
   &amp;quot;exclude&amp;quot;: [&amp;quot;node_modules&amp;quot;],
   &amp;quot;include&amp;quot;: [&amp;quot;next-env.d.ts&amp;quot;, &amp;quot;**/*.ts&amp;quot;, &amp;quot;**/*.tsx&amp;quot;]
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;react.json&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt; {
   &amp;quot;$schema&amp;quot;: &amp;quot;https://json.schemastore.org/tsconfig&amp;quot;,
   &amp;quot;display&amp;quot;: &amp;quot;React&amp;quot;,
   &amp;quot;extends&amp;quot;: &amp;quot;./base.json&amp;quot;,
   &amp;quot;compilerOptions&amp;quot;: {
     &amp;quot;jsx&amp;quot;: &amp;quot;react-jsx&amp;quot;,
     &amp;quot;plugins&amp;quot;: [
       {
         &amp;quot;name&amp;quot;: &amp;quot;typescript-plugin-css-modules&amp;quot;
       }
     ]
   }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;next.json&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt; {
   &amp;quot;$schema&amp;quot;: &amp;quot;https://json.schemastore.org/tsconfig&amp;quot;,
   &amp;quot;display&amp;quot;: &amp;quot;Next.js&amp;quot;,
   &amp;quot;extends&amp;quot;: &amp;quot;./base.json&amp;quot;,
   &amp;quot;compilerOptions&amp;quot;: {
     &amp;quot;jsx&amp;quot;: &amp;quot;preserve&amp;quot;,
     &amp;quot;plugins&amp;quot;: [
       {
         &amp;quot;name&amp;quot;: &amp;quot;next&amp;quot;
       }
     ],
     &amp;quot;noEmit&amp;quot;: true,
     &amp;quot;moduleResolution&amp;quot;: &amp;quot;bundler&amp;quot;
   },
   &amp;quot;include&amp;quot;: [&amp;quot;next-env.d.ts&amp;quot;, &amp;quot;**/*.ts&amp;quot;, &amp;quot;**/*.tsx&amp;quot;, &amp;quot;.next/types/**/*.ts&amp;quot;],
   &amp;quot;exclude&amp;quot;: [&amp;quot;node_modules&amp;quot;]
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;배포하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm publish-tsconfig             &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/@rendardev/tsconfig&quot;&gt;https://www.npmjs.com/package/@rendardev/tsconfig&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;npm에 잘 배포되었음을 확인 할수 있다.&lt;/p&gt;
&lt;h3&gt;마무리&lt;/h3&gt;
&lt;p&gt;config설정이 마무리 되었다 &lt;/p&gt;
&lt;p&gt;모노레포를 통해 템플릿엔진 어떻게 따지면, 기본 틀을 만드는 작업의 밑천이 완성된 셈이다.&lt;br&gt;사실 React, Nextjs 설정은 크게 어려울것이 없을 것 같지만, 내부 구조등에 많은 생각이 들어갈 것 같다. &lt;/p&gt;
&lt;p&gt;프로젝트할땐 무조건 이 템플릿을 써서 사용할수 있는 상태를 만들예정이다&lt;br&gt;react의 경우 react-rotuer, tanstack-query, zustand, axios(or fetch)&lt;br&gt;nextjs의 경우도 크게 다를게 없지만 react-router제외하고 다 비슷하게 가져갈 것같다 &lt;/p&gt;
&lt;p&gt;다음편에는 react 템플릿을 제작해서 넣어볼예정이다 (webpack이 아닌 Vite로)&lt;/p&gt;</description>
      <category>Front-end</category>
      <category>Next</category>
      <category>nextjs</category>
      <category>npm</category>
      <category>pnpm</category>
      <category>react</category>
      <category>tsconfig</category>
      <category>typescript</category>
      <category>모노레포</category>
      <category>템플릿</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/46</guid>
      <comments>https://rendaritfactory.tistory.com/46#entry46comment</comments>
      <pubDate>Mon, 24 Mar 2025 23:26:15 +0900</pubDate>
    </item>
    <item>
      <title>나의 전용 템플릿 엔진(?) 제작기 - Prettier</title>
      <link>https://rendaritfactory.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESLint용 패키지도 추가해서 배포까지 완료하였다.&lt;br /&gt;ESlint의 경우 설치 패키지등이 많아서 복잡했지만, Prettier의경우 단일패키지만 깔고 모두 프로젝트에 위임할것이기때문에 굉장히 쉬울 것 이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;환경구성&lt;/h2&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;my-monorepo-template/      
├── packages/
│   ├── eslint-config/
│   ├── **prettier-config/**
│   ├── tsconfig/
│   ├── react-template/
│   └── nextjs-template/
├── package.json
└── pnpm-workspace.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구조에서 &lt;code&gt;packages/prettier-confing&lt;/code&gt; 부분에 넣을예정이다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초기설정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;패키지내부에 폴더랑 파일을 추가해 주도록한다&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-bash&quot;&gt; mkdir -p packages/prettier-config
 cd packages/prettier-config

 # 패키지 초기화
 pnpm init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;내부 파일을 만들어서 넣는다&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-bash&quot;&gt; # 다음 파일들 생성
 touch index.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;prettier 패키지를 설치한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-bash&quot;&gt; pnpm --filter **${본인 패키지 이름}**/prettier-config add prettier&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;pacakge.json
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt; {
   &quot;name&quot;: &quot;@rendardev/prettier-config&quot;,
   &quot;homepage&quot;: &quot;https://github.com/RendarCP/rendar-mono-template&quot;,
   &quot;repository&quot;: {
     &quot;type&quot;: &quot;git&quot;,
     &quot;url&quot;: &quot;git+https://github.com/RendarCP/rendar-mono-template.git&quot;,
     &quot;directory&quot;: &quot;packages/prettier-config&quot;
   },
   &quot;version&quot;: &quot;1.0.0&quot;,
   &quot;description&quot;: &quot;Prettier configurations for Rendar Mono Template&quot;,
   &quot;main&quot;: &quot;index.js&quot;,
   &quot;files&quot;: [
     &quot;index.js&quot;,
     &quot;README.md&quot;
   ],
   &quot;publishConfig&quot;: {
     &quot;access&quot;: &quot;public&quot;
   },
   &quot;license&quot;: &quot;MIT&quot;,
   &quot;dependencies&quot;: {
     &quot;prettier&quot;: &quot;^3.4.2&quot;
   },
   &quot;peerDependencies&quot;: {
     &quot;prettier&quot;: &quot;&amp;gt;=3.0.0&quot;
   }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;peerDependecies에 최소 패키지 버전을 명시해준다&lt;/li&gt;
&lt;li&gt;index.js
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt; module.exports = {
   printWidth: 80, // 한 줄의 최대 길이를 80자로 제한
   tabWidth: 2, // 들여쓰기 시 탭 너비를 2칸으로 설정
   useTabs: false, // 들여쓰기에 탭 대신 스페이스 사용
   semi: true, // 문장 끝에 세미콜론 추가
   singleQuote: true, // 작은따옴표 사용
   quoteProps: &quot;as-needed&quot;, // 객체의 속성에 필요한 경우에만 따옴표 추가
   trailingComma: &quot;es5&quot;, // ES5에서 유효한 후행 쉼표 추가 (객체, 배열 등)
   bracketSpacing: true, // 객체 리터럴의 중괄호 사이에 공백 추가 ({ foo: bar })
   arrowParens: &quot;avoid&quot;, // 화살표 함수의 매개변수가 하나일 때 괄호 생략 (x =&amp;gt; x)
   proseWrap: &quot;preserve&quot;, // 마크다운 등의 prose 텍스트의 줄바꿈을 보존
   endOfLine: &quot;auto&quot;, // 운영체제에 따라 자동으로 개행 문자 설정
   htmlWhitespaceSensitivity: &quot;css&quot;, // CSS display 속성 값에 따라 HTML 공백 처리
   embeddedLanguageFormatting: &quot;off&quot;, // 템플릿 리터럴 내부 등의 포매팅 비활성화
 };
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;최소 기능을 추가해준다 (이외 기능은 프로젝트에 위임)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm에 배포를 해보자&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;pnpm publish-prettier&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/@rendardev/prettier-config&quot;&gt;https://www.npmjs.com/package/@rendardev/prettier-config&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매울 잘 배포되었음을 확인할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용방법은 패키지 링크 참조바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/RendarCP/rendar-mono-template/tree/main/packages/prettier-config&quot;&gt;https://github.com/RendarCP/rendar-mono-template/tree/main/packages/prettier-config&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 lint와 prettier설정은 완료되었다 tsconfig부분이 남아있으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 기본적인 설정은 완성된것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 참고했던 린트설정 패키지인데 네이버페이, 트리플의 코드를 참고하였다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/NaverPayDev/code-style&quot;&gt;https://github.com/NaverPayDev/code-style&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/titicacadev/triple-config-kit&quot;&gt;https://github.com/titicacadev/triple-config-kit&lt;/a&gt;&lt;/p&gt;</description>
      <category>Front-end</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/45</guid>
      <comments>https://rendaritfactory.tistory.com/45#entry45comment</comments>
      <pubDate>Wed, 19 Mar 2025 21:35:20 +0900</pubDate>
    </item>
    <item>
      <title>나의 전용 템플릿 엔진(?) 제작기 - ESLint</title>
      <link>https://rendaritfactory.tistory.com/44</link>
      <description>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;앞선 템플릿엔진용 모노레포를 구성하였다.&lt;br&gt;이번 내용에서는 템플릿 엔진용 ESLinst 구성을 해볼까한다. 사실 너무나 간단한 구성이라서 저번 내용에 넣을까 고민했지만, 분리시키는게 가독성이나 내용 측면에서 좋을 것 같아서 따로 분리시켰다.&lt;/p&gt;
&lt;h2&gt;환경구성&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;my-monorepo-template/      
├── packages/
│   ├── **eslint-config/**
│   ├── prettier-config/
│   ├── tsconfig/
│   ├── react-template/
│   └── nextjs-template/
├── package.json
└── pnpm-workspace.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;기존 구조에서 &lt;code&gt;packages/eslint-confing&lt;/code&gt; 부분에 넣을예정이다&lt;/p&gt;
&lt;h3&gt;초기설정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;패키지내부에 폴더랑 파일을 추가해 주도록한다&lt;/p&gt;
&lt;p&gt; Typescript 및 Eslint 등의 설치는 되어있는 상태로 설명&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; mkdir -p packages/eslint-config
 cd packages/eslint-config

 # 패키지 초기화
 pnpm init&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;내부 파일을 만들어서 넣는다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; # 다음 파일들 생성
 touch base.js index.js react.js next.js typeCheck.js prettier.js README.md&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;설치&lt;/h3&gt;
&lt;p&gt;패키지 내부에 의존성을 설치해준다 (현재 필자는 아래 패키지들만 설치)&lt;/p&gt;
&lt;p&gt;패키지 이름부분은 패키지내부 &lt;code&gt;package.json - name&lt;/code&gt; 부분을 따라간다 참고&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm --filter **${본인 패키지 이름}**/eslint-config add @next/eslint-plugin-next @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb eslint-config-next eslint-config-prettier eslint-config-standard eslint-config-standard-jsx eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-n eslint-plugin-prettier eslint-plugin-promise eslint-plugin-react eslint-plugin-react-hooks&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 파일별 설정은 아래와 같다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;base.js&lt;/code&gt;: 기본 ESLint 설정 (프론트엔드/백엔드 공통)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index.js&lt;/code&gt;: 기본 JavaScript/TypeScript 규칙&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react.js&lt;/code&gt;: React 관련 규칙&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next.js&lt;/code&gt;: Next.js 확장 규칙&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typeCheck.js&lt;/code&gt;: 강화된 TypeScript 타입 체크&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prettier.js&lt;/code&gt;: ESLint와 Prettier 통합&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;package.json&lt;/p&gt;
&lt;p&gt;homepage, repository는 각각 본인들의 깃 링크를 삽입, files또한 배포파일 경로를 넣는다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt; {
   &amp;quot;name&amp;quot;: &amp;quot;@rendardev/eslint-config&amp;quot;,
   &amp;quot;homepage&amp;quot;: &amp;quot;https://github.com/RendarCP/rendar-mono-template&amp;quot;,
   &amp;quot;repository&amp;quot;: {
     &amp;quot;type&amp;quot;: &amp;quot;git&amp;quot;,
     &amp;quot;url&amp;quot;: &amp;quot;git+https://github.com/RendarCP/rendar-mono-template.git&amp;quot;
   },
   &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
   &amp;quot;description&amp;quot;: &amp;quot;ESLint configurations for Rendar Mono Template&amp;quot;,
   &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
   &amp;quot;files&amp;quot;: [
     &amp;quot;*.js&amp;quot;,
     &amp;quot;README.md&amp;quot;
   ],
   &amp;quot;publishConfig&amp;quot;: {
     &amp;quot;access&amp;quot;: &amp;quot;public&amp;quot;
   },
   &amp;quot;license&amp;quot;: &amp;quot;MIT&amp;quot;,
   &amp;quot;dependencies&amp;quot;: {
     &amp;quot;@next/eslint-plugin-next&amp;quot;: &amp;quot;^15.1.4&amp;quot;,
     &amp;quot;@typescript-eslint/eslint-plugin&amp;quot;: &amp;quot;^8.20.0&amp;quot;,
     &amp;quot;@typescript-eslint/parser&amp;quot;: &amp;quot;^8.20.0&amp;quot;,
     &amp;quot;eslint-config-airbnb&amp;quot;: &amp;quot;^19.0.4&amp;quot;,
     &amp;quot;eslint-config-next&amp;quot;: &amp;quot;latest&amp;quot;,
     &amp;quot;eslint-config-prettier&amp;quot;: &amp;quot;^10.0.1&amp;quot;,
     &amp;quot;eslint-config-standard&amp;quot;: &amp;quot;^17.1.0&amp;quot;,
     &amp;quot;eslint-config-standard-jsx&amp;quot;: &amp;quot;^11.0.0&amp;quot;,
     &amp;quot;eslint-import-resolver-typescript&amp;quot;: &amp;quot;^3.8.7&amp;quot;,
     &amp;quot;eslint-plugin-import&amp;quot;: &amp;quot;^2.31.0&amp;quot;,
     &amp;quot;eslint-plugin-jsx-a11y&amp;quot;: &amp;quot;^6.10.2&amp;quot;,
     &amp;quot;eslint-plugin-n&amp;quot;: &amp;quot;^16.6.2&amp;quot;,
     &amp;quot;eslint-plugin-prettier&amp;quot;: &amp;quot;^5.2.1&amp;quot;,
     &amp;quot;eslint-plugin-promise&amp;quot;: &amp;quot;^6.6.0&amp;quot;,
     &amp;quot;eslint-plugin-react&amp;quot;: &amp;quot;^7.37.4&amp;quot;,
     &amp;quot;eslint-plugin-react-hooks&amp;quot;: &amp;quot;^5.1.0&amp;quot;
   },
   &amp;quot;peerDependencies&amp;quot;: {
     &amp;quot;eslint&amp;quot;: &amp;quot;&amp;gt;=8.0.0&amp;quot;
   }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;각 파일들&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;base.js&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  /**
   * base.js
   *
   * 기본 ESLint 설정 파일
   * 프론트엔드와 백엔드 모두에서 사용할 수 있는 공통 규칙 정의
   *
   * @type {import(&amp;#39;eslint&amp;#39;).Linter.Config}
   */
  module.exports = {
    // 실행 환경 설정
    env: {
      browser: true, // 브라우저 환경 지원
      es6: true, // ES6 문법 지원
      node: true, // Node.js 환경 지원
    },

    // TypeScript 파서 사용
    parser: &amp;quot;@typescript-eslint/parser&amp;quot;,

    // 확장할 기본 규칙 설정
    extends: [
      &amp;quot;eslint:recommended&amp;quot;, // ESLint 기본 권장 규칙
      &amp;quot;plugin:react/recommended&amp;quot;, // React 권장 규칙
      &amp;quot;plugin:prettier/recommended&amp;quot;, // Prettier 통합 지원
    ],

    // React 설정
    settings: {
      react: {
        version: &amp;quot;detect&amp;quot;, // React 버전 자동 감지
      },
    },

    // 파서 옵션 설정
    parserOptions: {
      ecmaFeatures: {
        jsx: true, // JSX 문법 지원
      },
      ecmaVersion: &amp;quot;latest&amp;quot;, // 최신 ECMAScript 버전 사용
      sourceType: &amp;quot;module&amp;quot;, // ES 모듈 시스템 사용
    },

    // 사용할 플러그인
    plugins: [
      &amp;quot;react&amp;quot;, // React 린트 규칙
      &amp;quot;react-hooks&amp;quot;, // React Hooks 린트 규칙
      &amp;quot;@typescript-eslint&amp;quot;, // TypeScript 규칙
      &amp;quot;prettier&amp;quot;, // Prettier 통합
      &amp;quot;import&amp;quot;, // import/export 문법 규칙
    ],

    // 세부 규칙 설정
    rules: {
      // React 관련 규칙
      &amp;quot;react-hooks/rules-of-hooks&amp;quot;: &amp;quot;error&amp;quot;, // Hooks 규칙 강제
      &amp;quot;react/jsx-no-useless-fragment&amp;quot;: 0, // 불필요한 Fragment 검사 비활성화
      &amp;quot;react/prop-types&amp;quot;: &amp;quot;off&amp;quot;, // PropTypes 검사 비활성화 (TypeScript 사용 시)
      &amp;quot;react/function-component-definition&amp;quot;: [
        2, // error
        {
          namedComponents: [&amp;quot;arrow-function&amp;quot;, &amp;quot;function-declaration&amp;quot;], // 화살표 함수와 함수 선언식 모두 허용
        },
      ],
      &amp;quot;react/jsx-props-no-spreading&amp;quot;: &amp;quot;off&amp;quot;, // props 전개 연산자 사용 허용
      &amp;quot;react/react-in-jsx-scope&amp;quot;: 0, // React import 필수 규칙 비활성화 (React 17+)
      &amp;quot;react/prefer-stateless-function&amp;quot;: 0, // 상태 없는 컴포넌트 강제 비활성화
      &amp;quot;react/jsx-filename-extension&amp;quot;: 0, // JSX 파일 확장자 제한 비활성화
      &amp;quot;react/jsx-one-expression-per-line&amp;quot;: 0, // JSX 내 한 줄 한 표현식 규칙 비활성화

      // Import 관련 규칙
      &amp;quot;import/order&amp;quot;: 0, // import 순서 규칙 비활성화
      &amp;quot;import/prefer-default-export&amp;quot;: &amp;quot;off&amp;quot;, // 기본 내보내기 선호 규칙 비활성화
      &amp;quot;import/extensions&amp;quot;: 0, // 확장자 명시 규칙 비활성화
      &amp;quot;import/no-unresolved&amp;quot;: 0, // 미해결 import 경로 검사 비활성화

      // JavaScript/TypeScript 관련 규칙
      &amp;quot;prefer-arrow-callback&amp;quot;: &amp;quot;off&amp;quot;, // 화살표 함수 콜백 선호 규칙 비활성화
      &amp;quot;no-var&amp;quot;: &amp;quot;error&amp;quot;, // var 대신 let/const 사용 강제
      &amp;quot;no-dupe-keys&amp;quot;: &amp;quot;error&amp;quot;, // 객체 내 중복 키 금지
      &amp;quot;no-nested-ternary&amp;quot;: 0, // 중첩 삼항 연산자 허용

      // TypeScript 관련 규칙
      &amp;quot;@typescript-eslint/no-explicit-any&amp;quot;: &amp;quot;error&amp;quot;, // any 타입 사용 금지
    },

    // 전역 변수 설정
    globals: {
      React: &amp;quot;writable&amp;quot;, // React를 전역 변수로 설정
    },
  };&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;index.js&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  /**
   * index.js
   *
   * 기본 ESLint 설정 - 주로 Node.js 환경에서 사용되는 설정
   * JavaScript/TypeScript 코드를 위한 핵심 규칙 정의
   *
   * @type {import(&amp;#39;eslint&amp;#39;).Linter.Config}
   */
  module.exports = {
    // Node.js 환경 설정
    env: {
      node: true,
    },

    // 확장할 규칙 설정
    extends: [
      &amp;quot;eslint:recommended&amp;quot;, // ESLint 기본 권장 규칙
      &amp;quot;plugin:import/recommended&amp;quot;, // import 관련 권장 규칙
      &amp;quot;plugin:promise/recommended&amp;quot;, // Promise 관련 권장 규칙
      &amp;quot;standard&amp;quot;, // JavaScript Standard 스타일
    ],

    // 기본 규칙
    rules: {
      // 함수 스타일 규칙
      &amp;quot;func-style&amp;quot;: [&amp;quot;error&amp;quot;, &amp;quot;declaration&amp;quot;, { allowArrowFunctions: true }], // 함수 선언식 강제, 화살표 함수 허용

      // 코드 품질 규칙
      &amp;quot;no-console&amp;quot;: &amp;quot;error&amp;quot;, // console.log() 등 사용 금지
      &amp;quot;no-empty-function&amp;quot;: &amp;quot;off&amp;quot;, // 빈 함수 허용
      &amp;quot;no-implicit-coercion&amp;quot;: [&amp;quot;error&amp;quot;, { allow: [&amp;quot;!!&amp;quot;] }], // 암시적 형변환 제한, !! 연산자는 허용
      &amp;quot;no-return-await&amp;quot;: &amp;quot;error&amp;quot;, // 불필요한 return await 금지
      &amp;quot;no-unused-expressions&amp;quot;: [
        // 미사용 표현식 제한
        &amp;quot;error&amp;quot;,
        {
          allowShortCircuit: true, // 단축 평가 허용
          allowTernary: true, // 삼항 연산자 허용
          allowTaggedTemplates: true, // 태그된 템플릿 허용
        },
      ],
      &amp;quot;no-unused-vars&amp;quot;: [
        // 미사용 변수 제한
        &amp;quot;error&amp;quot;,
        { ignoreRestSiblings: true, argsIgnorePattern: &amp;quot;^_+$&amp;quot; }, // rest 연산자 형제 무시, _ 시작 매개변수 무시
      ],
      &amp;quot;no-use-before-define&amp;quot;: [&amp;quot;error&amp;quot;, { functions: false }], // 정의 전 사용 금지, 함수는 예외
      &amp;quot;no-void&amp;quot;: [&amp;quot;error&amp;quot;, { allowAsStatement: true }], // void 연산자 제한, 문장으로는 허용
      &amp;quot;object-shorthand&amp;quot;: [&amp;quot;error&amp;quot;, &amp;quot;properties&amp;quot;], // 객체 속성 단축 문법 사용
      &amp;quot;require-atomic-updates&amp;quot;: &amp;quot;off&amp;quot;, // 원자적 업데이트 요구 비활성화

      // Import 관련 규칙
      &amp;quot;import/order&amp;quot;: [
        // import 순서 규칙
        &amp;quot;error&amp;quot;,
        {
          &amp;quot;newlines-between&amp;quot;: &amp;quot;always&amp;quot;, // 그룹 간 항상 새 줄 추가
          pathGroups: [
            {
              pattern: &amp;quot;@/**&amp;quot;, // @ 경로 패턴
              group: &amp;quot;parent&amp;quot;, // 부모 그룹에 포함
              position: &amp;quot;before&amp;quot;, // 부모 그룹 앞에 배치
            },
          ],
          pathGroupsExcludedImportTypes: [&amp;quot;builtin&amp;quot;], // 내장 모듈은 제외
        },
      ],
      &amp;quot;import/newline-after-import&amp;quot;: &amp;quot;error&amp;quot;, // import 문 후 새 줄 강제

      // Promise 관련 규칙
      &amp;quot;promise/catch-or-return&amp;quot;: [&amp;quot;error&amp;quot;, { allowFinally: true }], // Promise는 catch 또는 return 필수, finally 허용
      &amp;quot;promise/prefer-await-to-callbacks&amp;quot;: &amp;quot;error&amp;quot;, // 콜백보다 await 선호
      &amp;quot;promise/prefer-await-to-then&amp;quot;: &amp;quot;error&amp;quot;, // then보다 await 선호
    },

    // TypeScript 파일을 위한 추가 설정
    overrides: [
      {
        files: [&amp;quot;*.ts&amp;quot;, &amp;quot;*.tsx&amp;quot;], // TypeScript 파일 대상
        extends: [
          &amp;quot;plugin:@typescript-eslint/recommended&amp;quot;, // TypeScript 권장 규칙
          &amp;quot;plugin:import/typescript&amp;quot;, // TypeScript import 지원
        ],
        parser: &amp;quot;@typescript-eslint/parser&amp;quot;, // TypeScript 파서 사용
        settings: {
          &amp;quot;import/resolver&amp;quot;: {
            typescript: {
              alwaysTryTypes: true, // 항상 타입 정의 파일 시도
            },
          },
        },
        rules: {
          // TypeScript 관련 규칙
          &amp;quot;@typescript-eslint/consistent-type-assertions&amp;quot;: &amp;quot;error&amp;quot;, // 일관된 타입 단언 형식 사용
          &amp;quot;@typescript-eslint/consistent-type-definitions&amp;quot;: &amp;quot;error&amp;quot;, // 일관된 타입 정의 형식 사용
          &amp;quot;@typescript-eslint/explicit-member-accessibility&amp;quot;: &amp;quot;error&amp;quot;, // 명시적 접근 제한자 요구
          &amp;quot;@typescript-eslint/no-var-requires&amp;quot;: &amp;quot;off&amp;quot;, // require() 사용 허용
          &amp;quot;@typescript-eslint/no-empty-function&amp;quot;: &amp;quot;off&amp;quot;, // 빈 함수 허용
          &amp;quot;@typescript-eslint/no-empty-object-type&amp;quot;: [
            // 빈 객체 타입 제한
            &amp;quot;error&amp;quot;,
            { allowInterfaces: &amp;quot;always&amp;quot; }, // 인터페이스는 항상 허용
          ],

          // TypeScript로 재정의된 규칙
          &amp;quot;no-use-before-define&amp;quot;: &amp;quot;off&amp;quot;, // JS 버전 비활성화
          &amp;quot;@typescript-eslint/no-use-before-define&amp;quot;: [
            // TS 버전 활성화
            &amp;quot;error&amp;quot;,
            { functions: false }, // 함수는 정의 전 사용 허용
          ],
          &amp;quot;no-unused-expressions&amp;quot;: &amp;quot;off&amp;quot;, // JS 버전 비활성화
          &amp;quot;@typescript-eslint/no-unused-expressions&amp;quot;: [
            // TS 버전 활성화
            &amp;quot;error&amp;quot;,
            {
              allowShortCircuit: true, // 단축 평가 허용
              allowTernary: true, // 삼항 연산자 허용
              allowTaggedTemplates: true, // 태그된 템플릿 허용
            },
          ],
          &amp;quot;no-unused-vars&amp;quot;: &amp;quot;off&amp;quot;, // JS 버전 비활성화
          &amp;quot;@typescript-eslint/no-unused-vars&amp;quot;: [
            // TS 버전 활성화
            &amp;quot;error&amp;quot;,
            { ignoreRestSiblings: true, argsIgnorePattern: &amp;quot;^_+$&amp;quot; }, // rest 연산자 형제 무시, _ 시작 매개변수 무시
          ],
          // &amp;#39;@typescript-eslint/naming-convention&amp;#39;: createNamingConventionConfig(), // 명명 규칙 (주석 처리됨)

          // TypeScript에서 불필요한 import 규칙 비활성화
          &amp;quot;import/named&amp;quot;: &amp;quot;off&amp;quot;, // TS가 이미 확인함
          &amp;quot;import/namespace&amp;quot;: &amp;quot;off&amp;quot;, // TS가 이미 확인함
          &amp;quot;import/default&amp;quot;: &amp;quot;off&amp;quot;, // TS가 이미 확인함
          &amp;quot;import/no-named-as-default-member&amp;quot;: &amp;quot;off&amp;quot;, // TS가 이미 확인함
        },
      },
    ],
  };&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;react.js&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  /**
   * react.js
   *
   * React 애플리케이션을 위한 ESLint 설정
   * React, JSX, 접근성(a11y) 관련 규칙을 정의
   *
   * @type {import(&amp;#39;eslint&amp;#39;).Linter.Config}
   */
  module.exports = {
    // 브라우저 환경 설정
    env: {
      browser: true, // 브라우저 환경에서 실행
    },

    // 확장할 규칙 설정
    extends: [
      &amp;quot;plugin:react/recommended&amp;quot;, // React 권장 규칙
      &amp;quot;plugin:react/jsx-runtime&amp;quot;, // React 17+ JSX 변환 지원 (import React 불필요)
      &amp;quot;plugin:react-hooks/recommended&amp;quot;, // React Hooks 권장 규칙
      &amp;quot;plugin:jsx-a11y/recommended&amp;quot;, // JSX 접근성(a11y) 권장 규칙
      &amp;quot;standard-jsx&amp;quot;, // JSX 표준 스타일
    ],

    // React 설정
    settings: {
      react: {
        version: &amp;quot;detect&amp;quot;, // React 버전 자동 감지
      },
    },

    // React 관련 규칙
    rules: {
      // JSX 문법 관련 규칙
      &amp;quot;react/jsx-no-useless-fragment&amp;quot;: [&amp;quot;error&amp;quot;, { allowExpressions: true }], // 불필요한 Fragment 금지, 표현식은 허용
      &amp;quot;react/jsx-pascal-case&amp;quot;: &amp;quot;error&amp;quot;, // 컴포넌트 이름 파스칼 케이스 강제

      // 속성 관련 규칙
      &amp;quot;react/no-unknown-property&amp;quot;: [&amp;quot;error&amp;quot;, { ignore: [&amp;quot;css&amp;quot;] }], // 알 수 없는 속성 금지, css 속성은 허용 (CSS-in-JS)
      &amp;quot;react/prop-types&amp;quot;: &amp;quot;off&amp;quot;, // PropTypes 검사 비활성화 (TypeScript 사용 시)

      // 컴포넌트 스타일 규칙
      &amp;quot;react/self-closing-comp&amp;quot;: [
        &amp;quot;error&amp;quot;,
        {
          component: true, // 내용 없는 컴포넌트는 자체 닫는 태그 사용
          html: true, // HTML 요소도 적용
        },
      ],

      // 일반 코드 스타일 규칙
      curly: [&amp;quot;error&amp;quot;, &amp;quot;all&amp;quot;], // 모든 조건문에 중괄호 사용 강제
    },
  };&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;next.js&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  /**
   * next.js
   *
   * Next.js 프레임워크 프로젝트를 위한 ESLint 설정
   * React 설정을 확장하고 Next.js 특화 규칙 추가
   */
  module.exports = {
    // 확장할 설정
    extends: [
      &amp;quot;./react.js&amp;quot;, // React 기본 설정 확장
      &amp;quot;next/core-web-vitals&amp;quot;, // Next.js 코어 웹 바이탈 규칙 (성능 측정 지표)
    ],

    // Next.js 설정
    settings: {
      next: {
        rootDir: [&amp;quot;apps/*/&amp;quot;, &amp;quot;packages/*/&amp;quot;], // 모노레포 구조의 루트 디렉토리 설정
      },
    },
  };&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;prettier.js&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  /**
   * prettier.js
   *
   * ESLint와 Prettier 통합을 위한 설정
   * 코드 포맷팅 일관성 유지
   *
   * @type {import(&amp;#39;eslint&amp;#39;).Linter.Config}
   */
  module.exports = {
    // Prettier 관련 설정 확장
    extends: [&amp;quot;prettier&amp;quot;], // ESLint와 Prettier 충돌 규칙 비활성화

    // Prettier와 함께 사용할 ESLint 규칙
    rules: {
      /**
       * prettier에서 기본적으로 비활성화하지만
       * 필요에 의해 다시 활성화하는 규칙
       */
      curly: [&amp;quot;error&amp;quot;, &amp;quot;all&amp;quot;], // 모든 조건문에 중괄호 사용 강제
    },
  };&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;배포 및 테스트&lt;/h3&gt;
&lt;p&gt;전 글에서 root - package.json은 변동될 예정이다했는데 아래와같아진다&lt;/p&gt;
&lt;p&gt;아직은 eslint 설정 부분이지만 아래 과정으로 넣을 예정이다 (모두 배포예정)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prettier&lt;/li&gt;
&lt;li&gt;nextjs-template&lt;/li&gt;
&lt;li&gt;react-template&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;root&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
  &amp;quot;private&amp;quot;: true,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;build&amp;quot;: &amp;quot;pnpm -r build&amp;quot;,
    &amp;quot;test&amp;quot;: &amp;quot;pnpm -r test&amp;quot;,
    &amp;quot;lint&amp;quot;: &amp;quot;pnpm -r lint&amp;quot;,
    &amp;quot;publish-all&amp;quot;: &amp;quot;pnpm -r publish&amp;quot;,
    &amp;quot;publish-eslint&amp;quot;: &amp;quot;pnpm --filter @rendardev/eslint-config publish&amp;quot;,
    &amp;quot;publish-prettier&amp;quot;: &amp;quot;pnpm --filter @rendardev/prettier-config publish&amp;quot;,
    &amp;quot;publish-react&amp;quot;: &amp;quot;pnpm --filter @rendardev/react-template publish&amp;quot;,
    &amp;quot;publish-nextjs&amp;quot;: &amp;quot;pnpm --filter @rendardev/nextjs-template publish&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;eslint&amp;quot;: &amp;quot;^9.0.0&amp;quot;,
    &amp;quot;typescript&amp;quot;: &amp;quot;^5.8.2&amp;quot;
  },
  &amp;quot;workspaces&amp;quot;: [
    &amp;quot;packages/*&amp;quot;
  ],
  &amp;quot;packageManager&amp;quot;: &amp;quot;pnpm@8.9.0&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;배포&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  pnpm publish-eslint&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;테스트는 어렵지 않다&lt;br&gt;아래 링크에서 README.md를 참고하면된다&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/@rendardev/eslint-config?activeTab=readme&quot;&gt;https://www.npmjs.com/package/@rendardev/eslint-config?activeTab=readme&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;물론 모두 사용할리는 없겠지만 만약 모두 사용한다면 순서가 중요하다&lt;/p&gt;
&lt;p&gt;위 순서대로 오버라이드 시키는 편이 좋다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;module.exports = {
  extends: [
    &amp;quot;@rendar-mono-template/eslint-config&amp;quot;,
    &amp;quot;@rendar-mono-template/eslint-config/next&amp;quot;,
    &amp;quot;@rendar-mono-template/eslint-config/typeCheck&amp;quot;,
    &amp;quot;@rendar-mono-template/eslint-config/prettier&amp;quot;,
  ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;전편에 이어 전용 템플릿 시리즈 - ESLint 편을 보았다&lt;br&gt;사실 Lint 설정이랄게 크게 없어서 어려움은 없었지만 관련된 패키지 부분에서 많은 부분 시간을 쏟은 것 같다.&lt;/p&gt;
&lt;p&gt;GPT랑 클로드 활용해서 코드 분석 및 주석을 통해서 어떤 패키지인지 파악하는게 굉장히 쉬워져서&lt;br&gt;패키지 만드는데 큰 어려움이 없었던 것 같다&lt;/p&gt;
&lt;p&gt;다음은 가장 쉬운 prettier 부분으로 넘어갈려고한다&lt;/p&gt;</description>
      <category>Front-end</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/44</guid>
      <comments>https://rendaritfactory.tistory.com/44#entry44comment</comments>
      <pubDate>Fri, 14 Mar 2025 23:17:10 +0900</pubDate>
    </item>
    <item>
      <title>나만의 전용 템플릿 엔진(?) 제작기 - 초기세팅</title>
      <link>https://rendaritfactory.tistory.com/43</link>
      <description>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;최근 사이드프로젝트를 구상하다 보니 막상 떠오른 프로젝트(?)인지 모르겠지만, 나만의 템플릿을 제작해 보면 어떨까? 라는 생각을 많이 하게 되었다.&lt;/p&gt;
&lt;p&gt;Nextjs나 React(vite) 템플릿을 보면 공식 문서에서 이미 만들어진 틀을 준다.&lt;br&gt;사실 베이스부터 시작해서 입맛에 맞춰서 바꿔도 되지만, 아주 귀찮고 재미없는 일인건 알고 있다. 특히 일적으로 바쁜 회사에서 하나씩 뭐 넣고 하는 건 매우 비효율적 (커스텀 웹팩이나 아니면 전사 프로젝트로 진행한다면 다를 수도)&lt;br&gt;그래서 나만의 템플릿 엔진을 제작해 보기로 하였다&lt;br&gt;npx create &amp;lt;나만의 템플릿&amp;gt;을 통해서 어디서든지 어느 컴퓨터든지 내가 만들어놓은 lint, prettier 설정 등을 활용할 수 있게 하는 것으로 제작해 볼까 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt; 본 글은 시리즈물로 제작예정입니다 한번에 구성할시 글이 길어질수 있으므로 패키지별로 작성할 예정입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;들어가기앞서…&lt;/h2&gt;
&lt;p&gt;들어가기 앞서서 사전 지식이 필요하다.&lt;br&gt;현재 템플릿엔진 제작을 위해서는 확장성과 효율을 위하여 모노레포(mono-repo)형식으로 제작할려고한다.&lt;br&gt;사전지식으로는 모노레포가 필요하니 아래 네이버 D2에서 매우 잘 설명해두었으니 참고바란다.&lt;br&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/0923884&quot;&gt;https://d2.naver.com/helloworld/0923884&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;왜 모노레포인가? 멀티레포도 가능하지않나?&lt;/h3&gt;
&lt;p&gt;사실 구성자체는 멀티레포로 진행해두 무관하다&lt;br&gt;각 react, nextjs 등등의 템플릿을 만들어두고 해두 괜찮지만 상당한 단점이 많이 존재하는데&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;1. 멀티레포는 패키지 관리가 어렵다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;패키지관리가 어려운 부분은 바로 공통인 패키지를 관리하기 매우 까다로운 부분이다&lt;br&gt;쉽게 특정라이브러리를 업데이트할시 특정 프로젝트 버전이 낮으면 오류가 날 확률도 있고,&lt;br&gt;각 패키지간의 연동이 어느정도인지 파악하기 어렵다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rIW4m/btsLTwTg6tv/Vkw7iDDJ80q8OkokIzcLk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rIW4m/btsLTwTg6tv/Vkw7iDDJ80q8OkokIzcLk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rIW4m/btsLTwTg6tv/Vkw7iDDJ80q8OkokIzcLk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrIW4m%2FbtsLTwTg6tv%2FVkw7iDDJ80q8OkokIzcLk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;527&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;물론 서브모듈(submodule) 같은 방법으로 공통모듈을 넣을순있으나,&lt;br&gt;이 방법 또한 내부에서 깃 버전을 관리해야되므로 상당한 리소스를 소비할수 밖에 없다.&lt;/p&gt;
&lt;p&gt;반면 모노레포는 공통모듈, 혹은 root에 공용 패키지를 정의해 놓으면 각 프로젝트별로 관리할 리소스가 줄어든다&lt;br&gt;쉽게 공용패키지내에서만 관리하면 되기때문에 관리적 측면에서도 이득이있다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;2. 공통인 코드등의 관리가 어렵다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byTY8d/btsLUvMRhWb/4V8uoRbI3viDg6BQc9pWXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byTY8d/btsLUvMRhWb/4V8uoRbI3viDg6BQc9pWXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byTY8d/btsLUvMRhWb/4V8uoRbI3viDg6BQc9pWXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyTY8d%2FbtsLUvMRhWb%2F4V8uoRbI3viDg6BQc9pWXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;451&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;극단적인 예시이긴 하지만 A 프로젝트에서 A 컴포넌트가 쓰인다고 했을 때&lt;br&gt;B 프로젝트에서도 A 컴포넌트가 쓰인다면 어떻게 처리할 것인가? Submodule? 아니면 공통 모듈 패키지?&lt;br&gt;이런 식으로 공통인 코드들의 관리가 멀티레포상에서는 상당히 어렵다&lt;br&gt;막상 A 프로젝트에서만 쓰이는 컴포넌트인 줄 알았으나 다음에 B 프로젝트에서도 쓰인다는 기획이 나왔을 때&lt;br&gt;(물론 이것도 극단적인 것이고 보통은 공통 모듈로 빼는 편)&lt;br&gt;그 특정 컴포넌트의 위치가 애매해지는 상황이 발생한다.&lt;/p&gt;
&lt;p&gt;반면 모노레포인 경우에는 공용인 부분만 분리하여서 패키지에서 관리하므로 참조 형식으로 사용하면 된다.&lt;br&gt;얼마나 편한가?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;3. 개발설정이 매번 달라진다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 부분은 상황에 따라 다른데&lt;br&gt;개발 설정이 설정한 사람마다 달라질 수도 있다. 물론 회사 혹은 공통 config설정을 따로 문서화나 코드로 관리한다면 다른 문제지만 그렇지 않다면, 매번 개발 설정이 달라질 수밖에 없다.&lt;/p&gt;
&lt;p&gt;이러면 문제점이 바로 설정이 다르니 여기선 동작하던 게 저기선 동작하지 않는 부분이 발생하고, 여기선 괜찮은 lint 설정이지만, 특정 프로젝트는 빡빡하게 해서 빨간줄이 아무 빨갛게 물든 현상을 볼 수 있다.&lt;/p&gt;
&lt;h3&gt;pnpm workspace? yarn workspace?&lt;/h3&gt;
&lt;p&gt;사실 2개의 패키지 관리툴을 가지고 고민했었는데&lt;br&gt;개인적으로 pnpm을 경험해 보고 싶다는 생각이 강하여 채택하게 되었는데&lt;br&gt;이유는 총 3가지이다&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;yarn berry의 Plug’n’Play(PnP) 방식에 대한 경험 부족&lt;ol&gt;
&lt;li&gt;좋은 기술이긴 하나 개인적으로 패키지 업데이트할시에 zip 파일들의 관리나 이런부분에 대한 부분이 명확하지 않았다. (사실 어떻게 관리할지를 모르겠다.)&lt;/li&gt;
&lt;li&gt;또 좋다고 막상 쓸려니 이게 맞나? 늘어나는 파일들을 보고 의구심이 들었다 → 호환성 이슈도 있고…&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;pnpm의 빠른 속도/캐싱과 의존성관리&lt;ol&gt;
&lt;li&gt;애초에 pnpm 공식 문서에는 ‘&lt;strong&gt;빠르고 디스크 공간 효율적인 패키지 관리자’&lt;/strong&gt; 라고 적혀있다 그만큼 속도나 디스크 관리에 대해서 자부심이 있다는 생각이 들었다.&lt;br&gt;(말로는 2배 빠르다고 하는데 yarn도 npm대비 빠르긴 했었다.)&lt;/li&gt;
&lt;li&gt;그리고 패키지들을 중앙 캐시에 저장하여, 의존성을 심볼릭 링크로 연결하여 중복으로 설치되는 걸 방지해준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;무엇보다 출시 때부터 workspace 지원하는 부분이 마음에 든다&lt;ol&gt;
&lt;li&gt;대부분 패키지 툴은 일정 버전 이상일 때만 지원을 해줬는데, 출시 때부터 지원해 주었다. (뭐… 후속 주자라 당연한 건가?)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그러므로 이 글에서는 &lt;strong&gt;pnpm workspace&lt;/strong&gt;을사용 할 것이다.&lt;/p&gt;
&lt;h3&gt;초기세팅&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;pnpm 설치&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; node 16 버전이상 필요 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; # npm
 npm install -g pnpm

 # homebrew
 brew install pnpm&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;프로젝트 파일 생성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; # 폴더 생성
 mkdir my-monorepo-template

 # 폴더들어가기
 cd my-monorepo-template&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;프로젝트 초기화&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; pnpm init&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;init시에 package.json이 생성된다 (추후에 수정예정)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;{
&amp;quot;name&amp;quot;: &amp;quot;my-monorepo-template&amp;quot;,
&amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
&amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
&amp;quot;scripts&amp;quot;: {
  &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;
},
&amp;quot;keywords&amp;quot;: [],
&amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
&amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;프로젝트 구조&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; 아래 구조처럼 제작할 예정입니다&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; my-monorepo-template/      
 ├── packages/
 │   ├── eslint-config/
 │   ├── prettier-config/
 │   ├── tsconfig/
 │   ├── react-template/
 │   └── nextjs-template/
 ├── package.json
 └── pnpm-workspace.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pnpm-workspace.yaml&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt; packages:
   - &amp;quot;packages/*&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;workspace에대해서 공부할수있는 알찬 기회라고 생각이든다&lt;br&gt;그동안 이런 템플릿 있으면 좋았을거 같다는 생각을 많이 했었고, 찾던 와중에 npx create xxx 같은 명령어를 나두 활용해서 쓸수있다는 부분을 알게 되었다.&lt;br&gt;나만의 템플릿을 제작하여 새로운 프로젝트를 해볼까한다.&lt;br&gt;개인적으로 이번엔 서버에 중점을 두기보다 프론트에 더 중점을 둔 방향성으로 만들어야겠다.&lt;/p&gt;</description>
      <category>Front-end</category>
      <category>frontend</category>
      <category>mono-repo</category>
      <category>Next</category>
      <category>nextjs</category>
      <category>package</category>
      <category>pnpm</category>
      <category>react</category>
      <category>Workspace</category>
      <category>모노레포</category>
      <category>템플릿</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/43</guid>
      <comments>https://rendaritfactory.tistory.com/43#entry43comment</comments>
      <pubDate>Mon, 20 Jan 2025 22:47:53 +0900</pubDate>
    </item>
    <item>
      <title>4년차 프론트엔드 24년 회고록</title>
      <link>https://rendaritfactory.tistory.com/42</link>
      <description>&lt;h2&gt;순식간에 지나갔던 24년&lt;/h2&gt;
&lt;p&gt;참 시간이 빠른것 같다. 23년 회고록을 적은지 엊그제같은데,&lt;/p&gt;
&lt;p&gt;돌아서 생각해보니 사실 크나큰 이벤트는 없었던 것 같다. 벌써 이직한지 1년이 넘어버렸다. 아직도 믿기지 않는다 아직 회사 내부에 대해서 배울게 많은 것 같은데 1년?… (&lt;del&gt;난 아직 부족한거 같은데&lt;/del&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사실 근 1년 사이에 많은게 바뀐 것 같다. 특히 개발 관련된 부분인데 GPT, 클로드 같은 LLM 툴들이 발달되다보니 오히려 검색보단, AI 툴을 많이 사용했고, 오히려 내가 나보다 잘 짜는 경우도 있었다 (&lt;del&gt;부족함점은 있긴했지만&lt;/del&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하지만 아직까진 보조 수단으로 생각되는 부분이 많았다. 특히 디테일 적인 부분이나 없는 메소드 만들어서 있는척하는 &lt;strong&gt;할루시네이션&lt;/strong&gt;… 충격적 없다는거 말하니까 뻔뻔하게 “네 없습니다!” 이러는거보고 &lt;strong&gt;충격&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGn54Z/btsLCmJiAb7/AwkebB6FMwFyU3V9o8z5o0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGn54Z/btsLCmJiAb7/AwkebB6FMwFyU3V9o8z5o0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGn54Z/btsLCmJiAb7/AwkebB6FMwFyU3V9o8z5o0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGn54Z%2FbtsLCmJiAb7%2FAwkebB6FMwFyU3V9o8z5o0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;384&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;여행을 많이 다니다&lt;/h3&gt;
&lt;p&gt;24년 참 많은 여행을 다닌 것 같다.&lt;/p&gt;
&lt;p&gt;여자친구와 전국을 돌아다닌 것 같고, 가족여행으로 일본, 여자친구와도 일본 여행을 가면서 참 많이 다닌 것 같다 (내 통장은 텅장이??)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;23년도 회고로 여행을 많이 다닌다 했는데 얼떨결에 많이 다니게 된 것 같다. 사실 집돌 이에 가까운 집 콕 족이지만, 재택으로 집에 오래 있다 보니 오히려 외출이 더 반가워진 것 같다.&lt;br&gt;사실 나는 바다를 무척이나 좋아한다. 산보다 좋아하는 이유 중 하나는 광활한 바다 끝을 멍하니 보고 있으면 기분이 좋고, 바다 특유의 짠 내를 좋아한다. 특히 파도소리… ASMR 밤에 틀어놓고 자면 바로 잠들 수 있을 것 같다.&lt;/li&gt;
&lt;li&gt;생각나는대로 적어보면&lt;br&gt;을왕리, 강원도 홍천, 도쿄(가족여행), 송파, 수원(스타필드), 대전(성심당~), 당진, 예산, 부산, 경주, 강원도 양양 (가기 전 오션월드 + 워터밤), 거제도, 통영, 서산&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;적고보니 참 많은 곳을 갔는데 특히 도쿄 가족여행과, 강원도 양양(여름휴가)이 굉장히 맘에 들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1980&quot; data-origin-height=&quot;3520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cplq6A/btsLAzpmE4j/C1S94J1f1WXyYtgFwaxF90/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cplq6A/btsLAzpmE4j/C1S94J1f1WXyYtgFwaxF90/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cplq6A/btsLAzpmE4j/C1S94J1f1WXyYtgFwaxF90/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcplq6A%2FbtsLAzpmE4j%2FC1S94J1f1WXyYtgFwaxF90%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1980&quot; height=&quot;3520&quot; data-origin-width=&quot;1980&quot; data-origin-height=&quot;3520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;해리포터 스튜디오&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyZzja/btsLCIkVdXL/7Z20gx3ZbCiNHayjn34Kxk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyZzja/btsLCIkVdXL/7Z20gx3ZbCiNHayjn34Kxk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyZzja/btsLCIkVdXL/7Z20gx3ZbCiNHayjn34Kxk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyZzja%2FbtsLCIkVdXL%2F7Z20gx3ZbCiNHayjn34Kxk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;워터밤&lt;/p&gt;
&lt;p&gt;개인적으로 두가지는 힘들긴 했지만 굉장히 알찼다.&lt;br&gt;가족여행은 다음부터 힐링여행으로 가야된다는걸 깨닫게 되는 계기가 되어버렸고&lt;br&gt;얼떨결에 에약했던 양양에서는 오션월드 + 계곡 + 워터밤 + 바다 모두를 구경하게되었다. 여름여행을 굉장히 알차게 보낸 것에 난 정말 아주 좋았다 (숙소주인분께서 자기는 못 간다고 양도해주신 게 너무 감사하다 다음에도 그곳으로 여름휴가를 가야겠다.)&lt;/p&gt;
&lt;h3&gt;밀렸던 프로젝트의 시작&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;933&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S2HqJ/btsLBrqY5rD/6etEZnSZCeSYX06mEEq4e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S2HqJ/btsLBrqY5rD/6etEZnSZCeSYX06mEEq4e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S2HqJ/btsLBrqY5rD/6etEZnSZCeSYX06mEEq4e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS2HqJ%2FbtsLBrqY5rD%2F6etEZnSZCeSYX06mEEq4e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;933&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;933&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이번에 배포한 &lt;a href=&quot;https://673a04c28614bf804981780a-qmtafmavck.chromatic.com/?path=/docs/rendar-design-system-accordion--docs&quot;&gt;rendar-design-system&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;23년 회고에서 24년 목표로 프로젝트와 라이브러리 제작을 하기로 하였다.&lt;/p&gt;
&lt;p&gt;결론부터 말하자면 아직 부족하긴 하지만 몇 가지 해내긴 했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;기존 타이머 프로젝트 재개&lt;/li&gt;
&lt;li&gt;디자인 시스템 프로젝트 배포 (NPM 배포완료)&lt;/li&gt;
&lt;li&gt;Supabase, threejs 등의 라이브러리 학습&lt;/li&gt;
&lt;li&gt;알고리즘 등의 학습자료 업로드(릿트코드 학습재개)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;4가지를 하지 않았나 생각된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;타이머의 경우 정책, 기획까지 마쳤으나 사실상 완료 수순으로 돌아서기로 했다&lt;br&gt;(너무 오래 걸리기도 했고, 흥미가 부족하긴 하지만 프로젝트를 오래 할 수 없다고 생각들었다)&lt;/li&gt;
&lt;li&gt;디자인 시스템의 경우 드디어 24년에 배포를 완료하였다.&lt;br&gt;아직 부족하지만 Vite 기반으로 배포를 완료하였고, 최종적으로는 Rollup기반으로 변경하여 배포할 예정이다.&lt;br&gt;아마 최종배포할 때는 커스텀훅같은 유틸함수도 모노레포화 시켜서 첨부시킬 예정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아직 부족하지만, 알고리즘이나 라이브러리 학습도 계속 병행하고 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4년차… 많다면 많고 적다면 적을 수 있는 연차일 수도 있다. 하지만 연차가 들수록 생각되는 부분은 기초적인 부분 CS 지식 등이 굉장히 중요하다고 생각된다. 어쨌든 Nextjs나 nestjs 같은 프레임워크 등들도 결과론적으로 베이스 기반으로 제작된 도구들이다.&lt;br&gt;25년에는 기초적인 베이스 기반의 공부를 많이 해볼까 생각된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;25년에 5년차가 될 나에게 계획은?&lt;/h2&gt;
&lt;p&gt;사실 거창한 계획이랄게 없다.&lt;/p&gt;
&lt;p&gt;마음 같아선 더 큰기업, 대기업 이런 곳으로의 이직을 하고 싶고, 직무전환을해서 백엔드도 해보고 싶고, 아니면 AI쪽으로 공부를 해볼까도 생각해 볼게 너무 많다.&lt;/p&gt;
&lt;p&gt;하지만 큰틀에서는 아직까진 기본기나 풀스택에 대한 부분을 더 공부해볼까한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. CS 혹은 Javascript나 React,Nextjs에 대한 기초적인 공부
2. 백엔드 공부 재시작 및 강의수강 
3. 인터랙티브 웹에 대한 공부
4. 프론트엔드에 대한 딥다이브 (모듈, CSR, SSR, Server Actio, 모듈러, 브라우저 등)
5. 책 목표 10권 (24년 읽은 책: 6권)
6. 클라이밍 목표치 빨강 30개&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;CS 혹은 Javascript나 React,Nextjs에 대한 기초적인 공부&lt;/h3&gt;
&lt;p&gt;React나 라이브러리에 대한 부분만 너무 공부한 감이 크긴 했다.&lt;br&gt;쉽게 말하면 너무 기본적인 부분에 관한 공부가 부족했다. 예를 들면 React-Query를 사용할 시 캐시를 사용하는 전략이 어떤 건지, 만약 라이브러리를 안 쓰고 캐시를 구현한다면 어떻게 구현할 것 인지, 캐시는 도대체 왜 하는지에 대한 명확한 부분을 알지 못하였다. 거기다 퍼블리싱에 대한 부분도 사실 배울 게 많은데 부족한 것 같다 (예를 들면 애니메이션이라든지…) 25년에는 기초, 기본기를 다지는 연도가 되지 않을까 싶다.&lt;/p&gt;
&lt;h3&gt;백엔드 공부 재시작 및 강의수강&lt;/h3&gt;
&lt;p&gt;24년에 강의를 수강하다가 도중에 멈춰버렸다… (심지어 DB 강의까지 보고도)&lt;/p&gt;
&lt;p&gt;회사가 바쁜 감도 있기도 했었고, 무엇보다 현생 살기 바빴다는 핑곗거리 때문이었는데, 이 부분 때문에 문제지 않을까 싶다. 풀 스택 그리고 백엔드에 관한 공부는 계속하고 싶고, 무엇보다 Redis나 카프카같이 백엔드 단에서 비동기 처리 혹은 메시지 큐를 사용해 보면서 분산처리나 락 처리를 어떤 식으로 하는지도 경험해 보고 싶다.&lt;/p&gt;
&lt;h3&gt;프론트엔드에 대한 딥다이브 (모듈, CSR, SSR, Server Action, 모듈러, 브라우저 등)&lt;/h3&gt;
&lt;p&gt;2번째 계획에서 ‘네가 프론트엔드 개발자임을 망각한 거냐!’라고, 생각할 수 있지만, 아니다.&lt;br&gt;오히려 프론트엔드에 대한 딥다이브가 필요하다고 느껴졌다. 이 부분은 작년, 재작년에도 그랬지만, 요즘 더더욱 느끼는 것이 Vanilla Javascript를 회사에서 모듈로 만들기 시작하면서부터였을까?&lt;/p&gt;
&lt;p&gt;그리고 Nextjs를 배포하면서 최적화를 거치면서 더 궁금해졌다. 렌더링에 최악의 케이스와 최적의 케이스가 뭔지,&lt;br&gt;도대체 브라우저는 이런 걸 어떻게 평가하는 것 인지, 그러면 브라우저 엔진이 해석 과정에서 엔진이 어떤 역할을 하는지…&lt;/p&gt;
&lt;p&gt;그동안은 구현에 치우쳐져 있었다면 1번과 같이 프론트도 기본기 더 깊게 들어가 볼까 한다.&lt;/p&gt;
&lt;h3&gt;책 목표 10권&lt;/h3&gt;
&lt;p&gt;10권의 목표 결국 채우지 못하였다.&lt;/p&gt;
&lt;p&gt;작년보다 1권 더 많이 읽은 6권이였다. 진짜 연초에는 거창하게 한 달에 한 권! 하면서 읽었는데, 역시… 나태의 동물이라고 점차 나태해지는 나 자신을 발견하였다.&lt;br&gt;사실 다양한 책을 많이 읽었다. 자기계발서, 철학, 프론트엔드 개발서, Typescript 책 등등&lt;br&gt;내년에는 경제나 사회 문학 이런 곳도 읽어볼까 한다. (너무 계발서만 읽으니까, 재미가 없다. 소설도 읽어줘야지)&lt;/p&gt;
&lt;h3&gt;클라이밍 목표치 빨강 30개&lt;/h3&gt;
&lt;p&gt;사실 굉장히 애매해져 버린 클라이밍 목표다&lt;/p&gt;
&lt;p&gt;24년에는 다친 적도 많고 무엇보다 수영에 빠지다 보니 클라이밍에 대한 흥미가 낮아졌고, 그렇게 안 하게 되었다.&lt;br&gt;아마 빨강도 10개 못 채운 것 같다.&lt;br&gt;매번 비교하면서 나 자신을 한탄해하면서 클라이밍 잘하자 했었는데, 그런 모습은 어디 갔는지 (제발 돌아와 줘~!)&lt;br&gt;그래서 25년에도 힘내기 위하여 클라이밍 목표 30개를 잡을까 한다.&lt;br&gt;클라이밍 계정에 꾸준히 올리기 위해 노력해야겠다.&lt;/p&gt;</description>
      <category>Front-end</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/42</guid>
      <comments>https://rendaritfactory.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 30 Dec 2024 22:08:15 +0900</pubDate>
    </item>
    <item>
      <title>JWT refresh token 중복 호출 이슈</title>
      <link>https://rendaritfactory.tistory.com/41</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사내에서 기존 로그인 방식을 변경하여 JWT 토큰 로그인 방식으로 변경하게 되었다.&lt;br /&gt;JWT 토큰 로그인 방식은 많이 알려진 방식이긴 하나 대부분 백엔드에서 많이 처리해줘서 이번에 처음 프론트에 관심사가 모두 전달되어서 로그인 시 토큰관리 및 재갱신 부분도 만들게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현도중 토큰이 만료되었을시 실패했던 API 갯수만큼 refresh 토큰 재발급 api가 중복으로 호출되는 이슈를 확인하여 해결했던 부분을 남길려고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제의 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사내 로그인 시스템을 기존 백엔드에서 세션을 모두 관리하던 방식에서 &amp;rarr; JWT 토큰 방식으로 변경하게되었다&lt;br /&gt;물론 회사의 모든 백엔드 시스템과 프론트를 모두 교체해야되는 대규모 공사여서 굉장히 부담이 되는 프로젝트였던 것은 사실이다&lt;br /&gt;(사실 프론트팀과 같이 할려했으나 회사내 또 다른 대규모 프로젝트건 때문에 혼자 하게되었다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 &amp;rarr; JWT 방식은 사실 단순하게 관리주체가 어디냐에 따라 다르지만,&lt;br /&gt;이번에 추후 Nextjs 프로젝트를 대비(현재는 완료된 프로젝트)하여 관리 주체를 프론트로 넘어오게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 구조를 설명하자면 아래와 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; JWT토큰 Store or Cookie에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; API 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; header내에 Bearer ${token} 형식으로 Auth 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; API 응답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 토큰 만료시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; refresh token&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; JWT 재발급 &amp;rarr; store / cookie 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; api 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;토큰 만료시간은 보통 2시간으로 설정하지만 이부분은 회사내규나 회사 보안시스템에 따라 다른점 참고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 시작은 바로 API 호출이 아닌 토큰 만료시 재호출시 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 사용했던 방식의 경우 &lt;b&gt;axios interceptor&lt;/b&gt; 를 활용해서 중간에 요청을 가로채서 재발급 시킬려고 하였으나 문제는 재발급요청이 내가 실패했던 요청만큼 갔던 것이다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lMKBz/btsJ3jg5jbY/4Z7K5Iotkkae1KWhcry8n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lMKBz/btsJ3jg5jbY/4Z7K5Iotkkae1KWhcry8n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lMKBz/btsJ3jg5jbY/4Z7K5Iotkkae1KWhcry8n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlMKBz%2FbtsJ3jg5jbY%2F4Z7K5Iotkkae1KWhcry8n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;179&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고이미지 (링크:&lt;a href=&quot;https://klloo.github.io/memoization-api/&quot;&gt;https://klloo.github.io/memoization-api/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 사진을 못찍어 없지만 위와 같은 형태로 요청이 왔었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 코드를 첨부할수 없지만,&lt;br /&gt;검색해서 돌아다니는 코드를 통해서 inerceptor 코드를 해결하였지만 위와같은 문제가 발생하였다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇가지 방식을 통해서 해결은 하였지만 좀 이상한 방식으로 해결한 부분이 있어서 고치고 고쳐서 해결한 부분을 공유하도록하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Tanstack-Query 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 손쉽게 사용할수 있는 부분이였지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nextjs 변환전 회사 front코드의경우 react + redux로 API 호출을 담당하고 캐싱까지 해두었다&amp;hellip;&lt;br /&gt;쉽게말하면 모든 구조를 변경해야되므로 이부분은 해결 범위에서 벗어난 부분이라 딱히 해결법이 되진 못하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Lock 코드를 통해 API 중복호출 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock을 전역코드로 사용하여 lock시에는 기존 api코드를 발생시키고 그렇지 않을경우 refresh 코드를 실행하여 갱신하였다&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;  let lock = false;
  baseApi.interceptors.response.use(
    async (response) =&amp;gt; {
            // 내부 코드 생략
      if (lock) {
          // 이쪽에서 코드 실행
      }
      lock = true;
      // refresh 코드 발생 
      const accessToken = await refreshTokenAPI(baseApi, cookies.get('refresh_token'));
      // 내부 코드 생략
      return response;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 작동되었지만 사실상&amp;hellip; 일단 댐 구멍에 땜질해서 막은 꼴밖에 안보였다 그래서 더 서치하던중 가장 좋은 방법을 찾게 되었다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Observer 패턴을 사용 (채택)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4&quot;&gt;https://ko.wikipedia.org/wiki/옵서버_패턴&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Observer 패턴이란&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵저버(관찰자)들이 관찰하고 있는 대상자의 상태가 변화가 있을 때마다 대상자는 직접 목록의 각 관찰자들에게 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴을 적용하면 현재 interceptor에서 받은 정보를 계속 관찰하면서 refresh 상태에 따라 분리가 가능하겠다고 판단하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 클래스를 오랜만에 짜보기도 하고 Observer패턴은 직접 짜본적은 없었다.&lt;br /&gt;이번에 gpt의 도움과 다른 블로그를 참고하여서 개발하면서 패턴을 직접 만들어보았다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;class Observable {
  private observers: Array&amp;lt;(data: any) =&amp;gt; void&amp;gt; = [];

  subscribe(observer: (data: any) =&amp;gt; void): () =&amp;gt; void {
    this.observers.push(observer);
    return () =&amp;gt; (this.observers = this.observers.filter((obs) =&amp;gt; obs !== observer));
  }

  notify(data: any): void {
    this.observers.forEach((observer) =&amp;gt; observer(data));
  }

  clearObservers(): void {
    this.observers = [];
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용은 아래처럼 사용하면된다&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const tokenObservable = new Observable();

class TokenManager {
  private isRefreshing = false;

  constructor(private axiosInstance: AxiosInstance, private cookies: Cookies) {}

  async refreshToken() {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      try {
                // 리프레시가 발생했을때 코드 
        return response;
      } catch (error) {
        // 에러가 발생했을시 코드 
      } finally {
        this.isRefreshing = false;
      }
    }

// 만약 토큰이 갱신 중이라면, 새로운 토큰이 갱신될 때까지 대기합니다.
    return new Promise&amp;lt;void&amp;gt;((resolve) =&amp;gt; {
    // 토큰 갱신이 완료되면 구독된 함수가 호출되고, 이때 resolve가 호출됩니다.
      tokenObservable.subscribe((token) =&amp;gt; {
        resolve();
      });
    });
  }
}

export default TokenManager;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 확실하고 우아하게 짠 코드가 아닐지 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 tanstack-query를 사용했다면 더 쉽게 캐싱과 refetch를 날릴수있었겠지만&lt;br /&gt;마땅한 대안이 없었던 레거시에서 사용하다보니 만들었던 코드고 찾았던 코드였던 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 클래스 관련된 코드를 짜고 재미있던 것 같다.&lt;br /&gt;매번 함수형 코딩, 프로그래밍만 짜오다가 클래스를 보니 오히려 머리를 쓰게 되고, 또 학창시절배웠던 지식을 복기한 느낌이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 인증같은 경우 사용자의 최상단에서 사용하는 부분이라 가장 중요하고 보안적으로 필요한 부분인데&lt;br /&gt;어떻게 관리해야될지 아니면 어떻게 코드를 짜야될지 고민이 많았던 부분이였던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짜면서 access-token과 refresh-token 관리 방향성 그리고 이것을 저장하는 방법 탈취되었을시 가장 안전한 방법은? 하면서 생각을 많이하였다&lt;br /&gt;결론은 시스템적으로 이상없었고, 사용성도 해치지 않았다 (현재는 Nextjs로 넘어가면서 사실상 레거시코드)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@chanwoo00106/%ED%86%A0%ED%81%B0-%EC%9E%AC%EB%B0%9C%EA%B8%89-%EC%A4%91%EB%B3%B5-%EC%9A%94%EC%B2%AD-%EB%AC%B8%EC%A0%9C&quot;&gt;https://velog.io/@chanwoo00106/토큰-재발급-중복-요청-문제&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://klloo.github.io/memoization-api/&quot;&gt;https://klloo.github.io/memoization-api/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%98%B5%EC%A0%80%EB%B2%84Observer-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot;&gt;https://inpa.tistory.com/entry/GOF- -옵저버Observer-패턴-제대로-배워보자&lt;/a&gt;&lt;/p&gt;</description>
      <category>Front-end</category>
      <category>frontend</category>
      <category>JWT</category>
      <category>react</category>
      <category>REFRESH</category>
      <category>옵저버패턴</category>
      <category>인증</category>
      <category>프론트엔드</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/41</guid>
      <comments>https://rendaritfactory.tistory.com/41#entry41comment</comments>
      <pubDate>Sat, 12 Oct 2024 00:25:06 +0900</pubDate>
    </item>
    <item>
      <title>디자인시스템(4).시스템 구축(라이브러리 제작) vite편</title>
      <link>https://rendaritfactory.tistory.com/40</link>
      <description>&lt;h1&gt;디자인시스템 구축&lt;/h1&gt;
&lt;p&gt;전 글에서 compound component와 Polymorphic component에 대해서 다뤄봤고,&lt;br&gt;추가적으로 디자인 토큰이 어떤 것인지에 대해서도 알아보았다. &lt;/p&gt;
&lt;p&gt;이번에는 디자인시스템 라이브러리 구축을 위한 과정을 한번 다뤄볼까한다. &lt;/p&gt;
&lt;h2&gt;디자인 시스템 구축 과정&lt;/h2&gt;
&lt;p&gt;사실 Rollup으로 구축할려고했으나 vite의 강세와 더불어 내부로직으로 Rollup 설정이 가능한 vite로 빠르게 구축후 Rollup으로 포팅하는 과정을 할려고한다.&lt;/p&gt;
&lt;h3&gt;고려사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;최소한의 번들 사이즈를 유지하게 만든다 (tree shacking 기능이 있는걸 사용한다)&lt;/li&gt;
&lt;li&gt;storybook을 적극활용하여 컴포넌트를 관리한다&lt;/li&gt;
&lt;li&gt;Typescript기반으로 개발한다&lt;/li&gt;
&lt;li&gt;React 개발환경에 맞춰 개발한다 (다른 것들은 추후고려대상)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;개발 구축&lt;/h3&gt;
&lt;p&gt;빠른 개발환경 구축을 위해 vite의 template를 사용하여 설정한다.&lt;br&gt;라이브러리이긴하지만 개발 내부를 볼수있게 코드베이스에서 확인이 가능하도록 react 템플릿사용 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# npm 7+, &amp;#39;--&amp;#39;를 반드시 붙여주세요
npm create vite@latest rendar-design-system -- --template react-ts

# yarn
yarn create vite rendar-design-system --template react-ts

# pnpm
pnpm create vite rendar-design-system --template react-ts&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;storybook 설치&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  npx storybook@latest init

  # 위와 같아 설치하면 스토리북은 자동으로 설치된다 &lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;vite.config.ts 수정&lt;/p&gt;
&lt;p&gt;  처음 설정되어있을시에는 아래와 같을것이다 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;  import { defineConfig } from &amp;quot;vite&amp;quot;;
  import react from &amp;quot;@vitejs/plugin-react&amp;quot;;

  // https://vitejs.dev/config/
  export default defineConfig({
    plugins: [react()],
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  변경해주기위해서 2개의 라이브러리 설치가 필요하다 &lt;/p&gt;
&lt;p&gt;  &lt;code&gt;vite-plugin-dts&lt;/code&gt;: TypeScript 정의 파일을 생성해준다 ex) index.d.ts&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;vite-tsconfig-paths&lt;/code&gt;: tsconfig.json의 경올 설정을 Vite에 반영해주는 라이브러리&lt;/p&gt;
&lt;p&gt;  위 라이브러리 외 필요한 라이브러리는 설치해서 사용하면된다 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # yarn
  yarn add vite-plugin-dts vite-tsconfig-paths --dev

  # npm 
  npm install vite-plugin-dts vite-tsconfig-paths --save-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  그리고 아래와 같이 변경&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;  import { defineConfig } from &amp;quot;vite&amp;quot;; // Vite 설정을 정의하기 위한 함수
  import path from &amp;quot;path&amp;quot;; // 파일 및 디렉토리 경로 작업을 위한 Node.js 모듈
  import react from &amp;quot;@vitejs/plugin-react&amp;quot;; // React와의 통합을 위한 Vite 플러그인
  import dts from &amp;quot;vite-plugin-dts&amp;quot;; // TypeScript 정의 파일 생성을 위한 플러그인
  import tsconfigPaths from &amp;quot;vite-tsconfig-paths&amp;quot;; // tsconfig.json의 경로 설정을 Vite에 반영하기 위한 플러그인

  export default defineConfig({
    plugins: [
      react(), // React 플러그인 사용
      dts({
        insertTypesEntry: true, // 타입 정의 파일을 생성할 때 entry point를 추가
      }),
      tsconfigPaths(), // tsconfig.json의 paths 옵션을 Vite에서 사용할 수 있게 해줌
    ],
    build: {
      lib: {
        entry: path.resolve(__dirname, &amp;quot;index.ts&amp;quot;), // 라이브러리의 진입점 파일
        name: &amp;quot;rendar-design-system&amp;quot;, // 라이브러리의 글로벌 네임스페이스
        formats: [&amp;quot;es&amp;quot;, &amp;quot;cjs&amp;quot;], // 번들링 포맷: ES Module과 CommonJS
        fileName: (format) =&amp;gt; `rendar-design-system.${format}.js`, // 생성될 파일 이름
      },
      rollupOptions: {
        external: [&amp;quot;react&amp;quot;, &amp;quot;react-dom&amp;quot;, &amp;quot;@emotion/react&amp;quot;, &amp;quot;@emotion/styled&amp;quot;, &amp;quot;@emotion/server&amp;quot;], // 번들링에서 제외할 외부 모듈
      },
      sourcemap: true, // 소스맵 생성 여부 (디버깅 용이)
      emptyOutDir: true, // 빌드 시 출력 디렉토리를 비울지 여부
    },
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  주석으로 설명을 해놨지만 설명을 해보자면 &lt;/p&gt;
&lt;p&gt;  &lt;a href=&quot;https://ko.vitejs.dev/config/build-options.html#build-lib&quot;&gt;build.lib&lt;/a&gt; : Vite의 라이브러리로 빌드할수 있게 설정하는 옵션&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;entry&lt;/code&gt; : 라이브러리 실행 진입점 &lt;/p&gt;
&lt;p&gt;  &lt;code&gt;name&lt;/code&gt; : 라이브러리 글로벌 네이밍 &lt;/p&gt;
&lt;p&gt;  &lt;code&gt;formats&lt;/code&gt; :  번들링 포맷을 써준다 (현재 ES module과 CommonJS를 지원하게끔 설정)&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;fileName&lt;/code&gt; : 빌드해서 생성될 패키지 파일명 &lt;/p&gt;
&lt;p&gt;  &lt;code&gt;rollupOptions&lt;/code&gt; : 기존 Rollup 번들을 커스텀합니다. (Rollup 옵션과 동일)&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;external&lt;/code&gt; : 번들링시 제외할 외부 모듈 (설치됬다고 가정한다)&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;sourcemap&lt;/code&gt; :  소스맵 생성여부&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;emptyOutDir&lt;/code&gt; : 빌드시 출력 디렉토리 비울지 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;package.json&lt;/p&gt;
&lt;p&gt;  package.json은 아래와 같이 변경해준다 (주석으로 설명)&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;main&lt;/code&gt;  : 패키지 진입점&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;types&lt;/code&gt; : typescript 타입 정의 파일 경로&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;export&lt;/code&gt; : main 필드와 동일하기 진입 경로 설정 가능, 추가적으로 &lt;code&gt;conditional exports&lt;/code&gt; ,&lt;code&gt;subpath exports&lt;/code&gt; 지원&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;files&lt;/code&gt; : 패키지 설치시 포함될 항목 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  {
    &amp;quot;name&amp;quot;: &amp;quot;rendar-design-system&amp;quot;, // 패키지 이름
    &amp;quot;version&amp;quot;: &amp;quot;0.0.4&amp;quot;, // 패키지 버전
    &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;, // 이 패키지가 ES 모듈임을 명시
    &amp;quot;main&amp;quot;: &amp;quot;dist/index.es.js&amp;quot;, // 패키지의 진입점 파일 (기본적으로 ES 모듈을 사용)
    &amp;quot;types&amp;quot;: &amp;quot;dist/index.d.ts&amp;quot;, // TypeScript 타입 정의 파일 경로
    &amp;quot;exports&amp;quot;: { // 패키지 내보내기 설정
      &amp;quot;.&amp;quot;: { // 기본 경로 설정
        &amp;quot;import&amp;quot;: &amp;quot;./dist/index.es.js&amp;quot;, // ES 모듈 사용 시의 진입점
        &amp;quot;require&amp;quot;: &amp;quot;./dist/index.cjs.js&amp;quot;, // CommonJS 모듈 사용 시의 진입점
        &amp;quot;types&amp;quot;: &amp;quot;./dist/index.d.ts&amp;quot; // 타입 정의 파일
      },
      &amp;quot;./package.json&amp;quot;: &amp;quot;./package.json&amp;quot;, // package.json 파일 경로
      &amp;quot;./dist/*&amp;quot;: &amp;quot;./dist/*&amp;quot; // dist 디렉토리 내 모든 파일 접근 허용
    },
    &amp;quot;files&amp;quot;: [
      &amp;quot;/dist&amp;quot; // 패키지에 포함될 파일 또는 디렉토리 목록 (여기서는 dist 디렉토리만 포함)
    ],
      //.. 이하 패키지 동일
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;tsconfig.json&lt;/p&gt;
&lt;p&gt;  typescript를 지원하기 때문에 아래와 같이 해준다&lt;/p&gt;
&lt;p&gt;  vite설치시 기본항목은 동일하나&lt;br&gt;  &lt;code&gt;include&lt;/code&gt; 부분에 진입점 index.ts를 추가해줘야된다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  {
    &amp;quot;compilerOptions&amp;quot;: {
      &amp;quot;target&amp;quot;: &amp;quot;ES2020&amp;quot;, // 컴파일된 JavaScript 코드가 호환될 ECMAScript 버전을 지정 (ES2020).
      &amp;quot;useDefineForClassFields&amp;quot;: true, // 클래스 필드에 대해 define 대신 ESNext 방식 사용.
      &amp;quot;lib&amp;quot;: [&amp;quot;ES2020&amp;quot;, &amp;quot;DOM&amp;quot;, &amp;quot;DOM.Iterable&amp;quot;], // 컴파일 시 포함할 라이브러리 설정 (ES2020, DOM, DOM.Iterable).
      &amp;quot;module&amp;quot;: &amp;quot;ESNext&amp;quot;, // 모듈 시스템 설정 (ESNext).
      &amp;quot;skipLibCheck&amp;quot;: true, // 라이브러리 파일의 타입 검사를 생략하여 컴파일 속도 향상.

      /* Bundler mode */
      &amp;quot;moduleResolution&amp;quot;: &amp;quot;bundler&amp;quot;, // 모듈 해석 방식을 번들러 모드로 설정.
      &amp;quot;allowImportingTsExtensions&amp;quot;: true, // TypeScript 확장자를 포함한 import 허용.
      &amp;quot;resolveJsonModule&amp;quot;: true, // JSON 파일을 모듈로서 import 허용.
      &amp;quot;isolatedModules&amp;quot;: true, // 각 파일을 개별적인 모듈로 컴파일.

      &amp;quot;noEmit&amp;quot;: true, // 출력 파일을 생성하지 않음.
      &amp;quot;jsx&amp;quot;: &amp;quot;react-jsx&amp;quot;, // JSX 코드의 변환 방식을 설정 (React 17 이상의 jsx 변환 방식 사용).

      /* Linting */
      &amp;quot;strict&amp;quot;: true, // 엄격한 타입 검사 설정.
      &amp;quot;noUnusedLocals&amp;quot;: true, // 사용되지 않는 지역 변수를 허용하지 않음.
      &amp;quot;noUnusedParameters&amp;quot;: true, // 사용되지 않는 파라미터를 허용하지 않음.
      &amp;quot;noFallthroughCasesInSwitch&amp;quot;: true, // switch 문에서 case의 fallthrough를 허용하지 않음.

      &amp;quot;outDir&amp;quot;: &amp;quot;./dist&amp;quot;, // 컴파일된 출력 파일의 디렉토리 설정.
      &amp;quot;rootDir&amp;quot;: &amp;quot;./&amp;quot;, // 소스 파일의 루트 디렉토리 설정.
      &amp;quot;paths&amp;quot;: {
        &amp;quot;@/*&amp;quot;: [&amp;quot;./src/*&amp;quot;] // 모듈 경로 별칭 설정.
      }
    },
    &amp;quot;include&amp;quot;: [&amp;quot;index.ts&amp;quot;, &amp;quot;src&amp;quot;], // 포함할 파일과 디렉토리 설정 (라이브러리 index 파일도 포함).
    &amp;quot;references&amp;quot;: [{ &amp;quot;path&amp;quot;: &amp;quot;./tsconfig.node.json&amp;quot; }] // 참조할 다른 tsconfig 파일 경로 설정.
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;배포전 테스트&lt;/h3&gt;
&lt;p&gt;배포이전에 테스트를 하고 싶으면 build시킨 파일을 link 시키면된다 &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://yarnpkg.com/cli/link&quot;&gt;https://yarnpkg.com/cli/link&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn link 
# or 
npm link &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;node_modules&lt;/code&gt; 를 확인해보면 제작한 라이브러리 이름으로 설치가 된 것을 확인해 볼 수 있다.&lt;/p&gt;
&lt;p&gt;테스트가 끝났을경우 아래 unlink 명령어를 통해 제거해주어야한다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn unlink 
# or 
npm unlink &lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;컴포넌트 제작하기&lt;/h3&gt;
&lt;p&gt;배포를하기 위해 간단한 버튼을 만들어볼까한다&lt;br&gt;&lt;code&gt;components/Button/Button.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import styled from &amp;quot;@emotion/styled&amp;quot;;
import { ReactNode } from &amp;quot;react&amp;quot;;

const ButtonContainer = styled.button`
  border: 1px solid gray;
  border-radius: 8px;
  padding: 20px;
`;

interface ButtonProps {
  children: ReactNode;
}

export const Button = ({ children, ...props }: ButtonProps) =&amp;gt; {
  return &amp;lt;ButtonContainer {...props}&amp;gt;{children}&amp;lt;/ButtonContainer&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;components/index.tsx&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;컴포넌트를 모으기 위해 export 파일들을 관리해준다 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export { Button } from &amp;quot;./Button/Button&amp;quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;배포하기&lt;/h3&gt;
&lt;aside&gt;
  NPM 계정이 있다는 가정하에 설명하는 글입니다 
NPM 계정이 없을시에는 가입을 해주시기 바랍니다. [NPM](https://www.npmjs.com/)

&lt;/aside&gt;

&lt;p&gt;먼저 CLI를 통해서 NPM 계정에 로그인을 해봅시다 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm login&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CLI에 로그인을 하였다면 배포를 진행해봅시다&lt;br&gt;유료로 계정을 결제중이라면 private를 사용해두되지만 저같이 유료결제를 하지 않았을시에는 public을 명시해줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm publish --access=public&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;배포 확인하기&lt;/h3&gt;
&lt;p&gt;배포이후에는 계정으로 들어가 배포가되었는지 확인을해본다&lt;br&gt;profile → packages → 내가 만든 라이브러리 &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/rendar-design-system&quot;&gt;npm: rendar-design-system&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;아주 잘 배포가 되고 있었다. &lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;나만의 라이브러리, 나만의 디자인 시스템 구축이라는 말만해보고 실천해보지 못하였다 &lt;/p&gt;
&lt;p&gt;라이브러리는 나보다 더 나은 사람이 만드는거야 라는 생각으로 차일 피일 미루다보니 이렇게 되었는데&lt;br&gt;막상 만들고 보니 별거 아니였다&lt;/p&gt;
&lt;p&gt;물론 시중에 나온 유명한 라이브러리는 최적화 및 여러명의 컨트리뷰터, 구글등의 유명 회사 개발자분들이 만들다보니 내부 로직이나 성능 등은 뛰어날 것이다.&lt;/p&gt;
&lt;p&gt;하지만 해본것과 안해본것은 천지차이 아닌가? 라이브러리를 제작해본 경험만으로도 충분히 추후 회사나 나의 사이드프로젝트를 통해 라이브러리를 개발할수있다는 자신감을 가질수 있을 것 같다&lt;/p&gt;
&lt;p&gt;이번 프로젝트는 vite 번들러를 사용했지만, 라이브러리에 최적화 되어있는 rollup으로 마이그레이션을 진행해볼까한다.&lt;br&gt;아마 구현하면서 모노레포 형식으로 vanilla-extract 버전이나 tailwind 버전등을 제작해서 여러 부분으로 제작을 해볼까한다&lt;/p&gt;</description>
      <category>Front-end</category>
      <category>javascript</category>
      <category>Library</category>
      <category>npm</category>
      <category>react</category>
      <category>typescript</category>
      <category>디자인시스템</category>
      <category>디자인시스템 구축</category>
      <category>라이브러리</category>
      <category>라이브러리제작</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/40</guid>
      <comments>https://rendaritfactory.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 4 Sep 2024 23:28:05 +0900</pubDate>
    </item>
    <item>
      <title>맥북프로에서 에어로 넘어오기까지</title>
      <link>https://rendaritfactory.tistory.com/39</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 맥북구매&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;229&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbQVvQ/btsIsIJPtCi/xqGAuK6d00pSJIszDpyLFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbQVvQ/btsIsIJPtCi/xqGAuK6d00pSJIszDpyLFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbQVvQ/btsIsIJPtCi/xqGAuK6d00pSJIszDpyLFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbQVvQ%2FbtsIsIJPtCi%2FxqGAuK6d00pSJIszDpyLFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;229&quot; height=&quot;222&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;229&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2019년 첫 개발자로써 직장을 취직하고 회사 컴퓨터보다 좋은 맥북프로를 개인적으로 장만하게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 당시 혁신이라며 내새웠던 터치바 (지금은&amp;hellip;할말하않) 상당히 파격적이긴 했다. 솔직히 개인적으로 &amp;ldquo;오? 신기한데&amp;rdquo; 말곤 기능을 쓸만한건 없었다. (그때당시 유튜브 광고스킵정도?&amp;hellip;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 맥북이고 첫 맥사용이라 굉장히 설레고 두렵기도 했었다 무엇보다 고가의 맥을 산다는 기분이 너무 좋았다. 그 당시에 CTO의 미개봉품을 업자에게서 샀는데, 직접 수령하기 위해 용산역까지 갔던 기억이있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통장에 있던 월급 + 모아둔돈을 탈탈 털어서 샀던 기억이있는데 벌써&amp;hellip; 6년이나 지났구나&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비싸게 샀지만, 많이 쓰지 못했던 맥북&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 비싸게 주고샀지만 정작 많이 쓰지 못했다 이유중 하나로는 바로 회사 맥북을 굉장히 많이 이용했었는데 이유는&amp;hellip; 뭐 회사에 야근이 잦았고 무엇보다 퇴근하고나서 기능 분석, 코드분석을 또 하다 보니 내 맥북은 자연스럽게 안쓰게 되었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 이유가 더 있던 부분이 바로 호환성 부분도 컸다. 일단 지금은 맥북의 호환성이 매우 나아진편이지만 첫 맥북을 샀을 당시만하더라도 지원되는 부분이 많이 없었다. (심지어 공인인증서 때문에 윈도우를 키는 나를 발견하고 ㅠ&amp;hellip;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트등도 해봤지만 대부분 물거품이 된 경우도 많았고, 사실상 계륵에 가까웠다&amp;hellip; 지금이야 게임이외 모든 작업은 맥북으로 해도 무관한 호환성이지만 그 당시만해도 그저 개발용 컴퓨터에 불과하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;편입을 하고부터 활발해진 맥북&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥북을 진짜 잘쓰던 부분은 바로 대학에 편입한뒤에 과제나 프로젝트를 진행하였을때 였다. 진짜 맥북은 개발용도로 딱 맞다고 느끼던때도 이때였다. 윈도우에 비해 설정 부분이 편하고, 무엇보다 터미널이 매우매우 좋았다. (윈도우 쉘&amp;hellip;. 생각만하면)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 당시 자바, 안드로이드, C++, JSP, python 등등 여러가지를 내 맥북과 함께하였다 (이래서 좀 맥북내 폴더랑 파일이 더러워지긴 했다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에 졸작 또한 맥북으로 돌렸는데 사실 큰 이슈는 없었다 무엇보다 그당시 산지 2년된 노트북이라고는 믿기지 않을정도로 쾌적했고, 무엇보다 빨랐다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에서야 무지개 돌아가는걸 볼 정도로 메모리나 성능면에서는 정말 매우매우 만족하면서 썻던것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나에겐 너무 오버스펙인 맥북프로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 매우매우 잘 썻다고 말했는데 나에게는 매우 오버스펙이였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 학부생때는 AI랑 기타 여러가지 돌린다고 무겁게 돌린게 많았었지만 (가상머신 등) 지금 내 맥북은 글쓰기, 코딩, 검색 및 파일 정리 등 별거 없었다 내가 그만큼 전문가 수준으로 작업하냐라고 하였을때는 영상 편집도 안할뿐더러 GPU 가속을 쓰면서 AI를 굴리지도 않는다. (맥북으로 돌릴빠엔 aws나 GCP에서 컴퓨터 빌리는 편이 이득)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 매우매우 무겁다&amp;hellip; 앞서 성능은 매우매우 만족했지만 휴대성은 매우 불만족이다&amp;hellip; 당연히 윈도우 노트북에 비하면 가볍지만, 학부생때는 전공책이랑 노트북만하면&amp;hellip; 어깨가 이미 빠졌다 현재도 무게때문에 선뜻 맥북을 들고 나가는것을 망설이는 편이 더 많아졌다. (오히려 이점때문에 차라리 맥미니를 사서 쓸까?라는 생각을 하였다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 부분때문에 오히려 여자친구나 주말에 가볍게 카페에서 작업을 할려고 해도 망설여지게 되었다 또한 나는 게임이나 하드한 작업은 오히려 pc로 작업하고 있었기 때문에 고성능 스펙이 별루 필요하지 않았다 (내가 영상편집이나 영상을 찍는 것도 아니고&amp;hellip;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 m시리즈가 나온이상 intel 맥은 더이상 좋은 성능은 아니였다 (오히려 전성비 망작&amp;hellip;) 또 intel맥 특유의 비행기 이륙소리&amp;hellip; 분명 프론트 서버 하나만 돌렸는데 이미 이륙진행중이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;맥북에어를 구매하게 되었다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 고민을 많이 하였다. 개인적으로 회사가 재택이 많다보니 그냥 맥미니를 구매할까 라는 생각을 하였고, 그래도 개발자인데 맥북프로로 가야되지 않을까 라는 딜레마에 빠지게 되었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 맥북에어로 오게되었다. 오히려 난 무게를 더 선택하게 되었던 것이다. 내가 개인 차를 운용하고 있긴하지만 차를 운영해도 무게에 대한 부분은 버릴수가 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 나는 쿠팡에서 맥북에어 m2 제품을 구매하였고, 현재는 매우 만족하면서 쓰고 있다. 맥북프로는 어떻게 할까 고민하던 찰나였지만 맥북프로는 그대로 냅두고 침대나 밖에서 돌아다닐때 맥북에어를 쓰기위해서 맥북프로는 그대로 냅두기로 하였다 (시즈모드용 맥이랄까?)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에어의 만족도는?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 매우매우 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에어를 사고나서 느낀점은 역시 노트북은 가벼워야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까진 큰 작업을 하지 않아서 성능이 부족한점을 느끼진 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드?나 아니면 devOps쪽이라면 느낄수도 있었겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 거의 대부분 로컬보단 클라우드로 작업을 많이 하는 추세고 로컬로 작업을해도 서버만 키는 정도뿐이지 큰 작업은 없을거 같다는 생각이 들었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 어려운 작업이있다하면 나의 시즈모드 맥을 꺼내서 만들고 가볍게 실행만 해두고 에어만 해두지 않을까 싶다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아직까진 백엔드 무리한 작업이 없기때문에 그런것 같다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 지출이였지만 매우 만족하기 때문에 개인적으로 24년을 통틀어 가장 합리적 소비가 아닐지 생각하고 있다.&lt;/p&gt;</description>
      <category>개인글</category>
      <category>apple</category>
      <category>MAC</category>
      <category>macbook</category>
      <category>맥북</category>
      <category>맥북에어</category>
      <category>맥북프로</category>
      <category>애플</category>
      <author>rendar02</author>
      <guid isPermaLink="true">https://rendaritfactory.tistory.com/39</guid>
      <comments>https://rendaritfactory.tistory.com/39#entry39comment</comments>
      <pubDate>Wed, 10 Jul 2024 00:02:08 +0900</pubDate>
    </item>
  </channel>
</rss>