vue-ssr(上)

vue-ssr解决什么问题?

  • spa全部靠的是js来渲染,默认首页显示内容是一个空的div标签,不利于SEO搜索引擎搜索

  • 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的

  • spa应用会有首页白屏时间过长的问题,服务器渲染的好处是将访问好的数据拼接好给前端,首页白屏时间缩短

服务端渲染的缺点?

需要占用服务器的CPU和内存,前端中的一些生命周期函数无法使用

vue-ssr(上)_vue-ssr

vue-ssr使用

使用的包

vue 
vue-server-renderer 
koa
koa-router复制代码

示例

template.html: 这里的 <!--vue-ssr-outlet--> 是固定字段

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><!--vue-ssr-outlet--></body></html>复制代码
const Koa = require('koa');const Router = require('koa-router');const Vue = require('vue');const VueServerRenderer = require('vue-server-renderer');const app = new Koa();const router = new Router();const path = require('path');const fs = require('fs');const vm = new Vue({data: {name: '张三'},template: '<div>hello {{name}}</div>'})const template = fs.readFileSync(path.resolve(__dirname, 'template.html'), 'utf8');

router.get('/', async (ctx) => {
    ctx.body = await VueServerRenderer.createRenderer(template).renderToString(vm);
})//使用路由app.use(router.routes());
app.listen(3000, () => {console.log('start server success')
})复制代码

通过webpack实现编译vue项目

依赖包

npm i webpack webpack-cli webpack-merge -D
npm i @babel/core @babel/preset-env babel-loader -D
npm i vue-loader vue-style-loader vue-template-compiler -D
npm i css-loader html-webpack-plugin concurrently -D 
复制代码

项目结构

ssr
├── dist
│   ├── client.bundle.js
│   ├── client.html
│   ├── server.bundle.js
│   └── server.html
├── package-lock.json
├── package.json
├── public
│   ├── index.html
│   └── index.ssr.html
├── server.js
├── src
│   ├── App.vue
│   ├── client-entry.js
│   ├── components
│   │   ├── Bar.vue
│   │   └── Foo.vue
│   ├── main.js
│   └── server-entry.js
├── vue-ssr.md
├── webpack.base.js
├── webpack.client.js
└── webpack.server.js复制代码

公共webpack配置

const VueLoaderPlugin = require('vue-loader/lib/plugin')module.exports = {mode: 'development',output: '[name].bundle.js',module: {rules: [{test: /\.css$/,
                use: ['vue-style-loader', {loader: 'css-loader',options: {esModule: false, //注意配合vue-style-loader使用时需要加上这个属性}
                }]
            },
            {test: /\.js$/,
                use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env']
                    }
                }
            },
            {test: /\.vue$/,
                use: 'vue-loader'}
        ]
    },plugins: [new VueLoaderPlugin() //]
}复制代码

客户端webpack配置

const path = require('path');
cosnt HtmlWebpackPlugin = require('html-webpack-plugin');const {
    merge
} = reqire('webpack-merge');const base = require('./webpack.base.js');module.exports = (base, {entry: {client: path.resolve(__dirname, './src/client-entry.js')
    },plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname, './public/index.html'),filename: 'client.html'})
    ]
})复制代码

服务端webpack配置

const path = require('path');
cosnt HtmlWebpackPlugin = require('html-webpack-plugin');const {
    merge
} = reqire('webpack-merge');const base = require('./webpack.base.js');module.exports = merge(base, {target: 'node', //目标是给node使用entry: {server: path.resolve(__dirname, './src/server-entry.js')
    },output: {libraryTarget: 'commonjs2' //让打包后的server.bundle.js用module.exports导出},plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname, './public/index.ssr.html'),filename: 'server.html',excludeChunks: ['server'], //打包后server.html中不引入server.bundle.jsmimyfy: false, //不压缩,防止<!--vue-ssr-outlet-->被覆盖})
    ]
})复制代码

index.ssr.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><!--vue-ssr-outlet--><script src="./client.bundle.js"></script></body></html>复制代码

index.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><div id="app"></div></body></html>复制代码

main.js

const Vue from 'vue';const App from './App.vue';export default () => {const app = new Vue({render: h => h(App)
    })return {
        app
    }
}复制代码

client-entry.js

const createApp from './main.js';const {
    app
} = createApp();
app.$mount('#app');复制代码

server-entry.js

const createApp from './main.js';export default () => {const {
        app
    } = createApp();return app;
}复制代码

server.js

const Koa = require('koa');const Router = require('koa-router');const static = require('koa-static');const VueServerRenderer = require('vue-server-renderer');const fs = require('fs');const path = require('path');const app = new Koa();
cont router = new Router();const serverBundle = fs.readFileSync(path.resolve(__dirname, './dist/server.bundle.js'));const template = fs.readFileSync(path.resolve(__dirname, './dist/server.html'));const render = VueServerRenderer.createBundleRenderer(serverBundle, {
    template
})

router.get('/', async (ctx) => {
    ctx.body = new Promise((resolve, reject) => {
        render.renderToString((err, html) => {if (err) reject(err);
            resolve(html);
        })
    })
})

app.use(route.routes())
app.use(static(path.resolve(__dirname, './dist')));
app.listen(3000, () => {console.log('start server success');
})复制代码

App.vue

这里需要注意的是,在最外层需要添加一个 id='app' , ssr官网上有说 客户端激活 , data-server-rendered 特殊属性,让客户端 Vue 知道这部分 HTML 是由 Vue 在服务端渲染的,并且应该以激活模式进行挂载。注意,这里并没有添加 id="app",而是添加 data-server-rendered 属性:你需要自行添加 ID 或其他能够选取到应用程序根元素的选择器,否则应用程序将无法正常激活,

<template>
  <div id='app'>
    <Foo></Foo>
    <Bar></Bar>
  </div>
</template>

<script>
import Bar from "./components/Bar.vue";
import Foo from "./components/Foo.vue";
export default {
  components: {
    Foo,
    Bar,
  },
};
</script>复制代码

Bar.vue

<template>
    <div>bar</div>
</template>

<style scoped="true">
div{
    background:red
}
</style>复制代码

Foo.vue

<template>
    <div @click="show">foo</div>
</template>
<script>
export default {
    methods:{
        show(){
            alert(1)
        }
    }
}
</script>复制代码