회고/Techit Frontend School 10기

[멋쟁이 사자처럼 프론트엔드 스쿨] 61일차 TIL - Vite 환경 구성

kelly09 2024. 8. 3. 15:45

🍰 오늘은 그나마 중간중간 브레이크 걸어주셔서 간신히 따라갈 수 있었다. 그러나 머리에 남은 건 없는... 오늘 글 짱 깁니다. 주의.

1. 작업할 폴더 생성 후 이동

mkdir vite-react
cd vite-react/

2. README.md와 index.html과 .gitignore 파일 생성

touch README.md index.html .gitignore

3. .gitignore 파일 내용 추가

💡 Git이 의도적으로 추적하지 않을 파일 지정

.DS_Store(맥 사용자만)
node_modules
build
dist

4. Vite 설치

pnpm add vite -D # --save-dev

5. package.json 파일 수정

{
	"type": "module",
	"private": true,
	"name": "vite custom project",
	"version": "0.0.1",
	"description": "Vite와 React 환경 설정",
	"devDependencies": {
		"vite": "^5.3.5"
	}
}

6. index.html 내용 수정

<!DOCTYPE html>
<html lang="ko-KR">

  <head>
    <meta charset="UTF-8" />
    <title>Vite + React 스캐폴딩</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" type="image/svg+xml" href="/react.svg" />
  </head>

  <body>

    <noscript>이 앱을 사용하려면 JavaScript 활성화가 필요합니다.</noscript>

    <div id="react-app"></div>

  </body>

</html>

Vite 명령어는 node_modules/.bin/vite에 들어있음

7. package.json scripts에 명령어 등록

"dev": "vite",
"build": "vite build",
"preview": "vite preview"

각각 명령어 입력해보기

pnpm dev

pnpm build

pnpm preview

8. react, react-dom 설치

pnpm add react react-dom

package.json에 추가된 것 확인

	"dependencies": {
		"react": "^18.3.1",
		"react-dom": "^18.3.1"
	}

React 버전 확인 방법

pnpm show react versions

rc(Release Candidate): 출시 예정 버전 중 최종 테스트 거친 후, 출시 전 마지막 버전

곧 19 나오겠다!

9. src/main.jsx 생성 후 index.html과 연결

<script type="module" src="src/main.jsx"></script>

10. src/main.jsx 파일에 React를 import 해야 작업할 수 있음

import React from 'react';
import ReactDOM from 'react-dom/client';

11. index.html 파일의 <div id="react-app"></div> 이 내용을 렌더링 하기 위한 코드

ReactDOM.createRoot(
	document.getElementById('react-app')
).render()

근데 document.getElementById가 너무 기니까 변수에 할당함.

const domNode = document.getElementById('react-app');
ReactDOM.createRoot(domNode).render();

근데 ReactDOM.createRoot 부분도 길어서

import { createRoot } from 'react-dom/client';
createRoot(domNode).render();

뭔 차이?

default import vs named import & 구조 분해 할당

# default import 
- react-dom/client에서 ReactDOM 객체 전체를 가져옴

# named import
- react-dom/client에서 createRoot 함수만 가져옴

# 구조 분해 할당
- createRoot 함수만을 직접 가져옴
- ReactDOM 객체 전체를 가져오지 않고 필요한 함수만 가져올 수 있음

12. render 메서드 안에 App을 self-closing 후 렌더링

StrictMode도 React.StrictMode라고 써야 하는데

createRoot(domNode).render(
	<StrictMode>
		<App />
	</StrictMode>
);

번거로우니까 import React 부분을 수정

import { StrictMode } from 'react';

저 <App />부분이 App을 렌더링하기 위한 건데 App이 없어서 아무것도 뜨지 않음.

function App() {
	return (
		<div className="App">
			<h1>React 웹 앱</h1>
		</div>
	);
}

근데 아까 StrictMode로 바꾼 import 부분 때문에 에러남.

main.jsx에서 JSX 문법을 사용하고 있는데, JSX를 사용하려면 React가 필요함.

JSX 변환 과정에서 React가 필요함.

따라서 React를 명시적으로 import 해야 함.

import React, { StrictMode } from 'react';

13. favicon

favicon 생성

svg아이콘 → 화면 우클릭 → 검사 → Elements → <svg> 태그 우클릭 → Copy → Copy outerHTML

svg 최적화 시키기

svg 최적화 → Past markup에 붙여넣기 → 출력된 이미지 확인 후 복사

public 폴더에 vite.svg 파일 생성 후 최적화 된 svg 붙여넣기(네이밍은 알아서)

🍰 환경 변수 이해하기

Vite는 HTML 파일에서 환경 변수를 대체하는 기능도 지원함.

import.meta.env의 모든 속성은 특수한 %ENV_NAME% 구문을 사용해 HTML 파일에서도 사용할 수 있음.

main.jsx에서 console.log(import.met.env);

# 환경 변수들

BASE_URL: "/" 
// 애플리케이션의 기본 URL 경로.
// "/"는 애플리케이션이 도메인의 루트에서 실행되고 있음을 의미.
DEV: true 
// 현재 개발 모드에서 실행 중임을 나타냄.
// 개발 시 디버깅이나 핫 리로딩 같은 기능이 활성화.
MODE: "development"
// 현재 실행 모드가 개발 모드임.
// 프로덕션 빌드 시 "production".
PROD: false
// 프로덕션 모드가 아님.
// DEV의 반대 개념. 프로덕션 빌드 시 true.
SSR: false
// Server-Side Rendering을 사용하지 않고 있음.
// Client-Side Rendering 중임.

HTML 파일에 <h1>Vite is running in %MODE%</h1> 넣고 실행해 보면

화면에 이렇게 표시됨.

pnpm build

pnpm preview 후 화면에 렌더링 되는 건

공식 문서 설명

기본적으로, dev 명령으로 실행되는 개발 서버는 development 모드로 동작하고, 
build 명령으로 실행되는 경우에는 production 모드로 동작합니다.

다시말해 vite build 명령을 실행하게 되면 .env.production에 정의된 환경 
변수를 불러오게 됩니다.

Development 모드와 Production 모드

애플리케이션의 실행 환경을 구분하는 개념

Developtment 모드

  • 개발자 편의성 중심
  • 디버깅 정보 제공
  • 더 자세한 오류 메시지
  • 핫 리로딩 지원
  • 성능 최적화 되지 않음
  • 소스 맵 포함

Production 모드

  • Production 모드
  • 성능 최적화
  • 코드 축소 및 압축
  • 디버깅 정보 제거
  • 보안 강화
  • 캐싱 최적화

차이점: 개발 편의성과 최종 사용자 경험 사이의 균형 Development 모드는 개발 과정을 돕고, Production 모드는 최종 사용자에게 최적화된 경험을 제공함.

왜 알아야 하는지? ⇒ HTML에서는 할 수 없는데 Vite에서는 할 수 있는 일임.

Env.Variables를 사용해서 경로를 환경변수로 주면서, build 실행 시 dist 파일의 index.html에 자동으로 적용되게 하기 위함.

<link rel="icon" type="image/svg+xml" href="%BASE_URL%" />
// "/"로 나옴

%BASE_URL% 환경변수를 쓰지 않아도 “/”만으로도 배포 시 항상 %BASE_URL%을 기본적으로 포함함.

정적 자산 처리 - 이미지, CSS 파일 등의 정적 자산 URL도 자동으로 관리함.

필요 시 vite.config.js에서 base 옵션을 통해 BASE_URL을 변경할 수 있음.

<link rel="icon" type="image/svg+xml" href="/vite.svg" />

근데 vite.svg는 public 폴더에 있는데 어떻게 알아서 찾음?

  • Vite는 public 폴더를 특별 취급함. 이 폴더 내의 파일들은 개발 서버의 루트(/)에서 직접 제공됨.
  • 자동 경로 매핑: ‘/vite.svg’같은 루트 상대 경로는 자동으로 public 폴더 내의 파일로 매핑됨

공식 문서 설명

다음 에셋의 경우

- robots.txt와 같이 소스 코드에서 참조되지 않는 에셋
- 해싱 없이 항상 같은 이름을 갖는 에셋
- 또는 URL을 얻기 위해 굳이 import 할 필요 없는 에셋
public 디렉터리 아래에 에셋을 위치시키세요. 이 곳에 위치한 에셋은 
개발 시에 / 경로에, 배포 시에는 dist 디렉터리에 위치하게 됩니다.

만약 <root>/public 디렉터리가 아닌 다른 디렉터리를 사용하고자 하는 경우, 
publicDir 옵션을 이용할 수 있습니다.

마지막으로, 다음의 사항을 유의해주세요.

- public 디렉터리에 위치해 있는 에셋을 가져오고자 하는 경우, 항상 루트를 
기준으로 하는 절대 경로로 가져와야만 합니다. 
( public/icon.png 에셋은 소스 코드에서 /icon.png으로 접근이 가능합니다.)
- public 디렉터리에 위치한 에셋은 JavaScript 코드로 가져올 수 없습니다.

14. App()을 Component로 만들기

App.jsx 생성 후 아까 App 함수 붙여 넣기 → export default로 내보내기

main.jsx에서 App() import하기

import App from "/App";

console에 React is not defined → App.jsx에서 React import 하기

해결 된 걸 브라우저를 통해 확인하고 문제 파악을 위해

개발자 모드 → Network 패널 → Filter에 app.jsx 입력 후 Preview와 vscode 비교

Preview

function App() {
    return /* @__PURE__ */
    React.createElement("div", {
        className: "App"
    }, /* @__PURE__ */
    React.createElement("h1", null, "React 웹 앱"));
}

VSCode

function App() {
  return (
    <div className="App">
      <h1>React 웹 앱</h1>
    </div>
  );
}

둘이 다른 이유

  • Vite라는 번들러가 트랜스파일링(변환) 해주는 것.
  • 변환 과정은 Babel 같은 트랜스파일러에 의해 수행됨.
  • Vite는 기본적으로 이런 트랜스파일링을 자동으로 수행하여 JSX를 브라우저가 이해할 수 있는 표준 JavaScript 코드로 변환함.
  • 그 과정에서 React가 없다고 알려주는 것
  • 모듈화 작업을 하는 과정에서 React가 없는 상태기 때문에
  • 브라우저는 React가 읽을 수 없는 상태가 원인이 됨.
  • 따라서 모듈화 작업을 한 App.jsx에 import React from ‘react’;를 해준 것.

그럼 계속 import React를 해줘야 하는가? 너무 불편 → Vite plugin 설치

15. TypeScript 설치

현재 사용하는 개발 도구가 vscode랑 Vite고, 더 나은 개발자 경험(DX, Developer Experience)을 위해 TypeScript를 사용하는 게 유리함.

  • TypeScript는 필요 없어도 Types는 필요함.
  • 우리는 Types를 쓰기 위해 TypeScript가 필요함.
pnpm add @types/react @types/react-dom @types/node -D
혹은
pnpm add @types/{react,react-dom,node} -D

전자는 더 명확하고 이해하기 쉬움, 후자는 코드를 짧고 간결하게 만들 수 있음

기능은 차이 없음

설치 후 node_modules/@types/node/timers에 설치됨.

package.json에도 설치 됐는지 확인.

"devDependencies": {
		"@types/node": "^22.0.0",
		"@types/react": "^18.3.3",
		"@types/react-dom": "^18.3.0",
		"vite": "^5.3.5"
	},

 

그럼 이제 <React.StrictMode>로 써도 되고 <StrictMode>로 써도 됨.

TypeScript의 장점

  • 오류 검사
  • 자동 완성

TypeScript 의 결론

  • 우리는 리액트가 필요해서 리액트를 설치했는데, 리액트는 리액트 문법의 오류를 검사 해주지 않고, 심지어 코드 작성 시 자동 완성 조차 안해주기 때문에 편리함을 위해서 TypeScript 를 별도로 설치한 것으로 이해하면 될 것 같다. TypeScript 는 리액트가 안하는 오류 검사와 자동 완성도 해주기 때문이다.

16. Vite Plugin 구성

import React from ‘react’;

이걸 계속 쓰지 않기 위해 Babel, TypeScript, Vite에게 알려줘야 하는데 Vite는 기본적으로 그러한 기능이 없기 때문에 plugin을 설치 해야함.

@vitejs/plugin-react

pnpm add @vitejs/plugin-react -D

프로젝트 폴더에 vite.config.js 생성

import { defineConfig } from 'vite';
// Vite 설정을 정의하기 위한 defineConfig 함수를 Vite 모듈에서 가져옴
// 설정 객체의 타입을 명확하게 함
// 자동 완성 및 타입 검사 지원
import pluginReact from '@vitejs/plugin-react';
// Vite의 React 플러그인을 가져옴
// Vite에서 React 프로젝트를 쉽게 설정하고 빌드할 수 있도록 도와줌

const viteConfig = defineConfig({
// defineConfig 함수를 사용하여 Vite의 설정 객체(viteConfig) 정의
		plugins: [ // 이 배열에는 Vite가 사용할 플러그인들 나열
        pluginReact() 
// pluginReact()를 사용하여 Vite가 React 프로젝트를 처리할 수 있도록 설정
		]
});

export default viteConfig;
// Vite가 이 설정 파일을 읽고 사용할 수 있게 내보내기

이제 드디어 import React 할 필요 없이 자동으로 JSX를 불러오게 됨!

App.jsx랑 main.jsx의 import React 부분 제거

node_modules의 react 패키지에 jsx-runtime.js가 있음

⇒ React 라이브러리의 내부 파일

⇒ React 17 버전부터 도입된 JSX Transform에 관련된 기능 제공

⇒ JSX 코드를 런타임에서 해석할 때 사용됨

  • App.jsx는 import { jsx } from “react”/jsx-runtime을 써서 jsx 함수를 꺼내보면.. 자동으로 react가 들어가 있는 모습을 볼 수 있음.
  • 결론은 import React from “react” 일일이 안 써도 적용이 된다

17. ESLint 구성

ESLint 설치하기

pnpm create @eslint/config@latest
> 문법 체크와 오류 찾기
> JavaScript modules
> React
> No
> Browser, Node
> Yes
> pnpm

설치 후 package.json과 eslint.config.js 파일 확인

import globals from "globals";
import pluginJs from "@eslint/js";
import pluginReact from "eslint-plugin-react";

export default [
  { files: ["**/*.{js,mjs,cjs,jsx}"] },
  // 검사할 파일의 패턴 지정.
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
       // ESLint가 globals 라이브러리에서 제공하는 browser와 node 환경의
       // 전역 변수를 사용할 수 있도록 설정
      },
    },
  },
  pluginJs.configs.recommended, // 기본 JavaScript에 대한 규칙을 설정
  pluginReact.configs.flat.recommended, // React 관련 코드에 대한 규칙을 설정
  // pluginJs와 pluginReact에서 제공하는 권장 설정을 가져와 적용
];

⇒ ESLint가 JavaScript 및 React 코드를 검사할 때 기본적인 스타일과 오류 검사를 수행할 수 있도록 도와줌

근데 ESLint 설치로 인해 문제가 생김.

React plugin을 설치해서 automatic 버전으로 import React를 안 해도 작동되게 했는데 ESLint는 그걸 오류라고 인지함.

eslint.config.js에 해당 문서 보고 내용 추가.

import globals from "globals";
import pluginJs from "@eslint/js";
import pluginReact from "eslint-plugin-react";

export default [
  {
    files: ["**/*.{js,mjs,cjs,jsx}"],
  },
  {
    settings: {
      react: {
        version: "detect", // React의 버전 자동으로 감지
      },
    },
    plugins: {
      react: pluginReact,
      // eslint-plugin-react 플러그인 추가
    },
  },
  {
    languageOptions: {
      parserOptions: {
        ecmaFeatures: {
          jsx: true, // JSX 문법 사용 가능하게
        },
      },
      globals: { // 브라우저와 Node.js 환경에서 자주 사용하는 전역 객체 추가
      // 별도의 import 없이 이러한 객체들 사용 가능
        ...globals.browser, // globalThis, window, console, alert, ...
        ...globals.node, // global, process, ...
      },
    },
  },
  pluginJs.configs.recommended,
  pluginReact.configs.flat.recommended,
  {
    rules: {
      "react/react-in-jsx-scope": "off",
      // JSX 파일에서 React를 명시적으로 import 하지 않아도 되도록 
      // 해당 규칙 비활성화
    },
  },
];

이제 package.json에 scripts 등록

"lint": "eslint \\"./src/**\\" --report-unused-disable-directives --ignore-pattern .gitignore"

src 내의 모든 파일과 서브 디렉토리를 포함하여 eslint 검사

--report-unused-disable-directives :

ESLint에서 사용되지 않는 eslint-disable 주석을 감지하고 이를 보고 주석을 통해 특정 규칙을 비활성화했지만, 실제로는 해당 주석이 적용되지 않는 경우 이를 알려줌

--ignore-pattern .gitignore :

.gitignore 파일에 명시된 파일과 디렉토리를 ESLint가 무시하도록 설정. 로컬에만 저장 되는 파일은 검사 할 필요가 없고 lint로 검사 된 내용만 git push하면 되기 때문.

git push 하기 전에 pnpm lint 후 push 

18. eslint-plugin-react-hooks & eslint-plugin-react-refresh 설치

pnpm add eslint-plugin-react-hooks eslint-plugin-react-refresh -D

공식에서 지원하는 버전과 다르기 때문에 경고 출력.

eslint.config.js 파일에 규칙 추가 후 호환 시켜주기

추가 할 코드

import pluginReactHooks from "eslint-plugin-react-hooks";

plugins: {
            "react-hooks": pluginReactHooks,
        },
        
rules: {
            ...pluginReactHooks.configs.recommended.rules,
        },
        
// -----------------위는 hooks, 아래는 refresh 규칙--------------- //

import pluginReactRefresh from 'eslint-plugin-react-refresh';

plugins: {
      'react-refresh': pluginReactRefresh,
    },
rules: {
      'react-refresh/only-export-components': 'warn',
    },

 

19. 절대 경로 사용 설정

“@” 사용해서 경로 정해주기

vite.config.js에 내용 추가

import { defineConfig } from "vite";
import pluginReact from "@vitejs/plugin-react";
import { fileURLToPath, URL } from "node:url";

const viteConfig = defineConfig({
  plugins: [pluginReact()],
  resolve: { // 해당 부분 추가
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

export default viteConfig;

fileURLToPath(new URL = 해당 프로젝트 폴더를 지칭 , import.meta.url = 기본값)

(new URL(’./src’) = 해당 프로젝트 파일에 src 폴더를 지칭

절대 경로 설정 잘 되는 지 확인하기

src/styles/globals.css 생성 후 내용 추가

import "@/styles/globals.css";

문제: Vite는 절대 경로 이해하지만 vscode는 이해 못 함

jsconfig.json 파일 생성 후 추가(jsconfig.json이란?)

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"]
        }
    }
}

peer 종속성 문제

  • 피어 종속성 무시하고 설치
pnpm add eslint-plugin-react-hooks --legacy-peer-deps
  • eslint-plugin-react-hooks 패키지 관리자가 eslint 종속성 버전을 9까지 포함할 때까지 기다리기
  • eslint 패키지를 8버전도 설치하기(비추)
  • .npmrc 파일 생성 후 아래 내용 추가
legacy-peer-deps=true