서비스 워커를 생각할 때 고려해야 할 사항
서비스 워커는 강력하며 반드시 알아야 하는 주제입니다. 이를 통해 사용자에게 완전히 새로운 수준의 환경을 제공할 수 있습니다. 사이트가 즉시 로드될 수 있습니다. 오프라인에서도 작동할 수 있습니다. 웹의 도달범위와 자유를 갖춘 플랫폼별 앱으로 설치할 수 있으며, 웹의 도달범위와 자유를 갖춘 플랫폼별 앱으로 설치할 수 있으며,
하지만 서비스 워커는 대부분의 웹 개발자가 익숙한 것과는 다릅니다. 학습 곡선이 가파르고 주의해야 할 몇 가지 문제가 있습니다.
Google 개발자와 저는 최근 서비스 워커를 이해하기 위한 무료 게임인 Service Workies 프로젝트를 공동으로 진행했습니다. 빌드하고 서비스 워커의 복잡한 내부 작동 방식을 다루는 동안 몇 가지 문제가 발생했습니다. 가장 도움이 된 것은 묘사적인 비유를 생각해 내는 것이었습니다. 이 게시물에서는 이러한 멘탈 모델을 살펴보고 서비스 워커를 까다롭고 멋지게 만드는 역설적인 특성을 알아봅니다.
동일하지만 다름
서비스 워커를 코딩하는 동안 많은 부분이 익숙하게 느껴질 것입니다. 좋아하는 새로운 JavaScript 언어 기능을 사용할 수 있습니다. UI 이벤트와 마찬가지로 수명 주기 이벤트를 수신 대기합니다. 평소와 같이 약속으로 제어 흐름을 관리합니다.
하지만 다른 서비스 워커 동작은 혼란스러워하게 만듭니다. 특히 페이지를 새로고침해도 코드 변경사항이 적용되지 않는 경우
새 레이어
일반적으로 사이트를 빌드할 때는 클라이언트와 서버라는 두 가지 레이어만 고려하면 됩니다. 서비스 워커는 중간에 있는 완전히 새로운 레이어입니다.
서비스 워커는 사이트에서 사용자의 브라우저에 설치할 수 있는 일종의 브라우저 확장 프로그램이라고 생각하면 됩니다. 설치된 서비스 워커는 강력한 미들 레이어를 사용하여 사이트의 브라우저를 확장합니다. 이 서비스 워커 레이어는 사이트에서 실행하는 모든 요청을 가로채고 처리할 수 있습니다.
서비스 워커 레이어에는 브라우저 탭과는 별개인 자체 수명 주기가 있습니다. 페이지 새로고침만으로는 서비스 워커를 업데이트할 수 없습니다. 서버에 배포된 코드가 페이지 새로고침으로 업데이트되지 않는 것과 마찬가지입니다. 각 레이어에는 업데이트에 관한 고유한 규칙이 있습니다.
Service Workies 게임에서는 서비스 워커 수명 주기에 관한 다양한 세부정보를 다루고 이를 활용하는 방법을 다수 연습할 수 있습니다.
강력하지만 제한적
사이트에 서비스 워커를 사용하면 놀라운 이점을 얻을 수 있습니다. 사이트에서 다음 작업을 할 수 있습니다.
서비스 워커는 할 수 있는 일이 많지만 설계상 제한사항이 있습니다. 사이트와 동기식으로 또는 동일한 스레드에서 작업할 수는 없습니다. 즉, 다음 항목에 액세스할 수 없습니다.
- localStorage
- DOM
- 창
다행히 페이지에서 서비스 워커와 통신할 수 있는 방법은 몇 가지가 있습니다. 직접 postMessage
, 일대일 메시지 채널, 일대다 브로드캐스트 채널 등이 있습니다.
장기 지속이지만 단기 지속
활성 서비스 워커는 사용자가 사이트를 나가거나 탭을 닫은 후에도 계속 실행됩니다. 브라우저는 사용자가 다음에 사이트를 다시 방문할 때 준비되도록 이 서비스 워커를 유지합니다. 첫 번째 요청이 실행되기 전에 서비스 워커가 요청을 가로채 페이지를 제어할 수 있습니다. 이를 통해 사이트가 오프라인으로 작동할 수 있습니다. 서비스 워커는 사용자가 인터넷에 연결되어 있지 않더라도 페이지 자체의 캐시된 버전을 제공할 수 있습니다.
Service Workies에서는 Kolohe (친절한 서비스 워커)가 요청을 가로채고 처리하는 방식으로 이 개념을 시각화합니다.
중지됨
서비스 워커는 영원히 실행되는 것처럼 보이지만 거의 언제든지 중지될 수 있습니다. 브라우저는 현재 아무것도 하지 않는 서비스 워커에 리소스를 낭비하지 않으려고 합니다. 중지되는 것은 종료되는 것과는 다릅니다. 서비스 워커는 계속 설치되고 활성화된 상태로 유지됩니다. 절전 모드로 전환됩니다. 다음에 필요하면 (예: 요청을 처리하기 위해) 브라우저에서 다시 깨웁니다.
waitUntil
서비스 워커는 언제든지 절전 모드로 전환될 수 있으므로 중요한 작업을 하고 있고 낮잠을 자고 싶지 않다고 브라우저에 알릴 방법이 필요합니다. 여기서 event.waitUntil()
가 사용됩니다. 이 메서드는 사용되는 수명 주기를 확장하여 준비될 때까지 수명 주기가 중지되거나 수명 주기의 다음 단계로 넘어가지 않도록 합니다. 이렇게 하면 캐시를 설정하고 네트워크에서 리소스를 가져오는 등의 작업을 할 수 있습니다.
이 예에서는 assets
캐시가 생성되고 검 그림으로 채워질 때까지 서비스 워커 설치가 완료되지 않았다고 브라우저에 알립니다.
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
전역 상태 주의
이 시작/중지가 발생하면 서비스 워커의 전역 범위가 재설정됩니다. 따라서 서비스 워커에서 전역 상태를 사용하지 않도록 주의하세요. 그렇지 않으면 다음에 서비스 워커가 다시 시작될 때 예상과 다른 상태가 되어 곤란해질 수 있습니다.
전역 상태를 사용하는 다음 예시를 살펴보세요.
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
이 서비스 워커는 요청이 있을 때마다 0.13981866382421893
과 같은 숫자를 기록합니다. hasHandledARequest
변수도 true
로 변경됩니다. 이제 서비스 워커가 잠시 유휴 상태가 되므로 브라우저에서 이를 중지합니다. 다음번에 요청이 있으면 서비스 워커가 다시 필요하므로 브라우저가 이를 깨웁니다. 스크립트가 다시 평가됩니다. 이제 hasHandledARequest
가 false
로 재설정되고 favoriteNumber
는 완전히 다른 값인 0.5907281835659033
이 됩니다.
서비스 워커에서는 저장된 상태를 사용할 수 없습니다. 또한 메시지 채널과 같은 항목의 인스턴스를 만들면 버그가 발생할 수 있습니다. 서비스 워커가 중지/시작될 때마다 새 인스턴스가 생성되기 때문입니다.
서비스 워커 3장에서는 중지된 서비스 워커가 깨어나기를 기다리는 동안 모든 색상을 잃는 것으로 시각화합니다.
함께 있으면서도 분리된 상태
한 번에 한 개의 서비스 워커만 페이지를 제어할 수 있습니다. 하지만 한 번에 두 개의 서비스 워커를 설치할 수 있습니다. 서비스 워커 코드를 변경하고 페이지를 새로고침해도 실제로 서비스 워커는 수정되지 않습니다. 서비스 워커는 immutable. 대신 새 계정을 만들고 있습니다. 이 새 서비스 워커 (SW2라고 함)는 설치되지만 아직 활성화되지는 않습니다. 현재 서비스 워커 (SW1)가 종료될 때까지 (사용자가 사이트를 떠날 때) 대기해야 합니다.
다른 서비스 워커의 캐시를 조작함
설치하는 동안 SW2는 설정을 가져올 수 있습니다(일반적으로 캐시 생성 및 채우기). 하지만 이 새 서비스 워커는 현재 서비스 워커가 액세스할 수 있는 모든 항목에 액세스할 수 있습니다. 주의하지 않으면 대기 중인 새 서비스 워커가 현재 서비스 워커를 완전히 망칠 수 있습니다. 다음과 같은 경우 문제가 발생할 수 있습니다.
- SW2는 SW1에서 활발하게 사용 중인 캐시를 삭제할 수 있습니다.
- SW2가 SW1에서 사용 중인 캐시의 콘텐츠를 수정하면 SW1이 페이지에서 예상하지 못한 애셋으로 응답할 수 있습니다.
skipWaiting 건너뛰기
서비스 워커는 위험한 skipWaiting()
메서드를 사용하여 설치가 완료되는 즉시 페이지를 제어할 수도 있습니다. 버그가 있는 서비스 워커를 의도적으로 교체하려는 경우가 아니라면 일반적으로 좋지 않은 방법입니다. 새 서비스 워커가 현재 페이지에서 예상하지 못한 업데이트된 리소스를 사용하고 있을 수 있으므로 오류와 버그가 발생할 수 있습니다.
클린 시작
서비스 워커가 서로 충돌하지 않도록 하는 방법은 서로 다른 캐시를 사용하는 것입니다. 이를 실행하는 가장 쉬운 방법은 사용하는 캐시 이름의 버전을 지정하는 것입니다.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
새 서비스 워커를 배포할 때는 이전 서비스 워커와 완전히 별개의 캐시로 필요한 작업을 실행하도록 version
를 범프합니다.
깔끔한 마무리
서비스 워커가 activated
상태에 도달하면 서비스 워커가 인계되었음을 알 수 있으며 이전 서비스 워커는 중복됩니다. 이 시점에서 이전 서비스 워커를 정리하는 것이 중요합니다. 사용자의 캐시 저장용량 한도를 준수할 뿐만 아니라 의도하지 않은 버그도 방지할 수 있습니다.
caches.match()
메서드는 일치하는 항목이 있는 모든 캐시에서 항목을 검색하는 데 자주 사용되는 바로가기입니다. 하지만 생성된 순서대로 캐시를 반복합니다. 예를 들어 assets-1
및 assets-2
라는 두 가지 캐시에서 스크립트 파일 app.js
의 두 가지 버전이 있다고 가정해 보겠습니다. 페이지에서 assets-2
에 저장된 최신 스크립트를 예상합니다. 하지만 이전 캐시를 삭제하지 않은 경우 caches.match('app.js')
는 assets-1
에서 이전 캐시를 반환하며 사이트가 다운될 가능성이 큽니다.
이전 서비스 워커를 정리하는 데 필요한 작업은 새 서비스 워커에 필요하지 않은 캐시를 삭제하는 것뿐입니다.
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
서비스 워커가 서로 충돌하지 않도록 하는 데는 약간의 노력과 규율이 필요하지만 그만한 가치가 있습니다.
서비스 워커 사고방식
서비스 워커를 생각할 때 올바른 사고방식을 갖추면 자신 있게 워커를 빌드하는 데 도움이 됩니다. 이를 숙지하면 사용자에게 멋진 환경을 제공할 수 있습니다.
게임을 플레이하여 이 모든 것을 이해하고 싶다면 좋은 소식이 있습니다. Service Workies를 플레이하여 오프라인 괴물을 처치하기 위한 서비스 워커의 방법을 알아보세요.