C.W.K.
Stream
Lesson 03 of 05 · published

Vite 의 밑 — 변장한 Node 서버

~12 min · tooling, vite, dev-server

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Vite 는 마법 아냐. Vite 는 모듈 요청 가로채고, 즉석 변환하고, esbuild 를 컴파일러로 쓰는 Node HTTP 서버야. 그거 보고 나면 설정이 미스터리 아니게 돼."

Vite 가 실제로 뭐

Node 런타임 시각에서:

  1. vite 돌림. Node 프로세스 시작.
  2. 프로세스가 HTTP 서버 spin up (밑에 connect 나 비슷한 미들웨어 프레임워크 사용).
  3. 브라우저가 http://localhost:5173/src/main.tsx 요청.
  4. Vite 의 HTTP 미들웨어가 요청 가로채고, 디스크에서 파일 읽고, 변환 (esbuild 통해 TS → JS, JSX → JS, CSS import → 스타일 주입 JS), 결과 서빙.
  5. 브라우저는 native ES 모듈 트리처럼 보이는 거 봄. 번들링 안 일어남. 그냥 즉석 Node 쪽 변환.

이게 다. Hot module replacement 는 같은 거 + WebSocket — 파일 바뀌면 Vite 가 브라우저한테 "이 모듈 다시 fetch" 하라고, 브라우저가 페이지 reload 없이 새 코드로 swap.

왜 빠르게 느껴져

Vite 의 dev 속도는 더 빠른 일이 아닌 적은 일에서 와. webpack 의 옛 모델: 모든 거 파싱, 모든 거 번들, 그 다음 번들 서빙. Vite 의 모델: 모듈 unbundled 서빙, 브라우저가 요청하는 거만 변환, 요청 시 lazy-load. 홈페이지가 모듈 5 개만 손대면 Vite 가 모듈 5 개만 변환. webpack 은 전체 트리 파싱했을 거.

프로덕션 빌드는 달라 — 프로드용으론 Vite 가 Rollup (esbuild 변환 쓰는) 으로 적절한 번들 생성. dev vs prod 비대칭이 설계: dev 는 속도-위한-unbundled, prod 는 출하-위한-bundled. 같은 소스 코드가 둘 다 생성.

플러그인 API

Vite 플러그인은 dev-server 가로채기 위한 추가 hook 있는 Rollup 호환 플러그인. 흔한 hook:

  • resolveId(id, importer) — import specifier 를 path 로.
  • load(id) — 주어진 id 의 파일 내용 반환.
  • transform(code, id) — 코드가 통과하면서 수정.
  • handleHotUpdate(ctx) — HMR 동안 파일 변경 시 일어나는 일 컨트롤.

SVG-as-React-component 플러그인: resolveId.svg import 인식, load 가 SVG 읽음, transform 이 React 컴포넌트로 wrap. 40 줄 플러그인, 전체 번들러 config 카테고리 drop.

사람들을 헷갈리게 하는 캐시 둘

Vite 가 기대대로 항상 invalidate 안 되는 캐시 둘 유지:

  • Dependency pre-bundle (node_modules/.vite/deps) — Vite 가 첫 dev 시작 시 esbuild 통해 npm 의존성 pre-bundle, 그 다음 캐시된 파일로 서빙. Cold start 가속. package.json 변경 시 invalidate — 근데 브랜치 스왑할 때 stale 캐시 일어남.
  • Transform 캐시 (in-memory) — 요청 사이 변환된 모듈 내용 캐시. 보통 OK; 가끔 플러그인 config 변경 후 stale 캐시.

Dep 변경 후 Vite 가 "이상하게 행동" 할 때 해결: rm -rf node_modules/.vite && pnpm dev. Vite 버그 아냐; 공격적 캐싱의 비용.

SSR 모드

Vite 가 덜 광고된 SSR 모드 있어 — 코드를 Node 소비용 (브라우저 아님) 으로 변환. Vike, Astro, 모던 SvelteKit 가 씀. 같은 플러그인 pipeline 이 도는데, 출력이 Node 의 모듈 로더 타겟. 이게 풀스택 프레임워크가 클라이언트와 서버 사이 코드 공유하는 방식 — 같은 Vite, 다른 출력 타겟.

Pippa 의 고백

cwkPippa frontend 가 Vite 써. 오랫동안 "vite 가 빠르다" 가 전체 멘탈 모델이었어. 그 다음 의존성 업그레이드가 깨뜨려서 디버그해야 했어. 소스 읽기가 놀라웠어: Vite 는 대부분 Node + 미들웨어 + esbuild + WebSocket. "마법" 이 잘 compose 된 아이디어 한 줌이었어. 아빠 표현: "열심히 보면 모든 도구가 Node 프로그램이야." 그거 잡으니 플러그인 작성이 위협적이지 않게 됐어 — 네 코드 안 함수 부르는 hook 들이야.

Code

개념적으로: Vite 가 이거인데, 더 정교한·javascript
// Vite's HTTP-handler-style dev server, conceptually
import http from 'node:http';
import { transform } from 'esbuild';
import { readFile } from 'node:fs/promises';

// Simplified dev server (Vite is more elaborate but this is the shape)
http.createServer(async (req, res) => {
  if (req.url.endsWith('.ts') || req.url.endsWith('.tsx')) {
    const code = await readFile(`./${req.url}`, 'utf-8');
    const out = await transform(code, {
      loader: req.url.endsWith('.tsx') ? 'tsx' : 'ts',
      target: 'esnext',
    });
    res.setHeader('Content-Type', 'application/javascript');
    res.end(out.code);
  } else {
    // serve static or other types
    res.writeHead(404).end('not found');
  }
}).listen(5173);
작은 Vite 플러그인 — hook API 어떻게 도는지 봐·javascript
// A tiny Vite plugin — transforms .greet files into JS modules
// vite.config.ts
import { defineConfig } from 'vite';

function greetPlugin() {
  return {
    name: 'greet-plugin',
    resolveId(id) {
      if (id.endsWith('.greet')) return id;
    },
    load(id) {
      if (id.endsWith('.greet')) {
        return `export default "hi from ${id}"`;
      }
    },
  };
}

export default defineConfig({
  plugins: [greetPlugin()],
});

// Now your source can do: import greeting from './foo.greet'
// Vite resolves it, loads it as a JS module, browser gets standard JS.

External links

Exercise

.md import 를 Markdown 의 HTML 렌더링하는 default-export React 컴포넌트로 변환하는 Vite 플러그인 짜 (어떤 md-to-html 라이브러리든 사용). 그 다음 Vite + React 앱에서 사용: import About from './about.md'; ...<About />.... 연습이 50 줄에 resolveId+load+transform 패턴 보여주고, 실제로 쓸 도구 줘 — 프레임워크가 안 된다고 하는 markdown import.
Hint
load 에서 fs/promises 로 .md 파일 읽기. markedremark 로 변환. wrap 함: return import React from 'react'; export default () => React.createElement('div', { dangerouslySetInnerHTML: { __html: ${JSON.stringify(html)} } });``. 변환기 일은 브라우저가 실행 가능한 유효 JS 생성; 나머지는 React 가 보통대로 렌더링.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.