vue
专栏收录该内容
14 篇文章0 订阅
订阅专栏
文章目录
关于SSR
什么是SSR
为什么要用SSR
SSR原理
通用代码约束:
构建SSR应用程序
创建vue项目
修改router/index.js
修改store/index.js
修改main.js
服务端数据预取
客户端数据预取
构建配置
webpack进行打包操作
创建服务
关于SSR
什么是SSR
可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序
简单点说:就是将页面在服务端渲染完成后在客户端直接显示。无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。
为什么要用SSR
更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。
SSR原理
1)所有的文件都有一个公共的入口文件app.js
2)进入ServerEntry(服务端入口)与clientEntry(客户端入口)
3)经过webpack打包生成ServerBundle(供服务端SSR使用,一个json文件)与ClientBundle(给浏览器用,和纯Vue前端项目Bundle类似)
4)当请求页面的时候,node中ServerBundle会生成html界面,通过ClientBundle混合到html页面中
通用代码约束:
实际的渲染过程需要确定性,所以我们也将在服务器上“预取”数据 ("pre-fetching" data) - 这意味着在我们开始渲染时,我们的应用程序就已经解析完成其状态。
避免在 beforeCreate 和 created 生命周期时产生全局副作用的代码,例如在其中使用 setInterval 设置 timer
通用代码不可接受特定平台的 API,因此如果你的代码中,直接使用了像 window 或 document,这种仅浏览器可用的全局变量,则会在 Node.js 中执行时抛出错误,反之也是如此
构建SSR应用程序
创建vue项目
vue create vue-ssr-demo
1
根据提示启动项目
修改router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
export function createRouter(){
return new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: () => import(/* webpackChunkName: "about" */ '../views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
})
}
1
2
3
修改store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export function createStore(){
return new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
}
1
修改main.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
Vue.config.productionTip = false
export function createApp(){
const store = createStore();
const router = createRouter();
const app = new Vue({
store,
router,
render: h => h(App)
})
return {app, store, router}
}
1
2
3
服务端数据预取
在src目录下创建entry-server.js
// entry-server.js
import { createApp } from './main'
export default context => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp()
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
if (!matchedComponents.length) {
return reject({ code: 404 })
}
// 对所有匹配的路由组件调用 `asyncData()`
Promise.all(matchedComponents.map(Component => {
if (Component.asyncData) {
return Component.asyncData({
store,
route: router.currentRoute
})
}
})).then(() => {
// 在所有预取钩子(preFetch hook) resolve 后,
// 我们的 store 现在已经填充入渲染应用程序所需的状态。
// 当我们将状态附加到上下文,
// 并且 `template` 选项用于 renderer 时,
// 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
context.state = store.state
resolve(app)
}).catch(reject)
}, reject)
})
}
1
2
客户端数据预取
在src创建entry-client.js
import { createApp } from './main';
const {app, store, router} = createApp();
router.onReady(() => {
// 添加路由钩子函数,用于处理 asyncData.
// 在初始路由 resolve 后执行,
// 以便我们不会二次预取(double-fetch)已有的数据。
// 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。
router.beforeResolve((to, from, next) => {
const matched = router.getMatchedComponents(to)
const prevMatched = router.getMatchedComponents(from)
// 我们只关心非预渲染的组件
// 所以我们对比它们,找出两个匹配列表的差异组件
let diffed = false
const activated = matched.filter((c, i) => {
return diffed || (diffed = (prevMatched[i] !== c))
})
if (!activated.length) {
return next()
}
// 这里如果有加载指示器 (loading indicator),就触发
Promise.all(activated.map(c => {
if (c.asyncData) {
return c.asyncData({ store, route: to })
}
})).then(() => {
// 停止加载指示器(loading indicator)
next()
}).catch(next)
})
app.$mount('#app')
})
1
2
3
构建配置
在根目录下创建vue.config.js
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const nodeExternals = require('webpack-node-externals')
const merge = require('lodash.merge')
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const target = TARGET_NODE ? 'server' : 'client'
module.exports = {
css: {
extract: false
},
configureWebpack: () => ({
// 将 entry 指向应用程序的 server / client 文件
entry: `./src/entry-${target}.js`,
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
target: TARGET_NODE ? 'node' : 'web',
node: TARGET_NODE ? undefined : false,
output: {
libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,
// 并生成较小的 bundle 文件。
externals: TARGET_NODE
? nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
allowlist: [/.css$/]
})
: undefined,
optimization: {
splitChunks: TARGET_NODE ? false : undefined
},
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
return merge(options, {
optimizeSSR: false
})
})
}
}
1
2
webpack进行打包操作
修改package.json
"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build",
"build:win": "npm run build:server && move dist\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\vue-ssr-server-bundle.json"
1
2
3
执行build:win, 生成的dist目录如下:
创建服务
在根目录下创建server.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const { createBundleRenderer } = require('vue-server-renderer');
const app = express();
const serverBundle = require('./dist/vue-ssr-server-bundle.json');
const clientManifest = require('./dist/vue-ssr-client-manifest.json');
const template = fs.readFileSync(path.resolve('./src/index.template.html'), 'utf-8');
const render = createBundleRenderer(serverBundle, {
runInNewContext: false, // 推荐
template, // (可选)页面模板
clientManifest
});
app.use(express.static('./dist',{index:false}))
app.get('*', (req, res) => {
const context = { url: req.url }
// 这里无需传入一个应用程序,因为在执行 bundle 时已经自动创建过。
// 现在我们的服务器与应用程序已经解耦!
render.renderToString(context, (err, html) => {
console.log(html)
// 处理异常……
res.end(html)
})
})
const port = 3003;
app.listen(port, function() {
console.log(`server started at localhost:${port}`);
});
1
2
index.template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title></title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
运行node server.js
原文链接:https://blog.csdn.net/qq_26443535/article/details/107714957