首屏預渲染方案

江紀雲發表於2019-05-14

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

實現方法

通過 webpack 的 prerender-spa-plugin 編譯應用中的靜態頁面,並將其輸出到對應的索引目錄。

prerender-spa-plugin 外掛原理介紹

prerender-spa-plugin 利用了 Puppeteer 的爬取頁面的功能。 Puppeteer 是 Google Chrome 團隊官方的無介面(Headless)Chrome 工具,它是一個 Node 庫,提供了一個高階的 API 來控制 DevTools 協議上的無頭版 Chrome 。prerender-spa-plugin 原理是在 Webpack 構建階段的最後,在本地啟動一個 Puppeteer 的服務,訪問配置了預渲染的路由,然後將 Puppeteer 中渲染的頁面輸出到 HTML 檔案中,並建立路由對應的目錄。

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

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

在專案根目錄新增 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
          },
          // 這個是監聽 document.dispatchEvent 事件,決定什麼時候開始預渲染
          // document.dispatchEvent(new Event('render-event'))
          renderAfterDocumentEvent: 'custom-render-trigger',
        })
      })
    ]);
  }

  return config;
};
複製程式碼
create-react-app 配置已經 eject 的情況:

修改 config 資料夾下的 webpack.config.js ,參考程式碼如下:

plugins: [
  // 預渲染外掛
  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 詳細配置參考官方文件

頁面中使用方案推薦

純靜態頁面,無介面資料情況:

在首頁 react 元件的 didMount 事件中呼叫 document.dispatchEvent(new Event('custom-render-trigger'))

非靜態頁面,有介面資料情況:

方案一:

修改入口檔案 index.js,

//判斷是否是預渲染環境
if(window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.prerender){
  // loading 元件
  import('./skeleton/index.js');
}else{
  // 原先的入口檔案
  import('./page.js');
}
複製程式碼

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

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

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

在首頁元件中,當資料載入完成後,呼叫:

// 移除 loading 或者骨架元件
document.querySelector('.prerender-loading').remove();
// 展示首頁
...
複製程式碼

方案二:

首頁資料未載入前,靜態部分顯示,動態部分顯示骨架元件,參考程式碼如下:

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>靜態ui</div>
      {
        data === null
        ? <Skeleton>骨架ui</Skeleton> 
        : <div>動態資料ui</div>
      }
    </div>
  )
}
複製程式碼

相關文章