코드를 수정해서 새로 배포하면 사용 중인 웹에 바로 적용되면 좋겠다.
회사 플랫폼은 현재 강제 새로고침(ctrl + F5) 또는 인터넷 사용 기록을 삭제해야만 적용이 된다.
업데이트 여부도 모르는데 ctrl + F5를 직접 누르는 사용자는 없을 것이다.
처음에는 강제로 새로고침 하는 코드를 찾아봤지만 window.location.reload(true)
는 deprecated됐다.
(window.location.reload()는 존재하지만 이건 강제 새로고침이 아니다.)
두번째는 웹팩 설정을 통해 파일명에 매번 새로운 해시를 넣는 것이다.
하지만 해시는 이미 사용 중이었고 기존 열린 창에서는 업데이트를 알 수 없다.
그러다 발견한 것이 서비스 워커다.
서비스 워커를 사용하면 새로운 버전이 존재할 때마다 아래처럼 사용자에게 알림을 줄 수 있다.
CRA 4.0+부터 아래처럼 설정할 수 있다.
서비스워커를 사용하는 프로젝트를 생성한다.
npx create-react-app my-app --template cra-template-pwa-typescript
이미 존재하는 프로젝트에 사용하려면 2.1의 명령어를 통해 만들어진
service-worker.ts
, serviceWorkerRegistration.ts
파일을 기존 프로젝트의 'src/'에 추가한다.
또한 package.json
에 아래 내용을 추가한다.
"workbox-background-sync": "^6.5.4",
"workbox-broadcast-update": "^6.5.4",
"workbox-cacheable-response": "^6.5.4",
"workbox-core": "^6.5.4",
"workbox-expiration": "^6.5.4",
"workbox-google-analytics": "^6.5.4",
"workbox-navigation-preload": "^6.5.4",
"workbox-precaching": "^6.5.4",
"workbox-range-requests": "^6.5.4",
"workbox-routing": "^6.5.4",
"workbox-strategies": "^6.5.4",
"workbox-streams": "^6.5.4"
import * as serviceWorkerRegistration from "./serviceWorkerRegistration";
...
serviceWorkerRegistration.register({
onUpdate: (register: ServiceWorkerRegistration) => {
// update
},
onSuccess: (register: ServiceWorkerRegistration) => {
// success
},
});
...
서비스 워커를 사용하려면 루트 파일(index.tsx 또는 App.tsx)에 serviceWorkerRegistration.register
를 호출해야된다.
(serviceWorkerRegistration.unregister
는 사용하지 않을 때 등록 취소를 의미한다.)
그리고 서비스워커는 기본적으로 prod에서만 적용된다.
배포 후 개발자 도구(F12) > 애플리케이션 > Service Workers
를 확인하면 등록됐는지 확인 할 수 있다.
예시로 gmail을 가져왔다.
3번까지만 진행하면 사용자가 새 탭을 열 때까지 새로운 서비스워커가 존재하는지 확인할 수 없다.
const unlisten = history.listen(() => {
if (!navigator.serviceWorker) {
return;
}
navigator.serviceWorker.getRegistrations().then((regs) =>
regs.forEach((reg) => {
reg.update().catch((e: any) => {
// Fetching SW failed.
});
})
);
});
리액트 컴포넌트에서 페이지 path가 변경될 때마다 브라우저에 등록된 서비스 워커에 update를 수동으로 호출했다.
새로운 서비스워커가 존재하면 3번 코드의 onUpdate
가 호출된다.
참고로 history.listen의 리턴 값인 unlisten을 호출하면 listen을 멈춘다.
(컴포넌트가 mount할 때 마다 같은 이벤트가 계속 등록되어 여러번 호출되는 것을 막을 수 있다.)
navigator.serviceWorker.getRegistrations().then((regs) =>
regs.forEach((reg) => {
reg.waiting?.postMessage({ type: "SKIP_WAITING" });
})
);
새로운 서비스워커는 waiting
이라는 status를 갖게 되는데 이때 SKIP_WAITING
메시지를 보내면
서비스워커가 새거로 교체된다. (active
인 현재 것은 버리고 waiting
중인 새것이 installed됨)
새로운 버전이 존재하는 상태에서 아직 업데이트 안 했을때 개발자 도구를 열면 저렇게 보인다.
navigator.serviceWorker.addEventListener("controllerchange", () => {
window.location.reload();
});
서비스워커가 교체되면 controllerchange
이벤트가 발생하는데
이 때 페이지를 reload하면 새로 배포한 버전의 웹이 보인다.
배포 후 리프레시를 하면 아래의 문제가 발생했다.
2.[hash].chunk.js
가 서비스워커에 캐시되지 않는다. 서비스 워커가 캐시할수있는 파일 사이즈는 디폴트로 최대 5MB다.The maximumFileSizeToCacheInBytes configuration option might be at play here. In c-r-a, it's set to 5mb, so if your chunk is larger than 5mb, it will be excluded from the list of assets to precache.
해결
maximumFileSizeToCacheInBytes
를 변경한다.
이를 위해 worbox-webpack-plugins 설치한다.
npm i workbox-webpack-plugins
react-rewired를 사용중이어서 웹팩을 설정하는 코드인 config-overrides.js를 수정했다.
캐시 가능한 파일 사이즈를 더 큰 값으로 변경했다.
const { override, useBabelRc } = require('customize-cra');
const { GenerateSW } = require('workbox-webpack-plugin');
module.exports = {
webpack: override(
(config, env) => {
config.plugins.push(
new GenerateSW({
maximumFileSizeToCacheInBytes: INPUT_YOUR_CUSTOM_SIZE,
// 1MB = 1 * 1024 * 1024
})
)
return config;
}
),
}
적용 확인
- 변경 전
- 변경 후
--
개인 깃허브에 별도의 프로젝트를 만들어 코드를 작성했다.
Github
중간에 offline-plugin이란 것을 찾았었다.
시간을 설정해서 특정 시간마다 업데이트 여부를 확인한다.
적용했을때 잘 됐지만 last published가 4년 전이라서 사용하지 않았다.
Create react App - Making a Progressive Web App
스택오버플로우 답변 > 캐시크기 5MB 제한
elice - Progressive Web App (2) React PWA 적용 방법