서비스 워커 사고방식

서비스 워커에 대해 생각할 때 고려하는 방법

서비스 워커는 강력하며 배울 가치가 절대적으로 있습니다. 완전히 새로운 차원의 경험을 사용자에게 제공할 수 있습니다. 사이트가 즉시 로드됩니다. 오프라인에서 작동합니다. 특정 플랫폼에 맞는 앱으로 설치할 수 있고, 세련되면서도 자유로운 웹 환경을 제공합니다.

그러나 서비스 워커는 대부분의 웹 개발자가 익숙한 것과 다릅니다. 학습 곡선이 가파르고 몇 가지 문제가 있기 때문에 주의해야 합니다.

Google Developers와 저는 최근에 서비스 작업자를 이해하기 위한 무료 게임인 Service Workies라는 프로젝트를 공동으로 작업했습니다. 이를 구축하고 서비스 작업자의 복잡한 내부 및 외부와 작업하는 과정에서 몇 가지 문제가 발생했습니다. 가장 큰 도움이 된 것은 묘사하는 은유를 몇 개 제시하는 것이었습니다. 이 게시물에서는 이러한 정신적 모델에 대해 살펴보고, 서비스 근로자를 까다롭고 훌륭하게 만드는 역설적인 특성에 대해 뇌를 짚어볼 것입니다.

동일하지만 다르고

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

그러나 다른 서비스 워커 동작으로 인해 혼란을 야기할 수 있습니다. 특히 페이지를 새로고침할 때 코드 변경사항이 적용되지 않을 때 유용합니다.

새 레이어

일반적으로 사이트를 구축할 때는 클라이언트와 서버라는 두 가지 계층만 고려해야 합니다. 서비스 워커는 중간에 위치하는 완전히 새로운 레이어입니다.

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

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

서비스 워커 레이어는 브라우저 탭과 관계없이 자체 수명 주기를 갖습니다. 서버에 배포된 코드를 업데이트하기 위해 페이지 새로고침을 예상하지 않는 것처럼, 단순한 페이지 새로고침으로는 서비스 워커를 업데이트할 수 없습니다. 레이어마다 고유한 업데이트 규칙이 있습니다.

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장에서는 중지된 서비스 워커가 절전 모드가 해제되기를 기다리는 동안 모든 색상을 잃는 것으로 시각화합니다.

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

함께, 그러나 별도

페이지는 한 번에 하나의 서비스 워커로만 제어할 수 있습니다. 하지만 한 번에 2개의 서비스 워커를 설치할 수 있습니다. 서비스 워커 코드를 변경하고 페이지를 새로고침한다고 해서 실제로 서비스 워커가 수정되는 것은 아닙니다. 서비스 워커는 변경할 수 없습니다. 대신 완전히 새로운 이미지를 만듭니다. 새로운 서비스 워커 (SW2라고 함)가 설치되지만 아직 활성화되지는 않습니다. 현재 서비스 워커(SW1)가 종료될 때까지(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-1assets-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(서비스 워커)를 플레이해 보세요. 서비스 워커에서 오프라인 괴물을 물리치는 방법을 배울 수 있습니다.