30分钟教会你Vue SSR

30分钟教会你Vue SSR

首页角色扮演代号SSR更新时间:2024-06-27

肝就完了!!!!

Vue SSR

Vue 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