#0 왜 vite 인가?
다음은 vite 공식 문서를 참고해서 정리했다.
1. 먼저 번들링이란 무엇일까?
개념은 여러 개의 JavaScript 파일과 그 종속성들을 하나 또는 소수의 파일로 묶는 과정이다.
예를 들어 main.js / api.js / utils.js 등 여러가지 js 파일로 개발을 했으며 서로 종속성을 가지고 있다고 할 때, 이를 번들링하지 않으면 브라우저는 각 js 파일을 개별적으로 요청하게 된다. 이는 네트워크 요청이 많아져 로드 시간이 길어질 수 있다.
번들러 (Webpack 등)를 사용하여 모든 JS 파일을 하나의 bundle.js 로 묶는 등의 과정이 번들링이다.
vite는 이 번들러를 Esbuild라는 go로 작성된 프로그램을 사용하여 기존 webpack같은 번들러보다 10~100배 빠른 속도로 번들링을 진행한다.
2. 추가적으로 번들러 방식을 개선한다
번들러가 만능은 아니다. JS 파일이 수정 될 때마다 번들러 파일을 수정해야하기 때문이다.
공식 문서에서는 다음과 같이 이야기한다. https://ko.vite.dev/guide/why
요약은 그 아래에
"""
기존의 번들러 기반으로 개발을 진행할 때, 소스 코드를 업데이트 하게 되면 번들링 과정을 다시 거쳐야 했었습니다. 따라서 서비스가 커질수록 소스 코드 갱신 시간 또한 선형적으로 증가하게 됩니다. 일부 번들러는 메모리에서 작업을 수행하여 실제로 갱신에 영향을 받는 파일들만을 새롭게 번들링하도록 했지만, 결국 처음에는 모든 파일에 대한 번들링을 수행해야 했습니다. "모든 파일"을 번들링 하고, 이를 다시 웹 페이지에서 불러오는 것이 얼마나 비효율적인 것인지 느껴지시나요? 이러한 이슈를 우회하고자 HMR(Hot Module Replacement) 이라는 대안이 나왔으나, 이 역시 명확한 해답은 아니었습니다. 물론, vite는 HMR을 지원합니다. 이는 번들러가 아닌 ESM을 이용하는 것입니다. 어떤 모듈이 수정되면 vite는 그저 수정된 모듈과 관련된 부분만을 교체할 뿐이고, 브라우저에서 해당 모듈을 요청하면 교체된 모듈을 전달할 뿐입니다. 전 과정에서 완벽하게 ESM을 이용하기에, 앱 사이즈가 커져도 HMR을 포함한 갱신 시간에는 영향을 끼치지 않습니다. 또한 vite는 HTTP 헤더를 활용하여 전체 페이지의 로드 속도를 높입니다. 필요에 따라 소스 코드는 304 Not Modified로, 디펜던시는 Cache-Control: max-age=31536000,immutable을 이용해 캐시됩니다. 이렇게 함으로써 요청 횟수를 최소화하여 페이지 로딩을 빠르게 만들어 줍니다.
"""
여기서 HMR은 애플리케이션이 실행 중일 때 특정 모듈의 코드를 갱신하는 기술이다. 브라우저 전체를 새로고침하지 않고 변경된 부분만 업데이트하여 빠르게 개발 피드백을 받을 수 있습니다.
예를 들어 개발자가 Vue 컴포넌트에서 버튼의 텍스트를 수정했다고 가정하면, HMR은 변경된 버튼 컴포넌트 코드만 다시 로드하여 즉시 브라우저에 반영한다. 이로 인해 페이지의 상태(예: 폼 입력 데이터)가 초기화되지 않는, 흔히 사용해본적 있는 기능이다.
그런데 이러한 HMR 기술을 사용할 때, ESM을 기반으로 동작한다는 것이다.
코드를 수정했을 때 번들러를 사용해서 HMR 기술을 사용하는 경우 번들링 과정이 끝날 때까지 개발자가 기다려야 하지만,
ESM 기준으로 진행시 해당 과정을 거치지 않고 개발할 수 있다.
ESM이 뭐길래?
ECMAScript Modules의 줄임말로, ESM은 JavaScript 모듈의 표준 사양. 즉 모듈 간의 종속성을 효율적으로 관리하는 것을 말한다. 브라우저가 모듈을 개별적으로 요청하고 캐싱하며 실행할 수 있도록 합니다.
어려워 보이지만 import 문으로 의존성을 관리하는 걸 말한다.
import { sum } from './math.js';
console.log(sum(1, 2));
이런 식으로.
vite는 번들링을 캐싱해서 관리하는데, 이는 node_modules/.vite 디렉터리에 저장되며, 특정 조건이 성립할 때, 다시 번들링하여 저장된다.
https://ko.vite.dev/guide/dep-pre-bundling.html 참고
#1 Vite + React
Vite에서 React와 함께 사용할 때의 공식적인 파일 구조이다.
index.html이 public 파일 밖에 위치해있는게 눈에 띄는데 이는 공식 문서에 이유가 기술되어있다.
추가적인 번들링 과정 없이 index.html 파일이 앱의 진입점이 되게끔 하기 위함이라고 한다.
따라서 이 index,html에 추가한 CSS, JS 파일들은 종속성을 추가할 수 있다는 것.
npm create vite@latest . -- --template react
다음과 같은 명령어로 프로젝트를 시작한다.
#1.5 Vite + Tailwind css
https://tailwindcss.com/docs/guides/vite
위는 공식 문서.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
위와 같이 설치해주고, 공식 문서에서 나온대로 설정을 추가해주면 환경 설정이 완료된다.
#2 Vite 지원하는 기능들
1. React를 사용한다고 가정했을 때, vue에서 지원하는 기능들
1) HMR
자동 업데이트 기능인 HMR
2) esbuild로 JSX 기능 지원
3) CSS
기본적으로 scss 와 같은 파일을 지원하지는 않지만, 쉽게 다운로드하여 사용할 수 있음.
4) 정적 에셋 사용하기
import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
다음과 같은 방식으로 삽입한다.
또한 아래와 같은 방식으로 import 할 수 있다.
// URL로 에셋 가져오기
import assetAsURL from './asset.js?url'
// String 타입으로 에셋 가져오기
import assetAsString from './shader.glsl?raw'
// 웹 워커 가져오기
import Worker from './worker.js?worker'
// Base64 포맷의 문자열 형태로 웹 워커 가져오기
import InlineWorker from './worker.js?worker&inline'
5) json 파일 import
// 객체 형태로 가져오기
import json from './example.json'
// 필드를 지정해 가져오기 (트리 셰이킹 됩니다.)
import { field } from './example.json'
#3 vite 에셋 관리
import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
예를 들어, imgUrl 객체는 개발 시 /img.png 값으로 할당되겠으나, 실제 프로덕션 버전에서는 /assets/img.2d8efhg.png와 같은 값이 할당됩니다. (여기서 2d8efhg는 해시 값을 의미한다.)
+ svg에서 인라이닝 하려면
JS를 통해 직접 url()로 SVG URL을 전달하는 경우, 변수는 반드시 큰따옴표로 감싸져야 한다.
import imgUrl from './img.svg'
document.getElementById('hero-img').style.background = `url("${imgUrl}")`
#4 빌드하기
https://ko.vite.dev/guide/build.html
추후 확인
#5 SSR - SEO를 위한 추후 확인
https://ko.vite.dev/guide/ssr.html
#6 초기 세팅에서 변경
1. 초기 세팅
1) 다음과 같은 초기 세팅에서 app.css나 logo 파일들은 지워주고, 종속성되 제거해준다.
2) 기본으로 준비되어 있는 postcss는 무엇일까?
PostCSS란 무엇인가?
PostCSS는 CSS를 변환하는 데 사용되는 플러그인 기반의 도구이다. 단순한 CSS 전처리기로 시작했지만, 지금은 다양한 플러그인을 통해 코드 변환, 최적화, 호환성 유지 등 CSS 처리 전반에 활용됩니다.
postcss.config 내부를 뜯어보면 알 수 있는 내용은 다음과 같다.
plugins
PostCSS에서 가장 중요한 구성 요소입니다. plugins 객체는 사용하려는 PostCSS 플러그인을 정의합니다.
- tailwindcss: {}
- Tailwind CSS와 통합합니다.
- Tailwind는 유틸리티 기반 CSS 프레임워크로, PostCSS를 통해 스타일 정의를 생성 및 최적화합니다.
- Tailwind 설정은 보통 별도의 tailwind.config.js 파일에 정의됩니다.
- autoprefixer: {}
- CSS에 자동으로 벤더 접두사를 추가합니다.
- 예: display: flex; → -webkit-box, -ms-flexbox, flex.
- 브라우저 호환성을 높이고 CSS 표준화를 지원합니다
autoprefixer는 예를 들어 아직 firefox 브라우저에는 없는 CSS 를 사용했을 경우, 접두사로 호환 가능하도록 도와주게 된다.
2. React Router V6
1) createBrowserRouter
Djago urls처럼 라우팅을 하게 됨. 부모 라우트에 자식 라우트가 설정되어 있으면 Outlet 자리에 자식 route가 들어가게 된다.
routes.tsx의 구조는 다음과 같이 된다.
import { createBrowserRouter } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Root from "../Root";
const router = createBrowserRouter([
// 모든 라우터들의 컨테이너
{
path: "/",
element: <Root />,
children: [
{
path: "",
element: <Home />
},
{
path: "about",
element: <About />
}
]
}
])
export default router;
Root.tsx는 다음과 같이 된다.
import { Outlet } from "react-router-dom"
import Header from "./components/Header"
function Root() {
return (
<>
<h1 className="text-3xl font-bold underline">Root</h1>
<Header />
<Outlet />
</>
)
}
export default Root
+Outlet과 useOutletContext
(A) Outlet component의 동작 과정
Outlet component는 자신의 자식 컴포넌트로 설정된 컴포넌트를, 해당 자리에 출력시킨다.
+ (url을 쓸 때, /~~ 로 쓰면 절대 경로가, ~~로 사용하면 현재 경로 기준 절대경로가 된다)
그렇다면 부모 컴포넌트에서 자식 컴포넌트 Outlet에 정보를 전달하고자 한다면 어떻게 해야할까?
(B) useOutletComponent를 사용한다.
useParams로 사용할 수도 있고, 다른 방법으로 소개하는건 다음과 같다.
<Outlet context={} />
Context 내부에 어떠한 형태의 데이터든 담아서 보내고,
자식 컴포넌트 내부에서 타입 선언과, useOutletContext를 사용하면 된다.
interface IFollowersContext {
nameOfMyUser: string;
}
const { nameOfMyUser } = useOutletContext<IFollowersContext>();
console.log(nameOfMyUser)
이는 부모에서 한번 전달하면, 모든 자식 컴포넌트에게 데이터를 전달할 수 있다는 장점이 있다.
2) errorElement
컴포넌트들이 errorElemnt를 가진다. 컴포넌트에서 에러가 발생하거나 컴포넌트 위치를 찾지 못할 때,
미리 만들어둔 component를 출력하는 기능.
import { createBrowserRouter } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Root from "../Root";
import NotFound from "../pages/NotFound";
const router = createBrowserRouter([
// 모든 라우터들의 컨테이너
{
path: "/",
element: <Root />,
children: [
{
path: "",
element: <Home />
},
{
path: "about",
element: <About />
}
],
errorElement: <NotFound />
}
])
export default router;
특정 컴포넌트에서 발생한 에러가, 앱 전체를 마비시키는 걸 방지해준다.
3) useNavigate
user를 프로그래밍적으로 다른 위치로 이동시킬 때 사용한다.
4) useParams
아래와 같이 라우터 설정 후
const router = createBrowserRouter([
// 모든 라우터들의 컨테이너
{
path: "/",
element: <Root />,
children: [
{
path: "",
element: <Home />
},
{
path: "about",
element: <About />
},
{
path: "users/:userId",
element: <User/>,
},
],
errorElement: <NotFound />
}
])
export default router;
import { useParams } from "react-router-dom";
import { users } from "../db";
function User() {
const { userId } = useParams();
return (
<h1>
User with id {userId} is named: {users[Number(userId) - 1].name}
</h1>
);
}
export default User;
다음과 같이 useParams를 사용한다.
5) useSearchParams
search params란, ?뒤에 오는 보통 추가 정보들을 의미한다.
useSearchParams는 useState처럼 배열을 반환한다.
하나는 search parameter 정보를 담고 있는 관련 object를 받고, 하나는 setSearchParameter를 받는다.
const [readSearchParms, setSearchParams] = useSearchParams();
console.log(readSearchParms.get("geo"));
'프로젝트 기록 > LLM 게임 프로젝트' 카테고리의 다른 글
개발 리뷰 -1- 기능 설정 (1) | 2024.12.10 |
---|