您当前的位置:首页 > 计算机 > 编程开发 > JavaScript

vue-cli项目改造为SSR服务端渲染

时间:01-01来源:作者:点击数:
城东书院 www.cdsy.xyz

1.首先利用vue-cli初始化项目:

npm init webpack ssr-demo 

然后启动项目,确保项目能够正确跑起来:

npm run dev 

初始项目结构:

这里可以看到服务端返回回来界面只有一个 id=“app”的div

这一步完之后就开始我们的SSR之路了~

2.集成SSR

2.1首先安装SSR支持

npm i -D vue-server-renderer 

注意:vue-server-renderer版本需要和vue版本一致,相信很多人都不会注意这个的。

2.2在src目录下创建两个js文件

src 
├── entry-client.js # 仅运行于浏览器 
└── entry-server.js # 仅运行于服务器 

2.3修改路由配置

路由可以说是在一个网站最重要的了,即使是服务端渲染也要共用一套路由系统,为了避免单例的影响,我们为每一个请求都导出一个新的路由实例。(单例模式:单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。)

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

export function createRouter () {
  return new Router({
    mode: 'history',
    routes: [
      {
        path: '/',
        name: 'Hello',
        component: HelloWorld
      }
    ]
  })
}

2.4改造main.js文件

import Vue from 'vue'
import App from './App'
import { createRouter } from './router'

Vue.config.productionTip = false

export function createApp () {
  const router = new createRouter()//创建router实例
  const app = new Vue({
    router,
    render: h => h(app)//vue2.0的写法,render函数是渲染一个视图,然后提供给el挂载,如果没有render那页面什么都不会出来
  })
  return { app, router}
} 

2.5在entry-client里面添加以下内容

import { createApp } from './main'
const { app, router} = createApp()
// 因为可能存在异步组件,所以等待router将所有异步组件加载完毕,服务器端配置也需要此操作
router.onReady(()=> {
    app.$mount('#app')
})

2.6在entry-server.js里面添加以下内容

import { createApp } from './main'
export default context => {
  // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
  // 以便服务器能够等待所有的内容在渲染前,
  // 就已经准备就绪。
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    // 设置服务器端 router 的位置
    router.push(context.url)
    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // 匹配不到的路由,执行 reject 函数,并返回 404
      if (!matchedComponents.length) {
        // eslint-disable-next-line
        return reject({ code: 404 })
      }
      // Promise 应该 resolve 应用程序实例,以便它可以渲染
      resolve(app)
    }, reject)
  })
}

到这里vue的相关配置就完成了,接下来就是webpack的相关配置了:

2.7webpack配置

初始项目的相关webpack配置:

初始化配置文件里面包含了base,dev,prod三个文件,我们只需要在增加一个webpack.server.conf.js即可。

2.8webpack客户端的配置

(1)修改webpack.base.conf.js的entry入口配置为: ./src/entry-client.js。这样原 dev 配置与 prod 配置都不会受到影响。

:服务器端的配置也会引用base配置,但是会将entry通过merge覆盖为entry.server.js

(2)在webpack.prod.conf.js文件中引入一个插件,并配置到plugin中;

另外需要将 prod 的HtmlWebpackPlugin 去除,因为我们有了vue-ssr-client- manifest.json之后,服务器端会帮我们做好这个工作;

然后增加process.env.VUE_ENV:

const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const VueSSRClientPlugin  = require('vue-server-renderer/client-plugin')//增加插件

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env,
      'process.env.VUE_ENV': '"client"'//添加'process.env.VUE_ENV': '"client"'
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      // Setting the following option to `false` will not extract CSS from codesplit chunks.
      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
      allChunks: true,
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    // new HtmlWebpackPlugin({//注释掉HtmlWebpackPlugin
    //   filename: config.build.index,
    //   template: 'index.html',
    //   inject: true,
    //   minify: {
    //     removeComments: true,
    //     collapseWhitespace: true,
    //     removeAttributeQuotes: true
    //     // more options:
    //     // https://github.com/kangax/html-minifier#options-quick-reference
    //   },
    //   // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    //   chunksSortMode: 'dependency'
    // }),
    // keep module.id stable when vendor modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // enable scope hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    // This instance extracts shared chunks from code splitted chunks and bundles them
    // in a separate chunk, similar to the vendor chunk
    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ]),
     // 此插件在输出目录中
     // 生成 `vue-ssr-client-manifest.json`。
    new VueSSRClientPlugin()
  ]
})

2.9webpack服务端的配置

server的配置有用到新插件运行安装: npm i -D webpack-node-externals

webpack.server.conf.js配置如下:

const webpack = require('webpack')
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.conf.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
// 去除打包css的配置
baseConfig.module.rules[1].options = ''

module.exports = merge(baseConfig, {
  // 将 entry 指向应用程序的 server entry 文件
  entry: './src/entry-server.js',
  // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
  // 并且还会在编译 Vue 组件时,
  // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
  target: 'node',
  // 对 bundle renderer 提供 source map 支持
  devtool: 'source-map',
  // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
  output: {
    libraryTarget: 'commonjs2'
  },
  // https://webpack.js.org/configuration/externals/#function
  // https://github.com/liady/webpack-node-externals
  // 外置化应用程序依赖模块。可以使服务器构建速度更快,
  // 并生成较小的 bundle 文件。
  externals: nodeExternals({
    // 不要外置化 webpack 需要处理的依赖模块。
    // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
    // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
    whitelist: /\.css$/
  }),
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
      'process.env.VUE_ENV': '"server"'
    }),
    // 这是将服务器的整个输出
    // 构建为单个 JSON 文件的插件。
    // 默认文件名为 `vue-ssr-server-bundle.json`
    new VueSSRServerPlugin()
  ]
})

2.10 配置package.json增加打包服务器端构建命令并修改原打包命令

"scripts": {
    "dev": "node build/dev-server.js",
    "start": "npm run dev",
    "build:client": "node build/build.js",
    "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --progress --hide-modules",
    "build": "rimraf dist && npm run build:client && npm run build:server",
    "lint": "eslint --ext .js,.vue src",
    "start-prod": "node server.js"
  }, 

2.11修改index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>ssrnewdemo</title>
  </head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>

原来的<div id="app">删掉,只在 body 中保留一个标记即可:<!--vue-ssr-outlet-->。 服务器端会在这个标记的位置自动生成一个<div id="app" data-server-rendered="true">,客户端会通过app.$mount('#app')挂载到服务端生成的元素上,并变为响应式的。

2.12 运行构建命令

npm run build 

然后在dist目录下可见生成的两个 json 文件: vue-ssr-server-bundle.json与vue-ssr-client-manifest.json。

这两个文件都会应用在 node 端,进行服务器端渲染与注入静态资源文件。

遇到的问题:

1.可能是我之前安装失败了,从新安装一遍:

npm install vue vue-server-renderer --save 

2.'cross-env' 不是内部或外部命令,也不是可运行的程序或批处理文件,安装一下:

npm i -D cross-env 

3.这个错误似乎没有影响,可以先暂时不管,生成两个json文件就好了:

生成两个json文件:

2.13 构建服务器端(官方使用的express,这里我们使用koa2)

首先安装koa2:

npm i -S koa 

然后在项目根目录创建server.js文件,添加内容:

const Koa = require('koa')
const app = new Koa()

// response
app.use(ctx => {
  ctx.body = 'Hello Koa'
})

app.listen(3001)

然后运行:node server.js,访问localhost:3001,确保浏览器得到了Hello Koa。

2.14编写服务端代码

const Koa = require('koa')
const app = new Koa()
const fs = require('fs')
const path = require('path')
const { createBundleRenderer } = require('vue-server-renderer')

const resolve = file => path.resolve(__dirname, file)

// 生成服务端渲染函数
const renderer = createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'), {
  // 推荐
  runInNewContext: false,
  // 模板html文件
  template: fs.readFileSync(resolve('./index.html'), 'utf-8'),
  // client manifest
  clientManifest: require('./dist/vue-ssr-client-manifest.json')
})

function renderToString (context) {
  return new Promise((resolve, reject) => {
    renderer.renderToString(context, (err, html) => err ? reject(err) : resolve(html))
  })
}
app.use(require('koa-static')(resolve('./dist')))
// response
app.use(async (ctx, next) => {
  try {
    const context = {
      title: '服务端渲染测试', // default title
      url: ctx.url
    }
    // 将服务器端渲染好的html返回给客户端
    ctx.body = await renderToString(context)

    // 设置请求头
    ctx.set('Content-Type', 'text/html')
    ctx.set('Server', 'Koa2 server side render')
  } catch (e) {
    // 如果没找到,放过请求,继续运行后面的中间件
    next()
  }
})

app.listen(3001)
  .on('listening', () => console.log('服务已启动'))
  .on('error', err => console.log(err))

运行启动服务命令:

node server.js 

可能出现的错误:

1.Error: Cannot find module 'koa-static':

安装一下:

npm install koa-static 

启动成功:

成功访问,这里可以看到服务器返回回来的已经是一个HTML页面了。

整个项目结构:


项目源码地址:Hacker233/vue-ssr-demo

城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐