Vue SSR 将 express 服务器与 vue 应用程序捆绑在一起,因此应用程序可以 运行 从 build dist 文件夹复制到主机服务器
Vue SSR bundling the express server with the vue app so the app can be run from the build dist folder copied to a host server
我已经创建了一个 Vue SSR 应用程序和所有 instructions/wikis/blogs 我只读过告诉你如何 运行 在开发环境中应用程序。他们不会告诉您如何在生产环境中 运行 应用程序。
我以前在 React SSR 应用程序中编写过相同的应用程序。在那个应用程序中,构建生成一个包含包“server_bundle.js”的“dist”文件夹。这个包包含 Express 服务器 (server.js) 和 React 代码。我可以使用
从“dist”文件夹中 运行 应用程序
node dist/server_bundle.js
在 Vue SSR 应用程序中,构建还会生成一个“dist”文件夹。但是,它包含一个“vue-ssr-bundle.json”文件,该文件不包含 express 服务器(server.js)。对于 运行 开发中的应用程序,我必须使用位于项目根目录中的 Express 服务器文件,而不是 运行 从“dist”目录
中获取所有内容
node ./server.js
这在开发中没问题,因为我在我的项目中工作,但在生产中这将不起作用,因为我必须 运行“dist”文件夹中的所有内容。
建筑和运行
该应用程序是使用以下命令构建的(在开发中我添加了“--watch”参数)
webpack --config webpack.server.config.js
webpack --config webpack.client.config.js
这些构建脚本创建并填充 dist(服务器)和 public(客户端)文件夹
project_root
- dist
- vue-ssr-bundle.json
- public
- 0_client_bundle.js
- client_bundle.js
- any static images, such as "myImage.jpg"
到 运行 应用程序我 运行 Express 服务器使用
node ./server.js"
看到我在项目的根目录中使用“server.js”。它不在 dist 文件夹中。
问题
那么我如何 运行 生产中的应用程序没有我的项目代码,它只有我的构建生成的“dist”和“public”文件夹?
我的代码
项目结构
project_root
- src
- assets
- any static images, such as "myImage.jpg"
- client
- client_main.js
- components
- lots of files
- pages
- components which are top level pages
- server
- server_main.js
- vuex
- folders and files containing code pertaining to vuex
- app.js
- App.vue
- router.js
- index.html (the template html file to have content inserted into)
- server.js (the Express Server file)
- webpack.base.config.js
- webpack.client.config.js
- webpack.server.config.js
src/client/client_main.js
import { createApp } from '../app'
const { app, router, store } = createApp()
...
router.onReady(() => {
app.$mount('#app')
})
src/server/server_main.js
import { createApp } from '../app'
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 })
}
resolve(app)
}, reject)
})
}
src/app.js
import Vue from 'vue'
import App from './App.vue'
import { createStore } from './vuex/store'
import { createRouter } from './router'
import { sync } from 'vuex-router-sync'
export 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.js
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-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 }
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)
}
})
});
server.listen(8080);
webpack.base.config.js
const webpack = require('webpack')
module.exports = {
module: {
rules: I wont put them all here to reduce noise, but i am using 'vue-loader', 'babel-loader''file-loader'
},
resolve: {
alias: {'vue$': 'vue/dist/vue.esm.js'},
extensions: ['*', '.js', '.vue', '.json']
},
performance: {
hints: false
},
}
webpack.client.js
var path = require('path')
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config.js');
const config = {
entry: './src/client/main.js',
output: {
filename: 'client-bundle.js',
path: path.resolve(__dirname, 'public'),
},
module: {
rules: I wont put them all here to reduce noise, but i am using 'vue-style-loader', 'css-loader'
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
devtool: '#eval-source-map'
}
module.exports = merge(baseConfig, config);
webpack.server.js
const path = require('path')
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config.js');
const webpackNodeExternals = require('webpack-node-externals');
const VueSSRPlugin = require('vue-ssr-webpack-plugin')
const config = {
target: 'node',
entry: './src/server/main.js',
output: {
filename: 'server-bundle.js',
path: path.resolve(__dirname, './dist'),
libraryTarget: 'commonjs2'
},
externals: [webpackNodeExternals()],
devtool: '#source-map',
plugins: [
new VueSSRPlugin(),
]
}
module.exports = merge(baseConfig, config);
我试过的
我预计服务器构建会将 Express 服务器与 Vue 应用程序代码捆绑在一起,就像 React 一样,因此我只需要 运行 从 dist 文件夹中进行捆绑。我认为这是一个更好更清洁的解决方案。
我尝试将 Vue 应用程序更改为构建和 运行 像 React 应用程序,但没有成功。
移除 VueSSRPlugin
我删除了 webpack.server.config.js 中的 VueSSRPlugin 引用,并在“dist”文件中看到我现在有包和图像,就像 React 一样。然而,这个包中仍然没有 Express 服务器。我仍然不知道如何在 dist 文件夹中获取 express 服务器
将快递文件移至 src/server 文件夹
我考虑将 express 文件 (server.js) 移动到我的项目源中,希望它能被添加到包中。但是我不知道如何更改 express 文件,因为它引用了 express 服务器文件最终所在的 JSON 文件。
我已经通过将以下内容复制到我的服务器暂时解决了这个问题
- index.html
- server.js
- “dist”文件夹(包含服务器包)
- “public”文件夹(包含客户端包、样式表和其他文件)
然后在服务器上我可以运行
node server.js
因为 server.js 和 index.html 已被复制到服务器上。
我仍然更喜欢 React 的构建方式,以便 express server.js 文件和 index.html 在服务器包中,但至少这是有效的。
我使用我的 React SSR 弄清楚如何将我的所有 Vue SSR 应用程序(包括 express 服务器)捆绑到服务器包中。
这个解决方案的缺点是我不能再使用“index.html”模板和“bundleRenderer”。
这个解决方案的好处是,express 服务器现在可以使用 WebPack 配置中设置的变量。
项目结构
我的项目结构如下。我将 Express 服务器文件移动到 src/server 文件夹并删除了“index.html”文件。
project_root
- src
- assets
- any static images, such as "myImage.jpg"
- client
- client_main.js
- components
- lots of files
- server
- server_main.js
- server.js (the Express Server file)
- vuex
- folders and files containing code pertaining to vuex
- app.js
- App.vue
- router.js
- webpack.base.config.js
- webpack.client.config.js
- webpack.server.config.js
WebPack 配置更改
"webpack.base.config.js"不再需要
devtool: '#source-map',
"webpack.server.config.js" 不再需要 "VueSSRPlugin" 因此可以删除以下内容
const VueSSRPlugin = require('vue-ssr-webpack-plugin');
plugins: [
new VueSSRPlugin(),
],
将服务器条目更改为快速服务器文件
entry: './src/server/server.js',
服务器主文件:src/server/server_main.js
Return VUEX 商店和 Vue 应用程序。这是因为我们必须手动提取商店数据,将其序列化并将其添加到 HTML returned 到客户端。
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, store });
}, reject);
});
创建一个renderer.js文件负责生成HTML到return给客户端(即替换index.html
注意:您不必为此创建一个单独的文件,如果您愿意,可以将此代码包含在您的快速服务器文件中。
import serialize from 'serialize-javascript';
export default (store, html) => {
return `
<html>
<head>
<link rel="icon" href="/favicon.png" type="image/png">
</head>
<body>
<script>
window.__INITIAL_STATE__ = ${serialize(store.state)}
</script>
<div id="root">${html}</div>
<script src="/client-bundle.js"></script>
</body>
</html>
`;
};
Express 服务器文件更改 (src/server/server.js) - 不再使用 bundleRenderer
更改 express 服务器文件以使用 vue 服务器渲染器而不是 bundle 渲染器。
调用“generateApp”(即 server_main.js 中的函数未更改),当该承诺已解决时,所有 HTML 已生成并且 VUEX 存储包含所有数据。在传入已创建的 Vue 应用程序的 vue 渲染器上调用 renderToString。在 renderToString 的回调函数中调用渲染器传递生成的 HTML 和 VUEX 存储。渲染器将创建 HTML 到 return 包括序列化存储。然后 HTML 被 return 发送给客户端。
import generateApp from './server_main.js';
import renderer from './renderer';
const express = require('express');
const vueServerRenderer = require('vue-server-renderer').createRenderer();
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = { url: req.url };
generateApp(context).then((createdAppObj) => {
const { app, store } = createdAppObj;
vueServerRenderer.renderToString(
app,
(err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
// BUILD UP THE HTML TO RETURN
const content = renderer(store, html);
res.send(content);
}
},
);
});
});
server.listen(8080);
package.json变
现在,当所有内容构建完成后,“dist”目录将包含以下内容
- server-bundle.js
这包含 express 服务器文件作为入口文件,因此要 运行 您执行的应用程序
node ./server.js
此解决方案的一个优势:Express Server 可以使用 WebPack 配置中设置的变量
Vue 支持使用模板插值将变量添加到“index.html”模板中,请参阅https://ssr.vuejs.org/guide/#using-a-page-template。
但是在我的例子中,我需要在我的 WebPack 配置中使用一个变量设置来放入“index.html”。
我的 WebPack 配置设置来自“.env”文件的变量,然后在我的 Vue 应用程序中使用。
我需要在我的“index.html”中的这个“.env”文件中使用一个变量。由于 Express Server 未被 WebPack 解析和捆绑,因此不会设置变量。我将不得不阅读快速服务器文件中的“.env”文件。这意味着我还必须在 express 服务器文件所在的生产环境中拥有“.env”文件,以便从中读取。
通过此评论中描述的更改,WebPack 现在捆绑了 express 服务器文件,因此 express 服务器文件可以访问 WebPack 中设置的所有变量,因此它将可以访问从“.env”设置的变量
我已经创建了一个 Vue SSR 应用程序和所有 instructions/wikis/blogs 我只读过告诉你如何 运行 在开发环境中应用程序。他们不会告诉您如何在生产环境中 运行 应用程序。
我以前在 React SSR 应用程序中编写过相同的应用程序。在那个应用程序中,构建生成一个包含包“server_bundle.js”的“dist”文件夹。这个包包含 Express 服务器 (server.js) 和 React 代码。我可以使用
从“dist”文件夹中 运行 应用程序node dist/server_bundle.js
在 Vue SSR 应用程序中,构建还会生成一个“dist”文件夹。但是,它包含一个“vue-ssr-bundle.json”文件,该文件不包含 express 服务器(server.js)。对于 运行 开发中的应用程序,我必须使用位于项目根目录中的 Express 服务器文件,而不是 运行 从“dist”目录
中获取所有内容node ./server.js
这在开发中没问题,因为我在我的项目中工作,但在生产中这将不起作用,因为我必须 运行“dist”文件夹中的所有内容。
建筑和运行
该应用程序是使用以下命令构建的(在开发中我添加了“--watch”参数)
webpack --config webpack.server.config.js
webpack --config webpack.client.config.js
这些构建脚本创建并填充 dist(服务器)和 public(客户端)文件夹
project_root
- dist
- vue-ssr-bundle.json
- public
- 0_client_bundle.js
- client_bundle.js
- any static images, such as "myImage.jpg"
到 运行 应用程序我 运行 Express 服务器使用
node ./server.js"
看到我在项目的根目录中使用“server.js”。它不在 dist 文件夹中。
问题 那么我如何 运行 生产中的应用程序没有我的项目代码,它只有我的构建生成的“dist”和“public”文件夹?
我的代码
项目结构
project_root
- src
- assets
- any static images, such as "myImage.jpg"
- client
- client_main.js
- components
- lots of files
- pages
- components which are top level pages
- server
- server_main.js
- vuex
- folders and files containing code pertaining to vuex
- app.js
- App.vue
- router.js
- index.html (the template html file to have content inserted into)
- server.js (the Express Server file)
- webpack.base.config.js
- webpack.client.config.js
- webpack.server.config.js
src/client/client_main.js
import { createApp } from '../app'
const { app, router, store } = createApp()
...
router.onReady(() => {
app.$mount('#app')
})
src/server/server_main.js
import { createApp } from '../app'
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 })
}
resolve(app)
}, reject)
})
}
src/app.js
import Vue from 'vue'
import App from './App.vue'
import { createStore } from './vuex/store'
import { createRouter } from './router'
import { sync } from 'vuex-router-sync'
export 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.js
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-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 }
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)
}
})
});
server.listen(8080);
webpack.base.config.js
const webpack = require('webpack')
module.exports = {
module: {
rules: I wont put them all here to reduce noise, but i am using 'vue-loader', 'babel-loader''file-loader'
},
resolve: {
alias: {'vue$': 'vue/dist/vue.esm.js'},
extensions: ['*', '.js', '.vue', '.json']
},
performance: {
hints: false
},
}
webpack.client.js
var path = require('path')
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config.js');
const config = {
entry: './src/client/main.js',
output: {
filename: 'client-bundle.js',
path: path.resolve(__dirname, 'public'),
},
module: {
rules: I wont put them all here to reduce noise, but i am using 'vue-style-loader', 'css-loader'
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
devtool: '#eval-source-map'
}
module.exports = merge(baseConfig, config);
webpack.server.js
const path = require('path')
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config.js');
const webpackNodeExternals = require('webpack-node-externals');
const VueSSRPlugin = require('vue-ssr-webpack-plugin')
const config = {
target: 'node',
entry: './src/server/main.js',
output: {
filename: 'server-bundle.js',
path: path.resolve(__dirname, './dist'),
libraryTarget: 'commonjs2'
},
externals: [webpackNodeExternals()],
devtool: '#source-map',
plugins: [
new VueSSRPlugin(),
]
}
module.exports = merge(baseConfig, config);
我试过的
我预计服务器构建会将 Express 服务器与 Vue 应用程序代码捆绑在一起,就像 React 一样,因此我只需要 运行 从 dist 文件夹中进行捆绑。我认为这是一个更好更清洁的解决方案。
我尝试将 Vue 应用程序更改为构建和 运行 像 React 应用程序,但没有成功。
移除 VueSSRPlugin 我删除了 webpack.server.config.js 中的 VueSSRPlugin 引用,并在“dist”文件中看到我现在有包和图像,就像 React 一样。然而,这个包中仍然没有 Express 服务器。我仍然不知道如何在 dist 文件夹中获取 express 服务器
将快递文件移至 src/server 文件夹 我考虑将 express 文件 (server.js) 移动到我的项目源中,希望它能被添加到包中。但是我不知道如何更改 express 文件,因为它引用了 express 服务器文件最终所在的 JSON 文件。
我已经通过将以下内容复制到我的服务器暂时解决了这个问题
- index.html
- server.js
- “dist”文件夹(包含服务器包)
- “public”文件夹(包含客户端包、样式表和其他文件)
然后在服务器上我可以运行
node server.js
因为 server.js 和 index.html 已被复制到服务器上。
我仍然更喜欢 React 的构建方式,以便 express server.js 文件和 index.html 在服务器包中,但至少这是有效的。
我使用我的 React SSR 弄清楚如何将我的所有 Vue SSR 应用程序(包括 express 服务器)捆绑到服务器包中。
这个解决方案的缺点是我不能再使用“index.html”模板和“bundleRenderer”。
这个解决方案的好处是,express 服务器现在可以使用 WebPack 配置中设置的变量。
项目结构
我的项目结构如下。我将 Express 服务器文件移动到 src/server 文件夹并删除了“index.html”文件。
project_root
- src
- assets
- any static images, such as "myImage.jpg"
- client
- client_main.js
- components
- lots of files
- server
- server_main.js
- server.js (the Express Server file)
- vuex
- folders and files containing code pertaining to vuex
- app.js
- App.vue
- router.js
- webpack.base.config.js
- webpack.client.config.js
- webpack.server.config.js
WebPack 配置更改
"webpack.base.config.js"不再需要
devtool: '#source-map',
"webpack.server.config.js" 不再需要 "VueSSRPlugin" 因此可以删除以下内容
const VueSSRPlugin = require('vue-ssr-webpack-plugin');
plugins: [
new VueSSRPlugin(),
],
将服务器条目更改为快速服务器文件
entry: './src/server/server.js',
服务器主文件:src/server/server_main.js
Return VUEX 商店和 Vue 应用程序。这是因为我们必须手动提取商店数据,将其序列化并将其添加到 HTML returned 到客户端。
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, store });
}, reject);
});
创建一个renderer.js文件负责生成HTML到return给客户端(即替换index.html
注意:您不必为此创建一个单独的文件,如果您愿意,可以将此代码包含在您的快速服务器文件中。
import serialize from 'serialize-javascript';
export default (store, html) => {
return `
<html>
<head>
<link rel="icon" href="/favicon.png" type="image/png">
</head>
<body>
<script>
window.__INITIAL_STATE__ = ${serialize(store.state)}
</script>
<div id="root">${html}</div>
<script src="/client-bundle.js"></script>
</body>
</html>
`;
};
Express 服务器文件更改 (src/server/server.js) - 不再使用 bundleRenderer
更改 express 服务器文件以使用 vue 服务器渲染器而不是 bundle 渲染器。 调用“generateApp”(即 server_main.js 中的函数未更改),当该承诺已解决时,所有 HTML 已生成并且 VUEX 存储包含所有数据。在传入已创建的 Vue 应用程序的 vue 渲染器上调用 renderToString。在 renderToString 的回调函数中调用渲染器传递生成的 HTML 和 VUEX 存储。渲染器将创建 HTML 到 return 包括序列化存储。然后 HTML 被 return 发送给客户端。
import generateApp from './server_main.js';
import renderer from './renderer';
const express = require('express');
const vueServerRenderer = require('vue-server-renderer').createRenderer();
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = { url: req.url };
generateApp(context).then((createdAppObj) => {
const { app, store } = createdAppObj;
vueServerRenderer.renderToString(
app,
(err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
// BUILD UP THE HTML TO RETURN
const content = renderer(store, html);
res.send(content);
}
},
);
});
});
server.listen(8080);
package.json变
现在,当所有内容构建完成后,“dist”目录将包含以下内容
- server-bundle.js
这包含 express 服务器文件作为入口文件,因此要 运行 您执行的应用程序
node ./server.js
此解决方案的一个优势:Express Server 可以使用 WebPack 配置中设置的变量
Vue 支持使用模板插值将变量添加到“index.html”模板中,请参阅https://ssr.vuejs.org/guide/#using-a-page-template。
但是在我的例子中,我需要在我的 WebPack 配置中使用一个变量设置来放入“index.html”。
我的 WebPack 配置设置来自“.env”文件的变量,然后在我的 Vue 应用程序中使用。
我需要在我的“index.html”中的这个“.env”文件中使用一个变量。由于 Express Server 未被 WebPack 解析和捆绑,因此不会设置变量。我将不得不阅读快速服务器文件中的“.env”文件。这意味着我还必须在 express 服务器文件所在的生产环境中拥有“.env”文件,以便从中读取。
通过此评论中描述的更改,WebPack 现在捆绑了 express 服务器文件,因此 express 服务器文件可以访问 WebPack 中设置的所有变量,因此它将可以访问从“.env”设置的变量