我已经使用Vue CLI创建了一个Vue3应用程序,用Vuex和路由器创建我的应用程序。应用程序运行良好。
注意:我遵循了这个有用的文档,用于Vue3的Vuexhttps://blog.logrocket.com/using-vuex-4-with-vue-3/
要求现在我想更改我的Vue3应用程序,使其具有服务器端渲染支持(即SSR)。
我观看了关于使用Vue3创建SSR应用程序的精彩视频:https://www.youtube.com/watch?v=XJfaAkvLXyU我可以创建并运行一个简单的应用程序,如视频中所示。但是,当我尝试将其应用到我的主Vue3应用程序时,我被卡住了。
我目前的难点是如何在服务器代码上指定路由器和vuex。
我的代码
客户端条目文件(src/main.js)具有以下内容
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
createApp(App).use(store).use(router).mount('#app');
服务器条目文件(src/main.server.js)当前具有以下内容
import App from './App.vue';
export default App;
并在当前拥有的快速服务器文件(src/server.js)中
const path = require('path');
const express = require('express');
const { createSSRApp } = require('vue');
const { renderToString } = require('@vue/server-renderer');
...
...
server.get('*', async (req, res) => {
const app = createSSRApp(App);
const appContent = await renderToString(app);
我需要更改此代码,以便服务器端的应用程序像在客户端一样使用路由器和vuex。
问题
在express server文件中,我无法在客户端条目文件中导入路由器和vuex,因为它由于在模块外部导入而失败,因此在express server中,我无法执行以下操作
const app = createSSRApp(App).use(store).use(router);
我尝试将服务器条目文件(src/main.server.js)更改为以下内容,但这也不起作用。
import App from './App.vue';
import router from './router';
import store from './store';
const { createSSRApp } = require('vue');
export default createSSRApp(App).use(store).use(router);
当您的应用程序使用Vuex和路由器时,是否有人知道如何在Vue 3中进行SSR。
下面是我在Vue 2中是如何做到这一点的,以及我试图将其转换为Vue 3的内容
此应用程序的Vue2版本具有以下代码
src/app.js创建指定路由器和存储的Vue组件
客户端输入文件(src/Client/main.js)从app.js获取应用,用html中序列化的数据预先填充Vuex存储,在路由器准备好时挂载应用
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
import App from './pages/App.vue';
import createStore from './vuex/store';
import createRouter from './pages/router';
export default function createApp() {
const store = createStore();
const router = createRouter();
sync(store, router);
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return { app, router, store };
}
服务器条目文件(src/Server/main.js),从应用程序获取应用程序。js,获取匹配的路由,该路由将调用每个组件上的“serverPrefetch”以在Vuex存储中填充其数据,然后返回解析promise
import createApp from '../app';
export default (context) => new Promise((resolve, reject) => {
const { app, router, store } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject(new Error('404'));
}
context.rendered = () => {
context.state = store.state;
};
return resolve(app);
}, reject);
});
expressserver(/server.js)使用bundle呈现程序将应用程序呈现为字符串,以放入html中
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const dotenv = require('dotenv');
dotenv.config();
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-server-bundle.json'),
{
template: fs.readFileSync('./index.html', 'utf-8'),
},
);
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = {
url: req.url,
clientBundle: `client-bundle.js`,
};
bundleRenderer.renderToString(context, (err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
res.end(html);
}
});
});
const port = process.env.PORT || 3000
server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
您还可以使用Vite,它具有本地SSR支持,与Webpack不同,无需配置即可使用。
如果你使用vite-plugin-ssr,那就更容易了。
下面重点介绍了vite-plugin-ssr的Vuex示例的主要部分
<template>
<h1>To-do List</h1>
<ul>
<li v-for="item in todoList" :key="item.id">{{item.text}}</li>
</ul>
</template>
<script>
export default {
serverPrefetch() {
return this.$store.dispatch('fetchTodoList');
},
computed: {
todoList () {
return this.$store.state.todoList
}
},
}
</script>
import Vuex from 'vuex'
export { createStore }
function createStore() {
const store = Vuex.createStore({
state() {
return {
todoList: []
}
},
actions: {
fetchTodoList({ commit }) {
const todoList = [
{
id: 0,
text: 'Buy milk'
},
{
id: 1,
text: 'Buy chocolate'
}
]
return commit('setTodoList', todoList)
}
},
mutations: {
setTodoList(state, todoList) {
state.todoList = todoList
}
}
})
return store
}
import { createSSRApp, h } from 'vue'
import { createStore } from './store'
export { createApp }
function createApp({ Page }) {
const app = createSSRApp({
render: () => h(Page)
})
const store = createStore()
app.use(store)
return { app, store }
}
import { renderToString } from '@vue/server-renderer'
import { html } from 'vite-plugin-ssr'
import { createApp } from './app'
export { render }
export { addContextProps }
export { setPageProps }
async function render({ contextProps }) {
const { appHtml } = contextProps
return html`<!DOCTYPE html>
<html>
<body>
<div id="app">${html.dangerouslySetHtml(appHtml)}</div>
</body>
</html>`
}
async function addContextProps({ Page }) {
const { app, store } = createApp({ Page })
const appHtml = await renderToString(app)
const INITIAL_STATE = store.state
return {
INITIAL_STATE,
appHtml
}
}
function setPageProps({ contextProps }) {
const { INITIAL_STATE } = contextProps
return { INITIAL_STATE }
}
import { getPage } from 'vite-plugin-ssr/client'
import { createApp } from './app'
hydrate()
async function hydrate() {
const { Page, pageProps } = await getPage()
const { app, store } = createApp({ Page })
store.replaceState(pageProps.INITIAL_STATE)
app.mount('#app')
}
多亏了以下资源,我成功地找到了解决方案:
>
SSR Vuex路由器应用程序:https://github.com/shenron/vue3-example-ssr
从Vue 2迁移到Vue 3https://v3.vuejs.org/guide/migration/introduction.html
从VueRout3迁移到VueRout4https://next.router.vuejs.org/guide/migration/
从Vuex 3迁移到Vuex 4https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html
客户端条目文件(src/main.js)
import buildApp from './app';
const { app, router, store } = buildApp();
const storeInitialState = window.INITIAL_DATA;
if (storeInitialState) {
store.replaceState(storeInitialState);
}
router.isReady()
.then(() => {
app.mount('#app', true);
});
服务器条目文件(src/main server.js)
import buildApp from './app';
export default (url) => new Promise((resolve, reject) => {
const { router, app, store } = buildApp();
// set server-side router's location
router.push(url);
router.isReady()
.then(() => {
const matchedComponents = router.currentRoute.value.matched;
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject(new Error('404'));
}
// the Promise should resolve to the app instance so it can be rendered
return resolve({ app, router, store });
}).catch(() => reject);
});
src/app。js
import { createSSRApp, createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
const isSSR = typeof window === 'undefined';
export default function buildApp() {
const app = (isSSR ? createSSRApp(App) : createApp(App));
app.use(router);
app.use(store);
return { app, router, store };
}
服务器js
const serialize = require('serialize-javascript');
const path = require('path');
const express = require('express');
const fs = require('fs');
const { renderToString } = require('@vue/server-renderer');
const manifest = require('./dist/server/ssr-manifest.json');
// Create the express app.
const server = express();
// we do not know the name of app.js as when its built it has a hash name
// the manifest file contains the mapping of "app.js" to the hash file which was created
// therefore get the value from the manifest file thats located in the "dist" directory
// and use it to get the Vue App
const appPath = path.join(__dirname, './dist', 'server', manifest['app.js']);
const createApp = require(appPath).default;
const clientDistPath = './dist/client';
server.use('/img', express.static(path.join(__dirname, clientDistPath, 'img')));
server.use('/js', express.static(path.join(__dirname, clientDistPath, 'js')));
server.use('/css', express.static(path.join(__dirname, clientDistPath, 'css')));
server.use('/favicon.ico', express.static(path.join(__dirname, clientDistPath, 'favicon.ico')));
// handle all routes in our application
server.get('*', async (req, res) => {
const { app, store } = await createApp(req);
let appContent = await renderToString(app);
const renderState = `
<script>
window.INITIAL_DATA = ${serialize(store.state)}
</script>`;
fs.readFile(path.join(__dirname, clientDistPath, 'index.html'), (err, html) => {
if (err) {
throw err;
}
appContent = `<div id="app">${appContent}</div>`;
html = html.toString().replace('<div id="app"></div>', `${renderState}${appContent}`);
res.setHeader('Content-Type', 'text/html');
res.send(html);
});
});
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`You can navigate to http://localhost:${port}`);
});
vue。配置。js
用于指定网页包生成内容
const ManifestPlugin = require('webpack-manifest-plugin');
const nodeExternals = require('webpack-node-externals');
module.exports = {
devServer: {
overlay: {
warnings: false,
errors: false,
},
},
chainWebpack: (webpackConfig) => {
webpackConfig.module.rule('vue').uses.delete('cache-loader');
webpackConfig.module.rule('js').uses.delete('cache-loader');
webpackConfig.module.rule('ts').uses.delete('cache-loader');
webpackConfig.module.rule('tsx').uses.delete('cache-loader');
if (!process.env.SSR) {
// This is required for repl.it to play nicely with the Dev Server
webpackConfig.devServer.disableHostCheck(true);
webpackConfig.entry('app').clear().add('./src/main.js');
return;
}
webpackConfig.entry('app').clear().add('./src/main-server.js');
webpackConfig.target('node');
webpackConfig.output.libraryTarget('commonjs2');
webpackConfig.plugin('manifest').use(new ManifestPlugin({ fileName: 'ssr-manifest.json' }));
webpackConfig.externals(nodeExternals({ allowlist: /\.(css|vue)$/ }));
webpackConfig.optimization.splitChunks(false).minimize(false);
webpackConfig.plugins.delete('hmr');
webpackConfig.plugins.delete('preload');
webpackConfig.plugins.delete('prefetch');
webpackConfig.plugins.delete('progress');
webpackConfig.plugins.delete('friendly-errors');
// console.log(webpackConfig.toConfig())
},
};
src/路由器/索引。js
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const isServer = typeof window === 'undefined';
const history = isServer ? createMemoryHistory() : createWebHistory();
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
const router = createRouter({
history,
routes,
});
export default router;
src/store/index。js
import Vuex from 'vuex';
import fetchAllBeers from '../data/data';
export default Vuex.createStore({
state() {
return {
homePageData: [],
};
},
actions: {
fetchHomePageData({ commit }) {
return fetchAllBeers()
.then((data) => {
commit('setHomePageData', data.beers);
});
},
},
mutations: {
setHomePageData(state, data) {
state.homePageData = data;
},
},
});
Github示例代码
我发现我需要通过构建代码一步一步做只是SSR,只是路由器,只是Vuex,然后把它们都放在一起。
我的测试应用程序在github中
https://github.com/se22as/vue-3-with-router-basic-sample
问题内容: 我刚刚开始研究ReactJS,发现它为您提供了两种渲染页面的方法:服务器端和客户端。但是,我不知道如何一起使用。是使用两种单独的方法来构建应用程序,还是可以将它们一起使用? 如果可以一起使用,该如何做- 我们是否需要在服务器端和客户端重复相同的元素?或者,我们是否可以仅在服务器上构建应用程序的静态部分,而在客户端构建动态部分,而无需与已经预先渲染的服务器端建立任何连接? 问题答案: 对
info 如果您能了解下面这些技术,能加快您对本文的了解 vuex - Vue.js 应用程序开发的状态管理模式 Vue.js SSR - Vue.js 服务器端渲染 webpack - 编译构建工具 Lavas 服务器端渲染模板参考了 vue-hackernews 的渲染和开发机制,并且结合了 Lavas 的 App Shell 模板,导出的工程中会有 App Shell 等 如果您不了解 vu
React 提供了两个方法 renderToString 和 renderToStaticMarkup 用来将组件(Virtual DOM)输出成 HTML 字符串,这是 React 服务器端渲染的基础,它移除了服务器端对于浏览器环境的依赖,所以让服务器端渲染变成了一件有吸引力的事情。 服务器端渲染除了要解决对浏览器环境的依赖,还要解决两个问题: 前后端可以共享代码 前后端路由可以统一处理 Rea
如果你调研服务器端渲染(SSR)只是用来改善少数营销页面(例如/,/about,/contact等)的 SEO,那么你可能需要预渲染。无需使用 web 服务器实时动态编译 HTML,而是使用预渲染方式,在构建时(build time)简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点。 如果你使用 webpack,你可以使用prerende
我刚刚开始研究ReactJS,发现它提供了两种呈现页面的方法:服务器端和客户端。但是,我不明白如何一起使用它。构建应用程序有两种不同的方法,还是可以一起使用? 如果我们可以一起使用它,如何做到这一点——我们需要在服务器端和客户端复制相同的元素吗?或者,我们可以只在服务器上构建应用程序的静态部分,在客户端构建动态部分,而不与已经预渲染的服务器端建立任何连接吗?
服务端渲染一个很常见的场景是当用户(或搜索引擎爬虫)第一次请求页面时,用它来做初始渲染。当服务器接收到请求后,它把需要的组件渲染成 HTML 字符串,然后把它返回给客户端(这里统指浏览器)。之后,客户端会接手渲染控制权。 下面我们使用 React 来做示例,对于支持服务端渲染的其它 view 框架,做法也是类似的。 服务端使用 Redux 当在服务器使用 Redux 渲染时,一定要在响应中包含应用