`Service Worker` 자바스크립트에서 사용하기
이 글은 자바스크립트의 Service Worker
개념을 설명합니다.
Service Worker
의 기초부터 실전 캐시 제어까지 단계별로 설명합니다.
YouTube Video
offline.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Offline</title>
6</head>
7<body>
8 <h1>You are offline</h1>
9 <p>This is the offline fallback page.</p>
10</body>
11</html>
style.css
1body {
2 font-family: sans-serif;
3 background-color: #f0f0f0;
4 padding: 20px;
5}
6h1 {
7 color: #333;
8}
javascript-service-worker.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <div class="container">
111 <h2>HTML Sample</h2>
112 <button id="fetchBtn">Fetch Test</button>
113 </div>
114
115 <script>
116 // Override console.log to display messages in the #output element
117 (function () {
118 // Override console.log
119 const originalLog = console.log;
120 console.log = function (...args) {
121 originalLog.apply(console, args);
122 const message = document.createElement('div');
123 message.textContent = args.map(String).join(' ');
124 output.appendChild(message);
125 };
126
127 // Override console.error
128 const originalError = console.error;
129 console.error = function (...args) {
130 originalError.apply(console, args);
131 const message = document.createElement('div');
132 message.textContent = args.map(String).join(' ');
133 message.style.color = 'red'; // Color error messages red
134 output.appendChild(message);
135 };
136 })();
137
138 document.getElementById('executeBtn').addEventListener('click', () => {
139 // Prevent multiple loads
140 if (document.getElementById('externalScript')) return;
141
142 const script = document.createElement('script');
143 script.src = 'javascript-service-worker.js';
144 script.id = 'externalScript';
145 //script.onload = () => console.log('javascript-service-worker.js loaded and executed.');
146 //script.onerror = () => console.log('Failed to load javascript-service-worker.js.');
147 document.body.appendChild(script);
148 });
149 </script>
150</body>
151</html>
Service Worker
자바스크립트에서 사용하기
Service Worker
는 브라우저와 네트워크 사이에 위치하여 요청 캐싱 및 오프라인 지원을 가능하게 하는 자바스크립트 기능입니다. PWA(프로그레시브 웹 앱)의 핵심 기술로, 웹 애플리케이션에 네이티브 앱과 유사한 경험을 제공합니다.
Service Worker
란 무엇인가요?
Service Worker
는 브라우저의 백그라운드 스레드에서 동작하는 자바스크립트 파일입니다. 페이지와는 별도의 스레드에서 동작하며, UI에는 접근할 수 없지만 네트워크 요청을 가로채고, 캐시를 관리하며, 푸시 알림을 처리할 수 있습니다.
Service Worker
의 주요 특징은 다음과 같습니다:.
- 로컬호스트를 제외하면 HTTPS 환경에서만 동작합니다.
- Promise 기반의 비동기 API를 사용합니다.
install
,activate
,fetch
,push
와 같은 이벤트를 사용하는 이벤트 기반 구조입니다.
Service Worker
등록하기
먼저, 브라우저에 Service Worker
를 등록하는 코드를 작성해봅시다.
1if ('serviceWorker' in navigator) {
2 window.addEventListener('load', () => {
3 navigator.serviceWorker.register('/sw.js')
4 .then(registration => {
5 console.log(
6 'Service Worker registered with scope:',
7 registration.scope
8 );
9 })
10 .catch(error => {
11 console.error('Service Worker registration failed:', error);
12 });
13 });
14}
설명
navigator.serviceWorker.register()
를 사용하여/sw.js
(Service Worker
파일)을 등록합니다.- 등록 시에는
then
으로 성공 처리,catch
로 에러 처리를 할 수 있습니다. registration.scope
는Service Worker
의 영향을 받는 경로 범위(스코프)를 나타냅니다.- 기본적으로 스코프는 등록된 파일(이 경우,
/sw.js
)이 위치한 디렉터리와 그 하위 디렉터리입니다.
Service Worker
스코프
스코프를 제한하고 싶다면, register
의 두 번째 인자를 사용하여 scope
를 지정할 수 있습니다.
1navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
2.then(registration => {
3 console.log(
4 'Service Worker registered with scope:',
5 registration.scope
6 );
7});
설명
- 이 설정으로
/app/
아래의 페이지만Service Worker
가 제어하게 됩니다.
Service Worker
파일 작성하기
다음으로 sw.js
라는 파일을 만들고 기본 이벤트를 구현합니다.
1// sw.js
2const CACHE_NAME = 'my-cache-v1';
3const urlsToCache = [
4 '/',
5 '/index.html',
6 '/styles.css',
7 '/script.js',
8 '/offline.html'
9];
이 코드는 캐시될 리소스 목록을 정의합니다.
각 이벤트의 역할과 동작 원리
install
1// Install event (initial caching)
2self.addEventListener('install', event => {
3 console.log('[ServiceWorker] Install');
4 event.waitUntil(
5 caches.open(CACHE_NAME).then(cache => {
6 console.log('[ServiceWorker] Caching app shell');
7 return cache.addAll(urlsToCache);
8 })
9 );
10});
self.addEventListener('install')
은Service Worker
가 처음 등록될 때 실행됩니다. 이 단계에서는 필요한 파일들이 미리 캐시됩니다.
activate
1// Activation event (delete old caches)
2self.addEventListener('activate', event => {
3 console.log('[ServiceWorker] Activate');
4 event.waitUntil(
5 caches.keys().then(keyList => {
6 return Promise.all(keyList.map(key => {
7 if (key !== CACHE_NAME) {
8 console.log('[ServiceWorker] Removing old cache:', key);
9 return caches.delete(key);
10 }
11 }));
12 })
13 );
14 return self.clients.claim();
15});
activate
이벤트에서는 오래된 캐시를 삭제하여 저장 공간을 최적화합니다. 새 버전의 캐시만 유지됩니다.
fetch
1// Fetch event (cache-first strategy)
2self.addEventListener('fetch', event => {
3 console.log('[ServiceWorker] Fetch', event.request.url);
4 event.respondWith(
5 caches.match(event.request).then(response => {
6 return response || fetch(event.request).catch(() => caches.match('/offline.html'));
7 })
8 );
9});
모든 HTTP 요청을 가로채서 캐시된 버전이 있으면 반환하고, 없으면 네트워크에서 불러옵니다. 오프라인일 때는 대체 페이지(예: offline.html
)를 반환합니다.
작동을 확인합니다
실제로 Service Worker
가 어떻게 동작하는지 확인해봅시다.
1document.getElementById('fetchBtn').addEventListener('click', () => {
2 fetch('/style.css')
3 .then(response => response.text())
4 .then(data => {
5 console.log('Fetched data:', data);
6 })
7 .catch(error => {
8 console.error('Fetch failed:', error);
9 });
10});
- 여기서는
Service Worker
의 등록과 테스트 버튼을 클릭하여 리소스 가져오기 동작을 확인합니다.
캐싱 전략 예시
주요 캐싱 전략은 다음과 같습니다:.
캐시 우선(Cache First)
Cache First 전략 구현 예시는 다음과 같습니다:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});
- 이 코드는 캐시 우선 전략을 구현하며, 요청된 리소스가 캐시에 있으면 캐시에서 반환하고, 없으면 네트워크에서 가져옵니다.
네트워크 우선(Network First)
Network First 전략 구현 예시는 다음과 같습니다:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 fetch(event.request)
4 .then(response => {
5 return caches.open(CACHE_NAME).then(cache => {
6 cache.put(event.request, response.clone());
7 return response;
8 });
9 })
10 .catch(() => caches.match(event.request))
11 );
12});
- 이 코드는 네트워크 우선 전략을 구현하며, 요청된 리소스를 먼저 네트워크에서 가져오고 실패하면 캐시에서 가져옵니다.
스타일과 자바스크립트만 캐시하고, API는 실시간으로 접근
스타일과 자바스크립트는 캐싱하고, API에는 실시간으로 접근하는 구현 예시는 다음과 같습니다:.
1self.addEventListener('fetch', event => {
2 if (event.request.url.includes('/api/')) {
3 // Fetch API responses in real-time without caching
4 return;
5 }
6
7 // Use cache-first strategy for static files
8 event.respondWith(
9 caches.match(event.request).then(response => {
10 return response || fetch(event.request);
11 })
12 );
13});
- 이 코드는 항상 API 요청을 실시간으로 접근하고, 스타일시트나 자바스크립트와 같은 정적 파일에는 캐시 우선 전략을 적용합니다.
업데이트 흐름
서비스 워커의 업데이트 흐름은 다음과 같습니다:.
- 새로운
sw.js
가 감지됨 install
이벤트가 실행됨- 이전
Service Worker
가 유휴 상태가 될 때까지 대기함 activate
이벤트가 실행됨- 새로운 Service Worker로 전환됨
controllerchange
이벤트가 발생합니다.
업데이트 감지
Service Worker
가 설치되면 다음 방문까지 이전 것이 계속 사용됩니다. 업데이트를 적용하기 위해, 업데이트를 감지하고 페이지를 새로 고침하는 코드를 사용하는 것이 일반적입니다.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
controllerchange
이벤트는 서비스 워커의 컨트롤러, 즉 현재 페이지를 제어하는 서비스 워커가 변경될 때 발생합니다.- 이미 열려 있는 페이지는 계속 현재 서비스 워커를 사용하며, 새로 설치된 서비스 워커는 즉시 해당 페이지에 적용되지 않습니다. 따라서
controllerchange
이벤트를 이용해 새로운 컨트롤러가 활성화되었음을 감지하고, 페이지를 새로 고침하여 업데이트를 즉시 적용하는 기법이 사용됩니다.
주의사항 및 권장사항
Service Worker
사용 시 다음 사항을 유의하세요:.
-
HTTPS 필수 보안상의 이유로,
localhost
를 제외하면http://
에서는 동작하지 않습니다. -
해시 기반 파일명 사용 캐시 이름에는 파일명, URL, 버전 정보를 포함할 수 있습니다.
-
클라이언트와의 통신
postMessage
를 이용해Service Worker
와 페이지의 자바스크립트 간 통신이 가능합니다.
요약
Service Worker
는 웹 앱에서 오프라인 지원 및 성능 향상을 위한 필수 기술입니다. 설치, 활성화, fetch 처리 등 기본 동작 흐름을 이해하고, 적절한 캐싱 전략을 구현하면 더 완성도 높은 웹 애플리케이션을 개발할 수 있습니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.