HTML·CSS·JavaScript 파일 분리, 언제 해야 할까? 작은 웹게임 폴더 구조 기준

한 파일로 시작해도 됩니다. 수정할 곳을 찾기 어렵거나, 같은 코드를 여러 페이지에서 쓰기 시작했다면 나눌 때입니다. 처음에는 HTML·CSS·JavaScript 세 파일이면 충분합니다. CSS만 다른 페이지에서 재사용한다면 CSS부터 따로 빼도 됩니다.

이 글은 프레임워크와 빌드 도구가 없는 작은 웹게임을 기준으로 설명합니다. 파일 수를 늘리는 것이 목표는 아닙니다. 화면의 구조는 HTML, 모양은 CSS, 동작은 JavaScript에 두고 다음에 고칠 위치를 쉽게 찾는 것이 목적입니다.

지금 무엇을 나누면 될까?

현재 작업 먼저 할 일
한 화면의 규칙을 짧게 시험하고 있음 한 파일을 유지해도 됩니다.
같은 스타일을 여러 HTML 페이지에 복사함 공통 CSS를 외부 파일로 옮깁니다.
화면 코드와 동작 코드 사이를 계속 오가며 수정 위치를 놓침 CSS와 JavaScript를 역할별로 분리합니다.
점수·입력·저장 기능이 각자 바뀌거나 다른 게임에서도 필요함 필요한 기능부터 JavaScript 모듈로 나눕니다.
그림이 안 보이거나 파일 경로가 꼬임 폴더를 더 만들기 전에 요청 주소와 실제 파일 위치를 확인합니다.

몇 줄부터 나눠야 한다는 정답은 없습니다. 짧은 단일 파일 데모는 전달하기 편하고, 여러 페이지가 공통 스타일을 쓰는 작업은 외부 CSS가 수정하기 편합니다. 한 페이지 안에 같은 버튼이 여러 개 있다는 이유만으로 파일을 늘릴 필요도 없습니다. 먼저 같은 클래스를 써서 스타일을 공유할 수 있는지 살펴보세요.

파일을 더블클릭해서 실행할 수 있는 단일 파일 예제도 있지만, 모듈이나 서버에서 가져오는 자료가 들어가면 실행 조건이 달라집니다. ‘HTML 하나면 언제나 오프라인에서 된다’는 뜻은 아닙니다.

첫 분리는 세 파일부터

처음부터 복잡한 폴더 구조를 만들 필요는 없습니다. 같은 폴더에 세 파일을 두고, 그림이 있다면 images 폴더만 추가합니다.

my-game/
├── index.html
├── styles.css
├── game.js
└── images/
    ├── player.png
    └── background.png

HTML의 <style> 안에 있던 CSS는 styles.css로, <script> 안에 있던 JavaScript는 game.js로 옮깁니다. CSS·JS 파일 안에는 style이나 script 태그를 넣지 않습니다. HTML에는 연결 태그를 둡니다.

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="styles.css">
  <script defer src="game.js"></script>
</head>

위 예제는 외부 일반 스크립트에 defer를 붙인 방식입니다. HTML 해석을 마친 뒤 실행하므로 버튼이나 캔버스를 찾는 코드에 쓸 수 있습니다. 여러 defer 스크립트는 HTML에 적힌 순서대로 실행됩니다. async는 다운로드가 끝난 순서에 영향을 받으니 의존 관계가 있는 코드를 단순히 async로 바꾸지 마세요.

원래 모듈 스크립트나 외부 라이브러리를 쓰던 프로젝트라면 이 연결 예제로 무조건 바꾸지 않습니다. 기존의 type, 실행 순서, 의존 관계를 먼저 확인하세요.

CSS부터 옮기고, 동작은 그다음에

  1. 현재 한 파일 버전이 정상인지 확인하고 변경 전 복사본을 보관합니다.
  2. CSS만 외부 파일로 옮깁니다. 원래 style 태그가 있던 위치에 link를 두어 다른 스타일과의 순서를 유지합니다.
  3. 원래 style 블록은 제거합니다. 배치·색·배경이 같은지 확인합니다.
  4. JavaScript를 외부 파일로 옮기고 연결합니다. 옮긴 원래 script 블록은 제거하되, 다른 라이브러리 연결까지 지우지 않습니다.
  5. 버튼·키보드·터치·점수·재시작 등 기존 동작을 확인합니다.
  6. 분리가 끝난 뒤 새 기능이나 폴더 이동을 진행합니다.

원래 코드를 남긴 채 외부 파일도 연결하면 두 번 실행될 수 있습니다. 클릭 처리를 두 곳에서 각각 등록하면 한 번 눌러도 점수가 두 번 올라갑니다. 선언 방식에 따라서는 같은 이름을 다시 선언했다는 오류가 먼저 날 수도 있습니다.

CSS도 원래 style 블록을 남기면 외부 파일을 바꿔도 이전 규칙이 남아 보일 수 있습니다. CSS 위치를 옮길 때는 상대 경로뿐 아니라 다른 규칙과의 순서도 확인하세요. 분리 작업과 새 기능 추가를 함께 하면 무엇 때문에 달라졌는지 찾기 어려워집니다.

같은 이미지인데 왜 배경만 사라질까?

HTML의 <img src="images/player.png">는 문서의 기준 URL에서 경로를 찾습니다. 별도의 base 태그가 없는 일반적인 프로젝트에서는 HTML 파일 위치를 기준으로 보면 됩니다. 반면 외부 CSS의 url()은 CSS 파일 위치가 기준입니다.

styles.css를 styles/main.css로 옮겼다면 HTML의 연결은 href="styles/main.css"로, CSS 안의 배경 경로는 url('../images/background.png')로 바뀝니다. 여기서 ../는 styles 폴더에서 한 단계 위로 올라간다는 뜻입니다.

로컬에서는 되는데 서버에서는 안 된다면 대소문자도 비교하세요. Player.png와 player.png는 서버에 따라 다른 파일로 처리됩니다. 폴더와 파일 이름을 소문자로 맞추고 공백 대신 하이픈을 쓰면 이런 혼동을 줄일 수 있습니다.

작은 예제로 분리 전후를 확인한 결과

점수 버튼과 배경 이미지가 있는 최소 예제를 별도로 만들고, macOS의 Ego Lite 브라우저에서 로컬 HTTP 서버로 실행했습니다. Codex가 확인한 재현 결과이며 필자의 실제 웹게임 작업 경험은 아닙니다. 큰 게임의 모든 기능이나 로딩 성능을 시험한 결과로 확대할 수는 없습니다.

예제 조건 실제 확인 결과
HTML 안에 CSS·JS를 넣은 한 파일 클릭 한 번에 점수 1, 배경 이미지 정상
동일 내용을 CSS·JS로 분리하고 원래 블록 제거 클릭 한 번에 점수 1, 글자색·버튼 여백 동일, 배경 정상
원래 클릭 처리와 외부 클릭 처리를 함께 등록 클릭 한 번에 점수 2
CSS를 styles/main.css로 이동하고 이미지 경로 유지 styles/images/background.svg 요청, HTTP 404
CSS 경로를 ../images/background.svg로 수정 images/background.svg 요청, HTTP 200
검증에 쓴 버튼 예제 보기

앞의 head 연결 예제에 아래 내용을 더하면 됩니다. HTML의 body 안에 버튼과 점수 표시를 넣고, CSS와 JavaScript는 각각 연결된 외부 파일에 둡니다.

index.html의 body 내용

<button id="add-score" type="button">점수 +1</button>
<output id="score">0</output>

styles.css — images/background.svg는 아래처럼 최소 그림으로 만들 수 있습니다.

body { font-family: sans-serif; background-image: url('images/background.svg'); }
#score { color: rgb(18, 91, 168); }
button { padding: 10px 16px; }
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="16" height="16" fill="#f3f6fa"/></svg>

game.js

(() => {
  const button = document.querySelector('#add-score');
  const score = document.querySelector('#score');
  button.addEventListener('click', () => {
    score.textContent = String(Number(score.textContent) + 1);
  });
})();

중복 등록 조건은 위 JS를 원래 HTML의 script 블록에도 그대로 남긴 경우입니다. 정상 분리에서는 기존 블록을 제거합니다. HTTP 상태를 비교하려면 로컬 서버에서 실행합니다.

재현용 그림은 background.svg이고, 앞의 폴더 예시는 background.png입니다. 확장자는 실제 파일과 맞춰야 합니다. 이 비교에서 중요한 것은 파일 수가 아니라 기존 동작·스타일이 유지됐는지, 이동한 파일의 연결 경로가 맞는지입니다.

JavaScript 모듈은 기능이 갈라질 때

점수 계산을 따로 시험하거나, 입력 처리를 다른 게임에서 다시 쓰거나, 저장 기능만 바꾸게 됐다면 기능별 파일을 고려할 수 있습니다. 아래는 그런 경계가 생긴 뒤 사용할 수 있는 구조입니다. 파일 몇 개 없는 게임에 빈 폴더부터 만들 필요는 없습니다.

my-game/
├── index.html
├── styles/
│   └── main.css
├── scripts/
│   ├── main.js
│   ├── input.js
│   ├── score.js
│   └── storage.js
└── images/

HTML에서 <script type="module" src="scripts/main.js"></script>로 시작 파일을 연결하고, main.js가 필요한 기능을 import합니다. CSS·JS를 외부 파일로 옮기는 것과 모듈로 바꾸는 것은 다른 작업입니다. 역할별 분리를 마친 뒤 모듈 전환을 따로 확인하면 원인을 구분하기 쉽습니다.

브라우저에서 모듈을 쓸 때 상대 경로는 가져오는 JS 파일이 기준입니다. 별도 import map이 없는 경우 ./score.js처럼 경로와 확장자를 명확히 적고 export·import 이름을 맞춥니다. 모듈은 기본적으로 HTML 해석 뒤 실행되지만, 선언과 가져온 이름의 범위도 일반 스크립트와 다르므로 파일을 쪼개기만 하면 끝나는 것은 아닙니다.

file: 주소로 더블클릭해 열면 모듈 보안 정책 때문에 CORS 오류가 날 수 있습니다. 모듈 단계에서는 개발 환경의 로컬 서버나 미리보기 서버로 실행하고 http://localhost… 주소인지 확인하세요. 이 단계가 아니라면 모듈 도입부터 서두르지 않아도 됩니다.

연결이 안 될 때 볼 순서

증상 먼저 확인할 것
CSS·JS 요청이 404 Network의 요청 주소와 실제 폴더·파일 이름·대소문자 비교
화면은 보이는데 버튼이 멈춤 Console의 첫 오류, 요소 id와 선택자, 스크립트 실행 시점 확인
버튼이 두 번 반응함 중복 연결이나 이벤트 중복 등록 확인
배경 이미지만 안 보임 외부 CSS 위치를 기준으로 url() 경로 확인
모듈로 바꾼 뒤 실패 로컬 서버 실행, import 경로·이름·스크립트 type 확인

예를 들어 Cannot read properties of null처럼 요소를 못 찾는 오류가 보이면 선택자와 실행 시점부터 봅니다. id="start-button"인데 코드가 #startButton을 찾고 있거나, HTML 요소가 만들어지기 전에 실행되는지 확인합니다. 파일 분리 자체가 이런 문제를 자동으로 해결하지는 않습니다.

수정한 CSS가 계속 예전처럼 보이면 Network에서 실제 받은 파일 내용을 확인하고 캐시를 제외한 새로고침으로 비교하세요. 요청 자체가 보이지 않을 때는 연결 태그뿐 아니라 Network 기록 시점과 필터도 확인합니다. 창 너비를 줄여 버튼·캔버스가 잘리지 않는지도 마지막에 확인합니다.

파일 수보다 다음 수정이 쉬운가

같은 규칙을 여러 페이지에서 쓰거나 수정 위치를 찾는 시간이 길어지면 필요한 파일부터 분리하세요. 단순한 예제라면 한 파일을 유지해도 됩니다. 기능별 모듈은 점수·입력·저장처럼 책임이 갈라진 뒤에 도입하면 됩니다.

파일을 나누면 무조건 빨라진다는 뜻도 아닙니다. 요청 수·캐시·서버·파일 크기에 따라 달라지므로 성능은 실제 배포 환경에서 따로 측정해야 합니다. 작업 구조와 배포 결과물도 다를 수 있습니다. 첫 분리의 성공 기준은 같은 화면과 동작을 유지하면서 다음에 고칠 곳을 더 쉽게 찾는 것입니다.

components·utils·services 같은 이름은 필요해졌을 때 만들어도 됩니다. final·final2·real-final을 계속 복제하기보다는 변경 전 파일의 날짜·목적을 분명히 하고, 작업이 이어지면 Git 같은 버전 관리도 별도로 익힐 수 있습니다.

참고한 공식 자료

댓글 남기기