Webpack 및 Workbox로 PWA 구축
게시 됨: 2022-03-10PWA(프로그레시브 웹 앱)는 최신 기술을 사용하여 웹에서 앱과 같은 경험을 제공하는 사이트입니다. '웹 앱 매니페스트', '서비스 워커' 등과 같은 새로운 기술에 대한 포괄적인 용어입니다. 이러한 기술을 함께 사용하면 웹 사이트에서 빠르고 매력적인 사용자 경험을 제공할 수 있습니다.
이 문서는 기존의 한 페이지 웹사이트에 서비스 워커를 추가하기 위한 단계별 자습서입니다. 서비스 워커를 사용하면 사용자에게 사이트 업데이트를 알리는 동시에 웹사이트를 오프라인으로 작동할 수 있습니다. 이것은 Webpack과 함께 번들로 제공되는 소규모 프로젝트를 기반으로 하므로 Workbox Webpack 플러그인(Workbox v4)을 사용할 것입니다.
도구를 사용하여 서비스 워커를 생성하는 것은 캐시를 효율적으로 관리할 수 있도록 해주는 권장되는 접근 방식입니다. 우리는 이 튜토리얼에서 서비스 워커를 생성하기 위해 서비스 워커 코드를 쉽게 생성할 수 있게 해주는 라이브러리 세트인 Workbox를 사용할 것입니다.
프로젝트에 따라 다음 세 가지 방법으로 Workbox를 사용할 수 있습니다.
- 워크박스를 가지고 있는 모든 응용 프로그램에 통합할 수 있는 명령줄 인터페이스 를 사용할 수 있습니다.
- Node.js 모듈 을 사용할 수 있어 워크박스를 gulp 또는 grunt와 같은 모든 Node 빌드 도구에 통합할 수 있습니다.
- 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/
으로 이동합니다. 이 튜토리얼 전체에서 사용할 통화 앱을 볼 수 있어야 합니다.

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


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


사용자는 빈 화면을 싫어하기 때문에 앱 셸의 로딩 경험을 좋아합니다. 빈 화면은 사용자가 웹 사이트가 로드되지 않는 것처럼 느끼게 합니다. 웹 사이트가 멈춘 것처럼 느끼게합니다.
앱 셸은 탐색 모음, 탭 모음 및 요청한 콘텐츠가 로드 중임을 나타내는 로더와 같은 앱의 탐색 구조를 최대한 빨리 그리려고 시도합니다.
그렇다면 앱 셸은 어떻게 구축합니까?
앱 셸 패턴은 먼저 렌더링할 HTML, CSS 및 JavaScript의 로딩을 우선시합니다. 즉, 해당 리소스에 전체 우선 순위를 부여해야 하므로 해당 자산을 인라인해야 합니다. 따라서 앱 셸을 빌드하려면 앱 셸을 담당하는 HTML, CSS 및 JavaScript를 인라인하기만 하면 됩니다. 물론 모든 것을 인라인으로 처리해서는 안 되며 총 30~40KB 정도의 범위 내에서 유지해야 합니다.
index.html 에서 인라인된 앱 셸을 볼 수 있습니다. index.html 파일을 확인하여 소스 코드를 검사할 수 있으며 개발 도구에서 <main>
요소를 삭제하여 브라우저에서 미리 볼 수 있습니다.

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

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

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

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

서비스 워커는 웹 워커의 한 유형이기도 합니다. 즉, 기본 페이지와 별도로 실행되며 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 파일을 요청한 다음 다른 모든 파일을 요청합니다. 톱니바퀴 아이콘으로 명확하게 표시되어 있습니다.

따라서 Workbox는 초기화되고 사전 캐시 매니페스트에 있는 모든 파일을 사전 캐시합니다. .map 파일이나 앱 셸의 일부가 아닌 파일과 같이 사전 캐시 매니페스트 파일에 불필요한 파일이 없는지 다시 확인하는 것이 중요합니다.
네트워크 탭에서 서비스 워커로부터 오는 요청을 볼 수 있습니다. 이제 오프라인으로 전환하려고 하면 앱 셸이 이미 미리 캐시되어 있으므로 오프라인 상태에서도 작동합니다!

캐시 동적 경로
오프라인으로 전환했을 때 앱 셸은 작동하지만 데이터는 작동하지 않는다는 사실을 알고 계셨습니까? 이는 이러한 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
라고 합니다. 자주 사용되는 다른 두 가지가 있습니다.
-
CacheFirst
-
StaleWhileRevalidate
CacheFirst
는 먼저 캐시에서 이를 찾습니다. 찾을 수 없으면 네트워크에서 가져옵니다. StaleWhileRevalidate
는 네트워크와 캐시로 동시에 이동합니다. 페이지에 캐시의 응답을 반환합니다(백그라운드에 있는 동안). 새 네트워크 응답을 사용하여 다음에 사용할 때 캐시를 업데이트합니다.
사용 사례의 경우 매우 자주 변경되는 환율을 처리하기 때문에 NetworkFirst
를 사용해야 했습니다. 그러나 사용자가 오프라인 상태가 되면 최소한 10분 전의 요금을 표시할 수 있습니다. 그렇기 때문에 maxAgeSeconds
를 10 * 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 구축