시작하기에 앞서

동기부여를 하고 왔습니다

미니 리액트 구현을 떠났다가 오랜만에 다시 돌아오니 이전에 정리했던 내용이 흐릿해졌다. 핑계라면 핑계겠지만 회사 일도 있었고, 회사 다니다보니 저녁에는 쉬고 싶기도 했고, 무엇보다 공부가 그렇게 재밌지 않았다.

왜 재미 없을까? 했을 때, 내가 잘 몰라서 라는 결론을 내렸다. 내가 잘 알고, 내가 잘하면 재밌을텐데, 그 정도 수준이 아니기 때문에 공부하고 또 공부한 내용을 블로그에 정리하려면 꽤 많은 시간이 소요된다. 그래서 계속 미루고 싶었다.

최근 방향성을 고민하게 되어 만다라트를 그려봤다. 직업, 가정, 취미, 멘탈, 바라는 미래 모습 등등 여러 칸들 중 제일 먼저 채운 빈 칸은 직업 칸에 '일이 재밌을때까지 잘해보기'였다. 그러려면 공부해야지! 이번에는 끝가지 가보자!

이전에 공부한 내용 복습하기

이전에 적었던 블로그들을 보며 다시 기억을 떠올렸다. 다음 단계를 진행하면서 이전에 남겨놨던 기록이 많이 도움이 되었다.

  • jsx = JavaScript + HTML,
  • Babel : 브라우저가 이해하지 못하는 jsx를 자바스크립트로 트랜스파일링해주는 도구
  • react 17부터는 javascript 객체를 리턴하는 함수가 React.createElement -> jsx 로 바뀜

이전에 정리한 JSX에 대해 자세히 알고 싶다면? 👉 미니 React 개발 Day2. JSX

프로젝트 전체 코드는 포스팅 아래에 있습니다!
아래쪽의 전체 코드 mini-react 레포를 참고해주세요!


개발환경 설정

리액트 프로젝트를 셋팅할 때, 필요한 단계들이 있는 것처럼 mini-react가 실행될 개발 환경을 먼저 구축해보자
vite + javascript로 구현하기로 선택했다. 여러 빌드 툴이 있지만, 회사에서 vite를 쓰고 있어서 이 기회에 vite에 대해 더 공부해보고 싶어서 선택했고 javascript로 처음에 구현 후 다시 타입스크립트로 변환하는 과정을 거치면서 타입스크립트에 대해서도 더 알아보려고 한다.

vite로 프로젝트 설정하기

vite 문서에서 소개하는 방법처럼 바로 원하는 템플릿으로 설치할 수 있다.

# npm v7 이상에서는 `--` 를 반드시 붙여주세요:
npm create vite@latest mini-react -- --template vanilla

jsx를 다룰거기 때문에 main.jsmain.jsx로 바꿔주고 console에 jsx를 입력해보자
지금 당장은 에러가 발생하지만 지금부터 설정을 통해 jsx를 자바스크립트 객체로 표현되도록 할 것이다

//AS-IS
const MyName = () => {
  return <span>I'm sunny</span>;
};

console.log(
  <div>
    1<h1>hello</h1>
    <p>
      paragrahp<span>span tag</span>
      <MyName />
    </p>
  </div>
);
// TO-BE
{
  "type": "div",
  "key": null,
  "ref": null,
  "props": {
    "children": [
      "1",
      {
        "type": "h1",
        "key": null,
        "ref": null,
        "props": { "children": "hello" }
      },
      {
        "type": "p",
        "key": null,
        "ref": null,
        "props": {
          "children": [
            "paragrahp",
            {
              "type": "span",
              "key": null,
              "ref": null,
              "props": { "children": "span tag" }
            },
            {
              "type": "span",
              "key": null,
              "ref": null,
              "props": { "children": "I'm sunny" }
            }
          ]
        }
      }
    ]
  }
}

미니 리액트에서 커스텀 자바스크립트 객체 변환 함수를 정의할 것이기 때문에 이를 허용해주도록 vite 설정을 수정해야한다 vite는 esBuild를 활용해 ts, tsx, jsx 파일들을 변환시킨다.

react 17을 기준으로 jsx -> 자바스크립트 객체 변환에 사용되는 함수가 달라진다.
회사에서 18을 사용하고 있어서 미니 리액트도 18버전에서 사용되는 트랜스파일링으로 설정해보도록 하겠다.

1️⃣ esbuild 옵션 설정

// vite.config.js
import { defineConfig } from "vite";

export default defineConfig({
  esbuild: {
    jsx: "automatic", // 리액트 17부터 jsx 작업시 esbuild가 automatic으로 설정됨
    jsxImportSource: "react", // jsx  패키지 import 문이 자동으로 추가됨
  },
});

이렇게 설정을 마치고 다시 코드를 실행해보자 아래와 같은 에러가 발생했다면 automatic으로 설정이 바뀐 것이다!!

Failed to resolve import "react/jsx-dev-runtime" from "src/main.jsx".
Does the file exist?

/Users/fingerverse/Desktop/projects/mini-react/src/main.jsx:6:4
1  |  import { jsxDEV } from "react/jsx-dev-runtime";
   |                          ^

이 에러는 jsxDEV 함수가 있는 "react/jsx-dev-runtime"를 찾지 못해서 발생한다. 에러를 해결하는 방법에 대해서는 조금 있다 살펴보고, 먼저 esbuild에 설정한 옵션들에 대해 알아보자

  • jsx : automatic

    • 리액트 17부터 적용되는 값으로, esbuild에게 jsx를 automatic 방법으로 작업하라고 알려주는 역할
    • jsxImportSource에 명시한 패키지를 기준으로 자동으로 아래 import문 생성
    • import { jsxDEV } from "react/jsx-dev-runtime";
    • esbuild > jsx

  • jsxImportSource: "react"

    • jsx 옵션이 automatic으로 설정되어 있을 때 적용
    • esbuild가 jsx 헬퍼 함수들을 가져올 라이브러리를 변경할 수 있음
    • import { jsxDEV } from "${jsxImportsource 설정 값}/jsx-dev-runtime";
    • esbuild >jsx import source

다시 잠시 넘겨놨던 에러로 돌아가보자.
우리 프로젝트에는 react가 없으니까 어쩌면 에러가 발생하는 건 너무 당연하다. 하지만 리액트를 설치하지 않고,
직접 jsxDEV 함수를 구현할 거기 때문에 함수가 정의되어 있는 패키지 위치를 직접 설정하는 방식으로 해결할 것이다

import { defineConfig } from "vite";
import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      react: path.resolve(__dirname, "src/lib/react"), // 절대 경로 사용
    },
  },
  esbuild: {
    jsx: "automatic",
    jsxImportSource: "react",
  },
});

resolve.alias 옵션을 사용해 가져올 패키지의 위치를 로컬로 설정해줬다.



2️⃣ jsx -> 자바스크립트 객체 변환 함수 구현

esbuild 문서에 보면 jsxImportSource 옵션을 설정하면 아래 패키지들을 export 해야한다고 되어있다

import { createElement } from "your-pkg"
import { Fragment, jsx, jsxs } from "your-pkg/jsx-runtime"
import { Fragment, jsxDEV } from "your-pkg/jsx-dev-runtime"
  • jsxDEV는 jsx DEV 모드일 때 사용되고 그 외 환경에서는 jsxjsxs가 트랜스파일링에 사용된다
  • createElement는 jsx에 전개 연산자로 사용하는 props와 key 가 함께 있는 경우에 개발 모드와 관계없이 사용됨
    return <div {...props} key={key} />

위에 명시된 패키지 중에 자바스크립트 객체 변환에 사용되는 createElement, jsx , jsxs만 다루고자 한다.
이 함수들은 기본적으로 로직이 비슷하기 때문에 동일한 구현 로직을 바탕으로 작성했다

export const jsxDEV = (type, props, key) => {
  return {
    type,
    key: key ?? props?.key ?? null,
    ref: props?.ref ?? null,
    props: {
      ...props,
      children: props.children,
    },
  };
};

이전 포스팅에서 작성한대로 jsx를 트랜스파일링 했을 때 만들어지는 것은 자바스크립트 객체이기 때문에,
자바스크립트 객체를 리턴하도록 하는 함수를 작성했다.

const MyName = () => {
  return <span>I'm sunny</span>;
};

console.log(<MyName />);

평소에 리액트를 사용할 때 이렇게 함수 컴포넌트를 만들어서 작성하는 경우도 많다.
현재 jsxDEV는 이 케이스는 처리하지 못하기 때문에 함수 컴포넌트일 경우 처리 로직을 추가한다

export const jsxDEV = (type, props, key) => {
  if (typeof type === "function") {
    return type(props);
  }

  return {
    type,
    key: key ?? props?.key ?? null,
    ref: props?.ref ?? null,
    props: {
      ...props,
      children: props.children,
    },
  };
};

createElement도 동일한 로직으로 함수 시그니처에 맞게 jsxDEV를 수정해 작성했다.


결과 확인

제일 처음 jsx를 실행했던 예제로 돌아가보자.

const MyName = () => {
  return <span>I'm sunny</span>;
};

console.log(
  <div>
    1<h1>hello</h1>
    <p>
      paragrahp<span>span tag</span>
      <MyName />
    </p>
  </div>
);

두둥! 이제 코드를 실행해보면 자바스크립트 객체를 얻을 수 있다!

전체 코드는 여기에!
👉 mini-react


🍕 회고

다시 돌아와 공부를 하고 글을 정리하는 과정에서 꾸준함에 대해서 생각을 해보게 되었다. 포스팅의 시작 부분 '동기부여하고 왔습니다'에서 나눴던 것처럼 공부와 포스팅을 어렵게 했던 나의 핑계들은 여전히 그대로다. 나의 상황은 변하지 않았다. 하지만, 막상 다시 시작해보니 내가 바라는 속도와 기준만큼 공부하고 포스팅을 하지는 못했지만, 꾸준히 3-4일 하니 하나의 포스팅을 완성할 수 있었다.

Image

이 이미지처럼 내가 생각했던 꾸준함은 물잔에 가득(내가 생각한 기준에 도달), 매일 채우는 게 꾸준함이라고 생각했다. 그러다보니, 할 수 있는 날 < 할 수 없는 날이 되면서 점점 도달하지 못할 곳을 보지 않게 되었던 것 같다. 때로는 조금 적게 물을 따르더라도 매일 따를 수 있는 앞으로가 되었으면 좋겠다!

이번 챕터를 학습하면서 빌드도구에 대해 관심을 가지게 되었다. 사실, vite를 사용하면서도 vite가 어떻게 동작하는지에 대해서는 깊게 고민하거나 생각해보지 않았다. 이번 학습을 통해서 시야가 확장 된 것 같아서 좋았고, 아직은 어렵고 생소한 것이 더 많지만 하나씩 더 빌드도구에 대해 더 알아가보고 싶다는 생각이 든다!


참고