自从前端三大框架React、Vue、Angular面世以来,前端开发逐渐趋向规范化、统一化,大多数时候新建前端项目,首先想到使用的技术必定是三大框架之一,框架给前端开发带来了极大的便利和规范,可是因为这三大框架都是JS驱动,在JS没有解析加载完成以前页面没法展现,会处于长时间的白屏,带来了必定的用户体验问题,接下来本篇文章会介绍本人最近在白屏优化时遇到的一些问题和思考html
想到白屏问题,首先想到的解决方案通常都是服务端渲染,在服务端将渲染逻辑处理好,而后将处理好的HTML直接返回给前端展现,这样就能够解决白屏的问题,也能够解决seo的问题,由于不须要动态获取数据了,可是,这和我早期的写后端时的开发模式很像,前端和后端关联在了一块儿,不利于维护,同时,对于前端工程师来讲,要求变高来,须要了解必定的后端知识,虽然有相似Nuxt.js这类的SSR框架帮咱们简化了服务端的部分,可是在要作定制或是解决bug时仍是没法避免要对服务端部分进行调试、维护,成本颇高,还有须要考虑的服务端渲染会增长服务器压力,要处理并发、运行速度问题等等前端
这个方案是相对简单直接的一个解决办法,尝试成本也比较低,这里介绍如何用prerender-spa-plugin作预渲染,这样就能够在浏览器进行渲染,而不须要将Vue或者React代码部署到服务器上,以vue-cli3的官方demo为例作配置,看具体的配置文件:vue
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = {
configureWebpack: config => {
let plugins = []
plugins.push(new PrerenderSPAPlugin({
staticDir: path.resolve(__dirname, 'dist'),
routes: ['/', '/about'],
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
renderer: new Renderer({
renderAfterDocumentEvent: 'custom-render-trigger'
})
}))
config.plugins = [
...config.plugins,
...plugins
]
}
}
复制代码
上面代码是经常使用prerender-spa-plugin的配置,staticDir预渲染输出的文件地址,routes要作预渲染的路由,minify压缩相关的配置,renderer渲染引擎相关的配置,能够传入自定以的渲染引擎或者直接使用默认的PuppeteerRenderer,renderAfterDocumentEvent是渲染引擎配置中的一个属性,指当某个事件触发时才执行预渲染,这里 有关于渲染引擎的完整属性介绍,这很重要,尤为是对一些特定场景的下的需求,固然简单场景下彻底能够不配置renderer渲染引擎选项,直接用默认选项。
接下来执行编译,看看会发生什么? node
<div id="app">
<div id="nav">
<a href="/" class="router-link-exact-active router-link-active">Home</a> |
<a href="/about" class="">About</a>
</div>
<div class="home">
<img alt="Vue logo" src="/img/logo.82b9c7a5.png" />
<div class="hello" data-v-7b2de9b7="">
<h1 data-v-7b2de9b7="">Welcome to Your Vue.js App</h1>
<p data-v-7b2de9b7="">For a guide and recipes on how to configure / customize this project,<br data-v-7b2de9b7="" />check out the <a href="https://cli.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-cli documentation</a>.</p>
<h3 data-v-7b2de9b7="">Installed CLI Plugins</h3>
<ul data-v-7b2de9b7="">
<li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" data-v-7b2de9b7="" rel="noopener" target="_blank">babel</a></li>
<li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" data-v-7b2de9b7="" rel="noopener" target="_blank">eslint</a></li>
<li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest" data-v-7b2de9b7="" rel="noopener" target="_blank">unit-jest</a></li>
</ul>
<h3 data-v-7b2de9b7="">Essential Links</h3>
<ul data-v-7b2de9b7="">
<li data-v-7b2de9b7=""><a href="https://vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">Core Docs</a></li>
<li data-v-7b2de9b7=""><a href="https://forum.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">Forum</a></li>
<li data-v-7b2de9b7=""><a href="https://chat.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">Community Chat</a></li>
<li data-v-7b2de9b7=""><a href="https://twitter.com/vuejs" data-v-7b2de9b7="" rel="noopener" target="_blank">Twitter</a></li>
<li data-v-7b2de9b7=""><a href="https://news.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">News</a></li>
</ul>
<h3 data-v-7b2de9b7="">Ecosystem</h3>
<ul data-v-7b2de9b7="">
<li data-v-7b2de9b7=""><a href="https://router.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-router</a></li>
<li data-v-7b2de9b7=""><a href="https://vuex.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vuex</a></li>
<li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-devtools#vue-devtools" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-devtools</a></li>
<li data-v-7b2de9b7=""><a href="https://vue-loader.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-loader</a></li>
<li data-v-7b2de9b7=""><a href="https://github.com/vuejs/awesome-vue" data-v-7b2de9b7="" rel="noopener" target="_blank">awesome-vue</a></li>
</ul>
</div>
</div>
</div>
复制代码
为了方便,这里只贴了app节点里的代码,以往在没有使用预渲染插件时app节点里面是空的没有内容,从加载index.html文件开始到js文件解析完成以前,因为app节点里面是空的,所以页面会处于白屏状态,可是预渲染插件在编译阶段就将对应的路由编译好插入到app节点,这样就能在js文件解析过程当中有内容展现,js解析完成后,Vue会将app节点内的内容替换成Vue渲染好的内容,来看看chrome调试下渲染有什么区别: 常规渲染: webpack
骨架屏的实现原理和预加载相似,都是利用了Puppeteer爬取页面的功能,Puppeteer是chrome出的一个headlessChromenode库,提供了API能够抓取SPA并生成预渲染内容,和预加载不太同样的是骨架屏技术会在Puppeteer生成内容后,利用算法将生成的内容进行替换,生成骨架页面,page-skeleton-webpack-plugin是一个用来生成骨架屏的webpack插件,接下来就来看看怎么使用,仍是以vue-cli3生成的官方项目为例:git
<div id="app"><!-- shell --></div>
复制代码
const SkeletonPlugin = require('page-skeleton-webpack-plugin').SkeletonPlugin
const path = require('path')
module.exports = {
publicPath: '/',
outputDir: 'dist',
configureWebpack: config => {
let plugins = []
plugins.push(new SkeletonPlugin({
pathname: path.resolve(__dirname, './shell'), // pathname为来存储 shell 文件的地址
staticDir: path.resolve(__dirname, './dist'), // 最好和 `output.path` 相同
routes: ['/', '/about'], // 将须要生成骨架屏的路由添加到数组中
port: '7890'
}))
config.plugins = [
...config.plugins,
...plugins
]
},
chainWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.plugin('html').tap(opts => {
console.log(opts[0])
opts[0].minify.removeComments = false
return opts
})
}
}
}
复制代码
上面例子是对page-skeleton-webpack-plugin的简单配置,想要完整的配置能够自行前往github获取,须要注意的是这段代码:github
chainWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.plugin('html').tap(opts => {
console.log(opts[0])
opts[0].minify.removeComments = false
return opts
})
}
}
复制代码
这是修改了vue-cli3中集成的html-webpack-plugin的压缩配置,将移除注释去掉了,由于page-skeleton-webpack-plugin在编译时,注入代码依赖注释,而vue-cli3中集成的html-webpack-plugin会在编译作压缩,将注释去掉,所以要单独配置一下,不然会在编译时致使生成app节点下没有内容。
还有一个在使用时须要注意的点,若是你是vue-cli3脚手架生成的代码,运行时可能会报这样的错误: web
本篇文章简单介绍了我的在白屏优化实践上尝试过的方案,每一个方案都个有本身的优劣,须要本身根据实际的业务场景进行取舍,但愿对你们在解决此类问题时有所帮助。
若是有错误或不严谨的地方,欢迎批评指正,若是喜欢,欢迎点赞算法