肝就完了!!!!
Vue SSRVue SSR原理基本与React SSR类似,但是Vue比React更为简单一些,下面开始介绍下Vue SSR
Webpack配置// client端
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(baseConfig, {
entry: './src/entry-client.js',
plugins: [
// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
// 以便可以在之后正确注入异步 chunk。
// 这也为你的 应用程序/vendor 代码提供了更好的缓存。
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
// 此插件在输出目录中
// 生成 `vue-ssr-client-manifest.json`。
new VueSSRClientPlugin()
]
})
// server端
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(baseConfig, {
// 将 entry 指向应用程序的 server entry 文件
entry: './src/entry-server.js',
// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
// 并且还会在编译 Vue 组件时,
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
target: 'node',
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
output: {
libraryTarget: 'commonjs2'
},
externals: nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
whitelist: /\.css$/
}),
// 这是将服务器的整个输出
// 构建为单个 JSON 文件的插件。
// 默认文件名为 `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
]
})
经过以上两步,出口文件夹在正常输出文件以外多出两个json文件
同构同样是核心理念,在两端书写一套共有的代码
实例化
实例化Vue, 这里要主要的是上面讲到的是交叉渲染污染,所以设计成函数形式, 每次请求都会实例化一个新的app出来
// app.js
import { sync } from 'vuex-router-sync'
...
export function createApp() {
const router = createRouter()
const store = createStore()
sync(store, router) // 将vuex与vue-router状态共享
const app = new Vue({
store,
router,
render: h => h(App)
})
return {
app,
store,
router
}
}
路由同构
// router.js
export default function createRouter() {
return new Router({
... // 正常配置路由
})
}
浏览器正常使用, 在app.js实例化的时候就已经同步塞入Vue里了
服务端由于涉及到一些数据预期等等额外的操作, 需要进行简单处理下
根据官方定义的一个调用方法asyncData, 在服务端获取到客户端请求的时候,会先检测组件配置里是否定义了asyncData, 若有的话, 则进行调用, 在这里声明下asyncData是在Vue实例化之前调用的, 也就是说此时是无法获取到执行上下文的, 所以需要用到公共库Vuex来存储数据(当然自己写一个类似的也可以), asyncData会传入store与router的参数给到组件
import { createApp } from './app'
export default context => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp(context)
// 服务端与客户端路径确保一致
router.push(context.url)
// 保证路由全部加载完
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// 匹配不到的路由,执行 reject 函数,并返回 404
if (!matchedComponents.length) {
return reject({ code: 404 })
}
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)
// 对所有匹配的路由组件调用 `asyncData()`
}, reject)
}).catch(err => {
})
}
以上有个特别要指明的地方:
Vue有两种形式,一个是vue文件, 另一个是直接在Vue实例上进行视图驱动, 两种数据填充是不同的
import { createApp } from './app'
// 客户端特定引导逻辑……
const { app, store } = createApp()
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
实例
Vue会直接在data上定义一个相应数据, 可以直接使用
// store.js
mutations: {
setItem (state, { id, item }) {
Vue.set(state.items, id, item) // 直接定义一个响应数据
}
}
启动服务
Vue提供了一个api, createBundleRenderer, 用于处理官方介绍的问题
每次编辑过应用程序源代码之后,都必须停止并重启服务。这在开发过程中会影响开发效率。此外,Node.js 本身不支持 source map
好处:
const express = require('express')
const server = express()
const path = require('path')
const { createBundleRenderer } = require('vue-server-renderer')
const serverMainfest = path.resolve(__dirname, './dist/vue-ssr-server-bundle.json')
const renderer = createBundleRenderer(serverMainfest, {
runInNewContext: false, // true的时候 每次请求Vue会自己去实例化一个App, 这个主要就是避免交叉污染,但是其实我们已经避免了这个问题,所以可以设置为false, 毕竟这是一个性能消耗的过程
template: require('fs').readFileSync(path.join(__dirname, './index.html'), 'utf-8'), // html模板,
// <div id="app">
// <!--vue-ssr-outlet--> Vue会将内容自动填充到此处
// </div>
clientManifest: require(path.resolve(__dirname, './dist/vue-ssr-client-manifest.json')) // 这里就是之前webpack打包好的
})
server.use(express.static(path.resolve(__dirname, './dist')))
server.get('*', (req, res) => {
const context = {
url: req.url
}
renderer.renderToString(context, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(`
${html}
`)
})
})
server.listen(8080)
遇到的问题
解决: 由于服务端只会执行beforeCreate与created生命周期(因为服务端不会去挂载DOM), 所以可以在beforeMount方法里去动态加载模块
beforeMount() {
if (typeof window !== 'undefined') {
const { swiper, swiperSlide } = require('vue-awesome-swiper').default
this.$options.components.swiper = swiper
this.$options.components.swiperSlide = swiperSlide
}
},
mounted() {
if (typeof window !== "undefined") {
const Fastclick = require('fastclick')
Fastclick.attach(document.body)
}
}
后言
不知道我说的够不够清楚,总之,SSR核心理念就是同构, 写一套代码通用于两端
服务端职责
浏览器职责
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved