서비스워커란?
정의
- 특정 출처(사이트)의 하나 혹은 그 이상의 페이지를 제어하는 스크립트
- 이벤트 기반 워커로서 JavaScript로 작성된 파일
기능
- 서비스 워커는 자신이 제어하는 페이지에서 발생하는 이벤트를 수신할 수 있다.
- 웹에서 네트워크 요청과 같은 이벤트를 가로채어 수정할 수 있고, 이를 다시 페이지로 돌려보낼 수 있다.
- 또한, 서비스에서 사용하는 리소스를 캐싱할 수 있다.
서비스워커 사용법
1. 서비스워커 만들기
// app.js
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then(function (registration) {
console.log("Service Worker registered with scope:", registration.scope);
})
.catch(function (err) {
console.log("Service Worker registration failed", err);
});
}
- 브라우저가 서비스워커를 지원한다면 → navigator.serviceWorker.register를 호출
- navigator.serviceWorker.register: 서비스 워커 스크립트의 URL, option 객체의 2개 인자를 받는 함수
2. 웹에서 컨텐츠 가져오기
// /public/sw.js
self.addEventListener("fetch", function (event) {
console.log("Fetch request for: ", event.request.url);
if (event.request.url.includes("/img/logo.png")) {
event.respondWith(fetch("/img/logo-flipped.png"));
}
});
- 웹사이트에서 발생하는 모든 fetch 요청을 중간에 가로채서 분석하고 조작한다.
- 들어오는 요청 중 /img/logo-flipped.png인 요청을 가로채서 → 다른 로고(/img/logo-flipped.png)를 fetch로 response를 생성하고 → 원래 request 이벤트에 응답한다.
3. Scope로 요청의 범위 조정하기
- 서비스 워커는 서비스 워커 파일의 위치나 navigator.serviceWorker.register에서 지정해준 Scope 옵션에 따라 요청의 범위를 조정할 수 있다.
- 서비스워커를 root 폴더에 저장했으므로 모든 요청을 다 거친다.
// js 폴더에서 발생한 요청만 전달
navigator.serviceWorker.register("sw.js", {scope: "/js"})
// js 폴더 내부를 대상으로 하는 요청만 전달
navigator.serviceWorker.register("/js/sw.js")
- 만약 위 코드에서 js 폴더 내에 index.html 파일을 넣는다면
- 서비스워커는 /js/index.html 에서 발생하는 요청만 전달받는다!
4. 오프라인 요청 감지하기
self.addEventListener("fetch", function (event) {
event.respondWith(
fetch(event.request).catch(function () {
return new Response("hello world !\n");
})
);
});
- 모든 fetch 요청에 대해, 예외가 발생하면 catch로 받아 새로운 응답을 보내준다.
- 오프라인이면 당연히 fetch 요청에서 에러가 발생하므로!
- 이를 이용해서, 오프라인이면 특정 html 파일을 응답하도록 만들 수도 있다.
self.addEventListener("fetch", function (event) {
event.respondWith(
fetch(event.request).catch(function () {
return fetch('/offine.html');
})
);
});
CacheStorage API
오프라인 상태일 때 어떻게 상태를 알고 요청을 받아서 넘길 수 있을까?
- 온라인일 때 해당 파일도 같이 받아와서 캐시해야 한다.
- 그럼 파일을 캐시하는 과정을 한번 알아보자.
install Event에서 파일 캐싱하기
- 서비스 워커의 install 이벤트는 첫번째 단계 “설치 중” 에서 단 한번 발생한다.
- 서비스 워커가 가장 처음 등록된 직후
- 이벤트가 활성화 되기 전
- 서비스 워커가 페이지를 제어하고 fetch 이벤트 수신을 시작하기 전에, 오프라인화 가능한 모든 파일을 캐싱할 수 있다.
self.addEventListener("install", function (event) {
event.waitUntil(
caches.open("c").then(function (cache) {
return cache.add("/offline.html");
})
);
});
- install 이벤트는 설치 단계에서 새로운 서비스 워커가 등록된 직후 호출된다.
- 파일을 가져와 캐시에 저장하는 일이 비동기적으로 일어나기 때문에, 해당 이벤트가 완료될 때까지 install 이벤트를 연기해야 한다.
waitUntil
: 전달된 프로미스가 resolve될 때까지 이벤트의 수명을 연장한다.- 캐시가 정상적으로 되지 않을 때 install 자체를 취소해야 할 수도 있으므로, install에서 waitUntil을 해준다.
CacheStorage로부터 응답 받기
self.addEventListener("fetch", function (event) {
event.respondWith(
fetch(event.request).catch(function () {
return caches.match("./offline.html");
})
);
});
- 오프라인 감지 시 CacheStorage에서 caches.match를 사용하여 캐시된 데이터 중 원하는 파일을 반환받는다.
여러개 캐싱하고 원하는 응답 받기
const CACHE_NAME = "git-cache";
const CACHE_URLS = [
"/offline.html",
"/img/logo-header.png",
];
/** cache.addAll을 통해 여러 파일을 객체로 묶어 한번에 캐싱 */
self.addEventListener("install", function (event){
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(CACHE_URLS);
})
);
});
/** fetch 응답 로직
* request fetch 과정에서 실패해서 catch로 넘어오면
* -> 캐시에서 해당 request가 있는지 match해서 -> 있다면 응답 리턴
*/
self.addEventListener("fetch", function (event) {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request).then(function(response){
if (response) {
return response;
}
else if (event.request.headers.get("accept").includes("text/html")){
return caches.match("/offline.html");
}
});
})
);
});
- addAll 함수를 사용하여 원하는 파일들을 한번에 캐싱한다.
- match 후 캐시된 파일을 찾을 때 then으로 예외를 처리하는 이유
- cache.match는 찾지 못하면 undefined를 반환
- 프로미스를 reject하기 위해 then으로 처리
서비스워커의 생명주기
생명주기 5단계
- 설치 중 (Installing)
navigator.serviceWorker.register
를 사용하여 새로운 서비스 워커를 등록할 때, js 다운로드 및 파싱 후installing
에 들어간다.- 설치가 무사히 완료된다면(promise가 resolve되면)
installed
단계로 넘어간다.- 설치 중 에러가 발생하면 페이지를 새로고침해서 서비스워커를 다시 등록한다.
- 그래도 여전히 에러가 있으면
redundant(중복)
상태에 빠진다.
- install 이벤트 콜백 내에서
waitUntil
함수를 사용해서 installing 상태를 연장할 수 있다.
- 설치 됨 / 대기 중(installed/waiting)
- 서비스 워커가 성공적으로 설치되면 →
installed
- 현재 활성화된 다른 서비스워커가 앱을 제어하고 있는게 아니라면(단독이라면) →
activating
- 다른 서비스워커가 앱을 제어하고 있다면 →
waiting
- 현재 활성화된 다른 서비스워커가 앱을 제어하고 있는게 아니라면(단독이라면) →
- 서비스 워커가 성공적으로 설치되면 →
- 활성화 중(Activating)
- 서비스 워커가 활성화되어 앱을 제어하기 전에 해당 이벤트가 발생한다.
waitUntil
을 사용하여 호출을 연장할 수 있다.
- 활성화 중(Activating)
- 서비스워커가 활성화되면 페이지를 제어하고, fetch 이벤트와 같은 동작 이벤트를 받을 준비가 된다.
- 서비스워커가 활성화되기 전에 로딩이 시작된 페이지는 제어할 수 없다.
- 페이지 로딩이 시작되기 전에만 페이지 제어 권환을 가져올 수 있다.
- 중복(Redunant)
- 서비스 워커가 설치 중 실패하거나 새로운 버전으로 교체되면
Redunant(중복 상태)
가 된다. - 이 상태에서는 앱에 아무런 영향을 미칠 수 없다.
- 서비스 워커가 설치 중 실패하거나 새로운 버전으로 교체되면
서비스워커의 수명
- 사용자가 사이트를 방문하면 → 서비스 워커가 시작되고 → 페이지에서 이벤트 처리를 완료하면 → 종료된다.
- 다른 이벤트가 나중에 들어온다면 다시 서비스워커는 시작되고 → 종료된다.
- 때문에 이 이벤트 리스너 코드의 실행-종료가 중요하다. 서비스워커 작업 도중이어도 이벤트 리스터 코드의 실행이 종료되면 정상적으로 작동하지 않는다.
- 이때
waitUntil
을 사용해서 브라우저가 서비스워커 작업이 완료될 때까지 기다리게 할 수 있다.
- 이때
self.addEventListener("push", function (event) {
event.waitUntil( fetch("/updates").then(function () {
return self.registration.showNotification("New Update");
})
);
});
서비스 워커 업데이트
- 서비스 워커 내의 코드를 바꾸고 새로고침했을 때 업데이트된 내용이 반영되지 않을 수 있다.
- 페이지를 새로고침 하면 브라우저는 서비스 워커 스크립트에 대한 업데이트가 있는지 확인하고, 업데이트가 있다면 새로운 서비스 워커를 설치(install)한다.
- 하지만 바로 교체되지 않고 대기중(waiting) 상태에 남는다.
- 현재 활성화된 서비스 워커가 제어하는 페이지가 더 이상 열려 있지 않아야 ⇒ 새 서비스 워커가 활성화된다.
- 기존 탭과 윈도우 창이 종료되거나
- 범위를 벗어난 새로운 페이지로 이동할 때까지 유지
서비스 워커 캐시 관리
- 캐시명에 버전 넘버를 달고, 파일이 변경될 때마다 버전 숫자를 증가시키는 방법이 있다.
var CACHE_NAME = "gih-cache-v2";
var CACHED_URLS = [
"/index-offline.html",
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
"/css/gih-offline.css",
"/img/jumbo-background-sm.jpg",
"/img/logo-header.png",
];
self.addEventListener("install", function (event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(CACHED_URLS);
})
);
});
self.addEventListener("fetch", function (event) {
event.respondWith(
fetch(event.request).catch(function () {
return caches.match(event.request).then(function (response) {
if (response) {
return response;
} else if (event.request.headers.get("accept").includes("text/html")) {
return caches.match("/index-offline.html");
}
});
})
);
});
- 혹은 workbox 같은 pwa 라이브러리를 이용해서 캐싱을 쉽게 할 수 있다.
- https://developer.chrome.com/docs/workbox/what-is-workbox?hl=ko
- 이번 서비스에서도 workbox의 옵션을 이용해서 캐싱을 관리했다.
출처
[PWA] 서비스 워커의 생명 주기와 캐시 관리
본 포스팅은 아래 링크의 만들면서 배우는 프로그레시브 웹 앱 책을 보며 공부한 내용을 스스로 정리한 것 입니다.만들면서 배우는 프로그레시브 웹 앱이번 포스팅부터 사용할 실습 코드는 포
velog.io
'PWA' 카테고리의 다른 글
PWA 푸시알림 기능 조사 (1) | 2025.05.18 |
---|---|
PWA란? (3) | 2025.05.04 |