如何在webpack中做預渲染降低首屏空白時間

NO IMAGE

一、瀏覽器渲染過程

1、用戶打開頁面,空白屏,等待html的返回

2、html下載完畢,開始解析html,初始渲染

3、下載css、js等資源,執行js渲染虛擬DOM

4、發起請求、獲取數據,渲染內容

下面我們主要是討論一下如何通過預渲染的方式降低空白屏的時間

二、傳統頁面開發

在React、Vue這種數據驅動的框架還沒盛行的時候,一般我們都是直接在html上寫dom結構的,要不就是直接服務端直出,所以我們在下載完html頁面後,空白屏的時間是非常短的,因為dom是在html中的,並不是像現在以虛擬dom的方式寫在js中,所以,我們不需要等待js下載完畢後才開始渲染頁面,而是html下載完畢後直接渲染出dom結構。

如今我們運用Vue等框架進行開發的時候,一般在html結構都是下面這樣的

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<div id="app"></div>
<script src="/bound.js"></script>
</body>
</html>

在js資源沒有下載完畢的情況下,頁面一直都是處於空白的頁面,一直要等到虛擬dom插入到id為app的div中,這時候白屏才消失開始展現頁面,反正就是讓人感覺特別慢就是了!

既然知道了白屏是怎麼產生的,那我們下面就來嘗試一下如何在webpack中集成預渲染的功能,來降低白屏的時間。

三、在webpack中集成預渲染功能

github:webpack中如何集成預渲染功能

這裡我們嘗試將一個使用vue編寫的loading組件在webpack編譯過程中將虛擬dom預渲染到html中,下面是loading組件的內容

<template>
<div class="loading-img"></div>
</template>
<script>
export default {}
</script>
<style>
.loading-img {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
margin: auto;
display: inline-block;
width: 60px;
height: 60px;
background: url(__inline__) no-repeat center center;
background-size: contain;
}
</style>

上面__inline__是用於後面圖片插入的標記,這裡先不用管,其實這個組件就是一個簡單的loading組件

最終我們想要的效果是,將這個vue組件的虛擬dom預渲染到html文件當中

<html>
<head>
<meta charset="UTF-8">
<title>test</title>
<!-- pre-render-loading抽出的css -->
<style>
.loading-img {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
margin: auto;
display: inline-block;
width: 60px;
height: 60px;
<!-- 這裡我們會將loading圖編譯成base64直接插入到html中 -->
background: url(data:image/gif;base64,.....) no-repeat center center;
background-size: contain;
}
</style>
...
</head>
<body>
<div id="app">
<!-- loading base64圖 -->
<div class="loading-img"></div>
</div>
...
</body>
</html>

向上面那樣,在html頁面返回時編譯成base64內嵌到html中的loading就會馬上顯示,大大降低了白屏的時間,基本可以達到秒開頁面,這時候我們不需要等待js資源的下載以及虛擬dom的插入,當然這裡loading中的內容可以是任何你想要預先渲染的模板

如何在webpack中做預渲染降低首屏空白時間
如何在webpack中做預渲染降低首屏空白時間

因為這裡我們的loading組件是用vue寫的,所以我們試著看看如何來做預渲染並集成到webpack中(可以合著倉庫的代碼一起看,代碼挺簡單的,只是一個demo)

這裡我們先把vue單文件中的html與css單獨抽離出來

// render-loading.js
let vueAssets = null
let vueTplPath = resolvePath('./src/loading/pre-render-loading.vue')
const extractAssetsInVueTpl = (vueTplPath) => {
let vueTpl = clearEnter(fs.readFileSync(vueTplPath).toString())
let html = /<template>(.*)<\/template>/g.exec(vueTpl)[1]
let css = /<style>(.*)<\/style>/g.exec(vueTpl)[1]
return {
html,
css
}
}
vueAssets = extractAssetsInVueTpl(vueTplPath)

這裡我們通過正則的方式將template與style標籤中匹配到的內容單獨抽離了出來,接下來我們需要將gif圖轉成base64並插入到我們抽出的css代碼當中

let gifPath = resolvePath('./src/loading/imgs/loading.gif')
const transGifToCSSFile = (imgPath) => {
let ext = path.extname(imgPath).slice(1)
let preStr = `data:image/${ext};base64,`  // 根據尾綴自動拼接對應base64前綴
let bitDate = fs.readFileSync(imgPath)
let base64Str = bitDate.toString('base64')
let dataURL = preStr + base64Str
return dataURL
}
let dataURL = transGifToCSSFile(gifPath)

上面我們通過extractAssetsInVueTpl函數抽離出了css,這裡我們通過一個簡單的函數將佔位符替換成base64圖片

const injectDataURLToCSS = (cssStr, dataURL) => {
return cssStr.replace(/__inline__/, dataURL)
}
let cssStr = injectDataURLToCSS(vueAssets.css, dataURL)

下面我們就導出loading配置,包含了html模板與style樣式字符串

loading.html = vueAssets.html
loading.css = '<style>' + cssStr + '</style>'
module.exports = loading

簡單寫一個webpack入口配置,這裡我們需要使用html-webpack-plugin將loading插入到html中(這裡用到了插件的自定義模板)

const HtmlWebpackPlugin = require('html-webpack-plugin')
const loading = require('./render-loading')
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
loading: loading
})
]
}

在html中我們通過模板語法將loading的內容插入到html模板中對應的位置了

<html>
<head>
<meta charset="UTF-8">
<title>test</title>
...
<%= htmlWebpackPlugin.options.loading.css %>
</head>
<body>
<div id="app">
<!-- loading base64圖 -->
<%= htmlWebpackPlugin.options.loading.html %>
</div>
...
</body>
</html>

四、總結

這裡只是寫一個demo介紹一下原理,更復雜的可以使用vue-server-render來做同構直出或者使用一些像handlebars的模板引擎來生成模板,其實就是將服務端的渲染工作放到了編譯的過程當中。

相關文章

如何理解async/await

使用postcsswritesvg在retina屏繪製1px細線

webpack4搭建現代Hybirdh5工程

如何使用imageset適配移動端高清屏圖片