
내 사진 한 장으로 퍼스널 컬러 찾기 [1편] - 프로젝트 세팅과 모듈화 첫걸음

교육 현장에서 HTML과 Tailwind CSS를 가르치고 나면, 학생들이 꼭 하는 질문이 있습니다. "이제 뭘 만들어야 하나요?" JavaScript로 객체와 배열까지 배웠는데, 막상 뭔가 그럴듯한 걸 만들려고 하면 막막합니다. 파일 하나에 HTML, CSS, JavaScript를 전부 넣으면 금방 수백 줄이 되고, 어디를 고쳐야 하는지 찾는 것만으로도 지칩니다.
현업 개발자에게 들어보니, 처음 실무에 투입됐을 때 가장 당황했던 건 "파일이 수십 개로 나뉘어 있는 프로젝트 구조"였다고 합니다. 학원에서는 항상 파일 하나에 다 넣었으니까요. React나 Vue 같은 프레임워크를 쓰면 자연스럽게 파일을 나누게 되지만, 사실 프레임워크 없이도 JavaScript 파일을 깔끔하게 나눌 수 있습니다. ES 모듈이라는 표준 기능이 있기 때문입니다.
이 시리즈에서는 프레임워크 없이, 바닐라 JavaScript와 Tailwind CSS만으로 제대로 된 웹 애플리케이션을 만듭니다. 그것도 꽤 그럴듯한 걸 만듭니다. 얼굴 사진을 올리면 퍼스널 컬러를 분석해주는 웹사이트입니다. 백엔드 서버 없이 브라우저의 연산 능력만으로 얼굴을 인식하고, 피부색을 추출하고, 봄/여름/가을/겨울 타입을 판별합니다.
1편에서는 그 프로젝트의 뼈대를 세웁니다. Vite로 프로젝트를 만들고, Tailwind CSS를 연결하고, ES 모듈로 JavaScript 파일을 나누는 방법을 배웁니다.
시리즈 로드맵
이 시리즈는 총 4편으로 구성됩니다. 각 편이 끝날 때마다 실제로 동작하는 결과물이 나옵니다.
1편 (지금 읽고 있는 글): 프로젝트 세팅과 모듈화 첫걸음. Vite + Tailwind CSS 개발 환경을 만들고, ES 모듈로 JavaScript 파일을 나누는 방법을 익힙니다.
2편: 반응형 UI 만들기와 이미지 업로드. Tailwind CSS로 모바일에서도 예쁜 화면을 만들고, 사용자가 올린 이미지를 화면에 표시하는 기능을 구현합니다.
3편: 브라우저에서 얼굴 찾기. Google의 MediaPipe 라이브러리를 사용해 브라우저에서 직접 얼굴을 감지하고, 볼과 이마 영역의 피부색을 추출합니다.
4편: 퍼스널 컬러 판별과 무료 배포. 추출한 피부색 데이터로 웜톤/쿨톤을 판별하고 4계절 타입을 분류합니다. 완성된 사이트를 Vercel에 무료로 배포합니다.
지금부터 1편을 시작합니다.

- 만들 내용 -
준비물 확인: Node.js와 npm
프로젝트를 시작하기 전에 컴퓨터에 Node.js가 설치되어 있는지 확인합니다. 터미널(맥은 터미널, 윈도우는 PowerShell)을 열고 다음 명령어를 입력합니다.
node -v
v20.19.0 같은 버전 번호가 나오면 됩니다. 숫자가 20 이상이면 문제없습니다. 아무것도 나오지 않거나 에러가 뜨면 Node.js 공식 사이트(nodejs.org)에서 LTS 버전을 설치합니다.
npm도 확인합니다.
npm -v
10.8.0 같은 숫자가 나오면 됩니다. Node.js를 설치하면 npm은 자동으로 함께 설치됩니다.
Node.js가 왜 필요한지 궁금할 수 있습니다. 우리는 서버를 만들 것이 아닙니다. Node.js에 포함된 npm이라는 패키지 관리자를 쓰기 위해서입니다. npm으로 개발 서버를 실행하고, Tailwind CSS를 설치하고, 나중에 배포용 파일을 만듭니다. Node.js는 그 도구들을 돌리는 엔진이라고 생각하면 됩니다.
프로젝트 만들기: Vite
프로젝트를 만들 도구로 Vite를 사용합니다. Vite는 프론트엔드 개발 서버이자 빌드 도구입니다. 파일을 수정하면 브라우저에 즉시 반영되고, 나중에 배포할 때는 최적화된 파일을 만들어 줍니다.
터미널에서 프로젝트를 만들 폴더로 이동한 뒤, 다음 명령어를 실행합니다.
npm create vite@latest personal-color -- --template vanilla
이 명령어를 뜯어보겠습니다.
npm create vite@latest는 Vite 프로젝트 생성 도구를 실행하라는 뜻입니다. personal-color는 프로젝트 이름이자 폴더 이름입니다. --template vanilla는 React나 Vue 같은 프레임워크 없이 순수 JavaScript 프로젝트를 만들겠다는 뜻입니다. 중간에 있는 --는 npm에게 "여기부터 뒤의 옵션은 내가 아니라 Vite에게 전달해"라고 알려주는 구분자입니다.
명령어를 실행하면 personal-color 폴더가 생깁니다. 해당 폴더로 이동해서 필요한 패키지를 설치합니다.
cd personal-color npm install
설치가 끝나면 개발 서버를 한번 실행해 봅니다.
npm run dev
터미널에 http://localhost:5173 같은 주소가 나옵니다. 브라우저에서 이 주소를 열면 Vite의 기본 페이지가 보입니다. 카운터 버튼이 있는 간단한 페이지입니다. 여기까지 됐으면 프로젝트가 정상적으로 만들어진 것입니다. Ctrl + C를 눌러 개발 서버를 종료합니다.
Vite가 만들어 준 파일 살펴보기
생성된 프로젝트의 파일 구조를 보겠습니다. Vite 7.x 버전 기준입니다.
personal-color/ ├── index.html <- 메인 HTML 파일 ├── public/ │ └── vite.svg <- 파비콘 (나중에 교체) ├── src/ │ ├── main.js <- JavaScript 진입점 │ ├── counter.js <- 카운터 예제 (나중에 삭제) │ ├── style.css <- CSS 파일 │ └── javascript.svg <- 로고 이미지 (나중에 삭제) ├── package.json <- 프로젝트 설정 파일 └── package-lock.json <- 패키지 버전 고정 파일
Vite 7부터는 main.js, style.css 같은 소스 파일이 프로젝트 루트가 아닌 src/ 폴더 안에 생성됩니다. 이전 버전에서는 루트에 있었는데, 소스 파일과 설정 파일을 분리하는 방향으로 바뀌었습니다. 이 기사의 모든 경로는 Vite 7.x 구조를 기준으로 설명합니다.
여기서 주목할 파일은 package.json입니다. 에디터로 열어보면 이런 내용이 들어 있습니다.
{ "name": "personal-color", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "devDependencies": { "vite": "^7.3.1" } }
"type": "module" 부분이 중요합니다. 이 설정은 Node.js 환경에서 import와 export 구문을 사용할 수 있게 해줍니다. Vite의 설정 파일인 vite.config.js가 import를 쓸 수 있는 이유가 바로 이 설정 덕분입니다. Vite가 자동으로 설정해줬습니다.
scripts 부분을 보면 세 가지 명령어가 있습니다. npm run dev는 개발 서버 실행, npm run build는 배포용 파일 생성, npm run preview는 배포용 파일을 로컬에서 미리 확인하는 명령어입니다.
index.html도 열어보겠습니다.
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> </head> <body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body> </html>
<script type="module">이 핵심입니다. 일반 <script> 태그와 달리, type="module"을 붙이면 이 파일 안에서 import와 export를 쓸 수 있습니다. Vite가 이 설정도 자동으로 구성해줬습니다.
여기서 한 가지 짚고 넘어갈 것이 있습니다. 앞서 본 package.json의 "type": "module"과 지금 보는 <script type="module">은 비슷해 보이지만 서로 다른 환경을 위한 설정입니다. package.json의 설정은 Node.js 환경을 위한 것이고, index.html의 설정은 브라우저 환경을 위한 것입니다. Node.js에서는 package.json이, 브라우저에서는 <script> 태그가 각각 "이 파일은 모듈입니다"라고 알려주는 역할을 합니다. 결국 같은 목적이지만 동작하는 곳이 다릅니다.
Tailwind CSS v4 설치하기
Tailwind CSS를 이미 배웠다면 CDN 방식(<script> 태그 한 줄)으로 사용해 본 적이 있을 것입니다. CDN 방식은 학습용으로는 괜찮지만, 실제 프로젝트에서는 빌드 도구와 함께 쓰는 게 표준입니다. 필요한 스타일만 추출해서 파일 크기를 줄이고, 자동 완성 같은 개발 도구의 지원도 받을 수 있기 때문입니다.
Tailwind CSS v4는 이전 버전과 설치 방법이 많이 달라졌습니다. 오히려 더 단순해졌습니다. 터미널에서 다음 명령어로 두 개의 패키지를 설치합니다.
npm install -D tailwindcss @tailwindcss/vite
-D 플래그는 이 패키지가 개발용 도구라는 표시입니다. 개발할 때만 필요하고 배포할 때는 포함되지 않습니다. Tailwind CSS는 빌드 시점에 CSS를 생성하는 도구이므로 개발 의존성으로 설치하는 게 맞습니다.
tailwindcss는 Tailwind CSS 본체이고, @tailwindcss/vite는 Vite와 연결해주는 플러그인입니다. 이전 버전에서는 PostCSS 설정 파일, Tailwind 설정 파일 등 여러 파일을 만들어야 했는데, v4에서는 이 두 패키지만 설치하면 됩니다.
다음으로 Vite 설정 파일을 만듭니다. 프로젝트 루트에 vite.config.js 파일을 새로 생성합니다.
// vite.config.js import { defineConfig } from 'vite' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [tailwindcss()], })
이게 전부입니다. tailwindcss() 플러그인을 Vite에 등록하는 세 줄이 핵심입니다.
마지막으로 CSS 파일을 수정합니다. src/style.css의 내용을 전부 지우고 다음 한 줄만 남깁니다.
/* src/style.css */ @import "tailwindcss";
이전 버전에서는 @tailwind base; @tailwind components; @tailwind utilities; 세 줄을 써야 했는데, v4에서는 @import "tailwindcss" 한 줄로 통합됐습니다.
설정 파일도 필요 없어졌습니다. v3까지는 tailwind.config.js 파일을 만들어서 어떤 파일을 스캔할지, 어떤 색상을 추가할지 설정해야 했습니다. v4에서는 이런 설정을 CSS 파일 안에서 직접 합니다. 지금은 기본 설정만으로 충분하니 넘어가겠습니다.
Tailwind CSS 동작 확인
src/main.js의 내용을 전부 지우고 다음으로 교체합니다.
// src/main.js import './style.css' document.querySelector('#app').innerHTML = ` <div class="min-h-screen bg-gray-100 flex items-center justify-center"> <div class="bg-white p-8 rounded-xl shadow-lg text-center"> <h1 class="text-3xl font-bold text-gray-800 mb-4">퍼스널 컬러 분석기</h1> <p class="text-gray-600">프로젝트 세팅이 완료되었습니다</p> </div> </div> `
src/counter.js와 src/javascript.svg 파일은 Vite가 예제로 만들어 준 파일입니다. 이제 필요 없으니 삭제합니다. 프로젝트 루트에서 다음 명령어를 실행합니다.
rm src/counter.js src/javascript.svg
터미널이 익숙하지 않다면 에디터의 파일 탐색기에서 직접 삭제해도 됩니다.
개발 서버를 다시 실행합니다.
npm run dev
브라우저에서 열어보면 회색 배경 위에 흰색 카드가 나타나고, "퍼스널 컬러 분석기"라는 제목이 보입니다. Tailwind CSS의 유틸리티 클래스가 제대로 적용된 것입니다.
min-h-screen이 화면 전체 높이를 잡고, flex items-center justify-center가 카드를 정중앙에 배치합니다. rounded-xl shadow-lg가 카드에 둥근 모서리와 그림자를 줍니다. 이런 클래스들이 익숙하지 않다면 Tailwind CSS 공식 문서를 참고하면 됩니다. 이 시리즈에서는 Tailwind CSS 자체를 가르치지는 않고, 프로젝트를 만들면서 자연스럽게 사용합니다.
ES 모듈: JavaScript 파일을 나누는 기술
지금까지 JavaScript를 배울 때 파일 하나에 모든 코드를 넣었을 겁니다. 연습할 때는 그래도 됩니다. 하지만 코드가 200줄, 300줄을 넘어가면 문제가 생깁니다. 함수가 30개인 파일에서 원하는 함수를 찾으려면 스크롤을 한참 해야 합니다. 변수 이름이 겹치기도 합니다. 두 사람이 같은 파일을 동시에 수정하면 충돌이 납니다.
파일을 나누면 이런 문제가 사라집니다. 색상 관련 함수는 colors.js에, 화면 그리는 코드는 ui.js에, 이미지 처리 코드는 imageProcessor.js에 넣습니다. 각 파일은 자기 역할만 하고, 필요한 것만 서로 주고받습니다.
이렇게 파일을 나누고 서로 연결하는 걸 "모듈 시스템"이라고 합니다. JavaScript에는 ES 모듈이라는 표준 모듈 시스템이 있고, 2018년부터 모든 주요 브라우저에서 지원하고 있습니다. MDN Web Docs에 따르면 Chrome, Firefox, Safari, Edge 모두 ES 모듈을 완전히 지원합니다.
export: 이 함수를 공개합니다
export는 "이 파일에 있는 함수나 변수를 다른 파일에서도 쓸 수 있게 공개합니다"라는 뜻입니다.
src/utils/math.js라는 파일을 만들고 다음과 같이 작성한다고 가정합니다.
// src/utils/math.js export function add(a, b) { return a + b } export function multiply(a, b) { return a * b } function secretFormula(x) { return x * 42 }
add 함수와 multiply 함수 앞에 export를 붙였습니다. 이 두 함수는 다른 파일에서 가져다 쓸 수 있습니다. secretFormula 함수는 export가 없으므로 이 파일 안에서만 사용할 수 있습니다. 밖에서는 이 함수가 존재하는지조차 모릅니다.
이것이 모듈의 핵심입니다. 공개할 것만 골라서 export하고, 나머지는 파일 안에 감춥니다. 파일 하나가 하나의 울타리가 됩니다.
import: 다른 파일에서 가져옵니다
export한 것을 다른 파일에서 사용하려면 import로 가져옵니다.
// src/main.js import { add, multiply } from './utils/math.js' console.log(add(3, 5)) // 8 console.log(multiply(4, 6)) // 24
import { add, multiply } from './utils/math.js'를 한 줄씩 뜯어보겠습니다. import는 "가져오겠습니다"라는 선언입니다. 중괄호 { } 안에는 가져올 것들의 이름을 씁니다. from 뒤에는 어디서 가져올지 파일 경로를 씁니다. 경로는 현재 파일 기준 상대 경로이고, 반드시 ./로 시작해야 합니다. src/main.js에서 ./utils/math.js라고 쓰면 src/utils/math.js를 가리킵니다.
중괄호를 쓰는 이유는 한 파일에서 여러 개를 export할 수 있기 때문입니다. 필요한 것만 골라서 가져옵니다. add만 필요하면 import { add } from './utils/math.js'라고 쓰면 됩니다. multiply는 가져오지 않았으니 이 파일에서는 사용할 수 없습니다.
이 방식을 named export라고 합니다. 이름을 붙여서 내보내고, 같은 이름으로 가져오는 방식입니다.
default export: 파일의 대표 선수
파일에서 하나만 내보낼 때는 export default를 쓸 수 있습니다.
// src/utils/greeting.js export default function greet(name) { return `안녕하세요, ${name}님!` }
export default로 내보낸 것은 가져올 때 중괄호 없이 씁니다. 이름도 자유롭게 바꿀 수 있습니다.
// src/main.js import greet from './utils/greeting.js' // 또는 import sayHello from './utils/greeting.js' // 이름을 바꿔도 됩니다 console.log(greet('김개발')) // 안녕하세요, 김개발님!
named export와 default export의 차이를 표로 정리하면 다음과 같습니다.
| 구분 | named export | default export |
|---|---|---|
| 내보내기 | export function add() {} | export default function app() {} |
| 가져오기 | import { add } from './파일.js' | import app from './파일.js' |
| 중괄호 | 필요 | 불필요 |
| 이름 변경 | 같은 이름만 가능 | 자유롭게 변경 가능 |
| 개수 제한 | 파일당 여러 개 가능 | 파일당 1개만 가능 |
| 적합한 경우 | 유틸리티 함수 모음 | 파일의 핵심 기능 1개 |
어떤 것을 쓸지는 상황에 따라 다릅니다. 유틸리티 함수처럼 여러 개를 모아놓은 파일은 named export가 자연스럽고, 컴포넌트처럼 파일 하나에 하나의 핵심 기능이 있는 경우는 default export가 어울립니다. 이 프로젝트에서는 두 가지를 적절히 섞어 사용합니다.
프로젝트 폴더 구조 설계
이제 우리 프로젝트에 맞는 폴더 구조를 설계합니다. 파일을 아무 규칙 없이 나누면 나중에 "이 파일이 어디 있더라?" 하고 헤매게 됩니다. 미리 규칙을 정해두면 파일이 늘어나도 깔끔하게 유지할 수 있습니다.
Vite 7.x가 만들어 준 src/ 폴더 안에 하위 폴더를 추가합니다. 프로젝트 루트에서 다음 명령어를 실행합니다.
mkdir -p src/components src/utils src/styles
완성된 구조는 다음과 같습니다.
personal-color/ ├── index.html ├── vite.config.js ├── package.json ├── src/ │ ├── main.js <- JavaScript 진입점 │ ├── style.css <- Tailwind CSS 설정 │ ├── components/ <- UI 컴포넌트 (화면 조각들) │ ├── utils/ <- 유틸리티 함수 (계산, 변환 등) │ └── styles/ <- 추가 스타일 파일 └── public/ <- 정적 파일 (파비콘, 이미지 등)
각 폴더의 역할을 짚어 보겠습니다.
src/components/에는 화면을 구성하는 코드가 들어갑니다. 이미지 업로드 영역, 결과 표시 영역, 로딩 화면 같은 것들입니다. 2편에서 본격적으로 구현합니다.
src/utils/에는 화면과 직접 관계없는 로직이 들어갑니다. 색상 변환 함수, 피부색 분석 함수 같은 것들입니다. 3편과 4편에서 채워집니다.
src/styles/에는 Tailwind CSS의 커스텀 설정이 들어갑니다. 지금은 src/style.css 하나로 충분하지만, 프로젝트가 커지면 여기에 추가 스타일을 넣습니다.
src/main.js는 프로젝트의 진입점입니다. 이 파일에서 모든 모듈을 불러와서 연결합니다. React의 App.js와 비슷한 역할이라고 생각하면 됩니다.
첫 번째 모듈 만들기: 실습
이론은 여기까지 하고, 직접 코드를 작성해 보겠습니다. 퍼스널 컬러 프로젝트에서 실제로 쓸 유틸리티 함수를 모듈로 만들어 봅니다.
색상 유틸리티 모듈
퍼스널 컬러 분석에서는 색상을 다양한 형식으로 변환해야 합니다. RGB를 HSL로 바꾸거나, 색상 코드를 화면에 표시하는 일이 자주 생깁니다. 이런 함수들을 src/utils/colors.js에 모아 놓겠습니다.
// src/utils/colors.js /** * RGB 값을 '#ff5733' 같은 HEX 문자열로 변환합니다. */ export function rgbToHex(r, g, b) { const toHex = (value) => { const hex = value.toString(16) return hex.length === 1 ? '0' + hex : hex } return '#' + toHex(r) + toHex(g) + toHex(b) } /** * HEX 문자열을 { r, g, b } 객체로 변환합니다. */ export function hexToRgb(hex) { const result = hex.replace('#', '') return { r: parseInt(result.substring(0, 2), 16), g: parseInt(result.substring(2, 4), 16), b: parseInt(result.substring(4, 6), 16), } } /** * RGB 값을 HSL 값으로 변환합니다. * 반환값: { h: 0~360, s: 0~100, l: 0~100 } */ export function rgbToHsl(r, g, b) { r = r / 255 g = g / 255 b = b / 255 const max = Math.max(r, g, b) const min = Math.min(r, g, b) const diff = max - min let h = 0 let s = 0 let l = (max + min) / 2 if (diff !== 0) { s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min) if (max === r) { h = ((g - b) / diff) + (g < b ? 6 : 0) } else if (max === g) { h = ((b - r) / diff) + 2 } else { h = ((r - g) / diff) + 4 } h = h / 6 } return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100), } }
코드가 길어 보이지만 하나씩 살펴보면 어렵지 않습니다. rgbToHex는 RGB 숫자 세 개를 받아서 웹에서 쓰는 색상 코드로 바꿉니다. rgbToHex(255, 87, 51)을 호출하면 '#ff5733'을 돌려줍니다. hexToRgb는 반대로 색상 코드를 RGB 객체로 바꿉니다. rgbToHsl은 4편에서 피부색을 분석할 때 가장 많이 쓰는 함수입니다. 색상(Hue), 채도(Saturation), 명도(Lightness) 값으로 변환합니다.
세 함수 모두 export를 붙였으므로 다른 파일에서 가져다 쓸 수 있습니다.
한 가지 알아둘 점이 있습니다. 지금 이 함수들은 RGB 값이 0에서 255 사이의 정수로 들어온다고 가정합니다. 음수나 256 이상의 값을 넣으면 비정상적인 결과가 나오고, hexToRgb에 #fff 같은 축약형 HEX를 넣어도 제대로 동작하지 않습니다. 일부러 단순하게 만들었습니다. 사용자 입력을 직접 받는 4편에서 범위 체크와 축약형 HEX 변환 로직을 추가할 예정입니다.
앱 초기화 모듈
src/components/ 폴더에 앱의 메인 화면을 그리는 모듈을 만듭니다.
// src/components/app.js import { rgbToHex, rgbToHsl } from '../utils/colors.js' export default function renderApp(container) { container.innerHTML = ` <div class="min-h-screen bg-gradient-to-b from-purple-50 to-pink-50"> <header class="py-8"> <div class="max-w-2xl mx-auto px-4 text-center"> <h1 class="text-3xl font-bold text-gray-800">퍼스널 컬러 분석기</h1> <p class="mt-2 text-gray-500">사진 한 장으로 나에게 어울리는 색을 찾아보세요</p> </div> </header> <main class="max-w-2xl mx-auto px-4 pb-16"> <div class="bg-white rounded-2xl shadow-md p-6"> <p class="text-gray-600 mb-4"> 이 자리에 이미지 업로드 기능이 들어갈 예정입니다. (2편에서 구현) </p> <div id="color-demo" class="mt-6 border-t pt-6"> <h2 class="text-lg font-semibold text-gray-700 mb-3">모듈 동작 테스트</h2> <div id="color-samples" class="flex gap-3 flex-wrap"></div> </div> </div> </main> </div> ` displayColorSamples() } function displayColorSamples() { const samples = [ { r: 255, g: 87, b: 51 }, { r: 46, g: 134, b: 193 }, { r: 39, g: 174, b: 96 }, { r: 155, g: 89, b: 182 }, { r: 241, g: 196, b: 15 }, ] const container = document.querySelector('#color-samples') samples.forEach(({ r, g, b }) => { const hex = rgbToHex(r, g, b) const hsl = rgbToHsl(r, g, b) const card = document.createElement('div') card.className = 'flex flex-col items-center gap-1' card.innerHTML = ` <div class="w-16 h-16 rounded-lg shadow-sm" style="background-color: ${hex}"></div> <span class="text-xs text-gray-500">${hex}</span> <span class="text-xs text-gray-400">H:${hsl.h} S:${hsl.s} L:${hsl.l}</span> ` container.appendChild(card) }) }
이 파일이 하는 일은 이렇습니다. renderApp 함수가 메인 화면의 HTML을 만들어 넣습니다. 그 아래 displayColorSamples 함수가 5개의 샘플 색상을 카드 형태로 보여줍니다. 각 카드에는 색상 사각형, HEX 코드, HSL 값이 표시됩니다.
renderApp은 export default로 내보냈습니다. 이 파일의 핵심 기능이 딱 하나이기 때문입니다. displayColorSamples는 export 없이 내부에서만 사용합니다. 외부에서 이 함수를 직접 호출할 일이 없기 때문입니다.
맨 위의 import { rgbToHex, rgbToHsl } from '../utils/colors.js'를 보면, 아까 만든 색상 유틸리티 모듈에서 두 함수를 가져오고 있습니다. ../는 "한 단계 위 폴더"를 의미합니다. src/components/app.js에서 ../utils/colors.js는 src/utils/colors.js를 가리킵니다.
main.js에서 모듈 연결하기
마지막으로 src/main.js를 수정해서 모든 것을 연결합니다.
// src/main.js import './style.css' import renderApp from './components/app.js' const appContainer = document.querySelector('#app') renderApp(appContainer)
네 줄이 전부입니다. CSS를 불러오고, app.js에서 renderApp 함수를 가져와서, #app 요소에 연결합니다. main.js는 교통 정리만 하고, 실제 동작은 각 모듈이 맡습니다.
import 경로를 주의 깊게 보면 ./components/app.js라고 썼습니다. ./src/components/app.js가 아닙니다. 상대 경로의 기준은 항상 현재 파일의 위치입니다. main.js가 src/ 폴더 안에 있으므로, 같은 src/ 안의 components/app.js를 가리킬 때 ./components/app.js가 맞습니다. 이 규칙을 기억해 두면 경로 때문에 에러가 나는 일을 줄일 수 있습니다.
개발 서버를 실행해서 결과를 확인합니다.
npm run dev
브라우저에 보라색-핑크색 그라데이션 배경 위에 하얀 카드가 나타나고, 5개의 색상 사각형이 HEX 코드와 HSL 값과 함께 표시됩니다. 색상 사각형을 보면서 rgbToHex와 rgbToHsl 함수가 정확히 동작하는지 눈으로 확인할 수 있습니다.
코드 흐름 정리
지금까지 만든 파일들의 관계를 정리하면 이렇습니다.
index.html └── <script src="/src/main.js"> │ src/main.js (진입점) ├── import './style.css' -> Tailwind CSS 적용 └── import renderApp -> src/components/app.js │ renderApp(container) 호출 ├── 화면 HTML 생성 └── displayColorSamples() 호출 ├── rgbToHex() -> src/utils/colors.js └── rgbToHsl() -> src/utils/colors.js
index.html이 src/main.js를 로드하고, main.js가 app.js의 renderApp 함수를 호출합니다. renderApp은 화면 HTML을 생성한 뒤 displayColorSamples를 호출하고, 이 함수가 colors.js에서 가져온 rgbToHex와 rgbToHsl을 사용해 색상 카드를 만듭니다. 각 파일은 자기 역할만 합니다. colors.js는 색상 변환만 하고 화면은 건드리지 않습니다. app.js는 화면만 담당하고 색상 계산은 colors.js에 맡깁니다.
나중에 프로젝트가 커져도 이 구조를 그대로 유지할 수 있습니다. 3편에서 MediaPipe로 얼굴을 인식하는 코드를 추가할 때도 src/utils/faceDetector.js 파일을 만들어서 app.js에서 import { detectFace } from '../utils/faceDetector.js'로 불러오면 됩니다. 파일 이름만 보면 어디에 무슨 코드가 있는지 바로 알 수 있습니다.
마무리: 뼈대 위에 살을 붙일 차례
1편에서 한 일을 돌아보면 이렇습니다. Vite로 프로젝트를 만들었고, Tailwind CSS v4를 설치해서 연결했고, ES 모듈로 JavaScript 파일을 역할별로 나누는 방법을 배웠습니다. 색상 변환 유틸리티와 메인 화면 컴포넌트를 만들어서 실제로 동작하는 것까지 확인했습니다.
파일을 나누는 건 처음에는 번거롭게 느껴질 수 있습니다. 파일 하나에 다 넣으면 되는데 왜 굳이 여러 개로 나누는지 의문이 들 수도 있습니다. 하지만 2편에서 이미지 업로드 기능을 추가하고, 3편에서 얼굴 인식 기능을 붙이고, 4편에서 분석 알고리즘을 넣을 때, 각 기능이 깔끔하게 분리되어 있는 것의 장점을 체감하게 됩니다.
2편에서는 Tailwind CSS로 모바일에서도 잘 작동하는 반응형 UI를 만들고, 사용자가 이미지를 올리면 화면에 표시하는 기능을 구현합니다. src/components/ 폴더에 새로운 모듈이 하나둘 추가되는 과정을 지켜보면, 1편에서 잡아놓은 구조가 왜 필요했는지 자연스럽게 이해하게 됩니다.
참고 자료






댓글
댓글을 작성하려면 이 필요합니다.