现在的应用开发,基本上都是前后端分离的,前端主流框架有SPA、MPA等,那么解决页面渲染、白屏时间成为首要关注的点
webpack可以按需加载,减小首屏需要加载代码的体积;
使用CDN技术、静态代码等缓存技术,可以减小加载渲染的时长
问题:但是首页依然存在加载、渲染等待时长的问题。那么如何从视觉效果上减小首屏白屏的时间呢?
骨架屏:举个例子:其实就是在模版文件中id=app容器下面写想要展示的效果,在new Vue(option)之后,该id下的内容就被替换了( 这时候,可能Vue编译生成的内容还没有挂载。因为new Vue的时候会进行一系列的初始化,这也需要耗费时间的)。这样就可以从视觉上减小白屏的时间
骨架屏的实现方式
1、直接在模版文件id=app容器下面,写进想要展示的效果html
2、直接在模板文件id=app容器下面,用图片展示
3、使用vue ssr提供的webpack插件
4、自动生成并且自动插入静态骨架屏
方式1和方式2存在的缺陷:针对不同入口,展示的效果都一样,导致不能灵活的针对不同的入口,展示不同的样式
方式3可以针对不同的入口展示不同的效果。(实质也是先通过ssr生成一个json文件,然后将json文件内容注入到模板文件的id=app容器下)
方案一、直接在模版文件id=app容器下面,写进想要展示的效果html
在根目录的模版文件内写进内容,如红色圈出来的地方
在浏览器打开项目
在调用new Vue之前的展示效果(只是做了个简单效果,不喜勿喷):
可以看到elements中id=app的容器下内容,就是我们写进的骨架屏效果内容
在看下调了new Vue之后的效果,id=app容器下的内容被vue编译生成的内容替换了
方案二、直接在模板文件id=app容器下面,用图片展示(这个就不做展示了)
方案三、使用vue ssr提供的webpack插件:即用.vue文件完成骨架屏
在方案一的基础上,将骨架屏的代码抽离出来,不在模版文件里面书写代码,而是在vue文件里面书写效果代码,这样便于维护
1、在根目录下建一个skeleton文件夹,在该目录下创建文件App.vue文件(根组件,类似Vue项目的App.vue)、home.skeleton.vue(首页骨架屏展示效果的代码,类似Vue项目写的路由页面)、skeleton-entry.js(入口文件类似Vue项目的入口文件)、plugin/server-plugin.js(vue-server-renderer包提供了server-plugin插件,从里面将代码拷贝出来)
home.skeleton.vue(首页骨架屏展示效果的代码)
-
<template>
-
<div class="skeleton-home">
-
<div>加载中...
</div>
-
</div>
-
</template>
-
-
<style>
-
.skeleton-home {
-
width:
100vw;
-
height:
100vh;
-
background-color:
#eaeaea;
-
}
-
</style>
App.vue(根组件)
-
<template>
-
<div id="app">
-
<!-- 根组件 -->
-
<home style="display:none" id="homeSkeleton">
</home>
-
</div>
-
</template>
-
<script>
-
import home
from
'./home.skeleton.vue'
-
export
default{
-
components: {
-
home
-
}
-
}
-
</script>
-
<style>
-
#app {
-
font-family:
'Avenir', Helvetica, Arial, sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
text-align: center;
-
color:
#2c3e50;
-
}
-
*{
-
padding:
0;
-
margin:
0;
-
}
-
</style>
skeleton-entry.js(入口文件)
-
// 入口文件
-
import Vue
from
'vue'
-
import App
from
'./App.vue'
-
let skeleton =
new Vue({
-
render(h) {
-
return h(App)
-
}
-
})
-
export
default skeleton
plugin/server-plugin.js(vue-server-renderer包提供了server-plugin插件)
-
'use strict';
-
-
/* */
-
-
var isJS =
function (file) {
return
/\.js(\?[^.]+)?$/.test(file); };
-
-
var ref =
require(
'chalk');
-
var red = ref.red;
-
var yellow = ref.yellow;
-
-
var prefix =
"[vue-server-renderer-webpack-plugin]";
-
var warn =
exports.warn =
function (msg) {
return
console.error(red((prefix +
" " + msg +
"\n"))); };
-
var tip =
exports.tip =
function (msg) {
return
console.log(yellow((prefix +
" " + msg +
"\n"))); };
-
-
var validate =
function (compiler) {
-
if (compiler.options.target !==
'node') {
-
warn(
'webpack config `target` should be "node".');
-
}
-
-
if (compiler.options.output && compiler.options.output.libraryTarget !==
'commonjs2') {
-
warn(
'webpack config `output.libraryTarget` should be "commonjs2".');
-
}
-
-
if (!compiler.options.externals) {
-
tip(
-
'It is recommended to externalize dependencies in the server build for ' +
-
'better build performance.'
-
);
-
}
-
};
-
-
var VueSSRServerPlugin =
function VueSSRServerPlugin (options) {
-
if ( options ===
void
0 ) options = {};
-
-
this.options =
Object.assign({
-
filename:
'vue-ssr-server-bundle.json'
-
}, options);
-
};
-
-
VueSSRServerPlugin.prototype.apply =
function apply (compiler) {
-
var this$
1 =
this;
-
-
validate(compiler);
-
-
compiler.plugin(
'emit',
function (compilation, cb) {
-
var stats = compilation.getStats().toJson();
-
var entryName =
Object.keys(stats.entrypoints)[
0];
-
var entryAssets = stats.entrypoints[entryName].assets.filter(isJS);
-
-
if (entryAssets.length >
1) {
-
throw
new
Error(
-
"Server-side bundle should have one single entry file. " +
-
"Avoid using CommonsChunkPlugin in the server config."
-
)
-
}
-
-
var entry = entryAssets[
0];
-
if (!entry ||
typeof entry !==
'string') {
-
throw
new
Error(
-
(
"Entry \"" + entryName +
"\" not found. Did you specify the correct entry option?")
-
)
-
}
-
-
var bundle = {
-
entry: entry,
-
files: {},
-
maps: {}
-
};
-
-
stats.assets.forEach(
function (asset) {
-
if (asset.name.match(
/\.js$/)) {
-
bundle.files[asset.name] = compilation.assets[asset.name].source();
-
}
else
if (asset.name.match(
/\.js\.map$/)) {
-
bundle.maps[asset.name.replace(
/\.map$/,
'')] =
JSON.parse(compilation.assets[asset.name].source());
-
}
-
// do not emit anything else for server
-
delete compilation.assets[asset.name];
-
});
-
-
var json =
JSON.stringify(bundle,
null,
2);
-
var filename = this$
1.options.filename;
-
-
compilation.assets[filename] = {
-
source:
function () {
return json; },
-
size:
function () {
return json.length; }
-
};
-
-
cb();
-
});
-
};
-
-
module.exports = VueSSRServerPlugin;
2、新建一个骨架屏构建配置文件:build/webpack.skeleton.conf.js,这个文件配合vue-server-renderer插件,将App.vue内容构建成单个json格式的文件
-
'use strict'
-
-
const path =
require(
'path')
-
const nodeExternals =
require(
'webpack-node-externals')
-
const VueSSRServerPlugin =
require(
'../skeleton/plugin/server-plugin')
-
-
module.exports = {
-
// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
-
// 并且还会在编译 Vue 组件时,
-
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
-
target:
'node',
-
-
// 对 bundle renderer 提供 source map 支持
-
devtool:
'source-map',
-
-
// 将 entry 指向应用程序的 server entry 文件
-
entry: path.resolve(__dirname,
'../skeleton/skeleton-entry.js'),
-
-
output: {
-
path: path.resolve(__dirname,
'../skeleton'),
// 生成的文件的目录
-
publicPath:
'/skeleton/',
-
filename:
'[name].js',
-
libraryTarget:
'commonjs2'
// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
-
},
-
-
module: {
-
rules: [
-
{
-
test:
/\.vue$/,
-
loader:
'vue-loader',
-
options: {
-
compilerOptions: {
-
preserveWhitespace:
false
-
}
-
}
-
},
-
{
-
test:
/\.css$/,
-
use: [
'vue-style-loader',
'css-loader']
-
}
-
]
-
},
-
-
performance: {
-
hints:
false
-
},
-
-
// https://webpack.js.org/configuration/externals/#function
-
// https://github.com/liady/webpack-node-externals
-
// 外置化应用程序依赖模块。可以使服务器构建速度更快,
-
// 并生成较小的 bundle 文件。
-
externals: nodeExternals({
-
// 不要外置化 webpack 需要处理的依赖模块。
-
// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
-
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
-
allowlist:
/\.css$/
-
}),
-
-
// 这是将服务器的整个输出
-
// 构建为单个 JSON 文件的插件。
-
// 不配置filename,则默认文件名为 `vue-ssr-server-bundle.json`
-
plugins: [
-
new VueSSRServerPlugin({
-
filename:
'skeleton.json'
-
})
-
]
-
}
3、使用webpack-cli运行文件webpack.skeleton.conf.js,生成skeleton.json文件,放置在文件夹skeleton下
在package.json文件里面书写运行命令:create-skeleton
-
"scripts": {
-
"create-skeleton":
"webpack --progress --config build/webpack.skeleton.conf.js",
-
"fill-skeleton":
"node ./skeleton/skeleton.js"
-
}
在控制台上运行命令:
npm run create-skeleton
文件夹skeleton下就会多出skelleton.json文件
4、将生成的skeleton.json内容注入到根目录下的index.html(模版文件)
1)在文件夹skeleton下新建skeleton.js
-
// 将生成的skeleton.json的内容填充到模板文件中
-
const fs =
require(
'fs')
-
const { resolve } =
require(
'path')
-
const createBundleRenderer =
require(
'vue-server-renderer').createBundleRenderer
-
-
// 读取skeleton.json,以skeleton/index.html为模版写入内容
-
const renderer = createBundleRenderer(resolve(__dirname,
'../skeleton/skeleton.json'), {
-
template: fs.readFileSync(resolve(__dirname,
'../skeleton/index.html'),
'utf-8')
-
})
-
// 把上一步模版完成的内容写入根目录下的模版文件'index.html'
-
renderer.renderToString({},
(err, html) => {
-
if (err) {
-
return
console.log(err)
-
}
-
console.log(
'render complete!')
-
fs.writeFileSync(
'index.html', html,
'utf-8')
-
})
2)添加运行命令:fill-skeleton
"fill-skeleton": "node ./skeleton/skeleton.js"
3)在控制台上运行该命令,则skeleton.json文件内容被填充至根目录下的模板文件index.html了
参考文章:
利用Vue SSR 做骨架屏注入:https://www.cnblogs.com/goloving/p/11397371.html
在Vue中实现骨架屏:http://www.360doc.com/content/20/0709/11/21412_923150401.shtml
Vue ssr渲染踩过的坑:https://blog.csdn.net/chen801090/article/details/105974987/
转载:https://blog.csdn.net/tangxiujiang/article/details/116832585