create-react-app PWA에 "새 버전 사용 가능"알림 표시
— 하지만 내 컴퓨터에서는 앱이 정상적으로 작동합니다!! 무슨 일이야?!
시작하기 전에 메시지를 남기고 싶습니다.
create-react-app
패키지를 사용하여 만든 시스템에서 새로운 프런트 엔드 배포 문제를 해결하기 위해 내가 찾고 만든 것을 공유하는 방법으로 이 기사를 작성했습니다. 나는 누군가를 돕고 싶고 더 나은 해결책이나 제안이 있으면 아래에 자유롭게 의견을 말하십시오. 커뮤니티와 함께 더 많은 것을 배울 수 있다면 정말 좋을 것입니다.목차
Finally, the implementation
1. 내 앱은 어떻게 되었나요?
To understand what happened, first of all, it is necessary to know some basic concepts as, what is service worker and how it is used on PWAs.
According to Google Developers pageA service worker is a script that your browser runs in the background, separate from a web page [...] The reason this is such an exciting API is that it allows you to support offline experiences, giving developers complete control over the experience.
The intent of the service worker lifecycle is to:
- Make offline-first possible.
- Allow a new service worker to get itself ready without disrupting the current one.
- Ensure an in-scope page is controlled by the same service worker (or no service worker) throughout.
- Ensure there's only one version of your site running at once.
모바일 앱에서와 유사한 PWA의 이 오프라인 경험은 추가 방문을 위해 모든 정적 자산을 캐싱하여 이루어집니다. 그러나 이는 create-react-app documentation에 설명된 대로 기본 서비스 작업자 수명 주기 동작으로 인해 일부 결과가 발생할 수 있습니다.
After the initial caching is done, the service worker lifecycle controls when updated content ends up being shown to users. In order to guard against race conditions with lazy-loaded content, the default behavior is to conservatively keep the updated service worker in the "waiting" state. This means that users will end up seeing older content until they close (reloading is not enough) their existing, open tabs.
따라서 앱의 새 버전이 배포되고 고객이 액세스를 시도하면 브라우저는 새 버전을 식별하지만 고객은 다음 방문 시에만 액세스하고 사용하는 코드의 변경 사항에 따라 달라집니다. 이전 캐시된 앱의 경우 빈 페이지가 발생할 수 있습니다.
2. 코딩을 시작하기 전에 알아두어야 할 중요한 사항
If you take a look at the index.js
of a create-react-app
(CRA) project that uses service worker lifecycle you'll find something like this
...
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.register();
or even something like this, if you're using an old version of CRA
...
if (process.env.NODE_ENV === 'production') {
serviceWorker.register()
}
...
and it's this function, serviceWorker.register()
, that makes all the magic behind the scenes
Now, if you open src/serviceWorker.js
file, what you're gonna see is a bunch of code verifying if the environment is the production one and checking if there is new content waiting to be load on the next visit or if the content is already updated and cached for offline use.
If the content on serviceWorker.js
file seems to be complicated, don't worry! the icing on the cake is the discreet callback named onUpdate(registration)
called when there is a new content waiting to be used. It's that callback we're going to use.
function registerValidSW(swUrl, config) {
...
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
...
}
})
...
}
service-worker.js
will be generated by workbox . 이 파일은 아래와 같이 브라우저 검사 옵션의 source
탭에서 볼 수 있습니다.이전에 말했듯이 발견된 새 업데이트는 다음 액세스 시 로드될 때까지 대기하지만 이 대기 시간을 건너뛸 수 있는 옵션이 있으며 인쇄 화면의 빨간색 화살표는 해당 이벤트를 위해 생성된 이벤트를 가리킵니다.
3. 마지막으로 구현
Once we found such things, we need to implement a feature that sends a SKIP_WAITING
message for the service-worker on the browser when there is a new version available and the user clicks on an update button on a snackbar notification shown on the interface.
— What?!
이를 가능하게 하기 위해 두 가지 상태, 두 가지 기능 및
notistack
패키지를 사용하지만 다른 패키지를 사용할 수도 있습니다.3.1. 함수 만들기
Assuming that a new version could be detected in any route of the app, any of these routes may be able to trigger the skip message to the service-worker event listener when the app loads, so we're going to make the functions and the states on the main application component, the App.js
.
Notice that the approaching will depend on your project. The project I was working on has the App.js
component as a class
but feel free to use React Hooks if you're using functional components.
The first needed states are a boolean to manage the opening of the snackbar and an object for saving the waiting service worker. I named them newVersionAvailable
and waitingWorker
respectively and these states are going to be changed through the onUpdate
callback called when the browser finds out another version of the app. I named this callback onServiceWorkerUpdate()
as can be seen on the code block below.
onServiceWorkerUpdate = registration => {
this.setState({
waitingWorker: registration && registration.waiting,
newVersionAvailable: true
})
}
The next function declared and showed below was the updateServiceWorker()
, that posts the SKIP_WAITING
message and will be used on the snackbar refresh button.
updateServiceWorker = () => {
const { waitingWorker } = this.state
waitingWorker && waitingWorker.postMessage({ type: 'SKIP_WAITING' })
this.setState({ newVersionAvailable: false })
window.location.reload()
}
Besides the addition of these functions, the serviceWorker.register()
should be cut from index.js
and pasted on the App.js
. This register function needs to be executed on the first load of the application and we also need to pass onServiceWorkerUpdate()
function, made previously, as an argument to it as well as use updateServiceWorker()
as the snackbar onClick
function as you can see on the next code block.
componentDidMount = () => {
const { enqueueSnackbar } = this.props;
const { newVersionAvailable } = this.state;
if (process.env.NODE_ENV === 'production') {
serviceWorker.register({ onUpdate: this.onServiceWorkerUpdate });
}
if (newVersionAvailable) //show snackbar with refresh button
enqueueSnackbar("A new version was released", {
persist: true,
variant: "success",
action: this.refreshAction(),
});
};
refreshAction = (key) => { //render the snackbar button
return (
<Fragment>
<Button
className="snackbar-button"
size="small"
onClick={this.updateServiceWorker}
>
{"refresh"}
</Button>
</Fragment>
);
};
With these modifications made, the App.js
should look like this
import React, { Component, Fragment } from "react";
//default imports...
import { withSnackbar } from "notistack";
import * as serviceWorker from "./serviceWorker";
import { Button } from "@material-ui/core";
class App extends Component {
constructor(props) {
super(props);
this.state = {
newVersionAvailable: false,
waitingWorker: {},
};
}
onServiceWorkerUpdate = (registration) => {
this.setState({
waitingWorker: registration && registration.waiting,
newVersionAvailable: true,
});
};
updateServiceWorker = () => {
const { waitingWorker } = this.state;
waitingWorker && waitingWorker.postMessage({ type: "SKIP_WAITING" });
this.setState({ newVersionAvailable: false });
window.location.reload();
};
refreshAction = (key) => { //render the snackbar button
return (
<Fragment>
<Button
className="snackbar-button"
size="small"
onClick={this.updateServiceWorker}
>
{"refresh"}
</Button>
</Fragment>
);
};
componentDidMount = () => {
const { enqueueSnackbar } = this.props;
const { newVersionAvailable } = this.state;
if (process.env.NODE_ENV === 'production') {
serviceWorker.register({ onUpdate: this.onServiceWorkerUpdate });
}
if (newVersionAvailable) //show snackbar with refresh button
enqueueSnackbar("A new version was released", {
persist: true,
variant: "success",
action: this.refreshAction(),
});
};
render() {
//render components
}
}
export default withSnackbar(App); //uses the snackbar context
3.2. 스낵바 공급자 배치
Once the main App component is ready, the file index.js
is the next one to be changed.
On this file, it is necessary to wrap the main App component with the Snackbar provider.
//default imports
import { SnackbarProvider } from "notistack";
ReactDOM.render(
<React.StrictMode>
<SnackbarProvider>
<App/>
</SnackbarProvider>
</React.StrictMode>,
document.getElementById("root")
);
notistack
package I recommend accessing this site .기능 테스트
The last thing to do is testing the feature and to do so it's necessary to build the application . 이를 위해 아래 명령을 사용할 수 있습니다.npm run build
정적 서버를 보다 쉽게 처리하려면 다음 명령을 사용하여 설치 및 실행할 수 있는
serve
패키지를 사용하는 것이 좋습니다.npm install -g serve
serve -s build
이 명령을 실행하면 애플리케이션이 실행되고(아마도 5000 포트에서) 콘솔을 열면 다음과 같은 로그가 표시됩니다.
이제 코드로 돌아가서 예를 들어 버튼 레이블을 변경하는 등 몇 가지 수정 작업을 수행합니다. 이제
npm run build
를 다시 실행하고 제공된 페이지를 새로 고칩니다. 결과는 아래 GIF와 같아야 합니다.Reference
이 문제에 관하여(create-react-app PWA에 "새 버전 사용 가능"알림 표시), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/daniellycosta/showing-new-version-available-notification-on-create-react-app-pwas-3jc9텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)