브라우저에서 ESM(ES Modules)을 지원하기 전까지, JavaScript 모듈화를 네이티브 레벨에서 진행할 수 없었다. 그래서 소스 모듈을 브라우저에서 실행할 수 있는 파일로 크롤링, 처리 및 연결하는 "번들링(Bundling)"이라는 해결 방법을 사용해야 했다.
보통 리액트 등의 프론트 프로젝트를 시작 시 웹펙(webpack) 으로 번들링을 진행한다.
콜드 스타트 방식으로 개발 서버를 구동할 때, 번들러 기반의 도구의 경우 애플리케이션 내 모든 소스 코드에 대해 크롤링 및 빌드 작업을 마쳐야지만이 실제 페이지를 제공할 수 있다.
* 콜드 스타트(Cold Start): 최초로 실행되어 이전에 캐싱한 데이터가 없는 경우를 의미
위의 그림처럼 번들링 후에야 개발 서버를 구동할 수 있으므로 개발 서버를 구동하는데에 매우 오랜 시간이 걸린다.
이를 해결하기 위해 Vite(비트)가 등장하였다.
Vite는 애플리케이션의 모듈을 dependencies와 source code 두 가지 카테고리로 나누어 개발 서버의 시작 시간을 개선한다.
- Dependencies: 개발 시 그 내용이 바뀌지 않을 일반적인(Plain) JavaScript 소스 코드이다. 쉽게 말해 애플리케이션에서 사용되는 라이브러리들이 담긴 곳이라고 생각하면 된다. 기존 번들러로는 컴포넌트 라이브러리와 같이 몇 백 개의 JavaScript 모듈을 갖고 있는 매우 큰 Dependencies에 대한 번들링 과정이 매우 비효율적이었고 많은 시간을 필요로 했다.
- Vite의 사전 번들링 기능은 Esbuild를 사용하고 있다. Go로 작성된 Esbuild는 Webpack과 같은 기존의 번들러 대비 10-100배 빠른 속도를 제공한다.
- Source code: JSX, CSS 또는 컴포넌트와 같이 컴파일링이 필요하고, 수정 또한 매우 잦은 JavaScript 소스 코드는 어떻게 처리할까?
- Vite는 Native ESM을 이용해 소스 코드를 제공한다. 이것은 본질적으로 브라우저가 번들러의 작업의 일부를 차지할 수 있도록 한다. Vite는 브라우저가 요청하는 대로 소스 코드를 변환하고 제공하기만 하면 된다. 조건부 동적 import 이후의 코드는 현재 화면에서 실제로 사용되는 경우에만 처리된다.
위의 그림처럼 Native ESM 기반 방식의 Vite에서는 번들링이 필요가 없고 브라우저에서 필요한 모듈의 소소코드를 import할때 이것을 전달만 하면 되는 방식이다.
해당 방식은 현대 대부분의 브라우저에서 ESM일 지원하기에 가능한 것이다.
또한 Vite는 HMR(Hot Module Replacement)을 지원하고, HMR 수행 시 번들러가 아닌 ESM을 이용한다. 어떤 모듈이 수정되면 Vite는 그저 수정된 모듈과 관련된 부분만을 교체하고, 브라우저에서 해당 모듈을 요청하면 교체된 모듈을 전달한다. 전 과정에서 완벽하게 ESM을 이용하기에, 프로젝트 사이즈가 커져도 HMR을 포함한 갱신 시간에는 영향을 끼치지 않는다는 장점이 있다.
마지막으로 Vite는 HTTP 헤더를 활용하여 전체 페이지의 로드 속도를 높였다. 필요에 따라 소스 코드는 304 Not Modified로, Dependencies는 Cache-Control: max-age=31536000,immutable을 이용해 캐시된다. 이렇게 함으로써 요청 횟수를 최소화하여 페이지 로딩을 빠르게 만들어 준다.
304 Not Modified
클라이언트(브라우저)가 웹 서버에 요청 시 이미지, 파일 등 요청한 자원이 변경되지 않았으므로 클라이언트에서 캐시된 자원을 사용하라고 말하는 상태코드이다.
브라우저 캐시가 만료되면 캐시가 사라지는 것이 아니라 브라우저는 서버에 조건부 요청을 보내게 된다. 서버의 재검증 결과 캐시가 유효하다면, 304 Not Modified 응답을 내려주게 된다. 해당 응답은 HTTP본문을 포함하지 않아서 매우 빠르게 내려받을 수 있다고 한다.
해당 상태코드를 클라이언트에 보내줌으로써, 웹 서버는 자원을 다시 재전송하는 오버헤드를 줄일 수 있다.
이를 직접 Vite를 사용하여 로컬 서버에서 테스트 해보았다. App.tsx 파일에 간단한 코드가 작성되어있는 상태이다.
네트워크를 들어가보면 맨 처음 App.tsx 파일을 가져올 때 캐시된 자원이 없으므로 상태 200으로 리소스를 가져오는 것을 확인할 수 있다.
응답 헤더는 다음과 같다.
위에서 Cache-Control 이 no-cache로 설정되어 있는 것을 확인할 수 있다. no-cache를 설정하면 캐시의 유효 시간이 0초이므로 파일을 가져올 때 마다 서버에 재검증 요청을 보내게 된다.
그럼 이제 코드를 변경하지 않고 새로고침을 해보자.
304 Not Modified 상태를 받은 것을 확인할 수 있다. 즉, 브라우저 캐시가 만료되어 서버에서 재검증 과정을 거쳤지만 바뀐내용이 없으므로 그냥 캐시를 가져다 쓰라는 의미이다.
그러면 서버는 바뀐 내용이 없다는 것을 어떻게 아는 것일까? 바로 Etag의 개념이 여기서 사용된다.
처음 App.tsx 파일을 받았던 응답 헤더를 보면 Etag라는 값이 설정되어 있을 것이다.
그리고 새로고침 후 받은 요청 헤더를 보자.
If-None-Match 에 처음 요청 시 응답 헤더로 받았던 Etag의 값이 여기 그대로 있는 것을 확인할 수 있다.
즉, 캐시된 리소스의 Etag 값이 If-None-Match의 값과 같으면 캐시가 유효하다가 판단하고 304 Not Modified 상태를 반환해주는 것이다.
이 상태에서 한번 App.tsx 파일의 코드를 변경 후 저장해보았다.
위와 같이 이번에는 200 응답상태가 전달되었고, App.tsx 파일 내용을 다시 전달받은 것을 알 수 있다.
해당 요청의 Etag값을 보면 이전 Etag값과 다른 것을 확인할 수 있다. 즉, 서버에서 재검증 과정에서 캐시된 Etag값과 다른 Etag를 가진 요청이 들어왔기 때문에 200상태 코드와 새로운 리소스를 전달해 준 것이다.
Http 브라우저 캐시 관련된 내용은 추후 다른 포스팅에서 구체적으로 다뤄볼 예정이다.
Cache-Control: max-age=31536000,immutable
max-age는 리소스가 캐시될 시간을 나타내며(여기서는 1년), immutable 플래그는 리소스가 변경될 수 없음을 나타낸다. 이는 변경되지 않는 리소스에 대해 브라우저가 항상 로컬 캐시를 사용할 수 있도록 한다.
다만 Vite를 사용하여 프로젝트를 배포할 때는 번들 되지 않은 ESM을 가져오는 것은 중첩된 import로 인한 추가 네트워크 통신으로 인해 여전히 비효율적이라고 한다. 또한 배포 과정에서 번들링 시 현재는 빠른 esbuild 보다는 Rollup을 채택하고 있다.
그렇다 하더라도 Vite를 도입하면 불과 1초만에 개발 서버를 구동시킬 수 있고, 프로젝트가 커져가면서 웹팩 번들링을 통해 개발 서버를 구동 시 체감상 수십초를 기다려야 했던 과거와 작별 인사 할 수 있다.
그러면 사용하지 않을 이유가 전혀 없는 Vite로 우리의 프로젝트를 마이그레이션 해보자!!
✅패키지 매니저: npm (추후 yarn-berry 로 마이그레이션 예정)
✅윈도우(Windows)
✅React(Create React App + Typescript)
Dependencies 설치
npm install --save-dev vite @vitejs/plugin-react vite-plugin-svgr
먼저 위의 명령어를 통해 Vite관련 라이브러들을 설치해준다.
Vite.Confing.ts 파일 생성
그 후 프로젝트 루트 경로에 vite.config.ts 파일을 생성해 준 후,
import * as path from "path";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import svgrPlugin from "vite-plugin-svgr";
export default defineConfig({
plugins: [react(), svgrPlugin()],
server: {
port: 3000,
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
위와 같이 작성해준다.
Vite로 로컬 서버 구동 시 기본 localhost 주소는 http://localhost:5173으로 지정된다. 이를 기존 익숙한 http://localhost:3000으로 바꿔주었다.
또한 절대경로 설정을 위해 resolve의 alias를 설정해주었다. 이는 프로젝트가 복잡해짐에 따라 경로의 깊이가 너무 깊어지는 것을 방지할 수 있다.
ts.config.json 파일 수정
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["vite/client", "vite-plugin-svgr/client"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}
위는 수정된 tsconfig.json 파일이다.
절대경로를 사용하기 위해 baseUrl과 paths를 추가해주었다.
또한 types도 추가해주었고 이를 통해 Vite를 사용할 것을 말해준다.(vite-plugin-svgr/client 는 svg 파일을 컴포넌트로 사용하기 위해 사용된다.)
index.html 파일 수정 및 이동
기존 CRA 프로젝트 에서는 index.html 파일이 public 폴더 안에 있었다. Vite를 사용하려면 해당 파일을 루트 경로로 꺼내야한다. Vite에서는 위에서 언급하였듯이 개발 서버 실행 시 번들링을 수행하지 않고, index.html 파일이 진입점이 되게끔 의도적으로 설정했기 때문이다.
루트 경로로 해당 파일을 꺼냈으면 파일의 head 코드에서 favicon 같은 요소에 존재하는 %PUBLIC_URL%의 모든 참조를 제거해준다.
예시)
href="%PUBLIC_URL%/favicon-32x32.png" -> href="/favicon-32x32.png"
마지막으로 index.html의 <body>에 진입접을 추가해준다.
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
기존 CRA 프로젝트에서 마이그레이션 시에는 index.tsx 파일이 진입점이 되겠지만, vite template으로 프로젝트 생성시에는 main.tsx가 진입점이 될 것이라고 한다.
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
위처럼 root에 렌더링을 정의한 파일을 진입점을 설정해주면 된다.
vite-env.d.ts 파일 생성
타입스크립트 사용 시 import.meta.env에 대한 타입 정의를 제공하고 있다.
루트 경로에 vite-env.d.ts 파일을 생성해준 후
/// <reference types="vite/client" />
위처럼 작성해준다.
그 후
위처럼 환경 변수 사용 시 환경 변수의 타입을 지정해 줄 수 있다.
불필요한 dependency 제거
Vite에서는 더이상 react-scripts를 사용하지 않아도 되므로
npm uninstall react-scripts
위의 명령어를 통해 삭제해준다.
package.json script 수정
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
}
react-scripts를 제거하였으므로 위처럼 Vite사용을 위한 scripts로 수정해준다.
그 후 npm run dev 명령어를 수행하면 http://localhost:3000 주소로 개발 서버가 정상적으로 동작하는 것을 알 수 있다. 진짜 이제 개발 서버가 1초만에 열리는 것을 확인할 수 있다!!
다음 포스팅은 npm 패키지 매니저의 문제점 개선을 위해 yarn-berry 도입기...
'FrontEnd > React' 카테고리의 다른 글
Vite + Yarn berry 프로젝트 배포하기 (0) | 2023.12.24 |
---|---|
Vite + Yarn Berry 구축기 - 2 (4) | 2023.12.07 |
AWS EC2에 HTTPS 적용하기 (4) | 2023.11.28 |
배포 과정 Github Actions 로 자동화하기 (0) | 2023.11.27 |
Docker + AWS EC2 로 프론트 배포하기(2) (4) | 2023.11.26 |