reactjs 代码拆分导致SPA的新部署后无法加载区块

nbnkbykc  于 2022-12-26  发布在  React
关注(0)|答案(5)|浏览(185)

我有一个单页应用程序,我的代码在每个路线上分裂。当我部署我的应用程序的新版本,用户通常会得到一个错误,如果用户仍然有页面打开,并访问他们以前没有访问过的路线。
另一种可能发生这种情况的情况是应用启用了服务工作器。当用户在新部署后访问页面时,服务工作器将该高速缓存提供服务。然后,如果用户尝试访问不在其缓存中的页面,他们将获得区块加载失败。
目前,我禁用了我的应用程序中的代码拆分,以避免这个问题,但我一直很好奇什么是最好的方法来处理这个问题。我曾考虑过在用户完成加载初始页面后预加载所有其他路线,我相信这可能会解决路线上的代码拆分问题。如果我想在组件上进行代码拆分,那么这就意味着我必须设法弄清楚何时以及如何预加载所有这些组件。
所以我想知道人们是如何处理单页应用程序的这个问题的?谢谢!(我目前正在使用create-react-app)

lyr7nygr

lyr7nygr1#

我更喜欢让用户刷新,而不是自动刷新(这可以防止无限刷新循环错误的可能性)。
以下策略适用于React应用程序,在路由上进行代码拆分:

策略

1.将index.html设置为never cache,这样可以确保请求初始资源的主文件总是最新的(通常它并不大,所以不缓存它应该不是问题),参见MDN Cache Control
1.对你的块使用一致的块散列。这确保只有改变的块才会有不同的散列。(参见下面的webpack.config.js片段)
1.不要在部署时使CDN的缓存无效,这样旧版本就不会在部署新版本时丢失其块。
1.在路线之间导航时检查应用程序版本,以便通知用户是否在旧版本上运行并请求刷新。
1.最后,以防发生ChunkLoadError *:添加一个错误边界。(参见下面的错误边界)

来自webpack.config.js(网络包v4)的代码段

Uday Hiwarale开始:

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
          },
      }
  }

错误边界

React Docs for Error Boundary

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;
  }
}

注意:确保在内部导航事件中清 debugging 误(例如,如果您使用react-router),否则错误边界将在内部导航之后持续存在,并且只有在真正的导航或页面刷新时才会消失。

fzsnzjdm

fzsnzjdm2#

create-react-app中的问题是脚本标记所引用的块不存在,所以它在index.html中抛出了错误。

Uncaught SyntaxError: Unexpected token <    9.70df465.chunk.js:1
    • 更新**

我们解决这个问题的方法是将我们的应用程序制作成一个进步的Web应用程序,这样我们就可以利用服务人员。
将创建React应用转化为PWA非常简单。CRA Docs on PWA
然后,为了确保用户始终使用最新版本的service worker,我们确保每当有更新的worker等待时,我们都会将其告知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不会编译或构建任何语法错误,这应该是我们得到语法错误的唯一情况。

1szpjjfi

1szpjjfi3#

我们用一个稍微难看,虽然真的很简单的解决方案解决了这个问题。可能暂时是这样,但可能会帮助到一些人。
我们创建了一个AsyncComponent来加载块(即路由组件)。当这个组件加载一个块并接收到一个错误时,我们只需要做一个简单的页面重载来更新index.html和它对主块的引用。它丑陋的原因是因为取决于你的页面看起来像什么或者它是如何加载的,用户可能会在刷新之前看到一个短暂的空白页面,这可能有点刺耳,但这也可能是因为我们不希望SPA自动刷新。

    • 应用程序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
dwthyt8l

dwthyt8l4#

我正在使用AsyncComponent 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;
  };
}
rslzwgfq

rslzwgfq5#

"我有办法解决问题"
我也遇到过同样的情况,我在React项目中使用Vite,每次汇总生成bundle块时,它都会生成不同的哈希值,这些哈希值包含在文件名中(例如:在我的路线中,我也使用了很多代码拆分,所以每次我部署到生产环境中,块名称都会改变,这会在客户每次点击链接(指向旧块)时为客户生成空白页面,并且只有在用户刷新页面(强制页面使用新块)时才能解决。
我的解决方案是为我的React应用程序创建一个ErrorBoundary Package 器,它将"拦截"错误,并显示一个漂亮的错误页面,解释问题,并为用户提供重新加载页面(或自动重新加载)的选项

相关问题