woolta

PWA 적용해보기 - serviceWorker를 통한 오프라인 경험

wooltaUserImgb00032 | javaScript | 2019-08-23

PWA를 통한 오프라인 적용

이번에는 PWA 적용의 두번째인 service-worder 를 활용한 오프라인 환경 서비스를 구축해보도록 하겠습니다.

1. 웹에서의 오프라인

기본적으로 브라우저에서 각 웹서비스를 들어갈때 인터넷이 연결되지 않은 상황이라면 다음과 같은 에러 창을 볼 수 있습니다.

https://image.woolta.com/3fe21abdc1508df3.png

다음과 같이 오프라인 인 경우 네트워크가 되지 않고 그 어떤 서비스의 화면을 볼 수 없게 됩니다.

2. 오프라인 경험

PWA 를 사용해 앱과 유사한 경험을 위해 오프라인에서도 어느정도 정상적으로 서비스가 동작하는것을 제공 하고 싶다면.
PWA오프라인 캐싱 을 활용하여 다음과 같이 오프라인 상황 에서도 정상적으로 웹이 동작하는것처럼 구현할 수 있습니다. 물론 PWA를 한다고 해서 반드시 만들어야 하는기능은 아닙니다.!!

https://image.woolta.com/3f9e033d498fdf00.png 우측을 보시면 설정은 오프라인으로 설정하여 인터넷이 되지 않는 상황에도 정상적으로 화면이 출력되는것을 확인 할 수 있습니다. 이런 웹서비스의 오프라인 경험이 가능한 것은 serviceWorkerAppCache 에서 오프라인 경험을 지원하기 때문입니다. 이제부터 serviceWorker 를 사용해 오프라인 지원 기능을 작성해도록 하겠습니다.

3. 서비스워커(serviceWorker) 란?

서비스워커는 브라우저가 백그라운드에서 실행하는 스크립트로 웹페이지와는 별개로 동작하며 브라우저와 웹 서버 간의 미들웨어 역활을 수행합니다. 서비스 워커를 사용하게 되면 대표적으로 다음과 같은 기능들을 사용할 수 있습니다.

  • web-push service
  • 백그라운드 동기화 기능
  • 네트워크 요청을 가로채 캐쉬 상호작용

이때 서비스워커는 https 환경 혹은 localhost 환경에서만 동작하기 때문에 local 환경에서 특정 도메인을 반드시 사용해야 하는경우 이에 대해서도 반드시 https 설정을 해야 합니다. 이번예시는 localhost 환경에서 작업할 것이여서 따로 설정하지는 않습니다. 그리고 서비스워커(serviceWorker) 에서는 기존의 자바스크립트와 다른 쓰레드에서 사용되기때문에 self 를 통해 this 를 접근할수 있습니다.

4. 서비스워커(serviceWorker) 등록

서비스워커를 사용하기위해선 가장 처음으로 서비스 워커 파일을 등록해주어야 합니다. 등록을 위해 아래의 예시 코드를 작업할 프로젝트에 추가해 주도록 하겠습니다.

등록 예시

 if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('service-worker.js') // serviceWorker 파일 경로
            .then((reg) => {
                console.log('Service worker registered.', reg);
            })
            .catch(e => console.log(e));
    });
}

위의 코드를 설명하면 모든 브라우저가 serviceWorker를 지원하지 않기때문에 navigator 를 통해 지원유무를 파악하고 load 시점에 serviceWorker.register 를 에 등록해서 서비스 워커를 등록해 줄 수 있습니다. 등록 확인을 위해 크롬 개발자도구에서 application -> service Workers 탭을 들어가보도록 합니다.

https://image.woolta.com/3fea5392ed6c247a.png

정상적으로 등록이 된것을 볼 수 있습니다.

서비스워커 수정시 주의사항

서비스워커(serviceWorker) 는 등록 이후 수정시 바로 변경되지 않습니다. 수정 이후 service Workers 탭 에서 보게되면 다음과 같은 화면을 볼 수 있습니다.

https://image.woolta.com/3fc67718ff088fe4.png 위의 이미지에서 Status 를 보시면 현재 수정한 serviceWorker 는 하단에 대기중이고 수정전의 serviceWorker 가 게속 실행되는 모습을 볼 수 있습니다. 때문에 수정을 해도 반영이 안된것으로 생각할 수 있습니다. 이런현상이 생길시 skipWaiting버튼을 클릭하여 바로 적용하거나 우측상단의 Update on reload 를 활성화 하여 변경시 항상 새로 다시 등록할수 있도록 합니다.

5. 서비스워커(serviceWorker) 설치

이제 위의 예시의 등록을 위한 service-worker.js 파일을 작성해보도록 하겠습니다. 설치 이벤트에서는 미리 설정한 파일들을 캐쉬할 수 있습니다. 우선 설치이벤트 등록예시를 작성 후 알아보도록 하겠습니다.

//service-worker.js
const CACHE_NAME = 'woolta-blog-cache-v1'; // 캐쉬 이름을 설정합니다.

const FILES_TO_CACHE = [
    '/offline', // 캐쉬할 페이지 or 파일 들을 설정합니다.
];

self.addEventListener('install', (event) => {

    event.waitUntil(
    	// 캐쉬할 페이지들을 전부 캐쉬합니다.
        caches.open(CACHE_NAME).then((cache) => cache.addAll(FILES_TO_CACHE)})
    );

});

위의 예시대로 작성하게되면 서비스워커의 설치가 끝나고 설정한 offline 페이지를 캐싱하게 됩니다. 해당 install 이벤트가 끝나게 되면 offline 페이지는 캐쉬되어 있어 별도의 네트워크 요청 없이 사용 가능합니다. 이때 waitUntil 이벤트를 사용하게 되면 waitUntil 내부의 코드가 전부 실행되기전까지 설치가 되지 않습니다. 때문에 캐싱등의 설치에 시간이 걸릴수 있는 작업은 waitUntil 안에서 작업해주어야 합니다.

5. 서비스워커(serviceWorker) fetch 이벤트를 통한 오프라인 페이지 출력

이제 캐싱된 페이지를 오프라인 환경에서 보여주도록 설정해보도록 하겠습니다. 이를 위해 fetch 이벤트를 사용하는데요. fetch 이벤트는 웹 브라우저의 기본 fetch 활동을 가로챌 수 있습니다. (ex. axios) 이를 통해 우선 웹브라우저에서 네트워크 요청에 실패할 경우 캐싱된 오프라인 페이지를 보여주도록 하는 예시를 작성하도록 하겠습니다.

self.addEventListener('fetch', (event) => {

    if (event.request.mode !== 'navigate') { // page navigation 제외
        return;
    }
    
    event.respondWith(
        fetch(event.request)
            .catch(() => {
                return caches.open(CACHE_NAME)
                    .then((cache) => cache.match('offline'));
            }))
});

위의 예시는 fetch 이벤트가 실패해 예외가 발생하면 위의 install 이벤트에서 캐싱한 offline 페이지를 보여주도록 하였습니다. 위와 같이 설정 후 오프라인에서 요청하면 다음과 같이 캐싱된 offline 페이지가 나타나게 됩니다.!!

https://image.woolta.com/3fd7fdb440d3accc.png css까지 캐쉬는 하지 않아 보이는게 엉망이긴 하지만 오프라인 페이지가 정상적으로 출력되게 됩니다.!!

이때 사용되는 새로운 함수들의 기능은 다음과 같습니다.

respondWith & match 이벤트

  • respondWith : fetch 이벤트의 응답을 반환 합니다.
  • match : 해당 요청에 대한 cache가 있으면 cache를 찾아줍니다.

6. 유동적인 오프라인 환경 구현

위에서 만든 fetch 이벤트를 통해 오프라인이 된 경우 offline 페이지로 이동하도록 하였지만 진정한 오프라인 경험은 이런 정적인 이동이 아닌 오프라인에서도 정상적으로 응답되는것처럼 보이는것이 목표입니다. 이를 구현하기 위해 fetch 요청을 가로채 성공할 경우 해당 요청을 캐싱시키고 요청이 실패하면 이전에 캐싱된 파일을 보여주는 방식으로 처리하도록 하겠습니다.

self.addEventListener('fetch', (event) => {
    
    if (event.request.method !== 'GET') { // GET 요청만 캐싱 지원 처리
        return;
    }

    const fetchRequest = event.request.clone();

    event.respondWith(
        fetch(fetchRequest)
            .then((response) => {
                caches.open(CACHE_NAME) // 네트워크 요청 성공시 해당 결과값 캐싱
                      .then(cache => cache.put(event.request.url, response.clone()));
                return response;
            })
            .catch(() => {
                return caches.match(event.request.url)
                    .then(cache => {return cache;}) // 네트워크 요청 실패시 캐싱된 요청으로 응답.
            })
    )
    ;
});

코드가 복잡해지긴 했으나 큰 틀은 변하지 않습니다.!! 원리는 fetch 요청이 발생할 경우 정상호출된 경우 이에 대한 응답을 cache 에 추가하고 같은 fetch 요청이 실패할 경우 이전에 저장한 캐싱된 응답을 보여주도록 하였습니다. 즉 한번이라도 진입에 성공한 페이지는 다음에 다시 요청하게 되면 오프라인 환경일 경우 동일한 캐싱 응답으로 보여주도록 합니다. 또한 오프라인 캐시를 못찾더라도 맨처음 설치할때 캐쉬한 offline 페이지를 보여주도록 하여 사용자에게 더 좋은 오프라인 경험을 제공합니다.

https://image.woolta.com/offline.gif

위의 코드까지 적용하게 되면 다음과 같이 오프라인 환경에서도 정상적으로 네트워크 호출이 된듯한 경험을 줄 수 있습니다. 여기서 좀더 최적화를 위한다면 respondWith 에서 fetch 를 하지 않고 캐쉬된 파일을 찾아서 요청을 추가로 안하는 등의 활용도 충분히 가능합니다.!!

참고

Copyright © 2018 woolta.com

gommpo111@gmail.com