使用vue路由器进行vue.js服务器端渲染:分步指南

当我开始用vue收集有关ssr(服务器端呈现)的信息时,我必须从不同的文章和官方文档中获取一些信息,才能完全理解这个主题。

以下是我在这些资料中发现的一些问题:

  • 很多关于你应该拥有的信息的假设,比如webpack配置,连接vue路由器的正确方法等等。

  • 缺少某些重要的信息,给读者留下了一些空白。

  • 在给出的示例中,大多数都没有遵循官方文档提供的标准和最佳实践。

本文的目的是提供使SSR与Vue路由器一起工作可能需要的所有信息,并尽力避免任何可能在以后给您带来麻烦的漏洞。我也尽量尊重Vue团队的所有建议。

途径

在进入实际实现之前,有一些主要的概念你需要了解:

  • SSR包括为服务器上请求的路由创建一个完全加载的应用程序版本。一旦该页面在客户端呈现,客户端代码就拥有了所有权。

  • 您的应用程序将需要两个入口构建点,一个用于服务器,一个用于客户机。

考虑到这一点,我们将在本文中完成以下工作:

安装所需的依赖项

  1. Webpack配置

  2. NPM构建脚本

  3. 文件夹结构

  4. 应用程序配置

  5. 设置Vue路由器

  6. 客户端入口点

  7. 服务器入口点

  8. 服务器配置

让我们希望这个例子能使主题更加清晰!

依赖关系

让我们来看看我们将要安装的依赖项:

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:我们需要运行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脚本将运行我们刚刚提到的三个步骤。但是,如果需要,我们还设置了单独运行它们的脚本。

文件夹结构

1.jpg-600

  • 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来创建服务器。我们还导入了一些NodeJS功能。

  • 我们导入服务器包,这是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是你需要的。

以上就是使用vue路由器进行vue.js服务器端渲染:分步指南的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » Vue.js 教程