首屏预渲染方案
2019-05-14 本文已影响0人
jy789
该方案主要是为了解决,前端 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>
)
}