서비스 워커의 수명 주기를 이해하지 못하면 서비스 워커가 무엇을 하는지 알 수 없습니다. 내부 작동은 불투명하고 임의적으로 보일 수 있습니다. 다른 브라우저 API와 마찬가지로 서비스 워커 동작은 잘 정의되고 지정되며 오프라인 애플리케이션을 가능하게 하고 사용자 환경을 방해하지 않고 업데이트를 용이하게 한다는 점을 기억하는 것이 좋습니다.
Workbox를 자세히 알아보기 전에 Workbox의 작동 방식을 이해할 수 있도록 서비스 워커 수명 주기를 이해하는 것이 중요합니다.
용어 정의
서비스 워커 수명 주기에 대해 알아보기 전에 수명 주기가 작동하는 방식에 관한 몇 가지 용어를 정의하는 것이 좋습니다.
제어 및 범위
제어 개념은 서비스 워커가 작동하는 방식을 이해하는 데 중요합니다. 서비스 워커에 의해 제어되는 페이지는 서비스 워커가 대신 네트워크 요청을 가로챌 수 있는 페이지입니다. 서비스 워커가 존재하며 지정된 범위 내에서 페이지에 대한 작업을 실행할 수 있습니다.
범위
서비스 워커의 범위는 웹 서버의 위치에 따라 결정됩니다.
서비스 워커가 /subdir/index.html
에 있는 페이지에서 실행되고 /subdir/sw.js
에 있는 경우 서비스 워커의 범위는 /subdir/
입니다.
범위 개념을 실제로 확인하려면 다음 예시를 확인하세요.
- https://service-worker-scope-viewer.glitch.me/subdir/index.html로 이동합니다.
페이지를 제어하는 서비스 워커가 없다는 메시지가 표시됩니다.
하지만 이 페이지는
https://service-worker-scope-viewer.glitch.me/subdir/sw.js
에서 서비스 워커를 등록합니다. - 페이지를 새로고침합니다. 서비스 워커가 등록되고 현재 활성 상태이므로 페이지를 제어하고 있습니다. 서비스 워커의 범위, 현재 상태, URL이 포함된 양식이 표시됩니다. 참고: 페이지를 새로고침해야 하는 것은 범위와 관련이 없으며 나중에 설명할 서비스 워커 수명 주기와 관련이 있습니다.
- 이제 https://service-worker-scope-viewer.glitch.me/index.html로 이동합니다. 이 출처에 서비스 워커가 등록되었지만 현재 서비스 워커가 없다는 메시지가 계속 표시됩니다. 이 페이지가 등록된 서비스 워커의 범위에 있지 않기 때문입니다.
범위는 서비스 워커가 제어하는 페이지를 제한합니다.
이 예에서 /subdir/sw.js
에서 로드된 서비스 워커는 /subdir/
또는 하위 트리에 있는 페이지만 제어할 수 있습니다.
위의 내용은 기본적으로 범위 지정이 작동하는 방식이지만 허용되는 최대 범위는 Service-Worker-Allowed
응답 헤더를 설정하고 scope
옵션을 register
메서드에 전달하여 재정의할 수 있습니다.
서비스 워커 범위를 출처의 하위 집합으로 제한해야 할 매우 좋은 이유가 없는 한 범위가 최대한 넓어지도록 웹 서버의 루트 디렉터리에서 서비스 워커를 로드하고 Service-Worker-Allowed
헤더는 신경 쓰지 마세요. 이렇게 하면 모든 사람에게 훨씬 간단합니다.
클라이언트
서비스 워커가 페이지를 제어한다고 하면 실제로는 클라이언트를 제어하는 것입니다.
클라이언트는 URL이 해당 서비스 워커의 범위에 속하는 모든 열려 있는 페이지입니다.
구체적으로 WindowClient
의 인스턴스입니다.
새 서비스 워커의 수명 주기
서비스 워커가 페이지를 제어하려면 먼저 페이지가 생성되어야 합니다. 먼저 활성 상태의 서비스 워커가 없는 웹사이트에 새 서비스 워커가 배포되면 어떻게 되는지 살펴보겠습니다.
등록
등록은 서비스 워커 수명 주기의 첫 번째 단계입니다.
<!-- In index.html, for example: -->
<script>
// Don't register the service worker
// until the page has fully loaded
window.addEventListener('load', () => {
// Is service worker available?
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(() => {
console.log('Service worker registered!');
}).catch((error) => {
console.warn('Error registering service worker:');
console.warn(error);
});
}
});
</script>
이 코드는 기본 스레드에서 실행되며 다음을 실행합니다.
- 등록된 서비스 워커 없이 사용자가 웹사이트를 처음 방문하므로 페이지가 완전히 로드될 때까지 기다린 후 서비스 워커를 등록합니다. 이렇게 하면 서비스 워커가 항목을 미리 캐시하는 경우 대역폭 경합을 방지할 수 있습니다.
- 서비스 워커는 잘 지원되지만 빠른 확인을 통해 서비스 워커가 지원되지 않는 브라우저에서 오류를 방지할 수 있습니다.
- 페이지가 완전히 로드되고 서비스 워커가 지원되는 경우
/sw.js
를 등록합니다.
다음과 같은 몇 가지 주요 사항을 이해해야 합니다.
- 서비스 워커는 HTTPS 또는 localhost를 통해서만 사용할 수 있습니다.
- 서비스 워커의 콘텐츠에 문법 오류가 있으면 등록이 실패하고 서비스 워커가 삭제됩니다.
- 알림: 서비스 워커는 범위 내에서 작동합니다. 여기서 범위는 루트 디렉터리에서 로드되었으므로 전체 출처입니다.
- 등록이 시작되면 서비스 워커 상태가
'installing'
로 설정됩니다.
등록이 완료되면 설치가 시작됩니다.
설치
서비스 워커는 등록 후 install
이벤트를 실행합니다.
install
는 서비스 워커당 한 번만 호출되며 업데이트될 때까지 다시 실행되지 않습니다.
install
이벤트의 콜백은 addEventListener
를 사용하여 작업자 범위에 등록할 수 있습니다.
// /sw.js
self.addEventListener('install', (event) => {
const cacheKey = 'MyFancyCacheName_v1';
event.waitUntil(caches.open(cacheKey).then((cache) => {
// Add all the assets in the array to the 'MyFancyCacheName_v1'
// `Cache` instance for later use.
return cache.addAll([
'/css/global.bc7b80b7.css',
'/css/home.fe5d0b23.css',
'/js/home.d3cc4ba4.js',
'/js/jquery.43ca4933.js'
]);
}));
});
그러면 새 Cache
인스턴스가 생성되고 애셋이 미리 캐시됩니다.
나중에 미리 캐싱에 관해 자세히 설명할 기회가 있으므로 지금은 event.waitUntil
의 역할에 집중하겠습니다. event.waitUntil
는 약속을 수락하고 약속이 해결될 때까지 기다립니다.
이 예에서 이 약속은 두 가지 비동기 작업을 실행합니다.
'MyFancyCache_v1'
라는 새Cache
인스턴스를 만듭니다.- 캐시가 생성된 후 애셋 URL 배열이 비동기
addAll
메서드를 사용하여 미리 캐시됩니다.
event.waitUntil
에 전달된 약속이 거부되면 설치에 실패합니다.
이 경우 서비스 워커가 삭제됩니다.
약속이 해결되면 설치가 완료되고 서비스 워커의 상태가 'installed'
로 변경된 후 활성화됩니다.
활성화
등록 및 설치에 성공하면 서비스 워커가 활성화되고 상태가 'activating'
가 됩니다. 서비스 워커의 activate
이벤트에서 활성화 중에 작업을 실행할 수 있습니다.
이 이벤트의 일반적인 작업은 이전 캐시를 정리하는 것이지만, 새 서비스 워커의 경우 지금은 관련이 없으며 서비스 워커 업데이트를 다룰 때 자세히 설명할 예정입니다.
새 서비스 워커의 경우 install
가 성공한 직후에 activate
가 실행됩니다.
활성화가 완료되면 서비스 워커의 상태가 'activated'
로 됩니다.
기본적으로 새 서비스 워커는 다음 탐색 또는 페이지 새로고침이 될 때까지 페이지를 제어하지 않습니다.
서비스 워커 업데이트 처리
첫 번째 서비스 워커가 배포되면 나중에 업데이트해야 할 수 있습니다. 예를 들어 요청 처리 또는 미리 캐시 로직에 변경사항이 발생하면 업데이트가 필요할 수 있습니다.
업데이트 시점
브라우저는 다음과 같은 경우에 서비스 워커의 업데이트를 확인합니다.
- 사용자가 서비스 워커의 범위 내 페이지로 이동합니다.
navigator.serviceWorker.register()
가 현재 설치된 서비스 워커와 다른 URL로 호출됩니다. 그러나 서비스 워커의 URL은 변경하지 마세요.navigator.serviceWorker.register()
는 설치된 서비스 워커와 동일한 URL로 호출되지만 범위는 다릅니다. 다시 한번 말씀드리지만, 가능하면 범위를 출처의 루트에 유지하여 이러한 상황을 방지하세요.'push'
또는'sync'
와 같은 이벤트가 지난 24시간 이내에 트리거되었지만 아직 이러한 이벤트는 우려하지 마세요.
업데이트 방식
브라우저가 서비스 워커를 업데이트하는 시점을 아는 것도 중요하지만 '방법'도 중요합니다. 서비스 워커의 URL 또는 범위가 변경되지 않는다고 가정하면 현재 설치된 서비스 워커는 콘텐츠가 변경된 경우에만 새 버전으로 업데이트됩니다.
브라우저는 다음과 같은 몇 가지 방법으로 변경사항을 감지합니다.
importScripts
에서 요청한 스크립트의 바이트 단위 변경사항(해당하는 경우)- 브라우저에서 생성한 서비스 워커의 지문에 영향을 미치는 서비스 워커의 최상위 코드 변경사항
여기서 브라우저가 많은 작업을 실행합니다. 브라우저에 서비스 워커의 콘텐츠 변경사항을 안정적으로 감지하는 데 필요한 모든 항목이 있는지 확인하려면 HTTP 캐시가 콘텐츠를 보관하도록 지시하지 마세요. 또한 파일 이름을 변경하지 마세요. 서비스 워커의 범위 내에서 새 페이지로 이동하면 브라우저에서 자동으로 업데이트 확인을 실행합니다.
업데이트 확인 수동 트리거
업데이트와 관련하여 등록 로직은 일반적으로 변경해서는 안 됩니다. 하지만 웹사이트의 세션이 장기 지속되는 경우는 예외일 수 있습니다. 이는 애플리케이션의 수명 주기 시작 시 일반적으로 하나의 탐색 요청이 발생하므로 탐색 요청이 드문 단일 페이지 애플리케이션에서 발생할 수 있습니다. 이러한 경우 기본 스레드에서 수동 업데이트를 트리거할 수 있습니다.
navigator.serviceWorker.ready.then((registration) => {
registration.update();
});
기존 웹사이트 또는 사용자 세션이 오래 지속되지 않는 경우에는 수동 업데이트를 트리거할 필요가 없습니다.
설치
번들러를 사용하여 정적 애셋을 생성하면 이러한 애셋의 이름에 해시(예: framework.3defa9d2.js
)가 포함됩니다.
이러한 애셋 중 일부가 나중에 오프라인 액세스를 위해 미리 캐시되었다고 가정해 보겠습니다.
이렇게 하려면 업데이트된 애셋을 미리 캐시하기 위해 서비스 워커를 업데이트해야 합니다.
self.addEventListener('install', (event) => {
const cacheKey = 'MyFancyCacheName_v2';
event.waitUntil(caches.open(cacheKey).then((cache) => {
// Add all the assets in the array to the 'MyFancyCacheName_v2'
// `Cache` instance for later use.
return cache.addAll([
'/css/global.ced4aef2.css',
'/css/home.cbe409ad.css',
'/js/home.109defa4.js',
'/js/jquery.38caf32d.js'
]);
}));
});
이전의 첫 번째 install
이벤트 예시와 두 가지 차이점이 있습니다.
- 키가
'MyFancyCacheName_v2'
인 새Cache
인스턴스가 생성됩니다. - 미리 캐시된 애셋 이름이 변경되었습니다.
업데이트된 서비스 워커는 이전 워커와 함께 설치된다는 점에 유의하세요. 즉, 이전 서비스 워커가 여전히 열려 있는 페이지를 제어하고 있으며, 설치 후 새 서비스 워커는 활성화될 때까지 대기 상태로 전환됩니다.
기본적으로 새 서비스 워커는 이전 서비스 워커에 의해 제어되는 클라이언트가 없을 때 활성화됩니다. 이는 관련 웹사이트의 열려 있는 모든 탭이 닫힐 때 발생합니다.
활성화
업데이트된 서비스 워커가 설치되고 대기 단계가 종료되면 서비스 워커가 활성화되고 이전 서비스 워커는 삭제됩니다.
업데이트된 서비스 워커의 activate
이벤트에서 실행하는 일반적인 작업은 이전 캐시를 정리하는 것입니다.
caches.keys
를 사용하여 열려 있는 모든 Cache
인스턴스의 키를 가져오고 caches.delete
를 사용하여 정의된 허용 목록에 없는 캐시를 삭제하여 이전 캐시를 삭제합니다.
self.addEventListener('activate', (event) => {
// Specify allowed cache keys
const cacheAllowList = ['MyFancyCacheName_v2'];
// Get all the currently active `Cache` instances.
event.waitUntil(caches.keys().then((keys) => {
// Delete all caches that aren't in the allow list:
return Promise.all(keys.map((key) => {
if (!cacheAllowList.includes(key)) {
return caches.delete(key);
}
}));
}));
});
이전 캐시는 자동으로 정리되지 않습니다.
직접 해야 하며 그렇지 않으면 스토리지 할당량이 초과될 수 있습니다.
첫 번째 서비스 워커의 'MyFancyCacheName_v1'
가 오래되었으므로 캐시 허용 목록이 업데이트되어 'MyFancyCacheName_v2'
를 지정합니다. 그러면 다른 이름의 캐시가 삭제됩니다.
이전 캐시가 삭제되면 activate
이벤트가 완료됩니다.
이 시점에서 새 서비스 워커가 페이지를 제어하여 이전 페이지를 대체합니다.
수명 주기는 계속 진행됩니다.
Workbox를 사용하여 서비스 워커 배포 및 업데이트를 처리하거나 Service Worker API를 직접 사용하는지와 관계없이 서비스 워커 수명 주기를 이해하는 것이 좋습니다. 이를 이해하면 서비스 워커 동작이 신비롭기보다는 논리적으로 보일 것입니다.
이 주제에 대해 자세히 알아보려면 제이크 아치볼드의 이 도움말을 확인하세요. 서비스 수명 주기와 관련된 전체 절차에는 많은 미묘한 차이가 있지만 알 수 있습니다. 이 지식을 사용하면 Workbox를 사용할 때 큰 도움이 됩니다.