PWA, Service worker 그리고 캐시

Gisele·2021년 5월 20일
2

😵어떤 문제?

pwa를 적용한 앱에서 업데이트를 하고 배포하면 강력새로고침을 하지않으면 업데이트 내용이 반영이 안됐다. 서비스 워커가 index.html을 캐시하기 때문이라고 한다. 이 글은 그 해결 방법을 찾아가면서 관련 내용을 정리한 글이다.

🚀PWA란?

PWA는 웹과 네이티브 앱의 기능 모두의 이점을 갖도록 수 많은 특정 기술과 표준 패턴을 사용해 개발된 웹앱

특징

  • 발견 : 검색 엔진을 통해 컨텐츠를 발견할 수 있다
  • 설치 : 기기의 홈화면에서 사용할 수 있다
  • 공유 : 간단하게 URL을 전송해 공유할 수 있다
  • 네트워크 독립적 : 오프라인이나 불안한 네트워크에서도 동작한다
  • 브라우저의 기본 기능 사용 가능
  • 새 컨텐츠가 사용 가능할 때마다 알림을 보낼 수 있다
  • 앱 업데이트가 있을 때 변경된 컨텐츠만 업데이트 할 수 있다
  • 반응형
  • 보안

필수조건

HTTPS

manifest

  • 원하는 영역(예:모바일 홈 화면)에 표시되는 앱의 모양을 제어하는 수 있는 기능을 개발자에게 제공하고 실행 가능한 것과 실행 방식을 지시하는 단순한 JSON 파일
    • 웹 앱이 사용자의 android 홈 화면에 다운로드 가능
    • url 표시줄이 없는 전체 화면 모드로 웹 앱을 시작할 수 있다
    • 최적의 보기를 위해 웹 앱에서 화면 방향을 제어할 수 있다
    • 홈 화면 또는 URL 표시줄에서의 시작 여부를 웹 앱이 추절할 수 있다

service-worker🌟

브라우저가 백그라운드에서 실행하는 스크립트. 웹 어플리케이션-브라우저-네트워크 사이의 프록시 서버 역할을 하며 오프라인에서도 서비스를 사용할 수 있도록 한다

  • 서비스 워커는 출처와 경로에 대해 등록하는 이벤트 기반 워커로서 javascript 파일의 형태를 갖고 있다.

  • 네트워크 요청을 가로채서 네트워크 사용 가능 여부에 따라 적절한 행동을 취하고, 서버 리소스를 업데이트 할 수 있다.

  • 푸시 알림과 백그라운드 동기화 API로의 접근도 제공한다.

  • 리소스를 세부적으로 캐싱할 수 있다.

  • 서비스 워커는 워커 context에서 실행되기 때문에 DOM에 접근할 수 없다. 그리고 온전히 비동기적으로 설계되었으며 XHR이나 웹 저장소 등의 API를 서비스 워커 내에서 사용할 수 없다.

  • 보안 상의 이유로 HTTPS에서만 동작한다.

  • 생명주기 : 다운로드, 설치, 활성화

  • 서비스워커의 업데이트 : 범위 내 페이지로의 탐색 발생, 서비스 워커에서 이벤트가 발생했는데, 서비스 워커를 이전 24시간 내에 다운로드하지 않은 경우

  • 기존에 서비스 워커가 존재하던 경우, 새로운 버전을 백그라운드에서 설치하지만 활성화는 아직 하지 않는다(대기 중인 워커). 대기 중인 워커는 이전 버전의 서비스 워커를 사용하는 페이지가 모두 닫힌 경우 활성화되어 활성 워커가 된다.

    • ServiceWorkerGlobalScope.skipWaiting() : 활성화 절차를 더 빨리 시작할 수 있다
    • Clients.claim(): 새로운 활성 워커는 Clients.claim()을 사용해 이전 페이지를 회수

구현흐름

  1. 앱 셸 영역을 서비스 워커를 사용해 캐싱
  2. 통신으로 실시간 데이터를 호출하고 서비스 워커를 사용하여 캐싱
  3. 통신을 사용해 가져온 데이터는 항상 최신 데이터로 캐싱
  4. 앱 셸 영역의 데이터와 통신으로 가져온 데이터 모두 최초 1회에는 네트워크가 연결되어 있어야 한다.
  5. 네트워크 연결이 불안정하거나 안될 경우 최근 캐싱된 내용을 화면에 보여주고, 반복적으로 데이터를 요청
  6. 네트워크 연결이 정상적으로 돌아올 경우 응답받은 실시간 데이터를 서비스 워커를 사용하여 캐싱한다
  7. 캐싱된 최근 데이터를 화면에 보여준다

※앱 셸

  • PWA의 사용자 인터페이스를 구동하는 데 필요한 최소한의 HTML, CSS 및 자바스크립트
  • 앱이 빠르게 로딩 되도록 구현하기 위해 PWA는 앱 셸을 서비스 워커를 사용해 캐싱한다. 즉 네트워크를 통해 한 번 로드하고 나면 로컬 기기에 저장해 모든 UI 및 인프라는 서비스워커를 사용해 로컬로 캐시됨으로 PWA에 모든 것을 로드하는 대신 필요한 데이터만 검색한다.

리액트 CRA에서 PWA 만들기

CRA로 프로젝트를 생성하면 기본적으로 서비스 워커과 manifest를 만들어 주므로, 약간의 수정만하면 PWA를 만들 수 있다.

  1. manifest 설정
  2. 서비스워커 등록

1. manifest 설정

  • 앱 아이콘이 필요하다
    • favicomatic.com 이 사이트에서 Evert damn size, sir 메뉴에서 이미지를 올리면 나오는 내용을 index.html head 태그 안에 붙여넣는다
  • iso는 manifest를 지원하지 않으므로 별도로 추가
// public/index.html
<meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="#EBEBF9" />
  <meta name=" apple-mobile-web-app-title" content="외출 난이도" />
  <link rel="apple-touch-icon" sizes="152x152" href="%PUBLIC_URL%/assets/logo/logo152.png">
  <link rel="apple-touch-icon" sizes="167x167" href="%PUBLIC_URL%/assets/logo/logo167.png">
  <link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/assets/logo/logo180.png">
// manifest.json
{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

2. 서비스 워커 등록

// service-worker.ts
/// <reference lib="webworker" />
/* eslint-disable no-restricted-globals */

// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.

import { clientsClaim, setCacheNameDetails } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL, matchPrecache } from 'workbox-precaching';
import { registerRoute, setCatchHandler } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// 캐시 이름
// 빌드 일시 2021-05-18 18:50
// build/asset-manifest4json 의 index.html 삭제하기
setCacheNameDetails({
  prefix: 'weather-service',
  suffix: 'v25',
  precache: 'weather-service-precache',
});

declare const self: ServiceWorkerGlobalScope;

clientsClaim();

// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);

// Catch routing errors, like if the user is offline
setCatchHandler(async ({ event }: any) => {
  // Return the precached offline page if a document is being requested
  if (event.request.destination === 'document') {
    return matchPrecache('/offline.html');
  }

  return Response.error();
});

// Set up App Shell-style routing, so that all navi gation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
  // Return false to exempt requests from being fulfilled by index.html.
  ({ request, url }: { request: Request; url: URL }) => {
    // If this isn't a navigation, skip.
    if (request.mode !== 'navigate') {
      return false;
    }

    // If this is a URL that starts with /_, skip.
    if (url.pathname.startsWith('/_')) {
      return false;
    }

    // If this looks like a URL for a resource, because it contains
    // a file extension, skip.
    if (url.pathname.match(fileExtensionRegexp)) {
      return false;
    }

    // Return true to signal that we want to use the handler.
    return true;
  },
  createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'),
);

// 폰트 캐싱
registerRoute(
  // Add in any other file extensions or routing criteria as needed.
  ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
  // Customize this strategy as needed, e.g., by changing to CacheFirst.
  new StaleWhileRevalidate({
    cacheName: 'images',
    plugins: [
      // Ensure that once this runtime cache reaches a maximum size the
      // least-recently used images are removed.
      new ExpirationPlugin({ maxEntries: 50 }),
    ],
  }),
);

// 이미지 캐싱
registerRoute(
  // Add in any other file extensions or routing criteria as needed.
  ({ request }) => request.destination === 'image',
  // Customize this strategy as needed, e.g., by changing to CacheFirst.
  new StaleWhileRevalidate({
    cacheName: 'image-cache',
    plugins: [
      // Ensure that once this runtime cache reaches a maximum size the
      // least-recently used images are removed.
      new CacheableResponsePlugin({
        statuses: [200],
      }),
      new ExpirationPlugin({ maxEntries: 50 }),
    ],
  }),
);

// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

// Any other custom service worker logic can go here.
// app.js
import * as serviceWorkerRegistration from './serviceWorkerRegistration';

import App from './shared/App';

ReactDOM.render(
  <Provider store={store}>
    <ThemeProvider theme={theme}>
      <App />
    </ThemeProvider>
  </Provider>,
  document.getElementById('root'),
);

serviceWorkerRegistration.register();

HTTP cache와 Service Workder cache

HTTP cache

  • 브라우저는 설정한 기간동안 데이터를 저장하고 캐싱할 수 있다. 데이터가 만료되면 업데이트 된 버전을 가지고 온다.
  • 서버는 Expires 헤더를 사용해 '만료 날짜'를 설정한다.
  • 캐싱을 통해 웹 사이트의 성능을 향상시킬 수 있다.
  • HTTP 캐싱은 캐싱 시기와 만료 시키를 서버에 의존한다. 종속성이 있는 콘텐츠가 있는 경우 업데이트로 인해 서버에서 보낸 만료 날짜가 동기화되지 않고 사이트에 영향을 미칠 수 있다

Service Worker 캐싱

  • Service Worker는 클라이언트가 캐시에 대한 설정을 제어할 수 있다.

  • Service worker는 HTTP 요청을 가로챌 수 있다.

  • preCaching : 서비스 워커를 사용하면 수신되는 모든 HTTP 요청을 활용하고, 정확히 응답 할 방법을 결정할 수 있다. 서비스 워커에서 캐시 할 리소스, 충족해야하는 조건 및 캐시할 기간을 결정할 수 있다.


📑 reference

profile
한약은 거들뿐

0개의 댓글