问题描述
我使用 Vue CLI 创建了一个 Vue3 应用程序,以使用 Vuex 和路由器创建我的应用程序.应用程序运行良好.
注意:我遵循了这个有用的 Vuex 文档和 Vue3 https://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';从'./App.vue'导入应用程序;从'./router'导入路由器;从./store"导入商店;createApp(App).use(store).use(router).mount('#app');
服务器入口文件(src/main.server.js)目前有以下内容
import App from './App.vue';导出默认应用程序;
并且在快速服务器文件 (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 = 等待 renderToString(app);
我需要更改此代码,以便服务器端的应用程序像在客户端一样使用路由器和 vuex.
问题
在 express 服务器文件中,我无法像在客户端入口文件中一样导入路由器和 vuex,因为由于导入模块外部而失败,因此在 express 服务器中我无法执行以下操作
const app = createSSRApp(App).use(store).use(router);
我尝试将服务器入口文件 (src/main.server.js) 更改为以下内容,但这也不起作用.
import App from './App.vue';从'./router'导入路由器;从./store"导入商店;const { createSSRApp } = require('vue');导出默认 createSSRApp(App).use(store).use(router);
当你的应用使用 Vuex 和 Router 时,有谁知道如何在 Vue 3 中做 SSR.
我是如何在 Vue 2 中做到这一点的,以及我正在尝试改用 Vue 3 的内容
我的这个应用程序的 Vue2 版本有以下代码
src/app.js 使用指定的路由器和存储创建 Vue 组件
客户端入口文件 (src/client/main.js) 从 app.js 获取应用程序,使用在 html 中序列化的数据预填充 Vuex 存储,在路由器准备就绪时挂载应用程序
从'vue'导入Vue;从'vuex-router-sync'导入{同步};从'./pages/App.vue'导入应用程序;从 './vuex/store' 导入 createStore;从 './pages/router' 导入 createRouter;导出默认函数 createApp() {const store = createStore();const 路由器 = createRouter();同步(商店,路由器);const app = new Vue({路由器,店铺,渲染:(h) =>h(应用程序),});返回{应用程序,路由器,商店};}
服务器入口文件 (src/server/main.js),从 app.js 获取应用程序,获取将调用serverPrefetch"的匹配路由;在每个组件上将其数据填充到 Vuex 存储中,然后返回解析承诺
import createApp from '../app';导出默认值(上下文)=>新承诺((解决,拒绝)=> {const { app, router, store } = createApp();router.push(context.url);router.onReady(() => {const 匹配组件 = router.getMatchedComponents();如果(!matchedComponents.length){返回拒绝(新错误('404'));}context.rendered = () =>{context.state = store.state;};返回解决(应用程序);}, 拒绝);});
Express 服务器 (/server.js) 使用捆绑渲染器将应用程序渲染为字符串以放入 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'),{模板: fs.readFileSync('./index.html', 'utf-8'),},);const server = express();server.use(express.static('public'));server.get('*', (req, res) => {常量上下文 = {网址:req.url,clientBundle:`client-bundle.js`,};bundleRenderer.renderToString(context, (err, html) => {如果(错误){如果(错误代码=== 404){res.status(404).end('找不到页面');} 别的 {res.status(500).end('内部服务器错误');}} 别的 {res.end(html);}});});const port = process.env.PORT ||3000server.listen(port, () => {console.log(`监听端口 ${port}`);});
感谢以下资源,我设法找到了解决方案:
使用 Vue.js 3 进行服务器端渲染视频:https://www.youtube.com/watch?v=XJfaAkvLXyU&feature=youtu.be 和 git 存储库:https://github.com/moduslabs/vue3-example-ssr
SSR + Vuex + 路由器应用:https://github.com/shenron/vue3-example-ssr
从 Vue 2 迁移到 Vue 3https://v3.vuejs.org/guide/migration/introduction.html
从 VueRouter 3 迁移到 VueRouter 4https://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;如果(商店初始状态){store.replaceState(storeInitialState);}router.isReady().then(() => {app.mount('#app', true);});
服务器入口文件(src/main-server.js)
import buildApp from './app';导出默认值 (url) =>新承诺((解决,拒绝)=> {const { 路由器,应用程序,商店 } = buildApp();//设置服务器端路由器的位置路由器推送(网址);router.isReady().then(() => {const 匹配组件 = router.currentRoute.value.matched;//没有匹配的路由,用 404 拒绝如果(!matchedComponents.length){返回拒绝(新错误('404'));}//Promise 应该解析为应用程序实例,以便它可以被呈现return resolve({ app, router, store });}).catch(() => 拒绝);});
src/app.js
import { createSSRApp, createApp } from 'vue';从'./App.vue'导入应用程序;从'./router'导入路由器;从./store"导入商店;const isSSR = typeof window === 'undefined';导出默认函数 buildApp() {const app = (isSSR ? createSSRApp(App) : createApp(App));应用程序使用(路由器);应用程序使用(商店);返回{应用程序,路由器,商店};}
server.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');//创建快递应用.const server = express();//我们不知道 app.js 的名称,因为它在构建时有一个哈希名称//清单文件包含app.js"的映射;到创建的哈希文件//因此从位于dist"中的清单文件中获取值目录//并使用它来获取Vue Appconst 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')));//处理我们应用程序中的所有路由server.get('*', async (req, res) => {const { app, store } = await createApp(req);让 appContent = 等待 renderToString(app);const renderState = `<脚本>window.INITIAL_DATA = ${serialize(store.state)}`;fs.readFile(path.join(__dirname, clientDistPath, 'index.html'), (err, html) => {如果(错误){抛出错误;}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(`你可以导航到 http://localhost:${port}`);});
vue.config.js
用于指定 webpack 构建的东西
const ManifestPlugin = require('webpack-manifest-plugin');const nodeExternals = require('webpack-node-externals');模块.出口 = {开发服务器:{叠加:{警告:错误,错误:错误,},},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');如果(!process.env.SSR){//这是 repl.it 与开发服务器良好配合所必需的webpackConfig.devServer.disableHostCheck(true);webpackConfig.entry('app').clear().add('./src/main.js');返回;}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/router/index.js
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router';从'../views/Home.vue'导入首页;从'../views/About.vue'导入关于;const isServer = typeof window === '未定义';const 历史 = isServer ?createMemoryHistory() : createWebHistory();const 路由 = [{小路: '/',name: '家',组件:首页,},{路径:'/关于',name: '关于',组件:关于,},];const 路由器 = createRouter({历史,路线,});导出默认路由器;
src/store/index.js
从'vuex'导入Vuex;从 '../data/data' 导入 fetchAllBeers;导出默认 Vuex.createStore({状态() {返回 {主页数据:[],};},动作:{fetchHomePageData({ commit }) {返回 fetchAllBeers().then((数据) => {commit('setHomePageData', data.beers);});},},突变:{setHomePageData(状态,数据){state.homePageData = 数据;},},});
Github 示例代码
我发现我需要一步一步地构建代码,只做 SSR、路由器、Vuex,然后把它们放在一起.
我的测试应用程序在 github 中
https://github.com/se22as/vue-3-with-router-basic-sample
- 主人"分支:只是一个带有路由器的 vue 3 应用
- 已添加-ssr"分支:拿了主人"分支并添加 ssr 代码
- "add-just-vuex"分支:拿了主人"分支并添加 vuex 代码
- "添加了-vuex-to-ssr"分支:带有路由器、vuex 和 ssr 的应用程序.
I have created a Vue3 application using the Vue CLI to create my application with Vuex and Router. The application runs well.
Note: I followed this useful doc for the Vuex with Vue3 https://blog.logrocket.com/using-vuex-4-with-vue-3/
Requirement Now I would like to change my Vue3 application to have Server Side Rendering support(i.e. SSR).
I watched this awesome video on creating an SSR application using Vue3 : https://www.youtube.com/watch?v=XJfaAkvLXyU and I can create and run a simple application like in the video. However I am stuck when trying to apply it to my main Vue3 app.
My current sticking point is how to specify the router and vuex on the server code.
My Code
The client entry file (src/main.js) has the following
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');
The server entry file (src/main.server.js) currently has the following
import App from './App.vue';
export default App;
And in the express server file (src/server.js) it currently has
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);
I need to change this code so that the app on the server side is using the router and vuex like it is on the client.
Issues
In the express server file i can not import the router and vuex like in the client entry file as it fails due to importing outside a module, therefore in the express server I can not do the following
const app = createSSRApp(App).use(store).use(router);
I have tried changing the server entry file (src/main.server.js) to the following, but this does not work either.
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);
Does anyone know how to do SSR in Vue 3 when your app is using Vuex and Router.
How i did this in Vue 2 is below and what i am trying to change over to Vue 3
My Vue2 version of this application had the following code
src/app.js creates the Vue component with the router and store specified
Client entry file (src/client/main.js) gets the app from app.js, prepopulates the Vuex store with the data serialized out in the html, mounts the app when the router is ready
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 };
}
Server Entry file (src/server/main.js), gets the app from app.js, get the matched routes which will call the "serverPrefetch" on each component to get its data populated in the Vuex store, then returns the resolve 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);
});
Express server (/server.js) uses the bundle renderer to render the app to a string to put in the 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}`);
});
I have managed to find the solution to this thanks to the following resources:
Server Side Rendering with Vue.js 3 video: https://www.youtube.com/watch?v=XJfaAkvLXyU&feature=youtu.be and git repos: https://github.com/moduslabs/vue3-example-ssr
SSR + Vuex + Router app : https://github.com/shenron/vue3-example-ssr
migrating from Vue 2 to Vue 3https://v3.vuejs.org/guide/migration/introduction.html
migrating from VueRouter 3 to VueRouter 4https://next.router.vuejs.org/guide/migration/
migrating from Vuex 3 to Vuex 4https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html
client entry file (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);
});
server entry file (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 };
}
server.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.config.js
used to specify the webpack build things
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/router/index.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 sample code
I found I needed to go through the building the code step by step doing just SSR, just Router, just Vuex and then put it all together.
My test apps are in github
https://github.com/se22as/vue-3-with-router-basic-sample
- "master" branch : just a vue 3 app with a router
- "added-ssr" branch : took the "master" branch and added ssr code
- "add-just-vuex" branch : took the "master" branch and added vuex code
- "added-vuex-to-ssr" branch : app with router, vuex and ssr.
这篇关于使用 Vuex 和路由器的 vue 3 服务器端渲染的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!