Webpack 및 Workbox로 PWA 구축

게시 됨: 2022-03-10
빠른 요약 ↬ 이 튜토리얼은 오프라인에서 작동하지 않는 앱을 ​​오프라인에서 작동하고 업데이트 가능 아이콘을 표시하는 PWA로 변환하는 데 도움이 될 것입니다. 워크박스로 자산을 사전 캐싱하고, 동적 캐싱을 처리하고, PWA에 대한 업데이트를 처리하는 방법을 배우게 됩니다. 따라하면서 이러한 기술을 웹사이트에 어떻게 적용할 수 있는지 확인하십시오.

PWA(프로그레시브 웹 앱)는 최신 기술을 사용하여 웹에서 앱과 같은 경험을 제공하는 사이트입니다. '웹 앱 매니페스트', '서비스 워커' 등과 같은 새로운 기술에 대한 포괄적인 용어입니다. 이러한 기술을 함께 사용하면 웹 사이트에서 빠르고 매력적인 사용자 경험을 제공할 수 있습니다.

이 문서는 기존의 한 페이지 웹사이트에 서비스 워커를 추가하기 위한 단계별 자습서입니다. 서비스 워커를 사용하면 사용자에게 사이트 업데이트를 알리는 동시에 웹사이트를 오프라인으로 작동할 수 있습니다. 이것은 Webpack과 함께 번들로 제공되는 소규모 프로젝트를 기반으로 하므로 Workbox Webpack 플러그인(Workbox v4)을 사용할 것입니다.

도구를 사용하여 서비스 워커를 생성하는 것은 캐시를 효율적으로 관리할 수 있도록 해주는 권장되는 접근 방식입니다. 우리는 이 튜토리얼에서 서비스 워커를 생성하기 위해 서비스 워커 코드를 쉽게 생성할 수 있게 해주는 라이브러리 세트인 Workbox를 사용할 것입니다.

프로젝트에 따라 다음 세 가지 방법으로 Workbox를 사용할 수 있습니다.

  1. 워크박스를 가지고 있는 모든 응용 프로그램에 통합할 수 있는 명령줄 인터페이스 를 사용할 수 있습니다.
  2. Node.js 모듈 을 사용할 수 있어 워크박스를 gulp 또는 grunt와 같은 모든 Node 빌드 도구에 통합할 수 있습니다.
  3. Webpack 플러그인 을 사용하면 Webpack으로 빌드된 프로젝트와 쉽게 통합할 수 있습니다.

Webpack은 모듈 번들러입니다. 단순화하기 위해 JavaScript 종속성을 관리하는 도구로 생각할 수 있습니다. 라이브러리에서 JavaScript 코드를 가져오고 JavaScript를 하나 이상의 파일로 묶을 수 있습니다.

점프 후 더! 아래에서 계속 읽기 ↓

시작하려면 컴퓨터에서 다음 저장소를 복제하십시오.

 git clone [email protected]:jadjoubran/workbox-tutorial-v4.git cd workbox-tutorial-v4 npm install npm run dev

다음으로 https://localhost:8080/ 으로 이동합니다. 이 튜토리얼 전체에서 사용할 통화 앱을 볼 수 있어야 합니다.

이 기사에서 만들고 있는 통화 앱의 스크린샷.
'Currencies'는 유로(€) 통화에 대한 통화 변환 수수료를 나열하는 PWA입니다. (큰 미리보기)

앱 셸로 시작하기

애플리케이션 셸(또는 '앱 셸')은 네이티브 앱에서 영감을 얻은 패턴입니다. 앱을 보다 기본적으로 보이게 하는 데 도움이 됩니다. 웹 앱의 로딩 경험을 개선하기 위한 전환 화면인 데이터 없이 단순히 레이아웃과 구조를 앱에 제공합니다.

다음은 기본 앱의 앱 셸 몇 가지 예입니다.

Google 받은 편지함 앱 셸
Google Inbox 앱 셸: 이메일이 앱 셸에 로드되기 몇 밀리초. (큰 미리보기)
트위터 네이티브 앱 셸
Android용 Twitter의 기본 앱: 탐색 모음, 탭 및 로더를 표시하는 앱 셸. (큰 미리보기)

다음은 PWA의 앱 셸 예입니다.

트위터 PWA의 앱 셸
Twitter PWA의 앱 셸(큰 미리보기)
Flipkart PWA의 앱 셸
Flipkart의 PWA 앱 셸(큰 미리보기)

사용자는 빈 화면을 싫어하기 때문에 앱 셸의 로딩 경험을 좋아합니다. 빈 화면은 사용자가 웹 사이트가 로드되지 않는 것처럼 느끼게 합니다. 웹 사이트가 멈춘 것처럼 느끼게합니다.

앱 셸은 탐색 모음, 탭 모음 및 요청한 콘텐츠가 로드 중임을 나타내는 로더와 같은 앱의 탐색 구조를 최대한 빨리 그리려고 시도합니다.

그렇다면 앱 셸은 어떻게 구축합니까?

앱 셸 패턴은 먼저 렌더링할 HTML, CSS 및 JavaScript의 로딩을 우선시합니다. 즉, 해당 리소스에 전체 우선 순위를 부여해야 하므로 해당 자산을 인라인해야 합니다. 따라서 앱 셸을 빌드하려면 앱 셸을 담당하는 HTML, CSS 및 JavaScript를 인라인하기만 하면 됩니다. 물론 모든 것을 인라인으로 처리해서는 안 되며 총 30~40KB 정도의 범위 내에서 유지해야 합니다.

index.html 에서 인라인된 앱 셸을 볼 수 있습니다. index.html 파일을 확인하여 소스 코드를 검사할 수 있으며 개발 도구에서 <main> 요소를 삭제하여 브라우저에서 미리 볼 수 있습니다.

Navbar 및 로더가 있는 통화 PWA 앱 셸
이 기사에서 구축하고 있는 앱 셸. (큰 미리보기)

오프라인에서 작동합니까?

오프라인으로 전환하는 것을 시뮬레이션해 봅시다! DevTools를 열고 네트워크 탭으로 이동하여 '오프라인' 확인란을 선택합니다. 페이지를 새로고침하면 브라우저의 오프라인 페이지가 표시됩니다.

브라우저의 오프라인 오류 페이지
홈페이지에 대한 요청이 실패하여 결과적으로 이것을 분명히 볼 수 있습니다. (큰 미리보기)

인터넷이 오프라인 상태이기 때문에 /(index.html 파일을 로드함 / 에 대한 초기 요청이 실패하기 때문입니다. 요청 실패에서 복구할 수 있는 유일한 방법은 서비스 작업자를 확보하는 것입니다.

서비스 워커 없이 요청을 시각화해 보겠습니다.

클라이언트의 브라우저에서 인터넷으로 이동하는 네트워크 요청의 애니메이션입니다.
네트워크 요청은 브라우저에서 인터넷으로 갔다가 다시 돌아갑니다. (flaticon.com의 아이콘) (큰 미리보기)

서비스 워커는 프로그래밍 가능한 네트워크 프록시입니다. 즉, 웹 페이지와 인터넷 사이에 위치합니다. 이를 통해 수신 및 발신 네트워크 요청을 제어할 수 있습니다.

서비스 워커가 가로챈 네트워크 요청의 애니메이션입니다.
서비스 워커가 네트워크 요청을 가로챕니다. (flaticon.com의 아이콘) (큰 미리보기)

이것은 실패한 요청을 캐시로 다시 라우팅할 수 있기 때문에 유용합니다(캐시에 콘텐츠가 있다고 가정).

서비스 워커가 가로채 캐시로 리디렉션한 네트워크 요청의 애니메이션입니다.
네트워크 요청이 이미 캐시에 있는 경우 캐시로 리디렉션됩니다. (flaticon.com의 아이콘) (큰 미리보기)

서비스 워커는 웹 워커의 한 유형이기도 합니다. 즉, 기본 페이지와 별도로 실행되며 window 이나 document 개체에 액세스할 수 없습니다.

앱 셸 사전 캐시

앱이 오프라인에서 작동하도록 하기 위해 먼저 앱 셸을 미리 캐싱합니다.

Webpack Workbox 플러그인을 설치하여 시작하겠습니다.

 npm install --save-dev workbox-webpack-plugin

그런 다음 index.js 파일을 열고 서비스 워커를 등록합니다.

 if ("serviceWorker" in navigator){ window.addEventListener("load", () => { navigator.serviceWorker.register("/sw.js"); }) }

다음으로 webpack.config.js 파일을 열고 Workbox 웹팩 플러그인을 구성해 보겠습니다.

 //add at the top const WorkboxWebpackPlugin = require("workbox-webpack-plugin"); //add inside the plugins array: plugins: [ … , new WorkboxWebpackPlugin.InjectManifest({ swSrc: "./src/src-sw.js", swDest: "sw.js" }) ]

이것은 Workbox가 ./src/src-sw.js 파일을 기본으로 사용하도록 지시합니다. 생성된 파일의 이름은 sw.js 이며 dist 폴더에 있습니다.

그런 다음 루트 수준에서 ./src/src-sw.js 파일을 만들고 그 안에 다음을 작성합니다.

 workbox.precaching.precacheAndRoute(self.__precacheManifest);

참고 : self.__precacheManifest 변수는 워크박스에서 동적으로 생성할 파일에서 가져옵니다.

이제 npm run build 로 코드를 빌드할 준비가 되었으며 Workbox는 dist 폴더 안에 두 개의 파일을 생성합니다.

  • precache-manifest.66cf63077c7e4a70ba741ee9e6a8da29.js
  • sw.js

sw.js 는 CDN과 precache-manifest.[chunkhash].js 에서 워크박스를 가져옵니다.

 //precache-manifest.[chunkhash].js file self.__precacheManifest = (self.__precacheManifest || []).concat([ "revision": "ba8f7488757693a5a5b1e712ac29cc28", "url": "index.html" }, "url": "main.49467c51ac5e0cb2b58e.js" ]);

precache 매니페스트는 webpack에 의해 처리되고 dist 폴더에 있는 파일의 이름을 나열합니다. 이 파일을 사용하여 브라우저에서 미리 캐시합니다. 즉, 웹 사이트가 처음 로드되고 서비스 워커를 등록할 때 다음 번에 사용할 수 있도록 이러한 자산을 캐시합니다.

일부 항목에는 '개정'이 있는 반면 다른 항목에는 없는 것도 알 수 있습니다. 파일 이름의 청크 해시에서 개정판을 유추할 수 있기 때문입니다. 예를 들어 main.49467c51ac5e0cb2b58e.js 파일 이름을 자세히 살펴보겠습니다. 파일 이름에 수정본이 있으며 이는 chunkhash 49467c51ac5e0cb2b58e 입니다.

이를 통해 Workbox는 서비스 워커의 새 버전을 게시할 때마다 모든 캐시를 덤프하지 않고 변경된 파일만 정리하거나 업데이트하도록 파일이 변경되는 시기를 이해할 수 있습니다.

페이지를 처음 로드하면 서비스 워커가 설치됩니다. DevTools에서 볼 수 있습니다. 먼저 sw.js 파일을 요청한 다음 다른 모든 파일을 요청합니다. 톱니바퀴 아이콘으로 명확하게 표시되어 있습니다.

서비스 워커가 설치될 때 DevTools 네트워크 탭의 스크린샷.
️ 아이콘으로 표시된 요청은 서비스 작업자가 시작한 요청입니다. (큰 미리보기)

따라서 Workbox는 초기화되고 사전 캐시 매니페스트에 있는 모든 파일을 사전 캐시합니다. .map 파일이나 앱 셸의 일부가 아닌 파일과 같이 사전 캐시 매니페스트 파일에 불필요한 파일이 없는지 다시 확인하는 것이 중요합니다.

네트워크 탭에서 서비스 워커로부터 오는 요청을 볼 수 있습니다. 이제 오프라인으로 전환하려고 하면 앱 셸이 이미 미리 캐시되어 있으므로 오프라인 상태에서도 작동합니다!

API 호출 실패를 보여주는 Dev Tools 네트워크 탭의 스크린샷.
오프라인 상태가 되면 API 호출이 실패합니다. (큰 미리보기)

캐시 동적 경로

오프라인으로 전환했을 때 앱 셸은 작동하지만 데이터는 작동하지 않는다는 사실을 알고 계셨습니까? 이는 이러한 API 호출이 사전 캐시된 앱 셸 의 일부가 아니기 때문입니다. 인터넷에 연결되어 있지 않으면 이러한 요청이 실패하고 사용자는 통화 정보를 볼 수 없습니다.

그러나 이러한 요청은 해당 값이 API에서 제공되기 때문에 미리 캐시할 수 없습니다. 또한 여러 페이지가 시작되면 모든 API 요청을 한 번에 캐시하고 싶지 않습니다. 대신 사용자가 해당 페이지를 방문할 때 캐시를 원합니다.

우리는 이것을 '동적 데이터'라고 부릅니다. 여기에는 사용자가 웹사이트에서 특정 작업을 수행할 때(예: 새 페이지를 탐색할 때) 요청되는 API 호출과 이미지 및 기타 자산이 포함되는 경우가 많습니다.

Workbox의 라우팅 모듈을 사용하여 이를 캐시할 수 있습니다. 방법은 다음과 같습니다.

 //add in src/src-sw.js workbox.routing.registerRoute( /https:\/\/api\.exchangeratesapi\.io\/latest/, new workbox.strategies.NetworkFirst({ cacheName: "currencies", plugins: [ new workbox.expiration.Plugin({ maxAgeSeconds: 10 * 60 // 10 minutes }) ] }) );

이렇게 하면 URL https://api.exchangeratesapi.io/latest 와 일치하는 모든 요청 URL에 대해 동적 캐싱이 설정됩니다.

여기에서 사용한 캐싱 전략은 NetworkFirst 라고 합니다. 자주 사용되는 다른 두 가지가 있습니다.

  1. CacheFirst
  2. StaleWhileRevalidate

CacheFirst 는 먼저 캐시에서 이를 찾습니다. 찾을 수 없으면 네트워크에서 가져옵니다. StaleWhileRevalidate 는 네트워크와 캐시로 동시에 이동합니다. 페이지에 캐시의 응답을 반환합니다(백그라운드에 있는 동안). 새 네트워크 응답을 사용하여 다음에 사용할 때 캐시를 업데이트합니다.

사용 사례의 경우 매우 자주 변경되는 환율을 처리하기 때문에 NetworkFirst 를 사용해야 했습니다. 그러나 사용자가 오프라인 상태가 되면 최소한 10분 전의 요금을 표시할 수 있습니다. 그렇기 때문에 maxAgeSeconds10 * 60 초로 설정한 만료 플러그인을 사용했습니다.

앱 업데이트 관리

사용자가 페이지를 로드할 때마다 서비스 워커가 이미 설치되어 실행 중이더라도 브라우저는 navigator.serviceWorker.register 코드를 실행합니다. 이를 통해 브라우저는 서비스 워커의 새 버전이 있는지 감지할 수 있습니다. 브라우저는 파일이 변경되지 않았음을 감지하면 등록 호출을 건너뜁니다. 해당 파일이 변경되면 브라우저는 새 버전의 서비스 워커가 있음을 이해하므로 현재 실행 중인 서비스 워커와 병렬로 새 서비스 워커를 설치합니다 .

단, 서비스 워커는 동시에 한 명만 활성화할 수 있기 때문에 installed/waiting 단계에서 일시 중지됩니다.

서비스 워커의 수명 주기: 구문 분석됨, 설치됨/대기 중, 활성화됨 및 중복됨
서비스 워커의 단순화된 라이프 사이클(큰 미리보기)

이전 서비스 워커가 제어하던 모든 브라우저 창을 닫아야만 새 서비스 워커가 활성화되는 것이 안전해집니다.

또한 skipWaiting() (또는 self 가 서비스 워커의 전역 실행 컨텍스트이므로 self.skipWaiting() ))을 호출하여 수동으로 제어할 수도 있습니다. 그러나 대부분의 경우 사용자에게 최신 업데이트를 받을 것인지 묻는 후에만 수행해야 합니다.

고맙게도 workbox-window 는 이를 달성하는 데 도움이 됩니다. 창 쪽에서 일반적인 작업을 단순화하는 것을 목표로 하는 Workbox v4에 도입된 새로운 창 라이브러리입니다.

다음을 사용하여 설치를 시작하겠습니다.

 npm install workbox-window

다음으로 index.js 파일 맨 위에 있는 Workbox 를 가져옵니다.

 import { Workbox } from "workbox-window";

그런 다음 등록 코드를 아래와 같이 바꿉니다.

 if ("serviceWorker" in navigator) { window.addEventListener("load", () => { const wb = new Workbox("/sw.js"); wb.register(); }); }

그런 다음 ID가 있는 업데이트 버튼을 찾습니다. 앱 업데이트 workbox-waiting 이벤트를 수신합니다.

 //add before the wb.register() const updateButton = document.querySelector("#app-update"); // Fires when the registered service worker has installed but is waiting to activate. wb.addEventListener("waiting", event => { updateButton.classList.add("show"); updateButton.addEventListener("click", () => { // Set up a listener that will reload the page as soon as the previously waiting service worker has taken control. wb.addEventListener("controlling", event => { window.location.reload(); }); // Send a message telling the service worker to skip waiting. // This will trigger the `controlling` event handler above. wb.messageSW({ type: "SKIP_WAITING" }); }); });

이 코드는 새 업데이트가 있을 때(서비스 워커가 대기 상태일 때) 업데이트 버튼을 표시하고 SKIP_WAITING 메시지를 서비스 워커에게 보냅니다.

서비스 워커 파일을 업데이트하고 skipWaiting 을 호출하도록 SKIP_WAITING 이벤트를 처리해야 합니다.

 //add in src-sw.js addEventListener("message", event => { if (event.data && event.data.type === "SKIP_WAITING") { skipWaiting(); });

이제 npm run dev 를 실행한 다음 페이지를 다시 로드합니다. 코드로 이동하여 navbar 제목을 "Navbar v2"로 업데이트하십시오. 페이지를 다시 로드하면 업데이트 아이콘이 표시됩니다.

마무리

우리 웹 사이트는 이제 오프라인으로 작동하며 사용자에게 새 업데이트에 대해 알릴 수 있습니다. PWA를 구축할 때 가장 중요한 요소는 사용자 경험입니다. 항상 사용자가 사용하기 쉬운 경험을 구축하는 데 집중하세요. 우리는 개발자로서 기술에 너무 열광하는 경향이 있으며 결국 사용자를 잊어버리는 경우가 많습니다.

한 단계 더 나아가 사용자가 홈 화면에 사이트를 추가할 수 있도록 하는 웹 앱 매니페스트를 추가할 수 있습니다. Workbox에 대해 더 알고 싶다면 Workbox 웹사이트에서 공식 문서를 찾을 수 있습니다.

SmashingMag에 대한 추가 정보:

  • 모바일 앱이나 PWA로 더 많은 돈을 벌 수 있습니까?
  • 프로그레시브 웹 애플리케이션에 대한 광범위한 가이드
  • 네이티브 및 PWA: 도전자가 아닌 선택!
  • Angular 6을 사용하여 PWA 구축