实现思路

参考原文中在构建时使用 Vue 预渲染骨架屏一节介绍的思路,我将骨架屏也看成路由组件,在构建时使用 Vue 预渲染功能,将骨架屏组件的渲染结果 HTML 片段插入 HTML 页面模版的挂载点中,将样式内联到 head 标签中。这样等前端渲染完成时,Vue 将使用客户端混合,把挂载点中的骨架屏内容替换成真正的页面内容。

有了以上思路,让我们看看如何为一个简单的 Vue 应用添加骨架屏。

具体实现

为此我开发了一个 webpack 插件:vue-skeleton-webpack-plugin。下面将从以下三方面介绍部分实现细节:

  • 使用 Vue 预渲染骨架屏
  • 将骨架屏渲染结果插入 HTML 模版中
  • 开发模式下插入各个骨架屏路由

使用 Vue 预渲染骨架屏

我们使用 Vue 的预渲染功能渲染骨架屏组件,不熟悉的同学可以先阅读官方文档中的基本用法一节。

首先需要创建一个仅使用骨架屏组件的入口文件:

// src/entry-skeleton.js

import Skeleton from './Skeleton.vue';
// 创建一个骨架屏 Vue 实例
export default new Vue({
components: {
Skeleton
},
template: '<skeleton />'
});

接下来创建一个用于服务端渲染的 webpack 配置对象,将刚创建的入口文件指定为 entry 依赖入口:

// webpack.skeleton.conf.js
{
target: 'node', // 区别默认的 'web'
entry: resolve('./src/entry-skeleton.js'), // 多页传入对象
output: {
libraryTarget: 'commonjs2'
},
externals: nodeExternals({
whitelist: /\.css$/
}),
plugins: []
}

这里只展示单页应用的情况,在多页应用中,指定 entry 为包含各个页面入口的对象即可。关于多页中的 webpack 配置对象示例,可参考插件的多页测试用例或者Lavas MPA 模版

然后我们将这个 webpack 配置对象通过参数传入骨架屏插件中。

// webpack.dev.conf.js
plugins: [
new SkeletonWebpackPlugin({ // 我们编写的插件
webpackConfig: require('./webpack.skeleton.conf')
})
]

骨架屏插件运行时会使用 webpack 编译这个传入的配置对象,得到骨架屏的 bundle 文件。接下来只需要使用这个 bundle 文件内容创建一个 renderer,调用renderToString()方法就可以得到字符串形式的 HTML 渲染结果了。由于我们不需要将过程中的文件产物保存在硬盘中,使用内存文件系统memory-fs即可。

// vue-skeleton-webpack-plugin/src/ssr.js

const createBundleRenderer = require('vue-server-renderer').createBundleRenderer;
// 从内存文件系统中读取 bundle 文件
let bundle = mfs.readFileSync(outputPath, 'utf-8');
// 创建 renderer
let renderer = createBundleRenderer(bundle);
// 渲染得到 HTML
renderer.renderToString({}, (err, skeletonHtml) => {});

默认情况下,webpack 模块引用的样式内容是内嵌在 JavaScript bundle 中的。官方插件ExtractTextPlugin可以进行样式分离。我们也使用这个插件,将骨架屏样式内容输出到单独的 CSS 文件中。 关于插件更多用法,可参考官方文档或者Vue 基于 webpack 的模版

// vue-skeleton-webpack-plugin/src/ssr.js

// 加入 ExtractTextPlugin 插件到 webpack 配置对象插件列表中
serverWebpackConfig.plugins.push(new ExtractTextPlugin({
filename: outputCssBasename // 样式文件名
}));

至此,我们已经得到了骨架屏的渲染结果 HTML 和样式内容,接下来需要关心如何将结果注入 HTML 页面模版中。

注入渲染结果

Vue webpack 模版项目使用了HTML Webpack Plugin生成 HTML 文件。参考该插件的事件说明,我们选择监听html-webpack-plugin-before-html-processing事件,在事件的回调函数中,插件会传入当前待处理的 HTML 内容供我们进一步修改。

我们知道骨架屏组件最终的渲染结果包含 HTML 和样式两部分,样式部分可以直接插入 head 标签內,而 HTML 需要插入挂载点中。插件使用者可以通过参数设置这个挂载点位置,默认将使用<div id="app">

看起来一切都很顺利,但是在多页应用中,情况会变的稍稍复杂。多页项目中通常会引入多个 HTML Webpack Plugin,例如我们在Lavas MPA 模版中使用的Multipage Webpack 插件就是如此,这就会导致html-webpack-plugin-before-html-processing事件被多次触发。

在多页应用中,我们传给骨架屏插件的 webpack 配置对象是包含多个入口的:

// webpack.skeleton.conf.js
entry: {
page1: resolve('./src/pages/page1/entry-skeleton.js'),
page2: resolve('./src/pages/page2/entry-skeleton.js')
}

这就意味着每次html-webpack-plugin-before-html-processing事件触发时,骨架屏插件都需要识别出当前正在处理的入口文件,执行 webpack 编译当前页面对应的骨架屏入口文件,渲染对应的骨架屏组件。查找当前处理的入口文件过程如下:

// vue-skeleton-webpack-plugin/src/index.js

// 当前页面使用的所有 chunks
let usedChunks = htmlPluginData.plugin.options.chunks;
let entryKey;
// chunks 和所有入口文件的交集就是当前待处理的入口文件
if (Array.isArray(usedChunks)) {
entryKey = Object.keys(skeletonEntries);
entryKey = entryKey.filter(v => usedChunks.indexOf(v) > -)[];
}
// 设置当前的 webpack 配置对象的入口文件和结果输出文件
webpackConfig.entry = skeletonEntries[entryKey];
webpackConfig.output.filename = `skeleton-${entryKey}.js`;
// 使用配置对象进行服务端渲染
ssr(webpackConfig).then(({skeletonHtml, skeletonCss}) => {
// 注入骨架屏 HTML 和 CSS 到页面 HTML 中
});

至此,我们已经完成了骨架屏的渲染和注入工作,接下来有一个开发中的小问题需要关注。

开发模式下插入路由

前面说过,由于 Vue 会使用客户端混合,骨架屏内容在前端渲染完成后就会被替换,那么如何在开发时方便的查看调试呢?

使用浏览器开发工具设置断点是一个办法,但如果能在开发模式中向路由文件插入骨架屏组件对应的路由规则,使各个页面的骨架屏能像其他路由组件一样被访问,将使开发调试变得更加方便。向路由文件插入规则代码的工作将在插件的 loader中完成。如果您对 webpack loader 还不了解,可以参阅官方文档

我们需要向路由文件插入两类代码:引入骨架屏组件的代码和对应的路由规则对象。关于代码插入点,引入组件代码相对简单,放在文件顶部就行了,而路由规则需要插入路由对象数组中,目前我使用的是简单的字符串匹配来查找这个数组的起始位置。例如下面的例子中,需要向 loader 传入routes: [来确定插入路由的位置。

// router.js

import Skeleton from '@/pages/Skeleton.vue'
routes: [
{ // 插入骨架屏路由
path: '/skeleton',
name: 'skeleton',
component: Skeleton
}
// ...其余路由规则
]

在多页应用中,每个页面对应的骨架屏都需要插入代码,使用者可以通过占位符设置引入骨架屏组件语句和路由规则的模版。loader 在运行时会使用这些模版,用真实的骨架屏名称替换掉占位符。在下面的例子中,假设我们有Page1.skeleton.vuePage2.skeleton.vue这两个骨架屏,开发模式下可以通过/skeleton-page1/skeleton-page2访问这两个骨架屏路由。更多参数说明可以参考这里。

// webpack.dev.conf.js

module: {
rules: [] // 其他规则
.concat(SkeletonWebpackPlugin.loader({
resource: resolve('src/router.js'), // 目标路由文件
options: {
entry: ['page1', 'page2'],
importTemplate: 'import [nameCap] from \'@/pages/[name]/[nameCap].skeleton.vue\';',
routePathTemplate: '/skeleton-[name]'
}
}))

demo

multidemo

文章转载自https://juejin.im/entry/5ab37beb518825557005eecc

Vue页面骨架屏(二)的更多相关文章

  1. Vue页面骨架屏(一)

    在开发webapp的时候总是会受到首屏加载时间过长的影响,主流的解决方法是在载入完成之前显示loading图效果,而一些大公司会配置一套服务端渲染的架构来解决这个问题.考虑到ssr所要解决的一系列问题 ...

  2. Vue 项目骨架屏注入与实践

    作为与用户联系最为密切的前端开发者,用户体验是最值得关注的问题.关于页面loading状态的展示,主流的主要有loading图和进度条两种.除此之外,越来越多的APP采用了“骨架屏”的方式去展示未加载 ...

  3. vue搭建骨架屏步骤配置

    1.什么是骨架屏幕? 在页面加载数据之前,有一段空白时间,要么用loading加载,要么就用骨架屏. 在开发webapp的时候总是会受到首屏加载时间过长的影响,主流的解决方法是在载入完成之前显示loa ...

  4. Vue单页面骨架屏实践

    github 地址: VV-UI/VV-UI 演示地址: vv-ui 文档地址:skeleton 关于骨架屏介绍 骨架屏的作用主要是在网络请求较慢时,提供基础占位,当数据加载完成,恢复数据展示.这样给 ...

  5. Vue项目骨架屏注入实践

    相比于早些年前后端代码紧密耦合.后端工程师还得写前端代码的时代,如今已发展到前后端分离,这种开发方式大大提升了前后端项目的可维护性与开发效率,让前后端工程师关注于自己的主业.然而在带来便利的同时,也带 ...

  6. 骨架屏(page-skeleton-webpack-plugin)初探

    作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/2436173500265335 微信公众 ...

  7. 【vue】饿了么项目-页面骨架开发

    1.页面骨架开发 1.1组件拆分 手机浏览器是把页面放在一个虚拟的“窗口”(viewport)中,通常这个虚拟的“窗口”(viewport)比屏幕宽,这样就不用把每个网页挤到很小的窗口中(这样会破坏没 ...

  8. vue骨架屏以及seo优化

    参考文档 vue骨架屏 https://blog.csdn.net/ly124100427/article/details/81168908 vue seo优化 1.SSR服务器渲染: 2.静态化: ...

  9. 钉钉登录二维码嵌套在vue页面中

    转自 https://www.csdn.net/tags/OtDacg3sMjQ2NTgtYmxvZwO0O0OO0O0O.html 钉钉登录二维码嵌套在vue页面中 2021-09-04 14:42 ...

随机推荐

  1. SwipeLayou与ScrollerView滑动冲突

    在SwipeLayout内嵌套ScorllerView滑动会出现上滑滑动冲突,ScollerView不能往上滑,,,,,, mSlv.getViewTreeObserver().addOnScroll ...

  2. [POI2007]石头花园SKA

    Description Blue Mary是一个有名的石头收藏家.迄今为止,他把他的藏品全部放在他的宫殿的地窖中.现在,他想将他的藏品陈列在他的花园中.皇家花园是一个边长为1000000000单位的平 ...

  3. hibernate 中createQuery与createSQLQuery(转载)

    息: java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to com.miracle.dm.doc.catalog.mo ...

  4. (六)Mybatis总结之延迟加载

    应用场景: i.假如一个用户他有N个订单(N>=1000),那么如果一次性加载的话,一个用户对象的订单集合OrderList里面就会有1000多个Order的对象.计算:一个订单对象里面数据有多 ...

  5. mui 时间日期控件(浏览器上无法查看,在手机端可以点击)

    <head> <meta charset="utf-8"> <meta name="viewport" content=" ...

  6. 使用Kotlin,抛弃findViewById

    有没有觉得Android的findViewById挺烦人的.使用Kotlin可以让你彻底抛弃这个烦恼 步骤1.在build.gradle(Module:app)中添加如下一句话 这个在老一点版本的An ...

  7. 重构29-Remove Middle Man(去掉中间人)

    有时你的代码里可能会存在一些"Phantom"或"Ghost"类,Fowler称之为"中间人(Middle Man)".这些中间人类仅仅简单 ...

  8. java web 学习笔记 - servlet01

    ---恢复内容开始--- 1.Servlet介绍 Servlet 是用java语言编写的服务器端小程序,属于一个CGI程序,但与传统的CGI不同的是,它是多线程实现的,并且可以多平台移植. 用户自定义 ...

  9. [eclipse]的快捷键的设置

    今天新解压了一个eclipse,发现alt+/提示的快捷键不好用了,开始比较犯懒,但是发现开发效率低下,总结几个eclipse下快捷方式的解决办法. 第一个解决没有一点提示的情况. 1.首先通过菜单打 ...

  10. Java EE 目标

    在大三上学期学习了Java se,只是简单的学习了语法,而且没有及时的复习巩固,语法知识已经忘了许多.在这个新学期,又有了Java EE这门课,书上的内容是从没学习过的新知识,只是在网站上看到过像Sp ...