From e018f19937209263e37fbc81cc2f85093f809c13 Mon Sep 17 00:00:00 2001
From: 1ilsang <1ilsang@naver.com>
Date: Tue, 6 Feb 2024 03:30:44 +0900
Subject: [PATCH] docs(dev-server): Update descriptions
---
_posts/js/dev-server.md | 296 +++++++++++++++++++-----
src/features/styles/markdown/index.scss | 5 +-
2 files changed, 244 insertions(+), 57 deletions(-)
diff --git a/_posts/js/dev-server.md b/_posts/js/dev-server.md
index ca791d6e..2ce3e180 100644
--- a/_posts/js/dev-server.md
+++ b/_posts/js/dev-server.md
@@ -1,7 +1,7 @@
---
title: 'Vite Dev Server 이해하기 (feat. HMR)'
description: 'Dev 서버의 동작 방식은 어떻게 될까?'
-tags: ['vite', 'dev-server', 'hmr']
+tags: ['vite', 'dev-server', 'hmr', 'preact', 'prefresh']
coverImage: 'https://github.com/1ilsang/dev/assets/23524849/132b52c7-3c2b-4554-b0fb-8ec5f3193d7a'
date: '2024-02-04T13:50:51.772Z'
ogImage:
@@ -10,25 +10,26 @@ ogImage:
-요즘 `vite`의 매력에 푹빠져있다. 그러던 도중 "개발 서버는 어떻게 동작하는걸까?" 의문을 가지게 되었다. 따라서 오늘은 Vite Dev Server의 동작 방식을 이해하고 HMR 과정을 파헤쳐 보려고 한다.
+요즘 `vite`의 매력에 푹 빠져있다. 그러던 도중 "개발 서버는 어떻게 동작하는 걸까?" 의문을 가지게 되었다. 따라서 오늘은 Vite Dev Server의 동작 방식을 이해하고 HMR 과정을 파헤쳐 보려고 한다.
## Index
- [TR;DR!](#trdr)
- [1. 개발 서버 실행(서버 초기화)](#1-개발-서버-실행서버-초기화)
- [2. index.html 요청](#2-indexhtml-요청)
-- [3. index.html 렌더링(with WebSocket)](#3-indexhtml-렌더링with-websocket)
-- [4. 코드 변경 감지](#4-코드-변경-감지)
-- [5. 브라우저 리렌더링](#5-브라우저-리렌더링)
+- [3. index.html 렌더링과 자원 요청](#3-indexhtml-렌더링과-자원-요청)
+- [4. 렌더링 계속 진행(with WebSocket)](#4-렌더링-계속-진행with-websocket)
+- [5. 코드 변경 감지](#5-코드-변경-감지)
+- [6. 브라우저 리렌더링](#6-브라우저-리렌더링)
- [마무리](#마무리)
## TR;DR!
-![dev-server-logic-summary](https://github.com/1ilsang/dev/assets/23524849/778f568d-154a-4ac8-bfe0-3e1399c16588)
+![dev-server-logic-summary](https://github.com/1ilsang/dev/assets/23524849/03dab012-82a9-4649-8d80-15c0dfe0c129)
> 한 짤로 보는 Dev Server의 동작 방식
-이 글은 핵심 로직에 해당하는 노란색 박스를 위주로 설명하려고 한다. 위의 도식도를 쫓아오며 글을 읽는다면 도움이 될 것이라 생각한다.
+이 글은 핵심 로직에 해당하는 노란색 박스를 위주로 설명하려고 한다. 위의 도식도를 쫓아오며 글을 읽는다면 도움이 될 것으로 생각한다.
이 글은 Vite `v5.0.12` [버전을 기준](https://github.com/vitejs/vite/tree/v5.0.12)으로 작성되었다.
@@ -64,18 +65,14 @@ export async function _createServer(
}
```
-![init-server-phase](https://github.com/1ilsang/dev/assets/23524849/7d46712d-6118-4788-9c91-fa2d20f8c3c3)
-
-> 최초 서버 실행 이후의 상태
+`yarn vite` 등의 커맨드로 Dev Server를 실행시키면 `bin/vite.js`의 `cli.js`가 [호출](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/bin/vite.js#L44)된다. 이후 `src/node/cli.ts`가 [호출](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/cli.ts#L156)되면서 Dev Server가 실행된다.
-`yarn vite` 등의 커맨드로 Dev Server를 실행시키면 `bin/vite.js`의 `cli.js`가 [실행](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/bin/vite.js#L44)된다.
-
-이후 `src/node/cli.ts`가 [실행](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/cli.ts#L156)되면서 Dev Server가 실행된다.
-
-Dev Server는 아래와 같은 프로세스를 거치며 초기화를 진행한다.
+### Dev Server는 아래와 같은 프로세스를 거치며 초기화를 진행한다.
1. Dev Server가 실행되면 HTTP 서버와 웹 소켓 서버가 실행된다.
+
- 미들웨어는 Express에서 사용되는 [connect](https://www.npmjs.com/package/connect)로 연결된다.
+
2. 파일 시스템 옵저버를 설정한다.
- 파일 변경 감지를 위해 [chokidar](https://www.npmjs.com/package/chokidar)를 사용한다.
3. 모듈 그래프를 생성한다.
@@ -89,13 +86,19 @@ Dev Server는 아래와 같은 프로세스를 거치며 초기화를 진행한
- 플러그인은 이후 Dev Server의 특정 시점마다 훅을 실행시켜 미들웨어 역할을 하게 된다.
5. 클라이언트의 요청을 기다린다.
+
+
+![init-server-phase](https://github.com/1ilsang/dev/assets/23524849/7d46712d-6118-4788-9c91-fa2d20f8c3c3)
+
+> 최초 서버 실행 이후의 상태
+
## 2. index.html 요청
```ts
// server/index.ts
middlewares.use(indexHtmlMiddleware(root, server))
-// indexHtmlMiddleware.ts
+// https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/server/middlewares/indexHtml.ts#L438
// HTML 파일을 처리하고 변환한다. 스크립트 태그 주입 및 HMR 클라이언트 코드 삽입, 모듈 경로 변환 등의 작업을 한다.
html = await server.transformIndexHtml(url, html, req.originalUrl)
@@ -120,40 +123,82 @@ export function createDevHtmlTransformFn(...) {
![index.html-request-phase](https://github.com/1ilsang/dev/assets/23524849/ec8f01ad-b4a6-4da8-aaa6-0e5015438ba3)
-최초 유저의 요청(`GET /`)이 발생하면 `index.html`이 리턴된다. 이 과정에서 `transform`을 거치며 필요한 데이터들을 세팅한다.
+최초 유저의 요청(`GET /`)이 발생하면 `index.html`이 리턴된다. 이 과정에서 `transform`과 같은 [플러그인 훅](https://vitejs.dev/guide/api-plugin.html#universal-hooks)을 거치며 필요한 데이터들을 세팅한다.
1. 미들웨어에서 `transform` 함수가 실행된다.
- - 이는 플러그인 컨테이너를 실행시키게 된다.
- - 플러그인들은 실행 시점(`pre`, `normal`, `post`)에 맞춰 실행된다.
+
+ - 플러그인 컨테이너의 플러그인들이 실행 된다.
+ - 플러그인들은 실행 시점(`pre`, `normal`, `post`)에 맞춰 훅이 실행된다.
+
2. 의존성 사전 번들링
- - `node_modules`에 있는 의존성들은 [ESM](https://webpack.kr/guides/ecma-script-modules/)이 아닐 수 있다. Vite는 이들을 [사전 번들링](https://ko.vitejs.dev/guide/dep-pre-bundling.html)하여 브라우저가 이해할 수 있는 ESM 형태로 변환한다.
+ - `node_modules`에 있는 의존성은 [ESM](https://webpack.kr/guides/ecma-script-modules/)이 아닐 수 있다. Vite는 이들을 [사전 번들링](https://ko.vitejs.dev/guide/dep-pre-bundling.html)하여 브라우저가 이해할 수 있는 ESM 형태로 변환한다.
- 이 과정은 esbuild로 실행되어 빠르게 처리된다.
![transpile-ts-to-js](https://github.com/1ilsang/dev/assets/23524849/23f4b155-b5f5-487d-8500-b39796919829)
+> hmr.ts의 response에 타입이 사라진 모습.
+
3. 코드 변환
+
- TS 혹은 JSX 파일의 경우 JS로 변환된다.
- - 위의 그림과 같이 파일 명 자체는 변경되지 않지만 코드는 js로 변경된다.
+ - 위의 그림과 같이 파일명 자체는 변경되지 않지만, 코드는 js로 변경된다.
+
+
+
+```ts
+// 만약 index.html에서 해당 파일을 import 한다고 가정해 보자.
+// playground/hmr/hmr.ts
+import { foo as depFoo, nestedFoo } from './hmrDep'
+import './importing-updated'
+import './invalidation/parent'
+
+// hmr.ts 파일에 구성된 모듈 의존성 그래프
+ModuleNode {
+ url: '/hmr.ts',
+ file: '/User/user/VSCode/vite/playground/hmr/hmr.ts',
+ type: 'js',
+ // 클라이언트 측에서 사용되는 모듈들, 즉 브라우저에서 실행되는 모듈들의 목록을 추적하는 데 사용된다.
+ clientImportModules: Set(10) {
+ // 재귀적 구조
+ ModuleNode: {
+ url: '/hmrDep.js' // hmr.ts 내부에서 import 되는 hmrDep 이 추가된 모습.
+ file: '/User/user/VSCode/vite/playground/hmr/hmrDep.js',
+ clientImportModules: Set(10) {
+ ModuleNode: { ... }
+ }.
+ ModuleNode: {
+ url: '/importing-updated/index.js', // hmr.ts 내부에서 import 되는 importing-updated가 추가된 모습.
+ file: '/User/user/VSCode/vite/playground/hmr/importing-updated/index.js',
+ // ...
+ }
+ // ...
+```
-![module-graph](https://github.com/1ilsang/dev/assets/23524849/87b4fc0c-d9d6-4a27-bf0d-7b676136231a)
+4. 모듈 의존성 그래프 생성
+
+ - 위 코드를 보면 `hmr.ts`에서 import 되는 `./hmrDep`, `./importing-updated` 등이 `ModuleNode`에 설정되는 것을 알 수 있다.
+ - 만약 외부 의존성이 있다면 chokidar에 추가된다.
+ - 파일 시스템에 변경사항이 있을 때 모듈 그래프로 빠르게 전파시킨다.
+
+5. Dev Server의 기능에 필요한 사전 코드들(`@vite/client` 등)을 응답 자원에 추가한다.
-4. 모듈 파서
- - 모듈 의존성 그래프를 생성한다.
- - 만약 외부 의존성이 있다면 chokidar에 추가된다.
- - 파일 시스템에 변경사항이 있을 경우 모듈 그래프로 빠르게 전파시킨다.
-5. Dev Server의 기능에 필요한 사전 코드들(`@vite/client` 등)을 추가한다.
6. 변환된 html 파일을 리턴한다.
-## 3. index.html 렌더링(with WebSocket)
+## 3. index.html 렌더링과 자원 요청
![init-html](https://github.com/1ilsang/dev/assets/23524849/a5969d65-b177-4011-ab1b-d45129b9951e)
![browser-initiator](https://github.com/1ilsang/dev/assets/23524849/3eacdd15-514a-43a9-a71c-cc8ba228d574)
-브라우저는 응답 받은 html 파일을 파싱하면서 필요한 자원(js, css 등)을 다시 요청한다.
+> 브라우저는 위에서부터 아래로 해석해 나가므로 @vite/client, global.css, hmr.ts가 순차적으로 요청되는 것을 볼 수 있다.
+
+1. 브라우저는 응답받은 html 파일을 렌더링 하기 시작한다.
+
+2. 렌더링에 필요한 자원(js, css 등)을 다시 Dev Server에 요청한다.
+
+ - html의 최상단 `/@vite/client`을 시작점으로 `global.css`, `hmr.ts` 등이 요청된다.
-- html의 최상단 `/@vite/client` 파일을 가져온다면 실행되면서 웹 소켓이 실행된다.
-- 각 요청에 대해 Dev Server는 transformMiddleware에서 [2번 html 요청](#2-indexhtml-요청)과 비슷한 과정으로 응답한다.
+
```ts
// server/index.ts
@@ -167,13 +212,9 @@ export function transformMiddleware(...) {
html: req.headers.accept?.includes('text/html'),
})
if (result) {
- const depsOptimizer = getDepsOptimizer(server.config, false) // non-ssr
- const type = isDirectCSSRequest(url) ? 'css' : 'js'
- const isDep =
- DEP_VERSION_RE.test(url) || depsOptimizer?.isOptimizedDepUrl(url)
+ // transform된 코드, 소스코드를 캐시 설정해 리턴한다.
return send(req, res, result.code, type, {
etag: result.etag,
- // allow browser to cache npm deps!
cacheControl: isDep ? 'max-age=31536000,immutable' : 'no-cache',
headers: server.config.server.headers,
map: result.map,
@@ -182,11 +223,86 @@ export function transformMiddleware(...) {
}
```
-이때 Dev Server에 내장된 importAnalysis 플러그인에서 HMR이 가능한 파일이라면 `import.meta.hot`을 설정한다.
+3. transform 적용
+
+ - 각 요청에 대해 Dev Server는 [transformMiddleware](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/server/middlewares/transform.ts#L175)에서 [2번 html 요청](#2-indexhtml-요청)과 비슷한 과정으로 응답한다.
+ - `public` 폴더 내의 요청인지 외부 자원 요청인지 등의 분류 작업을 [미들웨어에서 진행](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/server/index.ts#L774)한다.
+ - `@fs` prefix는 vite 프로젝트의 루트(config 위치)를 벗어날 경우 설정된다(모노레포 혹은 파일 시스템 직접 접근 등의 경우).
+ - HMR 코드 적용
+ - 이때 Dev Server에 내장된 [importAnalysis 플러그인](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/plugins/importAnalysis.ts#L209)에서 HMR이 설정된 파일(\*1)이라면 `import.meta.hot`을 파일 최상단에 [추가](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/plugins/importAnalysis.ts#L715)한다.
+
+4. 변환된 자원을 브라우저에 응답(response)한다.
+
+
+
+> (\*1): preact의 prefresh 같은 HMR 라이브러리를 적용했거나(후술) import.meta.hot.accept을 직접 코드에 추가한 경우에 해당(아래 코드)한다.
+
+```html
+
+
+```
+
+![inject-import-meta-hot](https://github.com/1ilsang/dev/assets/23524849/57326d8a-3c7a-41d4-ac7b-b202bc851bc8)
+
+> 일반 스크립트의 응답에 createHotContext 생성 및 import.meta.hot에 바인딩된 모습.
+
+## 4. 렌더링 계속 진행(with WebSocket)
+
+이제 index.html의 요청 파일을 가져왔으므로 브라우저 렌더링이 계속 진행된다.
+
+```ts
+// @vite/client.ts
+function setupWebSocket(...) {
+ const socket = new WebSocket(`${protocol}://${hostAndPath}`, 'vite-hmr')
+ socket.addEventListener('message', async ({ data }) => {
+ handleMessage(JSON.parse(data))
+ });
+}
+```
+
+- `@vite/client` 파일
+ - Dev Server와의 통신 및 [HMR에 필요한 코드들](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/client/client.ts#L137)이 작성되어 있다.
+ - [WebSocket 연결](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/client/client.ts#L68) 및 `update` 이벤트를 기다린다.
+
+
+
+```tsx
+// AS-IS 원본 코드
+import { h, render } from 'preact';
+import App from './MyComponent';
+
+render(, document.getElementById('app'));
+
+// TO-BE 변경된 코드
+import { render } from '/node_modules/.vite/deps/preact.js';
+import { jsxDEV as _jsxDEV } from '/node_modules/.vite/deps/preact_jsx-dev-runtime.js';
+import App from '/src/MyComponent';
+
+render(_jsxDEV(App, ...), document.getElementById('app'))
+```
+
+만약 `react`와 같은 UI 라이브러리를 사용한다면 각 라이브러리가 의존하는 HMR 라이브러리가 호출된다. 여기서는 `preact`를 기준으로 설명(리액트와 거의 동일하다)하겠다.
+
+`jsxDEV`로 감싸진 하위 컴포넌트들은 HMR이 적용된다. 자세한 내용은 [6. 브라우저 리렌더링](#6-브라우저-리렌더링)에서 다루겠다.
+
+이제 브라우저가 더 이상 요청할 것이 없을 때까지 3 ~ 4 과정을 반복하며 렌더링을 마무리한다.
+
+
![index.html-rendering-phase](https://github.com/1ilsang/dev/assets/23524849/5321cc9c-f569-4653-8179-06669e82f630)
-## 4. 코드 변경 감지
+> 3 ~ 4. 브라우저 렌더링 및 정적 자원 요청 상황.
+
+## 5. 코드 변경 감지
```ts
watcher.on('change', async (file) => {
@@ -200,6 +316,16 @@ watcher.on('change', async (file) => {
});
```
+개발 중 파일이 변경되면(개발자의 코드 수정) chokidar에서 `change` [이벤트를 감지](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/server/index.ts#L673)한다.
+
+- 플러그인 컨테이너에 `update` 이벤트를 전파한다.
+ - 각 플러그인에서 필요시(listen) 플러그인 코드가 실행된다.
+- 의존성 그래프에 변경사항을 적용한다.
+ - 모듈 캐싱을 무효화해 refresh 되도록 함.
+- `onHMRUpdate` 함수를 호출해 hot-reloading을 준비한다.
+
+
+
```ts
function onHMRUpdate() {
// 관련 플러그인 훅 실행
@@ -218,36 +344,45 @@ function updateModules(...) {
moduleGraph.invalidateModule(mod, invalidatedModules, timestamp, true)
}
// 소켓 메시지 전송
- ws.send({
- type: 'update',
- updates,
- })
+ ws.send({ type: 'update', updates })
```
+`onHMRUpdate`는 `updateModules`를 [호출](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/node/server/hmr.ts#L142)한다.
+
+- HMR 관련 플러그인이 있을 때 훅(`handleHotUpdate`)을 실행시킨다.
+- 관련된 모듈 그래프를 갱신한다.
+- 브라우저에게 파일이 변경되었음을 WebSocket으로 알린다(update 이벤트 전송).
+
+
+
![file-change-phase](https://github.com/1ilsang/dev/assets/23524849/51800d15-f916-4c25-bc1f-6a6bd2044d54)
-## 5. 브라우저 리렌더링
+> 코드가 변경되었을 때의 Dev Server 모습
+
+## 6. 브라우저 리렌더링
![socket-update-event](https://github.com/1ilsang/dev/assets/23524849/23181d27-311e-4154-a04f-7efa2fb7cae9)
+> 브라우저 소켓이 Dev Server의 update 소켓 데이터를 받은 모습.
+
```ts
-// vite/client
+// @vite/client.ts
case 'update':
notifyListeners('vite:beforeUpdate', payload);
await Promise.all(payload.updates.map(async(update)=> {
if (update.type === 'js-update') {
- // queueUpdate는 업데이트 목록을 추가한다.
- return hmrClient.queueUpdate(update);
+ // queueUpdate는 업데이트 목록의 순서를 유지해준다.
+ return queueUpdate(hmrClient.fetchUpdate(update))
}
// ... CSS update는 생략
});
notifyListeners('vite:afterUpdate', payload);
-// queueUpdate
-queueUpdate(...) {
+// HMRClient > fetchUpdate
+fetchUpdate(...) {
fetchedModule = await this.importUpdatedModule(update);
-// importUpdatedModule
+// client/client.ts > importUpdatedModule
async function importUpdatedModule(...) {
const importPromise = import(
/* @vite-ignore */
@@ -261,19 +396,68 @@ async function importUpdatedModule(...) {
},
```
-react, preact 등 순수 자바스크립트가 아니라면 라이브러리 자체 flush를 호출한다.
+1. `@vite/client`에서 연결된 브라우저의 소켓은 `update` 이벤트를 받고 `hmrClient`에게 [업데이트를 지시](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/client/client.ts#L182)한다.
+
+2. hmrClient는 `fetchUpdate`에 데이터를 넘기고 `importUpdatedModule`을 [호출](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/shared/hmr.ts#L235)한다.
+
+ - importUpdatedModule은 hmrClient가 [생성될 때 적용](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/src/client/client.ts#L137)된다.
+
+![import response](https://github.com/1ilsang/dev/assets/23524849/d2d0de89-4eee-4496-9c51-d99d228eed0b)
+
+3. importUpdatedModule이 호출되면서 변경된 모듈이 `import` 되므로, Dev Server에 새로 요청하게 된다([3. 렌더링 자원 요청](#3-indexhtml-렌더링과-자원-요청)). 이때 `t` 값을 쿼리로 넣어(?t=123214123) 캐싱을 회피해 변경된 모듈의 코드를 응답으로 받을 수 있도록 한다.
+
+4. import로 요청한 응답이 정상적으로 오면 리렌더링 되기 시작([4. 렌더링 진행](#4-렌더링-계속-진행with-websocket))된다.
+
+HMR이 가능한 파일은 `import.meta.hot.accept` 함수의 [콜백으로 실행](https://ko.vitejs.dev/guide/api-hmr.html#hot-accept-cb)된다. HMR이 불가능한 파일이라면 전체 페이지를 리로딩한다.
+
+```ts
+// vite.config.ts
+import preact from '@preact/preset-vite';
+
+export default defineConfig({
+ plugins: [preact()],
+});
+
+// preact 플러그인이 호출되면서 소스코드를 transform 한다.
+// prefresh
+return {
+ code: `${prelude}${result.code}
+ if (import.meta.hot) {
+ self.$RefreshReg$ = prevRefreshReg;
+ self.$RefreshSig$ = prevRefreshSig;
+ // 해당 코드 덕에 HMR로 인식, Dev Server에서 호출되며 flushUpdate 실행
+ import.meta.hot.accept((m) => {
+ try {
+ flushUpdates();`
+
+// flushUpdates
+self.__PREFRESH__.replaceComponent(prev, next, true);
+```
+
+앞에서 잠깐 다뤘지만 `react`, `preact` 등 순수 자바스크립트가 아니라면 라이브러리 자체 HMR을 호출한다.
-- preact는 prefresh 에서 hmr 진행.
-- prefresh는 @preact/preset-vite에 포함되어 있다.
+- [preact-vite 플러그인](https://github.com/preactjs/preset-vite)(`@preact/preset-vite`)은 내부적으로 [prefresh](https://github.com/preactjs/prefresh)라는 HMR 라이브러리를 사용한다.
+- preact 플러그인은 `prefreshEnabled` 여부에 따라 [prefresh를 호출](https://github.com/preactjs/preset-vite/blob/a325c1f3811900f70277424304c9eb42fc60f8a7/src/index.ts#L246)한다.
+- prefresh는 `import.meta.hot.accept` [코드를 주입](https://github.com/preactjs/prefresh/blob/main/packages/vite/src/index.js#L87)하고 `flushUpdates`로 [HMR을 수행](https://github.com/preactjs/prefresh/blob/018f5cc907629b82ffb201c32e948efe4b40098a/packages/utils/src/index.js#L11)(컴포넌트 변경)한다.
-HMR이 가능한 파일은 import.meta.hot.accept 함수의 콜백으로 처리된다.
-불가능 하다면 페이지를 리로딩하고 캐싱을 피하기 위해 시간을 쿼리스트링으로 붙인다(?ts=123214123)
+이로써 HMR이 완전히 마무리되면서 다시 개발자의 입력을 기다리게 된다.
+
+
+
+![dev-server-logic-summary](https://github.com/1ilsang/dev/assets/23524849/03dab012-82a9-4649-8d80-15c0dfe0c129)
## 마무리
-Vite Dev Server의 많은 부분들이 Webpack의 동작과 비슷하다는 점에서 역시 크게 바뀌지는 않는구나 조금 안심했다.
+Vite Dev Server를 사용하면서 모호하게 알고 있던 부분을 이번 기회에 한번 쭉 정리할 수 있었다. 정리하면서 모르는 것이 참 많다고 느꼈다.
+
+팩트인지 확인하기 위한 소스코드 탐험과 디버깅 과정은 상당히 의미 있었다. 글이 너무 길어질 것 같아 생략한 함수들이 꽤 있는데 감탄하며 본 로직들이 많이 있었다. 역시 남의 코드를 많이 봐야 한다.
+
+이번 과정을 통해 두 가지 인사이트를 얻을 수 있었다.
+
+1. Vite Dev Server의 많은 부분들이 Webpack Dev Server의 동작과 비슷하다는 점이 인상적이었다. 하나를 잘해놓는 게 중요하다고 느꼈다.
+2. 소스코드의 탐험이 쉽지만은 않았만 어느 정도 자신감이 붙을 수 있었다. 이후에도 이렇게 공부해 나가야겠다고 생각했다.
-이 글을 쓰며 참고했던 혹은 유용했던 링크를 남기며 글을 마무리 하려고 한다.
+이 글을 쓰며 참고했던 혹은 유용했던 링크를 남기며 글을 마무리하려고 한다.
- [https://rajaraodv.medium.com/webpack-hot-module-replacement-hmr-e756a726a07](https://rajaraodv.medium.com/webpack-hot-module-replacement-hmr-e756a726a07)
- [https://webpack.kr/concepts/hot-module-replacement](https://webpack.kr/concepts/hot-module-replacement)
diff --git a/src/features/styles/markdown/index.scss b/src/features/styles/markdown/index.scss
index d4c9340b..f20556a2 100644
--- a/src/features/styles/markdown/index.scss
+++ b/src/features/styles/markdown/index.scss
@@ -26,8 +26,10 @@
color: $highlight-color;
}
- a:hover {
+ a:hover,
+ summary:hover {
text-decoration-line: underline;
+ cursor: pointer;
}
u {
@@ -155,6 +157,7 @@
@include image;
flex-direction: column;
+ justify-content: center;
}
.img-horizon-wrap {