Vue.js服务器端渲染与Vue路由器:分步指南

当我开始使用Vue收集有关SSR(服务器端渲染)的信息时,我必须从不同的文章以及官方文档中获取相关信息,以便全面了解这个主题。

以下是我在这些来源中发现的一些问题:

  • 很多关于你应该拥有的信息的假设,比如Webpack配置,连接Vue Router等的正确方法。
  • 缺乏一些重要的信息,并留下一些读者填写的空白。
  • 在给出的例子中,大多数不遵循官方文件提供的标准和最佳实践。

本文的目的是提供您可能需要的所有信息,以便使Vue Router与SSR一起工作,为避免任何可能会让您稍后头疼的差距做出额外的努力。我也尝试尊重所有Vue团队的建议。

途径

在进入实际实施之前,您需要了解一些主要概念:

  • SSR涉及在服务器上为请求的路由创建应用程序的完全加载版本。一旦该页面在客户端呈现,客户端代码将获得所有权。
  • 你需要为你的应用程序需要两个入口构建点,一个用于服务器,一个用于客户端。

考虑到这一点,我们将在这篇文章中完成:

  1. 安装所需的依赖关系
  2. Webpack配置
  3. NPM构建脚本
  4. 文件夹结构
  5. 应用配置
  6. 设置Vue路由器
  7. 客户入口点
  8. 服务器入口点
  9. 服务器配置

让我们希望这个例子给这个主题带来一些清晰!

依赖

我们来看看我们将要安装的依赖关系:

1.我们将使用一个已经具有VueJS应用程序的基本Webpack配置的模板。我们还需要安装vue-cli:

#install vue-cli
npm install -g vue-cli
#create project using webpack-simple
vue init webpack-simple vue-ssr

现在我们需要安装webpack-simple模板的所有依赖关系。在此之前,我们没有做任何与SSR有关的事情。我们只是建立一个通用的VueJS环境。

#go to project folder
cd vue-cli
#install dependencies
npm install

2.所以现在我们有一个VueJS项目开始添加SSR配置。在我们做之前,我们需要添加三个与SSR相关的依赖关系。

#install vue-server-render, vue-router, express and webpack-merge
npm install vue-server-renderer vue-router express webpack-merge --save
  • vue-server-render:SSR的Vue库。
  • vue-router:SPA的Vue图书馆。
  • express:我们需要运行一个NodeJS服务器。
  • webpack-merge:我们将使用它来合并webpack配置。

Webpack配置

我们将需要两个Webpack配置,一个构建客户端入口文件和一个构建服务器入口文件。

我们先来看看Webpack客户端配置,这也将成为我们的服务器入口配置的基本Webpack配置。我们只是要使用我们安装的模板附带的一个,除了我们正在改变条目entry-client.js。

var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/entry-client.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
],
},
{
test: /\.sass$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader?indentedSyntax'
],
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': [
'vue-style-loader',
'css-loader',
'sass-loader'
],
'sass': [
'vue-style-loader',
'css-loader',
'sass-loader?indentedSyntax'
]
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}

现在添加服务器webpack配置:

var path = require('path')var webpack = require('webpack')var merge = require('webpack-merge')var baseWebpackConfig = require('./webpack.config')var webpackConfig = merge(baseWebpackConfig, {
target: 'node',
entry: {
app: './src/entry-server.js'
},
devtool: false,
output: {
path: path.resolve(__dirname, './dist'),
filename: 'server.bundle.js',
libraryTarget: 'commonjs2'
},
externals: Object.keys(require('./package.json').dependencies),
plugins: [
new webpack.DefinePlugin({
'process.env': 'production'
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]})module.exports = webpackConfig

这里除了两件事之外没有什么奇怪的地方:条目是entry-server.js输出,我们commonjs用作图书馆的目标。

这就是Webpack的配置。现在让我们看看在package.json中构建应用程序的脚本。

package.json构建脚本

您可以根据自己的需要进行更改,但是需要执行以下三个步骤来启动应用程序:

  1. 您需要构建客户端条目
  2. 你需要建立服务器入口
  3. 你需要启动服务器
"scripts": {
"start": "npm run build && npm run start-server",
"build": "npm run build-client && npm run build-server",
"build-client": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"build-server": "cross-env NODE_ENV=production webpack --config webpack.server.config.js --progress --hide-modules",
"start-server": "node server.js"}

在配置中,我们正在使用start将要运行刚刚提到的三个步骤的脚本。但是,如果需要,我们还设置了脚本来分别运行它们。

文件夹结构

  • dist文件夹在构建时由webpack创建。
  • node_modules文件夹…你知道这是什么。
  • src包含我们的Vue应用程序。在里面,您将找到服务器和客户端入口点,Vue main.js文件,App组件,其他组件的文件夹(我们有家和关于组件),包含路由器配置的路由器文件夹,最后是资产夹。
  • .babelrc,.gitignore,packages.json …你可能知道它们是什么。
  • index.html是我们的应用程序的主要HTML。
  • server.js是服务器配置和启动文件。
  • 最后,这两个webpack配置文件。

索引HTML

这是我们的主要HTML文件。

<!doctype html>
<html lang="en">
<head>
<!-- use triple mustache for non-HTML-escaped interpolation -->
{{{ meta }}}
<!-- use double mustache for HTML-escaped interpolation -->
<title>{{ title }}</title>
</head>
<body>
<!--vue-ssr-outlet-->
<script src="dist/build.js">
</script>
</body>
</html>

有几件事要讨论:

  • 我已经添加了一些插值到模板来从服务器填充数据。这是Vue SSR的一个特性,我将在稍后展示。
  • 我们加载build.js这是从Webpack生成的客户端包。

App.vue组件

这个组件是我们的应用程序的根本组成部分,它有几个职责:

  1. 具有Vue路由器链接的菜单的配置。
  2. 设置要呈现的路由组件的容器。
  3. app使用将要用于装载应用程序的客户端部分的id设置元素。
<template>
<div id="app">
Hello World!
<p>
<router-link to="/">Go To Home</router-link>
<router-link to="/about">Go To About</router-link>
</p>
<router-view>
</router-view>
</div>
</template>
<script>
export default {
};</script>

路由器文件配置

由于我们的应用程序将在服务器上启动,因此我们需要为每个服务器请求提供一个新的路由器实例。在路由器文件夹里面,我们要用我们的路由器配置文件。

// router.jsimport Vue from 'vue';import Router from 'vue-router';import Home from '../components/Home.vue';import About from '../components/About.vue';
Vue.use(Router);
export function createRouter () {
return new Router({
mode: 'history',
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
});}

我们来看一下代码:

  • 我们导入所有我们需要的依赖关系。
  • 我们告诉Vue使用Vue路由器。
  • 我们导出一个提供路由器配置的新实例的函数。
  • 我们在历史模式下实例化路由器,并声明我们要处理的两条路由。

主要Vue文件配置

出于同样的原因,我们需要提供一个新的路由器实例,我们需要提供一个新的应用实例。该文件负责启动路由器和根应用程序组件。服务器入口点和客户端入口点都将使用此文件。

// main.jsimport Vue from 'vue'import App from './App.vue'import { createRouter } from './router/router.js'
// export a factory function for creating fresh app, router and store// instancesexport function createApp() {
// create router instance
const router = createRouter();
const app = new Vue({
router,
// the root instance simply renders the App component.
render: h => h(App)
});
return { app, router };}

我们来看一下代码:

  • 我们导入所有需要的依赖关系。
  • 我们导出一个提供应用程序和路由器的新实例的函数。
  • 我们使用之前在router.js文件中看到的方法实例化路由器。
  • 我们用路由器和一个渲染函数创建一个新的应用程序实例,传递根应用程序组件。
  • 我们返回这两个实例。

客户端入口点

这段代码非常简单。这是Webpack客户端构建配置的入口文件。

//client-entry.js
import { createApp } from './main.js';
const { app } = createApp()
// this assumes App.vue template root element has `id="app"`app.$mount('#app')

我们来看一下代码:

  • 我们导入所有需要的依赖关系。
  • 我们从main.js文件创建应用程序,并保持app实例。
  • 我们app在id设置为app的节点中进行装载。在本例中,包含该id的节点是App.vue组件模板的根元素。

服务器入口点

这个文件是webpack服务器构建的入口点。构建的结果是我们稍后在配置服务器时要实现的目标。

//server-entry.jsimport { createApp } from './main.js';
export default context => {
// since there could potentially be asynchronous route hooks or components,
// we will be returning a Promise so that the server can wait until
// everything is ready before rendering.
return new Promise((resolve, reject) => {
const { app, router } = createApp();
// set server-side router's location
router.push(context.url);
// wait until router has resolved possible async components and hooks
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject({ code: 404 });
}
// the Promise should resolve to the app instance so it can be rendered
resolve(app);
}, reject);
});}

我们来看一下代码:

  • 我们导入所有需要的依赖关系。
  • 我们导出一个接收上下文为param的函数。
  • 函数返回一个承诺。
  • 我们从main.js创建应用程序功能实例化应用程序和路由器。
  • 我们从上下文获取当前URL(这将由服务器提供),以便将正确的URL推送到路由器。
  • 一旦路由器准备就绪,我们检查路由是否与上下文URL匹配。如果是这样,我们解决承诺,并返回应用程序实例。否则,我们拒绝承诺。

配置和启动服务器

我们几乎准备好了一切。唯一缺少的是express服务器的配置和启动。

//server.js
const express = require('express');
const server = express();
const fs = require('fs');
const path = require('path');
//obtain bundle
const bundle =require('./dist/server.bundle.js');
//get renderer from vue server renderer
const renderer = require('vue-server-renderer').createRenderer({
//set template
template: fs.readFileSync('./index.html', 'utf-8')});
server.use('/dist', express.static(path.join(__dirname, './dist')));
//start serverserver.get('*', (req, res) => {
bundle.default({ url: req.url }).then((app) => {
//context to use as data source
//in the template for interpolation
const context = {
title: 'Vue JS - Server Render',
meta: `
<meta description="vuejs server side render">
`
};
renderer.renderToString(app, context, function (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)
}
});
}, (err) => {
console.log(err);
});
});
server.listen(8080);

哇!而你以为这是太多了。让我们深入代码,看看发生了什么。

  • 我们正在导入express来创建服务器。我们也在导入一些NodeJS功能。
  • 我们导入作为Webpack服务器构建结果的服务器包。
  • 我们导入vue-server-renderer库并创建渲染器,提供index.html模板的位置。
  • 我们配置express路径。
  • 我们启动服务器。
  • 这个bundle是serve-entry.js使用Webpack 构建的结果,所以我们可以使用接收上下文的默认函数作为URL的参数。由于这是一个承诺,我们设置了成功和错误回调。

成功回调做了一堆东西,所以让我们来看看:

  • 我们用要插入的数据创建一个常量index.html(我们在之前看到了index.html中的插值)。
  • 我们把渲染器的字符串函数作为接收应用程序的渲染器的字符串函数(由已解析的promise返回),我们刚刚创建的上下文(用于索引中的插值…这是可选的)以及回调函数(如果一切正常的话)好。
  • 呈现字符串回调函数检查是否有任何错误,如果不是,它只是发送生成的HTML作为响应。

最后,我们开始听8080端口。

现在,如果你运行这个脚本start并localhost:8080在浏览器中打开,你将会看到一个和vue-router一起工作的SSR。

就是这样,女士们,先生们!

结论

我不认为我需要说的是使事情有效的配置很多,但一旦完成,您不会触及很多事情。只要确定SSR是你所需要的。

未经允许不得转载:爱前端网 » Vue.js服务器端渲染与Vue路由器:分步指南

赞 (9) 打赏


觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏