서비스 워커 사고방식

서비스 워커를 생각할 때 어떻게 생각해야 할까요?

서비스 워커는 강력하며 배울 가치가 절대적으로 있습니다. 이를 통해 사용자에게 완전히 새로운 수준의 경험을 제공할 수 있습니다. 사이트를 즉시 로드할 수 있습니다. 오프라인에서도 사용 가능합니다. 플랫폼에 따라 설치할 수 있고 마치 웹처럼 광범위하고 자유로워서 세련된 느낌을 받을 수 있습니다.

하지만 서비스 워커는 대부분의 웹 개발자에게 익숙한 것과는 다릅니다. 학습 곡선이 가파르고 주의해야 할 몇 가지 어려움이 있습니다.

Google Developers와 저는 최근에 서비스 워커를 이해하는 데 도움이 되는 무료 게임인 Service Workies라는 프로젝트를 공동으로 진행했습니다. 이를 구축하고 복잡한 서비스 워커와 함께 작업하는 동안 몇 가지 문제가 발생했습니다. 몇 가지 묘사된 은유를 생각해 내는 데 가장 도움이 되었습니다. 이 게시물에서는 이러한 정신 모델을 살펴보고 서비스 워커를 까다롭고 멋지게 만드는 역설적인 특성에 관해 머리를 싸매고자 합니다.

동일하지만 다른 부분

서비스 워커를 코딩할 때는 많은 부분이 익숙하게 느껴질 것입니다. 즐겨 사용하는 새 JavaScript 언어 기능을 사용하게 됩니다. UI 이벤트와 마찬가지로 수명 주기 이벤트를 수신 대기합니다. 익숙한 프로미스를 사용하여 제어 흐름을 관리합니다.

그러나 다른 서비스 워커 동작으로 인해 혼란스러워할 수 있습니다. 특히 페이지를 새로고침할 때 코드 변경사항이 적용된 것을 확인할 수 없을 때 그렇습니다.

새 레이어

일반적으로 사이트를 구축할 때는 클라이언트와 서버의 두 가지 레이어만 고려하면 됩니다. 서비스 워커는 중간에 있는 새로운 레이어입니다.

서비스 워커는 클라이언트와 서버 사이의 중간 계층 역할을 함

서비스 워커는 사이트에서 사용자의 브라우저에 설치할 수 있는 일종의 브라우저 확장 프로그램이라고 생각하면 됩니다. 설치된 서비스 워커는 강력한 중간 레이어를 통해 사이트의 브라우저를 extends합니다. 이 서비스 워커 계층은 사이트의 모든 요청을 가로채서 처리할 수 있습니다.

서비스 워커 레이어에는 브라우저 탭과는 별개인 자체 수명 주기가 있습니다. 서버에 배포된 코드를 업데이트하기 위해 페이지를 새로고침한다고 생각하지 않는 것처럼, 단순한 페이지 새로고침만으로는 서비스 워커를 업데이트하는 데 충분하지 않습니다. 각 레이어에는 고유한 업데이트 규칙이 있습니다.

Service Workies 게임에서는 서비스 워커 수명 주기의 많은 세부정보를 다루며 이를 다루는 많은 연습을 제공합니다.

강력하지만 제한적

현장에 서비스 작업자가 있으면 놀라운 이점을 얻을 수 있습니다. 사이트에서 할 수 있는 작업:

  • 사용자가 오프라인일 때도 문제없이 작업할 수 있습니다.
  • 캐싱을 통해 성능이 크게 향상됩니다.
  • 푸시 알림 사용
  • PWA로 설치

서비스 워커가 할 수 있는 한, 설계상으로는 한계가 있습니다. 사이트와 동기식 또는 동일한 대화목록에서 아무것도 할 수 없습니다. 따라서 다음에 액세스할 수 없습니다.

  • 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로 변경됩니다. 이제 서비스 워커가 잠시 유휴 상태이므로 브라우저가 서비스 워커를 중지합니다. 다음에 요청이 있을 때 서비스 워커가 다시 필요하므로 브라우저가 절전 모드를 해제합니다. 스크립트가 다시 평가됩니다. 이제 hasHandledARequestfalse로 재설정되고 favoriteNumber는 완전히 다릅니다(0.5907281835659033).

서비스 워커의 저장된 상태에 의존할 수 없습니다. 또한, 메시지 채널과 같은 인스턴스를 생성하면 버그가 발생할 수 있습니다. 서비스 워커가 중지/시작될 때마다 완전히 새로운 인스턴스를 얻게 됩니다.

서비스 워커 3장에서는 중지된 서비스 워커가 깨어나기를 기다리는 동안 모든 색상이 손실되는 것으로 시각화합니다.

중지된 서비스 워커의 시각화

함께 하지만 분리

한 번에 하나의 서비스 워커로만 페이지를 제어할 수 있습니다. 하지만 한 번에 두 개의 서비스 워커를 설치할 수 있습니다. 서비스 워커 코드를 변경하고 페이지를 새로 고치는 경우 실제로 서비스 워커를 수정하는 것은 아닙니다. 서비스 워커는 변경할 수 없습니다. 대신 새 항목을 만듭니다. 이 새로운 서비스 워커 (SW2라고 함)는 설치되지만 아직 활성화되지는 않습니다. 사용자가 사이트를 떠날 때 현재 서비스 워커(SW1)가 종료될 때까지 대기해야 합니다.

다른 서비스 워커의 캐시 손상

설치하는 동안 SW2는 항목을 설정할 수 있습니다. 일반적으로 캐시를 생성하고 채웁니다. 하지만 주의: 이 새 서비스 워커는 현재 서비스 워커가 액세스할 수 있는 모든 항목에 액세스할 수 있습니다. 주의하지 않으면 대기 중인 새 서비스 워커가 현재 서비스 워커를 엉망으로 만들 수 있습니다. 문제를 일으킬 수 있는 몇 가지 예는 다음과 같습니다.

  • SW2는 SW1이 현재 사용 중인 캐시를 삭제할 수 있습니다.
  • SW2는 SW1이 사용 중인 캐시의 콘텐츠를 수정하여 SW1이 페이지에서 예상하지 못한 자산으로 응답할 수 있습니다.

건너뛰기 건너뛰기

또한 서비스 워커는 설치가 완료되는 즉시 위험한 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() 메서드는 일치하는 모든 캐시에서 항목을 검색하는 데 자주 사용되는 단축키입니다. 하지만 생성된 순서대로 캐시를 반복합니다. 예를 들어 스크립트 파일 app.js의 두 버전이 서로 다른 두 캐시(assets-1assets-2)에 있다고 가정해 보겠습니다. 페이지에 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를 플레이하여 오프라인 야수를 물리치기 위한 서비스 워커의 사용 방법을 알아보세요.