Недавно при оптимизации проекта, помимо общих изменений архитектуры, мы обнаружили, что проблема белого экрана на главной странице очень очевидна каждый раз при ее загрузке.
почему белый экран
Текущий интерфейсный фреймворк,React
,Vue
,Angular
Три гиганта доминируют на рынке, и большинство клиентских приложений на рынке основаны на этих трех фреймворках или библиотеках.Эти три фреймворка имеют общую черту, заключающуюся в том, что все они основаны на JS. page Ничего не будет отображаться, это так называемый белый экран.
Пользователям не нравится видеть белый экран, ничего не отображается, и пользователи, скорее всего, заподозрят, что с сетью или приложением что-то не так. Возьмем в качестве примера Vue.Когда приложение запускается, Vue проверит данные и данные в компоненте.computed
Передано среднее значение статусаObject.defineProperty
Методы преобразуются в свойства доступа SET, GET для отслеживания изменений данных.
Этот процесс выполняется при запуске приложения, что неизбежно приводит к тому, что фаза запуска страницы выполняется медленнее, чем драйверы, отличные от JS (например, приложения jQuery).
Таким образом, наша домашняя страница является типичным случаем. На одном из наших выходных еженедельных встреч наш босс задал нам вопрос, как дать пользователям лучший опыт.
SSR
На этот раз моей первой реакцией было, если постараться, чтобы не появлялся белый экран, можно использовать VueSSR, то есть сервер сразу пойдет на страницу.
Прежде всего, мы узнали, что рендеринг на стороне сервера имеет две основные цели: одна — SEO, а другая — ускорение отображения контента. Принося эти два преимущества, нам также необходимо оценить стоимость рендеринга на стороне сервера.Прежде всего, нам нужна поддержка на стороне сервера, поэтому это включает в себя создание сервиса, развертывание и т. д. В то же время веб проект это сайт с большой посещаемостью, что тоже нужно учитывать Нагрузка на сервер и соответствующая стратегия кеширования, особенно в нашей индустрии, из-за разного географического расположения страницы просматриваемые разными пользователями тоже разные, т.е. так называемые тысячи людей и тысячи лиц, что также вызывает определенные трудности для кэширования.
предварительный рендеринг
Так называемый предварительный рендеринг означает, что в процессе построения проекта с помощью некоторых механизмов рендеринга, таких какpuppeteer
илиjsdom
Страница визуализируется в процессе сборки, а затем вставляется в html, так что первое, что вы видите перед запуском страницы, это предварительно визуализированная страница.
skeleton.js
const puppeteer = require('puppeteer')
const devices = require('puppeteer/DeviceDescriptors') //puppeteer 提供了一些设备的参数选项
const { sleep , genScriptContent } = require('./util/utils') //公共工具方法
const scriptFns = require('./util/browserUtils')
const skeleton = async function(url, option = {}) {
const defaultOption = {
device: 'iPhone 6'
}
const {
device,
defer = 0, //延迟的时间
remove = [], //页面想要移除的class类名数组
excludes = [], //页面想要不包括的class类名数组
hide= [],//页面想要隐藏的class类名数组
launch: launchOpt
} = Object.assign({}, defaultOption, option)
// 当 Puppeteer 连接到一个 Chromium 实例的时候会通过 puppeteer.launch 或 puppeteer.connect 创建一个 Browser 对象。
// 返回一个新的 [Page] 对象。[Page] 在一个默认的浏览器上下文中被创建。
const browser = await puppeteer.launch(launchOpt)
const page = await browser.newPage() //新建一个页面
/**
* 根据指定的参数和 user agent 生成模拟器。此方法是和下面两个方法效果相同
* @param { options }
* viewport <[Object]>
width <[number]> 页面的宽度,单位像素.
height <[number]> 页面的高度,单位像素.
deviceScaleFactor <[number]> 定义设备缩放, (类似于 dpr). 默认 1。
isMobile <[boolean]> 要不要包含meta viewport 标签. 默认 false。
hasTouch<[boolean]> 指定终端是否支持触摸。默认 false
isLandscape <[boolean]> 指定终端是不是 landscape 模式. 默认 false。
userAgent <[string]>
*
*/
await page.emulate(devices[device])
await page.goto(url)
// 将一些 utils 插入到打开的页面执行环境中,这里会引入如何判断图片,文字的方法,将他们覆盖成灰色,也是骨架图中必不可缺的代买
await page.addScriptTag({
content: genScriptContent(...scriptFns)
})
/**
还应注意一点,defer 配置,用于告诉 Puppeteer 打开页面后需等待的时间,这是因为,在打开开发中页面后,页面中有些内容还未真正加载完成,如果在这之前进行骨架页面生成,很有可能导致最终生成的骨架页面和真实页面不符。使得生成骨架页面失败。
**/
await sleep(defer)
/**
* page.evaluate(pageFunction, ...args)
* pageFunction <[function]|[string]> 要在页面实例上下文中执行的方法
...args <...[Serializable]|[JSHandle]> 要传给 pageFunction 的参数
返回: <[Promise]<[Serializable]>> pageFunction执行的结果
*/
const html = await page.evaluate(async ( remove, excludes, hide ) => {
const $ = document.querySelectorAll.bind(document)
if (remove.length) {
const removeEle = $(remove.join(','))
Array.from(removeEle).forEach(ele => ele.parentNode.removeChild(ele))
}
if (hide.length) {
const hideEle = $(hide.join(','))
Array.from(hideEle).forEach(ele => ele.style.opacity = 0)
}
const excludesEle = excludes.length ? Array.from($(excludes.join(','))) : []
await traverse(document.documentElement, excludesEle)
return document.documentElement.outerHTML
}, remove, excludes,hide)
// browser.close()
return { html }
}
module.exports = skeleton