首屏預(yù)渲染方案

該方案主要是為了解決,前端 spa (單頁面應(yīng)用),首屏渲染慢,白屏?xí)r間過長問題。

實(shí)現(xiàn)方法

通過 webpack 的 prerender-spa-plugin 編譯應(yīng)用中的靜態(tài)頁面,并將其輸出到對(duì)應(yīng)的索引目錄。

prerender-spa-plugin 插件原理介紹

prerender-spa-plugin 利用了 Puppeteer 的爬取頁面的功能。 Puppeteer 是 Google Chrome 團(tuán)隊(duì)官方的無界面(Headless)Chrome 工具,它是一個(gè) Node 庫,提供了一個(gè)高級(jí)的 API 來控制 DevTools 協(xié)議上的無頭版 Chrome 。prerender-spa-plugin 原理是在 Webpack 構(gòu)建階段的最后,在本地啟動(dòng)一個(gè) Puppeteer 的服務(wù),訪問配置了預(yù)渲染的路由,然后將 Puppeteer 中渲染的頁面輸出到 HTML 文件中,并建立路由對(duì)應(yīng)的目錄。

在 create-react-app 中配置 prerender-spa-plugin

create-react-app 配置未 eject 的情況:

在項(xiàng)目根目錄添加 config-overrides.js 配置文件,參考配置如下:

const PrerenderSpaPlugin = require('prerender-spa-plugin');
const path = require('path');

module.exports = (config, env) => {
  if (env === 'production') {
    config.plugins = config.plugins.concat([
      new PrerenderSpaPlugin({
        staticDir: path.join(__dirname, 'build'),
        routes: ['/'],
        renderer: new PrerenderSpaPlugin.PuppeteerRenderer({
          injectProperty: '__PRERENDER_INJECTED',
          inject: {
            prerender: true
          },
          // 這個(gè)是監(jiān)聽 document.dispatchEvent 事件,決定什么時(shí)候開始預(yù)渲染
          // document.dispatchEvent(new Event('render-event'))
          renderAfterDocumentEvent: 'custom-render-trigger',
        })
      })
    ]);
  }

  return config;
};
create-react-app 配置已經(jīng) eject 的情況:

修改 config 文件夾下的 webpack.config.js ,參考代碼如下:

plugins: [
  // 預(yù)渲染插件
  isEnvProduction && new PrerenderSpaPlugin({
    staticDir: path.join(__dirname, '../build'),
    routes: ['/'],
    renderer: new PrerenderSpaPlugin.PuppeteerRenderer({
      injectProperty: '__PRERENDER_INJECTED',
      inject: {
        prerender: true
      },
      renderAfterDocumentEvent: 'custom-render-trigger',
    })
  }),
  ...
]

prerender-spa-plugin 詳細(xì)配置參考官方文檔

prerender-spa-plugin.png

頁面中使用方案推薦

純靜態(tài)頁面,無接口數(shù)據(jù)情況:

在首頁 react 組件的 didMount 事件中調(diào)用 document.dispatchEvent(new Event('custom-render-trigger'))

非靜態(tài)頁面,有接口數(shù)據(jù)情況:

方案一:

修改入口文件 index.js,

//判斷是否是預(yù)渲染環(huán)境
if(window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.prerender){
  // loading 組件
  import('./skeleton/index.js');
}else{
  // 原先的入口文件
  import('./page.js');
}

skeleton/index.js 將骨架組件或者 loading 組件輸出到 index.html,參考代碼如下:

// 創(chuàng)建loading容器
const container = document.createElement("div");
container.className = 'prerender-loading';
document.body.appendChild(container);

// 渲染骨架組件或者 loading 組件
ReactDOM.render(
  <div style={{padding: '16px'}}>
    <Skeleton/>
  </div>, 
container);

在首頁組件中,當(dāng)數(shù)據(jù)加載完成后,調(diào)用:

// 移除 loading 或者骨架組件
document.querySelector('.prerender-loading').remove();
// 展示首頁
...

方案二:

首頁數(shù)據(jù)未加載前,靜態(tài)部分顯示,動(dòng)態(tài)部分顯示骨架組件,參考代碼如下:

export default function Index() {
  const [data, setData] = useState(null)

  useMount(() => {
    document.dispatchEvent(new Event('custom-render-trigger'));
  });

  // 模擬getdata
  useTimeout(() => {
    setData({...});
  }, 200)

  return (
    <div className='Index'>
      <div>靜態(tài)ui</div>
      {
        data === null
        ? <Skeleton>骨架ui</Skeleton> 
        : <div>動(dòng)態(tài)數(shù)據(jù)ui</div>
      }
    </div>
  )
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容