当我开始用 vue 收集有关 ssr(服务器端呈现) 的信息时, 我必须从不同的文章和官方文档中获取一些信息, 才能完全理解这个主题.
以下是我在这些资料中发现的一些问题:
很多关于你应该拥有的信息的假设, 比如 webpack 配置, 连接 vue 路由器的正确方法等等.
缺少某些重要的信息, 给读者留下了一些空白.
在给出的示例中, 大多数都没有遵循官方文档提供的标准和最佳实践.
本文的目的是提供使 SSR 与 Vue 路由器一起工作可能需要的所有信息, 并尽力避免任何可能在以后给您带来麻烦的漏洞. 我也尽量尊重 Vue 团队的所有建议.
途径
在进入实际实现之前, 有一些主要的概念你需要了解:
SSR 包括为服务器上请求的路由创建一个完全加载的应用程序版本. 一旦该页面在客户端呈现, 客户端代码就拥有了所有权.
您的应用程序将需要两个入口构建点, 一个用于服务器, 一个用于客户机.
考虑到这一点, 我们将在本文中完成以下工作:
安装所需的依赖项
Webpack 配置
NPM 构建脚本
文件夹结构
应用程序配置
设置 Vue 路由器
客户端入口点
服务器入口点
服务器配置
让我们希望这个例子能使主题更加清晰!
依赖关系
让我们来看看我们将要安装的依赖项:
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:Vue library for SPA.
express: 我们需要运行 Node.JS 服务器.
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 生成脚本
你可以根据自己的需要进行修改, 但启动应用程序需要执行以下三个步骤:
您需要构建客户机条目
您需要构建服务器条目
您需要启动服务器
- "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 文件, 应用程序组件, 其他组件的文件夹 (我们有 home 和 about 组件), 包含路由器配置的路由器文件夹, 最后是 assets 文件夹.
.babelrc,.gitignore,packages.JSON... 你可能知道它们是什么.
index.HTML 是我们应用程序的主要 HTML.
server.JS 是服务器配置和启动文件.
最后, 介绍两个 webpack 配置文件.
Index HTML
这是我们的主要 HTML 文件.
- <!doctype HTML>
- <HTML>
- <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 元件
这个组件是我们 App 的根组件, 它有几个功能:
配置带有 Vue 路由器链接的菜单.
设置要呈现路由组件的容器.
使用 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.JS
- import 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 路由器.
我们导出一个提供路由器配置新实例的函数.
我们在历史模式中实例化路由器, 并声明要处理的两条路由.
主视图文件配置
就像我们需要提供一个新的路由器实例一样, 我们也需要提供一个新的 App 实例. 这个文件负责启动路由器和根应用程序组件. 服务器入口点和客户机入口点都将使用这个文件.
- // main.JS
- import 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
- // instances
- export 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 文件创建应用程序并保存应用程序实例.
我们在 ID 设置为 App 的节点中装载应用程序. 在本例中, 包含该 id 的节点是 App.vue 组件模板的根元素.
服务器入口点
这个文件是 webpack 服务器构建的入口点. 该构建的结果就是我们稍后配置服务器时要针对的目标.
- //server-entry.JS
- import { 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);
- });
- }
让我们来看看代码:
我们导入所需的所有依赖项.
我们导出一个函数, 该函数接收上下文作为参数.
函数返回一个 promise.
我们从 main.JS create App 函数实例化应用程序和路由器.
我们从上下文获取当前 URL(这将由服务器提供), 以便将正确的 URL 推送到路由器.
一旦路由器就绪, 我们检查路由是否匹配上下文 URL. 如果是, 我们解析 promise 并返回 App 实例. 否则, 我们拒绝承诺.
配置和启动服务器
我们几乎把一切都准备好了. 唯一缺少的是配置和启动 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 server
- server.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 来创建服务器. 我们还导入了一些 Node.JS 功能.
我们导入服务器包, 这是 Webpack 服务器构建的结果.
我们导入 vue-server-renderer 库并创建 renderer, 为模板提供 index.HTML 位置.
我们配置了 express 路径.
我们启动服务器.
这个 bundle 是使用 Webpack 构建 servlet -entry.JS 的结果, 因此我们可以使用默认函数来接收上下文作为带有 URL 的参数. 因为它是一个承诺, 所以我们设置了一个成功和错误回调.
成功回调做了很多事情, 让我们来看看:
我们使用将要在 index.HTML 中插入的数据创建一个 const(我们之前在 index.HTML 中看到过插值).
我们调用 renderer 的 render to string 函数, 该函数接收应用程序 (由已解析的 promise 返回), 以及我们刚刚创建的上下文 (用于索引中的插值... 这是可选的), 如果一切正常, 回调函数.
render to string 回调函数检查任何错误, 如果没有, 它只将生成的 HTML 作为响应发送.
最后, 我们开始监听端口 8080.
现在, 如果您运行脚本启动并在浏览器中打开 localhost:8080, 您将看到一个带有 vue-router 的工作 SSR.
我不认为我需要说这是很多配置使事情工作, 但一旦它完成, 你不会去碰它很多. 只要确定 SSR 是你需要的.
来源: http://www.css88.com/web/vue-js/12693.html