코드 분할로 인해 SPA 신규 도입 후 청크가 로드되지 않음
각 경로에서 코드 분할하는 단일 페이지 앱이 있습니다.새로운 버전의 앱을 전개할 때, 유저가 아직 페이지를 열고, 이전에 방문하지 않았던 루트를 방문하면, 유저는 통상 에러를 받습니다.
이 문제가 발생할 수 있는 또 다른 시나리오는 앱에 서비스 워커가 활성화되어 있는 경우입니다.신규 전개 후 사용자가 페이지를 방문하면 서비스 워커는 캐시에서 서비스를 받습니다.사용자가 자신의 캐시에 없는 페이지를 방문하려고 하면 청크 로딩에 실패합니다.
현재 이를 피하기 위해 앱에서 코드 분할을 비활성화했는데, 이 문제를 해결하는 가장 좋은 방법이 무엇인지 매우 궁금했습니다.사용자가 초기 페이지를 로드한 후 다른 모든 루트를 프리로드하는 것을 검토했습니다.이것에 의해, 루트의 코드 분할에 관한 문제가 해결될 가능성이 있다고 생각합니다.그러나 컴포넌트를 코드 분할한다고 가정하면 모든 컴포넌트를 프리로드하는 시기와 방법을 파악해야 합니다.
그래서 저는 사람들이 한 페이지 앱에 대해 이 문제를 어떻게 대처하는지 궁금합니다.감사합니다! (현재 create-react-app을 사용하고 있습니다.)
사용자가 자동으로 새로 고치는 것보다 새로 고치는 것을 선호합니다(이를 통해 무한 새로고침 루프 버그가 발생할 가능성을 방지할 수 있습니다).
다음 전략은 Respect 앱에서 잘 작동하며, 경로에서 코드가 분할됩니다.
전략.
index.html을 never cache로 설정합니다.이렇게 하면 초기 자산을 요청하는 기본 파일이 항상 최신 상태로 유지됩니다(일반적으로 크기가 크지 않으므로 캐싱하지 않아도 됩니다).MDN 캐시 제어를 참조하십시오.
청크에 대해 일관된 청크 해시를 사용합니다.이렇게 하면 변경되는 청크만 다른 해시를 가질 수 있습니다.(아래 webpack.config.js 스니펫 참조)
새 버전을 배포할 때 이전 버전의 CDN 캐시가 손실되지 않도록 배포 시 CDN 캐시를 비활성화하지 마십시오.
경로 간 이동 시 앱 버전을 확인하여 사용자에게 이전 버전에서 실행 중인지 알리고 새로 고침을 요청합니다.
마지막으로 ChunkLoadError가 발생할 경우에 대비하여 오류 경계를 추가합니다(아래 오류 경계 참조).
webpack.config.js(Webpack v4)의 단편
optimization: {
moduleIds: 'hashed',
splitChunks: {
cacheGroups: {
default: false,
vendors: false,
// vendor chunk
vendor: {
name: 'vendor',
// async + async chunks
chunks: 'all',
// import file path containing node_modules
test: /node_modules/,
priority: 20
},
}
}
오류 경계
import React, { Component } from 'react'
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error('Error Boundary Caught:', error, errorInfo);
}
render() {
const {error, hasError} = this.state
if (hasError) {
// You can render any custom fallback UI
return <div>
<div>
{error.name === 'ChunkLoadError' ?
<div>
This application has been updated, please refresh your browser to see the latest content.
</div>
:
<div>
An error has occurred, please refresh and try again.
</div>}
</div>
</div>
}
return this.props.children;
}
}
주의: 내부 내비게이션이벤트(리액트 라우터를 사용하는 경우 등)에서는 반드시 에러를 클리어해 주세요.그렇지 않으면 에러 경계가 내부 네비게이션을 지나도 그대로 유지되며 실제 네비게이션 또는 페이지 새로고침 시에만 사라집니다.
create-react-app의 문제는 스크립트 태그가 참조하고 있는 청크가 존재하지 않기 때문에 index.html에 오류가 발생했다는 것입니다.이것이 우리가 받고 있는 오류입니다.
Uncaught SyntaxError: Unexpected token < 9.70df465.chunk.js:1
갱신하다
이 문제를 해결하는 방법은 서비스 워커를 활용할 수 있도록 앱을 진보적인 웹 앱으로 만드는 것입니다.
리액트 앱을 PWA로 변환하는 것은 간단합니다.PWA 상의 CRA 문서
그 후 사용자가 항상 최신 버전의 서비스 워커를 사용하고 있는지 확인하기 위해 새로운 워커가 대기하고 있을 때마다 SKIP_WAITING에 해당 워커가 전달되도록 했습니다.즉, 다음 번에 브라우저를 새로 고쳤을 때 최신 코드를 얻을 수 있습니다.
import { Component } from 'react';
import * as serviceWorker from './serviceWorker';
class ServiceWorkerProvider extends Component {
componentDidMount() {
serviceWorker.register({ onUpdate: this.onUpdate });
}
onUpdate = (registration) => {
if (registration.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
}
render() {
return null;
}
}
export default ServiceWorkerProvider;
벨로우는 우리가 처음으로 시도했던 것이고 어떤 무한 루프에 부딪혔던 것이다.
이 기능을 사용하려면 index.html의 모든 스크립트태그 위에 window.onerror 함수를 추가합니다.
<script>
window.onerror = function (message, source, lineno, colno, error) {
if (error && error.name === 'SyntaxError') {
window.location.reload(true);
}
};
</script>
더 나은 방법이 있으면 좋겠지만, 이것이 제가 생각해낼 수 있는 가장 좋은 방법이고, create-react-app은 구문 오류로 컴파일 또는 빌드되지 않기 때문에 매우 안전한 솔루션이라고 느낄 수 있습니다.이것이 구문 오류가 발생하는 유일한 상황입니다.
우리는 이 문제를 아주 간단한 해결책으로 약간 추악한 방법으로 해결했습니다.지금은 일시적이지만 누군가를 도울 수 있을 거야
청크를 로드하기 위해 작성한 Async Component(루트 컴포넌트)가 있습니다.이 컴포넌트가 청크를 로드하고 에러를 수신하면 index.html을 업데이트하기 위해 간단한 페이지 새로고침을 수행하면 메인 청크를 참조할 수 있습니다.페이지가 보기 흉한 이유는 페이지의 모양이나 로드 방법에 따라 새로 고침 전에 빈 페이지가 잠시 깜박이는 것을 볼 수 있기 때문입니다.다소 불편할 수도 있지만 SPA가 자동으로 갱신되지 않을 것으로 예상되기 때문일 수도 있습니다.
App.js
// import the component for the route just like you would when
// doing async components
const ChunkedRoute = asyncComponent(() => import('components/ChunkedRoute'))
// use it in the route just like you normally would
<Route path="/async/loaded/route" component={ChunkedRoute} />
비동기 컴포넌트.js
import React, { Component } from 'react'
const asyncComponent = importComponent => {
return class extends Component {
state = {
component: null,
}
componentDidMount() {
importComponent()
.then(cmp => {
this.setState({ component: cmp.default })
})
.catch(() => {
// if there was an error, just refresh the page
window.location.reload(true)
})
}
render() {
const C = this.state.component
return C ? <C {...this.props} /> : null
}
}
}
export default asyncComponent
Async Component HOC을 사용하여 부하 청크를 느리게 하고 있는데 같은 문제가 발생하였습니다.에러를 특정하고 하드 새로고침을 한 번 하는 것입니다.
.catch(error => {
if (error.toString().indexOf('ChunkLoadError') > -1) {
console.log('[ChunkLoadError] Reloading due to error');
window.location.reload(true);
}
});
전체 HOC 파일은 다음과 같습니다.
export default class Async extends React.Component {
componentWillMount = () => {
this.cancelUpdate = false;
this.props.load
.then(c => {
this.C = c;
if (!this.cancelUpdate) {
this.forceUpdate();
}
})
.catch(error => {
if (error.toString().indexOf('ChunkLoadError') > -1) {
console.log('[ChunkLoadError] Reloading due to error');
window.location.reload(true);
}
});
};
componentWillUnmount = () => {
this.cancelUpdate = true;
};
render = () => {
const props = this.props;
return this.C ? (
this.C.default ? (
<this.C.default {...props} />
) : (
<this.C {...props} />
)
) : null;
};
}
난 그 문제에 대한 해결책이 있어!
나도 같은 상황에 직면해 있었다.이 경우 React 프로젝트에서 Vite를 사용하고 있으며 롤업이 번들 청크를 생성할 때마다 파일 이름에 포함된 여러 해시가 생성됩니다(예: LoginPage.esm.162.js).또, 코드의 분할도 많이 사용하고 있습니다.따라서 실제 가동 환경에 도입할 때마다 청크 이름이 변경되어 링크를 클릭할 때마다 클라이언트의 공백 페이지가 생성되고(이전 청크를 가리킴), 이는 사용자가 페이지를 새로 고쳤을 때만 해결됩니다(페이지에 새 청크를 사용하도록 강제 적용).
해결방법은 React 어플리케이션용 ErrorBoundary 래퍼를 생성하여 문제를 설명하고 사용자가 페이지를 새로고침(또는 자동으로 새로고침)할 수 있는 옵션을 제공하는 것이었습니다.
언급URL : https://stackoverflow.com/questions/44601121/code-splitting-causes-chunks-to-fail-to-load-after-new-deployment-for-spa
'programing' 카테고리의 다른 글
jquery window.open in ajax 성공 차단 (0) | 2023.03.17 |
---|---|
모든 MUI 컴포넌트에 대해 박스 섀도를 글로벌하게 디세블로 하려면 어떻게 해야 합니까? (0) | 2023.03.12 |
AJAX POST 및 플러스 기호( + ) -- 부호화 방법 (0) | 2023.03.12 |
어레이 및 기능과의 옵션 체인을 사용하려면 어떻게 해야 합니까? (0) | 2023.03.12 |
Jquery Ajax 웹 서비스에 JSON 게시 (0) | 2023.03.12 |