pwa를 적용한 앱에서 업데이트를 하고 배포하면 강력새로고침을 하지않으면 업데이트 내용이 반영이 안됐다. 서비스 워커가 index.html을 캐시하기 때문이라고 한다. 이 글은 그 해결 방법을 찾아가면서 관련 내용을 정리한 글이다.
PWA는 웹과 네이티브 앱의 기능 모두의 이점을 갖도록 수 많은 특정 기술과 표준 패턴을 사용해 개발된 웹앱
브라우저가 백그라운드에서 실행하는 스크립트. 웹 어플리케이션-브라우저-네트워크 사이의 프록시 서버 역할을 하며 오프라인에서도 서비스를 사용할 수 있도록 한다
서비스 워커는 출처와 경로에 대해 등록하는 이벤트 기반 워커로서 javascript 파일의 형태를 갖고 있다.
네트워크 요청을 가로채서 네트워크 사용 가능 여부에 따라 적절한 행동을 취하고, 서버 리소스를 업데이트 할 수 있다.
푸시 알림과 백그라운드 동기화 API로의 접근도 제공한다.
리소스를 세부적으로 캐싱할 수 있다.
서비스 워커는 워커 context에서 실행되기 때문에 DOM에 접근할 수 없다. 그리고 온전히 비동기적으로 설계되었으며 XHR이나 웹 저장소 등의 API를 서비스 워커 내에서 사용할 수 없다.
보안 상의 이유로 HTTPS에서만 동작한다.
생명주기 : 다운로드, 설치, 활성화
서비스워커의 업데이트 : 범위 내 페이지로의 탐색 발생, 서비스 워커에서 이벤트가 발생했는데, 서비스 워커를 이전 24시간 내에 다운로드하지 않은 경우
기존에 서비스 워커가 존재하던 경우, 새로운 버전을 백그라운드에서 설치하지만 활성화는 아직 하지 않는다(대기 중인 워커). 대기 중인 워커는 이전 버전의 서비스 워커를 사용하는 페이지가 모두 닫힌 경우 활성화되어 활성 워커가 된다.
ServiceWorkerGlobalScope.skipWaiting()
: 활성화 절차를 더 빨리 시작할 수 있다Clients.claim()
: 새로운 활성 워커는 Clients.claim()을 사용해 이전 페이지를 회수※앱 셸
CRA로 프로젝트를 생성하면 기본적으로 서비스 워커과 manifest를 만들어 주므로, 약간의 수정만하면 PWA를 만들 수 있다.
Evert damn size, sir
메뉴에서 이미지를 올리면 나오는 내용을 index.html head 태그 안에 붙여넣는다// 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"
}
// 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();
Service Worker는 클라이언트가 캐시에 대한 설정을 제어할 수 있다.
Service worker는 HTTP 요청을 가로챌 수 있다.
preCaching : 서비스 워커를 사용하면 수신되는 모든 HTTP 요청을 활용하고, 정확히 응답 할 방법을 결정할 수 있다. 서비스 워커에서 캐시 할 리소스, 충족해야하는 조건 및 캐시할 기간을 결정할 수 있다.
📑 reference