Jaeilit

UI 라이브러리 초기세팅 본문

TIL

UI 라이브러리 초기세팅

Jaeilit 2024. 6. 27. 23:17
728x90

UI 라이브러리 초기 세팅 (모노레포 -v)

 

1. 미리보는 폴더 구조

폴더구조

 

 

모노레포 구조로 세팅하기

- 실제 ui가 담길 packages/**

- react + vite 가 담긴 app/docs 구조

 

1. 프로젝트 생성

패키지 매니저는 pnpm 을 사용했다.

패키지 매니저 init 명령어를 입력하고 pacakge.json 이 생성 된 것을 확인 후에 packages 폴더와 app/docs 디렉토리를 만들어준다.

 

package.json 생성 이후 필요없는 부분은 지워주고 name 에 패키지의 이름을 지어준다.

패키지를 npm에 배포할 목적이라면 npm 에 가입하고 Organization 을 생성해주고 사용하는게 좋은데 배포 목적이 아니라면 딱히 이름을 어떻게 짓던 상관없을 것 같다.

pnpm init


// pagckage.json

{
    "name": "jaeil-ui",
    "version": "0.0.0",
    "license": "MIT",
}

 

2. 워크플레이스 생성

 

생성한 디렉토리를 워크플레이스 포함/제거하기 위해 루트에 워크플레이스를 생성해준다.

디렉토리 이름이 링크와 다르지 않기 때문에 링크에 내용을 그대로 복붙해도 좋다. 다르다면 조금 응용이 필요하다.

// pnpm-workspace.yaml

packages:
    - "packages"
    - "apps"

 

3. react- vite 띄우기

 

apps/docs 폴더로 이동하여 vite react template 을 설치해준다.

cd apps/docs && pnpm create vite .

 

react-vite 를 띄우려면 cd apps/docs 로 이동하여 pnpm dev 로 run 시킬수도 있지만 매번 그런식으로 디렉토리를 이동하여 run 하는 것 보다 루트에서 할 수 있는게 더 효과적이기 때문에 루트에서 하는 방법을 알아본다.

 

vite 생성 후 package.json 에 초기 상태는 아래와 같다.

여기서 name 부분만 수정해주면 되는데 이 부분이 우리가 루트에서 실행 시킬 때 필요한 식별자다.

일단 docs 라고 수정할려고 한다.

// apps/docs/package.json

{
  "name": "docs", // 수정
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@typescript-eslint/eslint-plugin": "^7.13.1",
    "@typescript-eslint/parser": "^7.13.1",
    "@vitejs/plugin-react": "^4.3.1",
    "eslint": "^8.57.0",
    "eslint-plugin-react-hooks": "^4.6.2",
    "eslint-plugin-react-refresh": "^0.4.7",
    "typescript": "^5.2.2",
    "vite": "^5.3.1"
  }
}

 

루트에서 실행 시킬 때 아까 지어준 이름(식별자)로 filter 명령어로 실행 시키는데 함축하여 -F 로 가능하다.

// root/package.json

{
    "name": "jaeil-ui",
    "version": "0.0.0",
    "license": "MIT",
    "scripts": {
        "dev": "pnpm run -F docs dev"
    },
    ....
  }

 

pnpm run -F docs dev 에서 run을 생략하고 pnpm -F docs dev 로 해도 상관은 없다. pnpm --filter docs dev 또한 상관없다.

 

오류발생 

명령어를 입력했더니 오류가 발생했다. 읽어보면 무엇을 말하려는지 살짝 느낌이 오는데 경로에 일치하는 프로젝트가 없다라는 것 같다.

No projects matched the filters in ~~path

 

워크플레이스 수정

packages:
    - "packages"
    - "apps/**" // ** 을 입력하여 수정

 

전에는 apps 만 입력을 해뒀는데 이건 바로 하위 디렉토리까지만 포함이 되는 것 이다. apps/docs까지 존재하고 그 안에서 vite 프로젝트가 있기 때문에 apps/** 와일드 카드를 사용해서 하위 디렉토리까지 포함되도록 명시해주도록 한다.

 

이제 오류가 없이 vite 가 웹에 띄워지는것을 확인했다면 UI 라이브러리를 한번 만들어보도록 한다.

 

 

 

UI 라이브버리(ui-kit) 초기 세팅

 

1. 필요한 패키지 설치

root 에서 prettier + eslint + typescript + tsup 설치

> pnpm install -D eslint prettier typescript tsup

 

prettier 와 eslint typescript 는 익히 잘 알고 있지만 tsup 은 처음 볼 수도 있다.

간단한 설명을 하자면 tsup 은 번들러이다. 나중에 tsup 을 활용하여 build 하게되면 각 설정해놓은 format 버전별로 자동 빌드를 해준다.

 

예시) button build

// button/package.json

scripts:{
	"build": "tsup src/index.tsx --format esm,cjs,iife --dts",
}

 

빌드 명령어 실행시 --format 옵션으로 esm,cjs,iife 을 사용하면 3버전으로 빌드가 된다. 

타입스크립트 또 한 iife 버전을 제외한 esm의 .mts cjs의 .(c)ts 로 2버전으로 빌드가 된 것을 확인 할 수 있다.

esm,cjs,iife

 

마찬가지로 prettier 와 eslint 도 각자 사용에 맞게 root 에서 설정해주도록 한다.

 

tsconfig.json

루트에서 설정한 perttier 나 eslint 설정들도 하위 프로젝트에서도 똑같이 적용이 된다.

tsconfig.json 도 마찬가지지만 잘못된 설정으로 프로젝트가 이상해질수도 있으니 신경 써주는 것이 좋다.

npx tsc --init
// root/tsconfig.json
{
    "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "jsx": "react-jsx",
        "lib": ["dom", "esnext"],
        "declaration": true,
        "sourceMap": true,
        "moduleResolution": "node",
        "skipLibCheck": true,
        "strict": true,
        "isolatedModules": true,
        "noFallthroughCasesInSwitch": true,
        "resolveJsonModule": true,
        "allowSyntheticDefaultImports": true,
        "downlevelIteration": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true
    },

    "include": ["packages"],
    "exclude": ["**/node_modules", "**/dist"]
}

 

pacagkes/components/button 디렉토리에도 tsconfig.json 을 생성해서 방금 root 에서 만든 tsconfig 를 extends 해서 사용하는 것을 추천한다.

// packages/components/button

{
    "compilerOptions": {},
    "extends": ["../../../tsconfig.json"],
    "include": ["src", "index.tsx", "stories"]
}

 

 

2. Button 만들기

 

packages

  - components

        -  button

              - src

                  - index.tsx

 

components/button 디렉토리를 생성 후 pnpm init 으로 package.json 을 생성하고 tsconfig.json 이 없다면 tsconfig.json 을 생성해서 위에 설정들을 해준다. package.json 에는 간단히 name 과 description 등 수정할 부분만 수정해두고 scripts 명령어에 아까 설치한 tsup 명령어를 입력해준다.

{
    "name": "@jaeil-ui/button",
    "version": "0.0.0",
    "description": "button",
    "main": "src/index.tsx",
    "license": "MIT",
     "scripts": {
        "build": "tsup src/index.tsx --format esm,cjs --dts"
    }
}
import {ComponentPropsWithRef} from "react";

export interface ButtonProps extends ComponentPropsWithRef<"button"> {
    label: string;
}
export const Button = ({label, ...props}: ButtonProps) => {
    return <button {...props}>{label}</button>;
};

 

간단하게 버튼 컴포넌트를 작성해주고 build 명령어로 명시한 버전별로 빌드가 됬는지 확인해준다면 준비는 끝났다.

 

Storybook 설치

방금 만든 버튼 컴포넌트를 시각적으로 확인하기 위해 스토리북을 설치해준다.

 

설치를 하면 템플릿이 없다고하면서 어떤 템플릿을 설치할지 물어본다면 vite 를 선택해주고 install 후에 필요없는 파일을 전부 지워준다. package.json 파일 안에서도 나도 모르게 설치 된 패키지가 있다면 스토리북과 vite를 제외하고 지워주면 된다.

// main.ts

import type {StorybookConfig} from "@storybook/react-vite";

const config: StorybookConfig = {
	// 이 부분
	stories: [
        "../../components/**/*.mdx",
        "../../components/**/*.stories.@(js|jsx|mjs|ts|tsx)",
    ],
    addons: [
        "@storybook/addon-onboarding",
        "@storybook/addon-links",
        "@storybook/addon-essentials",
        "@chromatic-com/storybook",
        "@storybook/addon-interactions",
    ],
    framework: {
        name: "@storybook/react-vite",
        options: {},
    },
};
export default config;

main.ts 파일에서 config 설정에서 stories 에서 component 파일의 stories 파일을 읽도록 설정해준다면 준비는 끝났다.

button 컴포넌트로 돌아가서 src 와 동일 레벨에 storeis/button.stories.tsx 파일을 생성해주고 storybook 코드를 작성해준다.

import {Button} from "../src";
import {fn} from "@storybook/test";

export default {
    title: Jaeil-UI/Button",
    component: Button,
    tags: ["autodocs"],
    parameters: {
        layout: "center",
    },
    args: {
        onLogin: fn(),
        onLogout: fn(),
        onCreateAccount: fn(),
    },
};

export const Button = {
    args: {
        label: "Button",
    },
};

 

root 에서 실행할 수 있도록 pacakage.json 파일에 name과 scripts 을 수정해준다.

그리고 root scripts 명령어도 추가해준다.

 

주의) 워크플레이스 파일에서 pacakges 로만 되어있던 것을 /** 와일드 카드로 수정해주지 않으면 또 매치되는 패키지가 없다고 No projects matched the filters in~~ 에러가 나오니까 packages/** 로 수정한다.

// packages/storybook/package.json

{
    "name": "@jaeil-ui/storybook",
    "version": "0.0.0",
    "description": "jaeil ui storybook",
    "keywords": [
        "storybook"
    ],
    "scripts": {
        "dev": "storybook dev -p 6006",
        "build:sb": "storybook build"
    },
    "dependencies": {
        "react": "^18.3.1",
        "react-dom": "^18.3.1"
    },
    "devDependencies": {
        "@chromatic-com/storybook": "^1.5.0",
        "@storybook/addon-essentials": "^8.1.10",
        "@storybook/addon-interactions": "^8.1.10",
        "@storybook/addon-links": "^8.1.10",
        "@storybook/addon-onboarding": "^8.1.10",
        "@storybook/blocks": "^8.1.10",
        "@storybook/react": "^8.1.10",
        "@storybook/react-vite": "^8.1.10",
        "@storybook/test": "^8.1.10",
        "@types/react": "^18.3.3",
        "@types/react-dom": "^18.3.0",
        "storybook": "^8.1.10",
        "typescript": "^5.2.2",
        "vite": "^5.3.1"
    }
}

// root/package.json

scripts: {
	"sb": "pnpm run -F @jaeil-ui/storybook dev",
        "build:sb": "pnpm run -F @jaeil-ui/storybook build:sb"
}

 

Vitest 설치

> pnpm install --save-dev @testing-library/react @testing-library/dom @types/react @types/react-dom -w

 

root 에 vite.config.ts, vitest-setup.ts 생성

// vitest-setup.ts

import "@testing-library/jest-dom/vitest";

 

tsconfig.ts 에도 추가해준다.

// tsconfig.json
{
	"compilerOptions":{
    ...
    "types": ["vitest/globals"],
    ...
        },
        "include": ["packages", "./vitest-setup.ts"]
    }
}
// vite.config.ts

/// <reference types="vitest" />

import {defineConfig} from "vite";

export default defineConfig({
    test: {
        globals: true,
        environment: "jsdom",
        setupFiles: ["./vitest-setup.ts"],
    },
});

 

vite.config.ts 에서 import 하는 부분에 vite 모듈이 없다고 에러가 뜰 수 있다. 다른 UI 라이브러리를 확인해보니 루트에서도 react / react-dom / vite 를 설치가 되어있길래 설치를 해줬다.

 

tsconfig에 types 를 추가하지않으면 describe 같은 testing library 타입이 지원되지 않고 include 를 포함하지 않으면 다른 세팅을 다 했다고 하더라도 어센셜이 Jest-dom이 아니라 htmlelement 로 추론되어 toBeInDocument 같은 타입이 지원되지 않는다.

 

간단히 테스트

import * as React from "react";
import {Button} from "../src";
import {render, screen} from "@testing-library/react";

describe("Button", () => {
    it("should render correctly", () => {
        render(<Button label="button" />);

        const buttonEl = screen.getByRole("button", {name: /button/i});

        expect(buttonEl).toBeInTheDocument();
    });
});

test 성공

 

여기까지 초기세팅을 다뤄봤는데, 사실 자세히 다루고 싶었는데 내용이 많아서 몇일 걸쳐서 하다보니 내용이 중간에 맥락이 안맞는 부분도 있을 것이고 이미 어느 정도 선행을 한 뒤에 블로그 글을 적은 부분도 있어서 빠트린 부분이 있을지도 모르겠다.

 

다음으로는 알아볼 것은 component 가 button 만 있지만 또 다른 ui 들을 만들어야하는데 이 때마다 버튼에서 맞춰준 초기세팅들을 다른데에서도 반복 작업하지 않도록 plop 패키지에 대해서도 알아보고, 혹시나 npm org 에 등록을 했다면 (저는 함) 어떻게 배포할지도 알아보려고한다. 배포는 packages 파일안에 core 또는 다른 명칭으로 react 로 된 곳에다가 패키지화 하여 배포하는 것으로 확인되었다. 조금 더 자세히 알아보기 위해 글도 기니까 여기서 그만하려고 한다.

728x90

'TIL' 카테고리의 다른 글

Git action + CICD (EC2 + ECR)  (1) 2024.08.30
smtp sendgrid 이메일 보내기  (0) 2024.08.29
토글버튼 만들어보기  (0) 2024.06.20
렌더링과 리렌더링 최적화(메모이제이션)  (0) 2024.05.02
Graphql 과 RESTful API 차이점  (0) 2024.04.11