[ 공지 ]
프로그레시브 웹 앱의 전반적인 내용에 대해 더 자세하게 배울 수 있는 도서가 출간되었습니다!
현재 블로그에 업로드된 PWA 강좌보다 더 다양하고 자세한 내용으로 구성되어있으며,
하나의 SNS 웹 앱을 개발해나가는 방식으로 실습을 진행합니다.
(도서 소개 및 실습 미리보기)
SNS 앱 예제로 배우는 프로그레시브 웹 앱
(구매 링크 / 2020.07.31 출간)
[YES24]
[교보문고]
[알라딘]
[인터파크도서]
프로그레시브 웹 앱의 기본적인 내용부터 시작하여, 서비스 워커, 오프라인 캐싱, 백그라운드 동기화 및 푸시 알림까지
상세한 설명을 통해 프로그레시브 웹 앱에 대해 더 다가갈 수 있을 것입니다.
안녕하세요~
이번 강좌에서는 오프라인에서 우리의 웹 앱이 작동할 수 있도록 필요한 리소스를 캐싱하고,
사용하지 않는 캐시는 지우도록 하는 로직을 구현해보도록 하겠습니다!
이번 강좌에서는 지난 강좌에 작성한 코드에 이어서 진행합니다.
혹시나 소스코드가 없거나 다시 이어서 하고싶으신 분들은 아래 깃허브에서 내려받아주세요~!
(다운로드 방법은 첫 번째 강좌 확인)
[소스코드 다운로드]
https://github.com/leegeunhyeok/pwa-example
(ch3 폴더)
그럼 세 번째 강좌를 시작합시다!
[1. 버전 변경 및 캐싱 리스트 추가]
service-worker.js 파일을 열어 아래와 같이 변경합니다.
버전은 기존의 v2에서 v3으로 변경해주시구요
아래의 cacheList에 항목을 추가해주세요
이 리스트에 추가되어있는 URL은 곧 우리가 캐싱할 친구들입니다~
오프라인에서 페이지를 보여주기 위해 필요한 리소스를 모두 캐싱해야합니다.
[2. 리소스 캐싱]
service-worker.js 파일의 install 이벤트 핸들러 코드를 아래와 같이 작성합니다.
먼저 self.skipWaiting() 이 추가되었는데요
이 부분은 지난 강좌의 서비스워커 생명주기에서 언급되었습니다.
기존의 서비스워커가 있다면 새 서비스워커 설치를 완료해도 바로 활성화가 되지 않고 기존 서비스워커가 종료되어야 활성화 되었습니다.
그래서 매번 개발자도구를 열고 skipWaiting을 눌러서 수동으로 대기상태에서 빠져나갔죠..
self.skipWaiting()을 install 이벤트 핸들러 안에 추가해주면 앞으로 서비스워커가 업데이트 된다 하더라도
대기상태에 머무르지 않고 활성화됩니다.
그 아래에는 event.waitUntil()이 등장했는데요 이 친구도 지난 강좌에서 언급되었습니다.
캐싱하거나 시간이 소요되는 작업을 할 땐 waitUntil을 통해 install 단계를 확장(연장) 시킬 수 있습니다.
우리는 필요한 리소스(앱 셸)들을 캐싱해야 하기 때문에 waitUntil을 통해 설치 단계를 연장시켜야 합니다.
waitUntil에는 작업할 프라미스를 전달하면 됩니다.
waitUntil 안에 있는 코드입니다.
프라미스를 전달해야하는데 복잡해보이는 코드가 들어있습니다.
어렵게 생각하지 않으셔도 됩니다.
caches는 캐시를 저장하는 전역객체입니다.
[참고]
https://developer.mozilla.org/ko/docs/Web/API/Cache
open 메소드를 통해 지정한 이름(키 값)의 캐시를 불러올 수 있습니다.
만약 이름의 캐시가 없다면 빈 캐시를 반환합니다.
우리는 캐시를 받아왔으니 오프라인 환경에서도 불러올 수 있도록 필요한 리소스를 담아봅시다.
cache.addAll()은 프라미스를 반환합니다.
인자로는 캐싱할 리소스 URL이 있는 리스트를 받는데 우리의 cacheList를 전달해줍시다.
/, /index.css, /images/1.jpg 등 필요한 리소스가 잘 추가가 될 것 입니다.
혹시나 리스트에 있는 리소스 캐싱 작업 도중 하나라도 실패할 경우 모두 실패로 처리됩니다.
실제 운영 환경에서는 캐싱할 요소를 1개씩 각각 처리하여 예외를 처리해야합니다.
(예제에서는 예외처리 없이 간단히 진행함)
waitUntil을 통해 install 과정의 캐싱작업 시간을 추가로 연장했습니다.
캐싱작업 Promise가 마무리되면 서비스워커는 활성화 됩니다.
(skipWaiting() 을 추가했기 때문에 다른 서비스워커가 있어도 바로 전환 됨)
한 번 웹 페이지에서 새로고침하여 잘 캐싱되었는지 확인해봅시다.
개발자 도구 상단의 Application 탭 -> Cache Storage를 선택해봅시다.
우리가 추가했던 캐싱할 리소스들이 캐시저장소에 잘 들어가있네요
방금 추가한 캐시 이름은 v1 입니다. (service-worker.js의 cacheName)
이제 캐시된 데이터를 불러오도록 기능을 구현해봅시다.
[3. 캐싱된 데이터 Fetch 하기]
service-worker.js 파일의 fetch 이벤트 핸들러 코드를 아래와 같이 수정합니다.
기존의 2.jpg로 응답하는 코드는 모두 지우신 후 위와 같이 코드를 작성해주세요
respondWith는 웹 브라우저의 기본 fetch를 막고 서비스워커가 (사실은 프로그래머가 작성하는대로)
Reponse에 대한 Promise를 제공할 수 있도록 하는 메소드라는건 지난 시간에 확인해보았습니다.
요청할 리소스가 캐시 목록에 있는지 확인하고
존재한다면 캐싱한 데이터를 제공, 아니면 그대로 fetch할 수 있도록 구현해봅시다.
caches.match는 캐시 객체에서 첫 번째로 일치하는 요청과 연결된 Response 객체를 반환합니다.
만약 일치하는 Response가 없다면 undefined가 반환 됩니다.
return response || fetch(event.request);
이 부분은 캐시에서 반환된 response가 undefined일 경우 새로 fetch 할 수 있도록 하는 코드입니다.
if 문으로 response를 비교해도 되지만 || 를 통해 짧게 축약할 수 있습니다.
위 코드를 간단히 정리하자면 fetch를 요청한 응답 객체가 캐시에 저장되어있으면 캐시에 있는 응답 객체를 전달하고,
만약 없으면 새로 fetch(네트워크 필요)하도록 하는 코드입니다.
service-worker.js 파일의 _version 값을 v4로 저장한 후 웹 페이지를 새로고침 해 봅시다.
v4 서비스워커 Activate 후 한 번 더 새로고침 진행해주세요
(이미지 로드 후 서비스워커가 로드되기 때문)
웹 브라우저의 요청대로 1.jpg가 응답되었습니다.
첫 강좌때와 달라진게 하나도 없는것같지만 PWA의 핵심 기술은 이미 구현되었습니다.
개발자도구의 Network탭을 확인해봅시다.
캐싱 리스트에 추가했던 css, js, 이미지들을 확인해보시면 (ServiceWorker) 라는 문구로 표시되어있습니다.
이는 지난 강좌때와 마찬가지로 서비스워커를 통해 응답되었기 때문인데요
가장 중요한것은 네트워크를 통해 fetch 된것이 아니라 캐시에서 fetch한 응답입니다.
즉, 네트워크를 통해 서버에서 받아온것이 아니라는 의미죠
그럼 오프라인에서 작동하는지 확인해볼까요?
상단의 Offline을 체크한 후 페이지를 새로고침 해 봅시다.
오프라인이어도 웹 페이지에 정상적으로 접근 가능하며 캐싱해둔 리소스들이 잘 보입니다!
개발자도구의 Network를 다시 확인해보면 아래와 같이 보이실겁니다.
(manifest.json은 캐싱하지 않았기 때문에 캐시에서 가져올 수 없음)
위의 localhost (index.html을 의미함), index.css, app.js, 1.jpg가 오프라인 상태에서 정상적으로 로드되었습니다!
이제 오프라인 상태에서도 웹 페이지에 접근할 수 있습니다!
이어서 manifest.json 파일도 캐싱할 수 있도록 리스트에 추가해봅시다.
cacheList에 /manifest.json을 추가했습니다.
그리고 cacheName의 값을 v1에서 v2로 변경해줍니다.
웹 페이지를 새로 고친 후 저장된 캐시 목록을 확인해봅시다!
v2 캐시가 추가된것을 확인하실 수 있습니다.
하지만 이전에 사용하던 v1 캐시가 아직도 남아있네요
사용하지 않는 캐시는 제거하도록 코드를 추가해봅시다!
[4. 불필요한 캐시 지우기]
service-worker.js 파일의 activate 이벤트 핸들러 코드를 아래와 같이 작성합니다.
캐싱하는 작업과 마찬가지로 waitUntil을 사용하여 캐시를 지우는 시간동안 activate 과정을 연장시켜줘야 합니다.
caches.keys는 브라우저에 저장된 모든 캐시의 키 값(이름)을 리스트로 반환합니다.
keyList에 캐시 이름들이 있으며 Array.map을 통해 캐시 삭제 프라미스를 하나의 리스트로 매핑해줍니다.
묶여진 프라미스 리스트는 Promise.all 을 통해 한번에 처리가 됩니다.
(실제 Prod 환경에서는 각각의 프라미스에 대한 예외처리를 해줘야합니다.)
캐시는 그냥 삭제하는것이 아니라 현재 서비스워커의 cacheName과 일치하지 않는지
확인 후 일치하지 않는 캐시값만 제거하도록 조건을 작성하였습니다.
이전에는 v1 이라는 이름의 캐시를 사용했지만 현재는 이름이 v2로 변경되었고 캐싱한 데이터도 변경된 상태입니다.
위 조건에서 'v1' !== 'v2'는 참이기 때문에 v1 캐시는 제거됩니다.
코드를 저장한 후 웹 페이지를 새로고침 해 봅시다.
v1 캐시가 말끔히 지워졌습니다.
로그를 확인해보니 Removing old cache v1 라는 로그가 남아있네요
앞으로 캐시 리스트의 항목이 변경될 때 마다 cacheName을 변경해주며 이전에 사용하던 캐시를 지울 수 있도록 해야합니다.
다음 포스팅에서는 서비스워커 상태변경 이벤트에 대해 알아보도록 하겠습니다~
감사합니다!