該方案主要是為了解決,前端 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>
)
}
複製程式碼