<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hclee.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 11 Jan 2021 13:56:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hclee.log</title>
            <url>https://images.velog.io/images/octo-5/profile/4f5cb295-502c-4ca3-8cb5-6d218029ef84/스크린샷 2021-01-13 오후 11.21.44.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hclee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/octo-5" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Node.js + GraphQL 성능]]></title>
            <link>https://velog.io/@octo-5/Node.js-GraphQL-%EC%84%B1%EB%8A%A5</link>
            <guid>https://velog.io/@octo-5/Node.js-GraphQL-%EC%84%B1%EB%8A%A5</guid>
            <pubDate>Mon, 11 Jan 2021 13:56:12 GMT</pubDate>
            <description><![CDATA[<p>express + type-graphql로 GraphQL API를 구현하는 중에 문뜩 처리 성능이 어떤지 궁금해서 찾아보게 되었다.</p>
<blockquote>
<p><a href="https://github.com/benawad/node-graphql-benchmarks">성능 측정 출처 : https://github.com/benawad/node-graphql-benchmarks</a></p>
<h4 id="요약">요약</h4>
<ul>
<li>graphql-jit을 활용해라</li>
<li>모니터링을 위한 tracing은 꺼라</li>
<li>apollo-server, type-graphql 사용은 지양해라<h4 id="측정-방법">측정 방법</h4>
5개의 클라이언트가 동일한 쿼리문으로 5초간 반복 요청하여 성능을 측정함</li>
</ul>
</blockquote>
<pre><code class="language-GraphQL">query {
    authors {
        id
            name
            md5
            books {
                id
                    name
            }
        }
}</code></pre>
<p>성능 결과를 보면 GraphQL의 초당 처리량은 좋아봐야 REST의 70% 밖에 안되며 응답 지연 시간은 대략 5배 이상 더 걸리는 것으로 보인다.</p>
<p>결과로는 무작정 GraphQL이 REST보다 나쁜 것으로 보이는데 이 테스트는 Overfetching, Underfetching을 고려하지 않은 테스트이고 실제 서비스에 GraphQL을 적용하니 전반적으로 빨라졌다 라는 글이 있는걸 보면 REST vs GraphQL보다 Node.js에서 GraphQL 요청 처리가 왜 느려지나?, 어떤 Graphql 구현체, 라이브러리를 써야 하나? 의 관점으로 테스트 결과를 봐야 할 것 같다.</p>
<p>성능, 개발/학습 비용을 보고 어느 선에서 타협할지를 선택해야 하는데 mercurius가 준수한 성능에 graphql-jit 캐싱 기능 등 지원해주는게 많아서 적절해 보이긴 한데 그냥 Golang으로 짜기로 했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[당근 마켓 클론] 지역 정보와 DB 선택]]></title>
            <link>https://velog.io/@octo-5/%EB%8B%B9%EA%B7%BC-%EB%A7%88%EC%BC%93-%ED%81%B4%EB%A1%A0-%EC%A7%80%EC%97%AD-%EC%A0%95%EB%B3%B4%EC%99%80-DB-%EC%84%A0%ED%83%9D</link>
            <guid>https://velog.io/@octo-5/%EB%8B%B9%EA%B7%BC-%EB%A7%88%EC%BC%93-%ED%81%B4%EB%A1%A0-%EC%A7%80%EC%97%AD-%EC%A0%95%EB%B3%B4%EC%99%80-DB-%EC%84%A0%ED%83%9D</guid>
            <pubDate>Wed, 06 Jan 2021 11:56:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="erd-링크">ERD 링크</h4>
<p><a href="https://www.erdcloud.com/d/2mDmcrHWY3CqW4Rrp">https://www.erdcloud.com/d/2mDmcrHWY3CqW4Rrp</a></p>
</blockquote>
<p>지난 글에서 작성된 구현 기능 목록을 바탕으로 데이터 모델링을 수행했다.</p>
<p>데이터 모델링에 대한 이야기는 특별히 할게 없지만 이런 저런 삽질하면서 발견한 정보들을 공유한다.</p>
<h2 id="행정동-지역-경계-중심-좌표-정보">행정동 지역 경계, 중심 좌표 정보</h2>
<p>유저들과 중고 물품들을 구분하기 위한 지역 정보가 필요하다.
통계청 데이터 대신 미리 변환해 놓은 것이 있어서 사용했다.</p>
<blockquote>
<p><strong>행정동 정보 출처 :</strong> <a href="https://github.com/vuski/admdongkor">https://github.com/vuski/admdongkor</a></p>
<ul>
<li>통계청 통계지리정보서비스에서 제공하는 행정동 경계 파일을 바탕으로 작성</li>
<li>좌표계 EPSG:4326 기준으로 작성된 지역 경계 정보를 geojson로 제공</li>
<li>좌표계 EPSG:4326 기준으로 작성된 지역 중심 좌표를 csv로 제공</li>
</ul>
</blockquote>
<h2 id="공간-데이터를-관리하기-위한-rdbms-선택">공간 데이터를 관리하기 위한 RDBMS 선택</h2>
<p>결론만 먼저 말하면 Postgresql + PostGIS 플러그인을 사용한다.</p>
<p>특정 지역 범위를 계산하는 ST_Buffer와 교차 지역들을 계산하는 ST_Intersects를 MySQL8에서 사용하기가 영 불편하기 때문이다.</p>
<p>그 이유는 다음과 같다.</p>
<ul>
<li>ST_Buffer가 4326 좌표계를 지원하지 않음</li>
<li>ST_Buffer 호출 결과를 ST_Intersects가 사용하기 위해서 변환 함수를 여러번 호출해야 됨</li>
<li>ST_Buffer 전달 인자의 범위 값에 대한 단위를 알 수 없음</li>
</ul>
<p>특정 범위와 교차되는 지역들을 구하는 쿼리를 비교해보면 Postgresql의 쿼리가 더 짧고 범위에 대한 설정 값(미터 단위)이 직관적이다.</p>
<pre><code class="language-sql">// In MySQL8 
SELECT
    *
FROM
    &quot;행정동 테이블&quot;
WHERE
    ST_Intersects(&quot;행정동 지역 경계값&quot;, ST_GeomFromText(ST_AsText(ST_Buffer(ST_GeomFromText(ST_AsText(ST_GeomFromText(&#39;POINT(129.075041 35.179768)&#39;, 4326))), 0.005)), 4326)); // 0.005 ??</code></pre>
<pre><code class="language-sql">// In Postgresql
SELECT
    *
FROM
    &quot;행정동 테이블&quot; 
WHERE
    ST_Intersects(&quot;행정동 지역 경계값&quot;, ST_Buffer(ST_GeomFromText(&#39;POINT(129.075041 35.179768)&#39;, 4326)::geography, 500)::geometry); // 500 미터</code></pre>
<h2 id="mikro-orm">Mikro-ORM</h2>
<p>그동안 typeorm을 써왔는데 mikro-orm이라는 것을 발견했다.</p>
<p>knex.js 쿼리 빌더를 기반으로 작성된 ORM 패키지인데</p>
<p>typeorm보다 인기가 적지만 성능이 좋은것 같아서 써보기로 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[당근 마켓 클론] 기능 분석]]></title>
            <link>https://velog.io/@octo-5/%EB%8B%B9%EA%B7%BC-%EB%A7%88%EC%BC%93-%ED%81%B4%EB%A1%A0-%EA%B8%B0%EB%8A%A5-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@octo-5/%EB%8B%B9%EA%B7%BC-%EB%A7%88%EC%BC%93-%ED%81%B4%EB%A1%A0-%EA%B8%B0%EB%8A%A5-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Thu, 17 Dec 2020 09:42:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/octo-5/post/fc8f59ee-5ddc-429e-9f19-312dc3eecb81/1*RFzbeFt7h-4DIUr89uv-hA@2x.png" alt="당근마켓 로고"></p>
<p>당근마켓 클론 구현에 필요한 요구사항들을 나열해본다.
동네 생활, 지역 광고등 여러 기능이 있지만 중고 거래 시스템만 구현하는 것을 목표로 한다.</p>
<h2 id="회원-가입">회원 가입</h2>
<ul>
<li>회원 가입 시 본인, 지역 인증이 필요하다.</li>
<li>회원 가입 시 현재 위도, 경도 기반의 기본 지역이 지정된다. </li>
<li>회원 가입 후 활동 지역 범위 설정을 수행해야 한다.</li>
</ul>
<h2 id="회원-탈퇴">회원 탈퇴</h2>
<ul>
<li>가입된 회원은 탈퇴할 수 있다.</li>
<li>회원 탈퇴 시 모든 중고 물품, 후기, 채팅 내역은 삭제된다.</li>
</ul>
<h2 id="프로필-설정">프로필 설정</h2>
<ul>
<li>회원은 자신의 프로필 명과 사진을 변경할 수 있다.</li>
</ul>
<h2 id="활동-범위-설정">활동 범위 설정</h2>
<ul>
<li>회원은 자신이 활동할 지역과 범위를 설정할 수 있다.</li>
</ul>
<h2 id="중고-물품-등록">중고 물품 등록</h2>
<ul>
<li>중고 거래를 위해 중고 물품에 대한 글을 작성한다.</li>
<li>제목을 작성해야 한다.</li>
<li>최소 1개, 최대 10개의 사진을 포함할 수 있다.</li>
<li>중고 물품이 속하는 카테고리를 선택해야 한다.</li>
<li>가격을 설정해야 하며 가격 협의 가능 여부를 지정할 수 있다.</li>
<li>물품에 대한 설명을 작성할 수 있다.</li>
<li>중고 물품이 노출될 지역 범위를 설정할 수 있다.</li>
</ul>
<h2 id="중고-거래-검색">중고 거래 검색</h2>
<ul>
<li>키워드 기반 중고 물품 검색을 제공한다.</li>
<li>카테고리 기반 중고 물품 검색을 제공한다.</li>
<li>인기 검색어 목록이 출력된다.</li>
<li>지역 내 인기 중고 물품 목록이 출력된다.</li>
<li>개인화 추천 중고 물품 물품이 출력된다.</li>
</ul>
<h2 id="가격-제안하기">가격 제안하기</h2>
<ul>
<li>가격 제안 가능 중고 물품인 경우 가격을 선 제안해야 한다. (구매자 별 최대 1회 가능)</li>
<li>판매자에게 가격 제안 알림이 발생되고 판매자가 수락하는 경우 거래를 위한 1:1 채팅이 생성된다.</li>
<li>판매자가 수락하는 경우 구매자의 구매 내역에 중고 물품이 [예약 중] 상태로 등록된다.</li>
<li>판매자가 거절하는 경우 구매자의 구매 내역의 중고 물품은 [가격 제안 거절] 상태로 변경된다.</li>
<li>판매자가 수락하는 경우 중고 물품의 정보의 상태에 [예약 중] 뱃지가 등록된다.</li>
</ul>
<h2 id="채팅으로-거래하기">채팅으로 거래하기</h2>
<ul>
<li>가격 제안이 불가능한 제품은 구매자가 1:1채팅으로 거래를 요청한다.</li>
<li>구매자의 가격 제안을 판매자가 수락한 경우 1:1 채팅으로 거래가 진행된다.</li>
</ul>
<h2 id="구매판매-후기">구매/판매 후기</h2>
<ul>
<li>중고 거래후 구매자와 판매자는 서로에게 후기를 남길 수 있다.</li>
<li>후기는 긍정 평가, 부정 평가가 있으며 평가는 회원의 매너 온도 점수에 반영된다.</li>
<li>판매 후기 작성 시 중고 물품의 상태는 거래 완료로 표기된다.</li>
</ul>
<h2 id="구매판매-내역-보기">구매/판매 내역 보기</h2>
<ul>
<li>자신이 구매/판매한 중고 물품들의 목록을 조회할 수 있다.</li>
<li>채팅을 했거나 가격 제안을 한 중고 물품은 구매 내역에 추가된다.</li>
<li>새로운 중고 물품을 등록한 경우 판매 내역에 추가된다.</li>
</ul>
<h2 id="관심-목록-보기">관심 목록 보기</h2>
<ul>
<li>중고 물품의 정보를 조회하여 관심 대상 설정을 하거나 해제할 수 있다.</li>
<li>관심 대상들은 관심 목록 보기에서 조회할 수 있다.</li>
</ul>
<h2 id="키워드-알림-등록">키워드 알림 등록</h2>
<ul>
<li>지정된 키워드를 포함하는 중고 물품이 등록된 경우 알림을 받기 위한 키워드들을 등록한다.</li>
<li>최대 30개 키워드 알림을 등록할 수 있다</li>
</ul>
<h2 id="채팅-목록-조회">채팅 목록 조회</h2>
<ul>
<li>거래 종료/진행 중인 중고 거래 채팅 목록을 조회할 수 있다.</li>
<li>채팅 목록을 클릭할 경우 해당 채팅 창으로 이동한다.</li>
</ul>
<h2 id="모아보기">모아보기</h2>
<ul>
<li>사용자는 다른 사용자를 팔로잉하거나 해제할 수 있다.</li>
<li>자신의 팔로잉 내역은 다른 사용자들은 알 수 없다.</li>
<li>팔로잉한 사용자들의 게시글 목록을 한곳에서 조회할 수 있다.</li>
</ul>
<h2 id="사용자-차단">사용자 차단</h2>
<ul>
<li>차단된 사용자의 모든 게시글과 답글, 채팅은 보이지 않는다.</li>
<li>차단된 사용자는 차단 사용자 보기에서 목록으로 조회할 수 있다.</li>
</ul>
<h1 id="기능-목록">기능 목록</h1>
<h4 id="👍-구현할-기능-우선순위-순">👍 구현할 기능 (우선순위 순)</h4>
<blockquote>
<ol>
<li>회원 가입, 정보 수정, 탈퇴</li>
<li>중고 거래 지역 등록, 수정</li>
<li>중고 물품 조회, 등록, 수정</li>
<li>이미지 업로딩</li>
<li>중고 거래용 채팅</li>
<li>구매/판매자 리뷰 및 중고 거래 점수 레이팅</li>
<li>차단 유저 등록, 삭제</li>
<li>관심 유저 등록, 삭제 및 알림</li>
<li>관심 상품 등록, 삭제 및 알림</li>
<li>가격 제시 (구매자 -&gt; 판매자) 및 알림 </li>
<li>키워드 등록, 삭제 및 알림</li>
</ol>
</blockquote>
<h4 id="👎-누락된-기능">👎 누락된 기능</h4>
<blockquote>
<ul>
<li>동네 생활 </li>
</ul>
</blockquote>
<ul>
<li>동네 광고 </li>
<li>중고 물품 신고</li>
<li>중고 물품 추천</li>
<li>검색 키워드 추천</li>
<li>이외에 딥러닝 관련된 기능들</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react-native-web 모노레포] - 웹 빌드 환경 구성]]></title>
            <link>https://velog.io/@octo-5/react-native-web-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EC%9B%B9-%EB%B9%8C%EB%93%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@octo-5/react-native-web-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EC%9B%B9-%EB%B9%8C%EB%93%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Sat, 05 Dec 2020 09:20:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/octo-5/post/23358673-7e82-4193-94bb-7c05cb4f3d3b/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202020-12-04%20%EC%98%A4%ED%9B%84%206.40.35.png" alt=""></p>
<p>웹과 모바일에서 동일한 사용자 경험을 제공하기 위해 단일 React-Native 앱을 작성하고 모바일과 웹 모두 빌드할 수 있는 모노 레포지토리를 구성해보자.</p>
<p><a href="https://github.com/octo-5/nextjs-rn-monorepo/tree/setup-react-native-web-monorepo">github 레포지토리</a></p>
<h4 id="주요-패키지-버전">주요 패키지 버전</h4>
<ul>
<li>react : 16.13.1</li>
<li>react-native : 0.63.3</li>
<li>react-native-web : 0.14.8</li>
<li>styled-components : 5.2.1</li>
<li>nextjs : 10.0.3</li>
<li>typescript : 4.1.2</li>
<li>@babel/core : 7.12.1</li>
<li>yarn : 1.22.10</li>
</ul>
<p>이전 포스트에서 공통 앱과 mobile 빌드 환경 구성하였다. 남은 web(nextjs) 빌드 환경을 구성해보자.</p>
<hr>
<h3 id="nextjs-rn-monrepoweb-패키지-생성">@nextjs-rn-monrepo/web 패키지 생성</h3>
<pre><code class="language-bash">cd ~/nextjs-rn-monorepo/packages
npx create-next-app web --example with-typescript

cd web
rm -rf components interfaces pages utils
mkdir pages</code></pre>
<h3 id="의존성-패키지-설치">의존성 패키지 설치</h3>
<pre><code class="language-bash">cd ~/nextjs-rn-monorepo/packages/web

yarn add
    react-dom \
    react-is \
    react-native \
    react-native-web \
    styled-components

yarn add --dev \
    @babel/core \
    @babel/plugin-transform-runtime \
    @babel/preset-env \
    @babel/runtime-corejs3 \
    babel-plugin-react-native-web \
    next-compose-plugins \
    next-transpile-modules

yarn workspace @nextjs-rn-monorepo/web add @nextjs-rn-monorepo/app@0.0.1</code></pre>
<h3 id="babel-설정">babel 설정</h3>
<pre><code class="language-javascript">// ~/nextjs-rn-monorepo/packages/web/babel.config.js

module.exports = api =&gt; {
  api.cache(true);

  return {
    presets: [
      &quot;next/babel&quot;,
      [
        &quot;@babel/preset-env&quot;,
        {
          targets: {
            browsers: [&quot;&gt; 1%&quot;, &quot;last 2 versions&quot;, &quot;not ie &lt;= 10&quot;]
          },
        }
      ]
    ],
    plugins: [
      [
        &quot;@babel/plugin-transform-runtime&quot;, 
        { corejs: 3 } 
      ],
      [
        &quot;styled-components&quot;,
        {
          ssr: true,
          displayName: true,
          preprocess: false
        }
      ],
    ],
  }
};</code></pre>
<h3 id="nextjs-설정">Next.js 설정</h3>
<pre><code class="language-javascript">// ~/nextjs-rn-monorepo/packages/web/next.config.js

const withPlugins = require(&#39;next-compose-plugins&#39;);
const withTM = require(&#39;next-transpile-modules&#39;)(
  [
    // 참조할 다른 로컬 패키지와 해당 패키지가 사용하는 의존성 패키지들을 명시
    &#39;@nextjs-rn-monorepo/app&#39;,
    &#39;react-native&#39;,
    &#39;styled-components&#39;,
  ],
);

module.exports = withPlugins([withTM], {
  webpack(config) {
    config.resolve.alias = {
      ...(config.resolve.alias || {}),
      &#39;react-native$&#39;: &#39;react-native-web&#39;, 
    }
    config.resolve.extensions = [
      &#39;.web.ts&#39;,
      &#39;.web.tsx&#39;,
      &#39;.ts&#39;,
      &#39;.tsx&#39;,
      &#39;.web.js&#39;,
      &#39;.web.jsx&#39;,
      &#39;.js&#39;,
      &#39;.jsx&#39;,
      ...config.resolve.extensions,
    ]
    return config;
  }
});</code></pre>
<h3 id="인덱스-웹-페이지-작성">인덱스 웹 페이지 작성</h3>
<blockquote>
<p>react-native 앱을 서버 사이드 렌더링 할 수 있게 커스텀 도큐먼트 파일을 작성한다.</p>
</blockquote>
<pre><code class="language-tsx">// ~/nextjs-rn-monorepo/packages/web/pages/_document.tsx

import React, { Children } from &#39;react&#39;
import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from &#39;next/document&#39;
import { AppRegistry } from &#39;react-native&#39;

import { name as appName } from &#39;../package.json&#39;

// Force Next-generated DOM elements to fill their parent&#39;s height
const normalizeNextElements = `
  #__next {
    display: flex;
    flex-direction: column;
    height: 100%;
  }

  html {
    height: 100%;
  }

  body {
    height: 100%;
    overflow: hidden;
  }
`

export default class MyDocument extends Document {
  static async getInitialProps({ renderPage }: DocumentContext) {
    AppRegistry.registerComponent(appName, () =&gt; Main)
    const { getStyleElement } = AppRegistry.getApplication(appName)
    const page = await renderPage()
    const styles = [
      // eslint-disable-next-line react/no-danger
      &lt;style dangerouslySetInnerHTML={{ __html: normalizeNextElements }} /&gt;,
      getStyleElement(),
    ]
    return { ...page, styles: Children.toArray(styles) }
  }

  render() {
    return (
      &lt;Html&gt;
        &lt;Head /&gt;
        &lt;body&gt;
          &lt;Main /&gt;
          &lt;NextScript /&gt;
        &lt;/body&gt;
      &lt;/Html&gt;
    )
  }
}
</code></pre>
<blockquote>
<p>getApplication 함수를 찾지 못해서 에러가 발생하는 데 이것을 우회하기 위해 react-native의 AppRegistry를 재정의한다. (서버 사이드 렌더링을 위한 웹 전용 함수로 등록된 앱의 element를 반환하는 함수)</p>
</blockquote>
<pre><code class="language-typescript">// ~/nextjs-rn-monorepo/packages/web/pages/@types/react-native.d.ts

// 아래 import 구문이 없는 경우 react-native의 다른 함수나 타입들을 사용할 수가 없어서 임의로 추가
import * as RN from &#39;react-native&#39;; 

declare module &#39;react-native&#39; {
  namespace AppRegistry {
    function registerComponent(
      appKey: string,
      componentProvider: () =&gt; React.ComponentType,
    ): void

    function getApplication(appKey: string): { getStyleElement: () =&gt; string }
  }
}</code></pre>
<pre><code class="language-tsx">// ~/nextjs-rn-monorepo/packages/web/pages/index.tsx

import { App } from &#39;@nextjs-rn-monorepo/app&#39;;

export default App;</code></pre>
<h3 id="yarn-build--yarn-start">yarn build &amp;&amp; yarn start</h3>
<p><img src="https://images.velog.io/images/octo-5/post/4bd45c6e-167a-472f-9228-20c4188124d1/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202020-12-05%20%EC%98%A4%ED%9B%84%206.17.00.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react-native-web 모노레포] - 공통 앱 작성, 모바일 빌드 환경 구성]]></title>
            <link>https://velog.io/@octo-5/react-native-web-monorepo</link>
            <guid>https://velog.io/@octo-5/react-native-web-monorepo</guid>
            <pubDate>Fri, 04 Dec 2020 09:59:52 GMT</pubDate>
            <description><![CDATA[<p>웹과 모바일에서 동일한 사용자 경험을 제공하기 위해 단일 React-Native 앱을 작성하고 모바일과 웹 모두 빌드할 수 있는 모노 레포지토리를 구성해보자.</p>
<p><a href="%5Bgithub%5D(https://github.com/octo-5/nextjs-rn-monorepo/tree/setup-react-native-web-monorepo)">github 레포지토리</a></p>
<h4 id="주요-패키지-버전">주요 패키지 버전</h4>
<ul>
<li>react : 16.13.1</li>
<li>react-native : 0.63.3</li>
<li>react-native-web : 0.14.8</li>
<li>styled-components : 5.2.1</li>
<li>nextjs : 10.0.3</li>
<li>typescript : 4.1.2</li>
<li>@babel/core : 7.12.1</li>
<li>yarn : 1.22.10</li>
</ul>
<hr>
<h2 id="1-yarn-workspace-설정">1. yarn workspace 설정</h2>
<h3 id="워크-스페이스-생성">워크 스페이스 생성</h3>
<pre><code class="language-bash">mkdir -p ~/nextjs-rn-monorepo/packages
cd ~/nextjs-rn-monorepo
yarn init</code></pre>
<h3 id="packagejson에-워크-스페이스-설정-추가">package.json에 워크 스페이스 설정 추가</h3>
<p>워크 스페이스의 로컬 패키지 경로를 package.json에 추가한다.</p>
<pre><code class="language-json">// ~/nextjs-rn-monorepo/package.json 

{
  ...
  &quot;private&quot;: true,
  &quot;workspaces&quot;: {
    &quot;packages&quot;: [
      &quot;packages/*&quot;
    ]
  },
  ...
}</code></pre>
<hr>
<h2 id="2-nextjs-rn-monorepoapp-패키지-설정">2. @nextjs-rn-monorepo/app 패키지 설정</h2>
<pre><code class="language-bash">mkdir -p ~/nextjs-rn-monorepo/packages/app
cd ~/nextjs-rn-monorepo/packages/app
yarn init -y</code></pre>
<h3 id="apppackagejson-수정">app/package.json 수정</h3>
<pre><code class="language-json">// ~/nextjs-rn-monorepo/packages/app/package.json

{
  &quot;name&quot;: &quot;@nextjs-rn-monorepo/app&quot;,
  &quot;version&quot;: &quot;0.0.1&quot;,
  &quot;private&quot;: true,
  &quot;main&quot;: &quot;./src/index.ts&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;devDependencies&quot;: {
    &quot;@types/react&quot;: &quot;16.14.0&quot;,
    &quot;@types/react-native&quot;: &quot;0.63.3&quot;,
    &quot;@types/styled-components&quot;: &quot;5.1.4&quot;
  },
  &quot;peerDependencies&quot;: {
    &quot;react&quot;: &quot;^16.13.1&quot;,
    &quot;react-native&quot;: &quot;^0.63.3&quot;,
    &quot;styled-components&quot; : &quot;^5.2.1&quot;
  }
}</code></pre>
<h3 id="react-native-앱-작성">React-Native 앱 작성</h3>
<p>화면에 REACT-NATIVE-WEB과 앱이 동작되는 Platform(ios, android, web)을 출력하는 간단한 앱을 작성한다.</p>
<blockquote>
<p>app/src 파일</p>
</blockquote>
<pre><code class="language-bash">src ➜ tree
.
├── App.tsx
├── SubTitle
│   ├── SubTitle.native.tsx
│   ├── SubTitle.web.tsx
│   └── index.ts
└── index.ts</code></pre>
<pre><code class="language-tsx">// ~/nextjs-rn-monorepo/packages/app/src/App.tsx

import React from &#39;react&#39;;
import { Platform  } from &#39;react-native&#39;;
import Styled from &#39;styled-components/native&#39;;

import SubTitle from &#39;./SubTitle&#39;;

const Container = Styled.View`
  display: flex;
  justify-content: center;
  align-items: center;
  background: #4B7EFF;
  height: 100%;
  width: 100%;
`;

const Title = Styled.Text`
  color: white;
  font-size: 32px;
  font-weight: 900;
`;


export const App = () =&gt; {
  console.log(Platform.OS);
  return (
    &lt;Container&gt;
      &lt;Title&gt;REACT NATIVE WEB&lt;/Title&gt;
      &lt;SubTitle /&gt;
    &lt;/Container&gt;
  )
}</code></pre>
<pre><code class="language-tsx">// ~/nextjs-rn-monorepo/packages/app/src/index.ts

export { App } from &#39;./src/App&#39;;</code></pre>
<pre><code class="language-tsx">// ~/nextjs-rn-monorepo/packages/app/src/Subtitle.native.tsx

import React from &#39;react&#39;;
import { Platform } from &#39;react-native&#39;;
import Styled from &#39;styled-components/native&#39;;

const Text = Styled.Text`
  color: white;
  font-size: 24px;
  font-weight: bold;
`;

export default function SubTitle() {
  return &lt;Text&gt;{`On ${Platform.OS.toUpperCase()}`}&lt;/Text&gt;
}</code></pre>
<pre><code class="language-tsx">// ~/nextjs-rn-monorepo/packages/app/src/Subtitle.web.tsx

import React from &#39;react&#39;;
import Styled from &#39;styled-components/native&#39;;

const Text = Styled.Text`
  color: white;
  font-size: 24px;
  font-weight: bold;
`;

export default function SubTitle() {
  return &lt;Text&gt;On WEB&lt;/Text&gt;
}</code></pre>
<pre><code class="language-tsx">// ~/nextjs-rn-monorepo/packages/app/src/SubTitle/index.ts

import SubTitle from &#39;./SubTitle.native&#39;;

export default SubTitle;</code></pre>
<hr>
<h2 id="3-nextjs-rn-monorepomobile-로컬-패키지-설정-react-native">3. @nextjs-rn-monorepo/mobile 로컬 패키지 설정 (react-native)</h2>
<p>react-native 앱을 빌드하기 위한 로컬 패키지를 설정한다. </p>
<h3 id="nextjs-rn-monorepomobile-패키지-생성">@nextjs-rn-monorepo/mobile 패키지 생성</h3>
<pre><code class="language-bash">cd ~/nextjs-rn-monorepo/packages
npx react-native init mobile --version 0.63.3 --template react-native-template-typescript</code></pre>
<blockquote>
<p>의존성 패키지 설치 에러가 발생하지만 무시 <img src="https://images.velog.io/images/octo-5/post/cdfde918-563f-4450-b23a-ee12cdc8910d/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202020-12-01%20%EC%98%A4%ED%9B%84%208.27.41.png" alt=""></p>
</blockquote>
<p>패키지가 생성되었다면 패키지명을 수정한다.</p>
<pre><code class="language-json">// ~/nextjs-rn-monorepo/packages/mobile/package.json

{
  &quot;name&quot;: &quot;@nextjs-rn-monorepo/mobile&quot;, 
  ...
}</code></pre>
<h3 id="의존성-패키지를-루트-node_modules로-이동">의존성 패키지를 루트 node_modules로 이동</h3>
<p>모든 로컬 패키지의 의존성 패키지들을 제거하고 루트 경로에 재설치한다. ios/andriod 원활할 빌드를 위해 로컬 경로가 아닌 루트 경로의 node_modules로 모든 의존성 패키지를 이동한다.</p>
<ul>
<li>주의 - 로컬 패키지 간에 의존성 버전 충돌이 있는 경우 루트 node_modules로 이동되지 않음</li>
</ul>
<pre><code class="language-bash">cd ~/nextjs-rn-monorepo
rm -rf node_modules packages/*/node_modules
yarn install --check-file</code></pre>
<h3 id="metro-번들러-설정-변경">metro 번들러 설정 변경</h3>
<p>metro 번들러가 모바일 빌드시 다른 로컬 패키지나 루트 node_modules의 의존성 패키지를 참조할 수 있도록 워크 스페이스의 루트 경로를 추가한다.</p>
<pre><code class="language-javascript">// ~/nextjs-rn-monorepo/packages/mobile/metro.config.js

const path = require(&#39;path&#39;)

module.exports = {
  watchFolders: [path.resolve(__dirname, &#39;../..&#39;)],
  ...
}</code></pre>
<h3 id="react-nativeconfigjs-생성">react-native.config.js 생성</h3>
<p>ios의 pod 패키지 설치 시 참조하기 위한 react-native 의존성 패키지 경로를 설정한다.</p>
<pre><code class="language-javascript">// ~/nextjs-rn-monorepo/packages/mobile/react-native.config.js 

module.exports = {
  reactNativePath: &#39;../../node_modules/react-native&#39;,
}</code></pre>
<h3 id="ios-빌드-설정-변경">ios 빌드 설정 변경</h3>
<p>mobile의 모든 의존성 패키지들이 루트 경로로 이동했기 때문에 ios 빌드 관련 파일들 내 node_modules의 경로도 루트 node_modules로 수정해야 한다.</p>
<h4 id="대상-파일">대상 파일</h4>
<ul>
<li>mobile/ios/Podfile</li>
<li>mobile/ios/mobile.xcodeproj/project.pbxproj</li>
</ul>
<blockquote>
<p><a href="https://github.com/octo-5/nextjs-rn-monorepo/commit/cef594aa1cb75a397cf9708c62788f445d504d3c#diff-ffa90fc6a14b25d15b401bf5339ce08da8ba164a4be374613762624b723ffeed">node_modules 수정 내역</a></p>
</blockquote>
<p>경로를 모두 수정하였다면 pod 패키지를 모두 설치하고 ios 빌드 설정을 마무리한다.</p>
<pre><code class="language-bash">cd ~/nextjs-rn-monorepo/packages/mobile/ios
pod install</code></pre>
<h3 id="android-빌드-설정-변경">android 빌드 설정 변경</h3>
<p>ios 빌드 설정처럼 android 빌드 관련 파일 내 node_moduels의 경로도 루트 node_modules로 수정해야 한다.</p>
<h4 id="대상-파일-1">대상 파일</h4>
<ul>
<li>mobile/android/build.gradle</li>
<li>mobile/android/settings.gradle</li>
<li>mobile/android/app/build.gradle</li>
</ul>
<blockquote>
<p><a href="https://github.com/octo-5/nextjs-rn-monorepo/commit/cef594aa1cb75a397cf9708c62788f445d504d3c#diff-ffa90fc6a14b25d15b401bf5339ce08da8ba164a4be374613762624b723ffeed">node_modules 수정 내역</a></p>
</blockquote>
<p>경로를 모두 수정하였다면 android 빌드에 필요한 패키지들이 자동 설치될 수 있게 mobile/android 프로젝트를 안드로이드 스튜디오에서 열어 주자.</p>
<h3 id="mobile에-app-추가">mobile에 App 추가</h3>
<p>기존 App.tsx를 삭제하고 @nextjs-rn-monorepo/app에서 작성한 앱을 index.js에 추가한다.</p>
<pre><code class="language-bash">cd ~/nextjs-rn-monorepo/packages/mobile
rm -f App.tsx
yarn add styled-components@5.2.1
yarn workspace @nextjs-rn-monorepo/mobile add @next-rn-monorepo/app@0.0.1</code></pre>
<pre><code class="language-javascript">// ~/nextjs-rn-monorepo/packages/mobile/index.js

import { AppRegistry } from &#39;react-native&#39;;
import { App } from &#39;@nextjs-rn-monorepo/app&#39;;
import {name as appName} from &#39;./app.json&#39;;

AppRegistry.registerComponent(appName, () =&gt; App);</code></pre>
<h3 id="iosandroid-npm-스크립트-수정">ios/android npm 스크립트 수정</h3>
<p>기본 npm 빌드 스크립트로 ios나 anroid를 실행하는 경우 빌드 실패가 발생하는데 이 문제를 우회하기 위해 package.json을 수정한다.</p>
<pre><code class="language-bash">yarn add --dev concurrently</code></pre>
<pre><code class="language-json">// ~/nextjs-rn-monorepo/packages/mobile/package.json


  &quot;scripts&quot;: {
    &quot;android&quot;: &quot;concurrently \&quot;yarn start\&quot; \&quot;react-native run-android --no-packager\&quot;&quot;,
    &quot;ios&quot;: &quot;concurrently \&quot;yarn start\&quot; \&quot;react-native run-ios --no-packager\&quot;&quot;,  
    ...</code></pre>
<h3 id="ios--android-빌드-확인">ios / android 빌드 확인</h3>
<h4 id="yarn-run-ios">yarn run ios</h4>
<p><img src="https://images.velog.io/images/octo-5/post/90dad9f2-ca94-4bac-a6e2-3519801586b1/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202020-12-04%20%EC%98%A4%ED%9B%84%206.55.33.png" alt=""></p>
<h4 id="yarn-run-android">yarn run android</h4>
<p><img src="https://images.velog.io/images/octo-5/post/0c2613b4-9489-4c45-9982-7c7f914c9cad/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202020-12-04%20%EC%98%A4%ED%9B%84%206.56.44.png" alt=""></p>
<hr>
]]></description>
        </item>
    </channel>
</rss>