如何在webpack中做预渲染降低首屏空白时间

一、浏览器渲染过程

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中的内容可以是任何你想要预先渲染的模板

因为这里我们的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的模板引擎来生成模板,其实就是将服务端的渲染工作放到了编译的过程当中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值