通过vue-cli3构建一个SSR应用程序的方法


Posted in Javascript onSeptember 13, 2018

1、前沿

1.1、什么是SSR

SSR(服务端渲染)顾名思义就是将页面在服务端渲染完成后在客户端直接展示。

1.2、客户端渲染与服务端渲染的区别 传统的SPA模式

即客户端渲染的模式

  1. Vue.js构建的应用程序,默认情况下是有一个html模板页,然后通过webpack打包生成一堆js、css等等资源文件。然后塞到index.html中
  2. 用户输入url访问页面 -> 先得到一个html模板页 -> 然后通过异步请求服务端数据 -> 得到服务端的数据 -> 渲染成局部页面 -> 用户

SSR模式

即服务端渲染模式

用户输入url访问页面 -> 服务端接收到请求 -> 将对应请求的数据渲染完一个网页 -> 返回给用户

 1.3、为什么要使用SSR呢?

ssr的好处官网已经给出,最有意思的两个优点如下:

  1. 更有好的SEO。由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
  2. 更快的内容到达时间(time-to-content)

1.4、SSR原理

这是vue.js官方的SSR原理介绍图,从这张图片,我们可以知道:我们需要通过Webpack打包生成两份bundle文件:

  • Client Bundle,给浏览器用。和纯Vue前端项目Bundle类似
  • Server Bundle,供服务端SSR使用,一个json文件

不管你项目先前是什么样子,是否是使用vue-cli生成的。都会有这个构建改造过程。在构建改造这里会用到 vue-server-renderer 库,这里要注意的是 vue-server-renderer 版本要与Vue版本一样。

通过vue-cli3构建一个SSR应用程序的方法 

2、开始构建基于vue-cli3 的SSR应用程序

了解ssr原理后,来开始step by step搭建一个自己的SSR应用程序

安装vue-cli3

全局安装vue-cli脚手架

npm install @vue/cli -g --registry=https://registry.npm.taobao.org

创建一个vue项目

vue create ssr-example

会进入到一个交互bash界面,按自己需要选择

通过vue-cli3构建一个SSR应用程序的方法

一步一步回车,根据自己需要选择

运行项目

npm run serve

通过vue-cli3构建一个SSR应用程序的方法

看到这个提示,说明成功了一半了,接下来进行后一半的改造。

3、进行SSR改造

3.1 安装需要的包

  1. 安装 vue-server-renderer
  2. 安装 lodash.merge
  3. 安装 webpack-node-externals
  4. 安装 cross-env
npm install vue-server-renderer lodash.merge webpack-node-externals cross-env --registry=https://registry.npm.taobao.org --save-dev

3.2 在服务器中集成

在项目根目录下新建一个server.js

安装koa2

npm install koa koa-static --save --registry=https://registry.npm.taobao.org

在koa2集成ssr

// server.js
// 第 1 步:创建一个 Vue 实例
const Vue = require("vue");

const Koa = require("koa");
const app = new Koa();
// 第 2 步:创建一个 renderer
const renderer = require("vue-server-renderer").createRenderer();


// 第 3 步:添加一个中间件来处理所有请求
app.use(async (ctx, next) => {
 const vm = new Vue({
  data: {
   title: "ssr example",
   url: ctx.url
  },
  template: `<div>访问的 URL 是: {{ url }}</div>`
 });
 // 将 Vue 实例渲染为 HTML
 renderer.renderToString(vm, (err, html) => {
  if(err){
   res.status(500).end('Internal Server Error')
   return
  }
  ctx.body = html
 });
});

const port = 3000;
app.listen(port, function() {
 console.log(`server started at localhost:${port}`);
});

运行server.js

node server.js

通过vue-cli3构建一个SSR应用程序的方法

看到这个说明一个简单的ssr构建成功了。

不过到目前为止,我们并没有将客户端的.vue文件通过服务端进行渲染,那么如何将前端的.vue文件与后端node进行结合呢?

3.3 改造前端配置,以支持SSR

1、修改源码结构

  • 在src目录下新建两个文件,一个 entry-client.js 仅运行于浏览器 一个 entry-server.js 仅运行于服务器
  • 修改main.js

main.js 是我们应用程序的「通用 entry」。在纯客户端应用程序中,我们将在此文件中创建根 Vue 实例,并直接挂载到 DOM。但是,对于服务器端渲染(SSR),责任转移到纯客户端 entry 文件。app.js 简单地使用 export 导出一个 createApp 函数:

// main.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from "./router";

// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
 const router = createRouter();
 const app = new Vue({
  router,
  // 根实例简单的渲染应用程序组件。
  render: h => h(App)
 })
 return { app }
}

修改entry-client.js

客户端 entry 只需创建应用程序,并且将其挂载到 DOM 中

import { createApp } from './app'

// 客户端特定引导逻辑……
const { app } = createApp()

// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')

修改entry-server.js

服务器 entry 使用 default export 导出函数,并在每次渲染中重复调用此函数。

import { createApp } from "./main";

export default context => {
 // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
 // 以便服务器能够等待所有的内容在渲染前,
 // 就已经准备就绪。
 return new Promise((resolve, reject) => {
  const { app, router, store } = createApp();

  // 设置服务器端 router 的位置
  router.push(context.url);

  // 等到 router 将可能的异步组件和钩子函数解析完
  router.onReady(() => {
   const matchedComponents = router.getMatchedComponents();
   // 匹配不到的路由,执行 reject 函数,并返回 404
   if (!matchedComponents.length) {
    return reject({
     code: 404
    });
   }
   // Promise 应该 resolve 应用程序实例,以便它可以渲染
   resolve(app);
  }, reject);
 });
};

修改router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export function createRouter(){
 return new Router({
  mode: 'history', //一定要是history模式
  routes: [
   {
    path: '/',
    name: 'home',
    component: Home
   },
   {
    path: '/about',
    name: 'about',
    component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
   }
  ]
 })
}

2、修改webpack配置

在vue-cli3创建的vue项目,已经没有了之前的webpack.base.conf.js、webpack.dev.conf.js、webpack.prod.conf.js。那么如何进行webpack的配置呢?

在vue-cli官网上也说明了如何使用。 调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象,该对象将会被 webpack-merge 合并入最终的 webpack 配置。

在项目根目录下,新建一个vue.config.js

// vue.config.js

const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";


module.exports = {
 configureWebpack: () => ({
  // 将 entry 指向应用程序的 server / client 文件
  entry: `./src/entry-${target}.js`,
  // 对 bundle renderer 提供 source map 支持
  devtool: 'source-map',
  target: TARGET_NODE ? "node" : "web",
  node: TARGET_NODE ? undefined : false,
  output: {
   libraryTarget: TARGET_NODE ? "commonjs2" : undefined
  },
  // https://webpack.js.org/configuration/externals/#function
  // https://github.com/liady/webpack-node-externals
  // 外置化应用程序依赖模块。可以使服务器构建速度更快,
  // 并生成较小的 bundle 文件。
  externals: nodeExternals({
   // 不要外置化 webpack 需要处理的依赖模块。
   // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
   // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
   whitelist: [/\.css$/]
  }),
  optimization: {
   splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 2,
    maxAsyncRequests: 5,
    maxInitialRequests: 3
   }
  },
  plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
 }),
 chainWebpack: config => {
  config.module
   .rule("vue")
   .use("vue-loader")
   .tap(options => {
    merge(options, {
     optimizeSSR: false
    });
   });
 }
};

修改package,新增三个脚本来生成bundle.json

"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build",
"build:win": "npm run build:server && move dist\\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\\vue-ssr-server-bundle.json",

通过vue-cli3构建一个SSR应用程序的方法 

执行命令

npm run build:win

在dist目录下会生成两个json文件

通过vue-cli3构建一个SSR应用程序的方法 

3.4 改造server.js 代码

const fs = require("fs");
const Koa = require("koa");
const path = require("path");
const koaStatic = require('koa-static')
const app = new Koa();

const resolve = file => path.resolve(__dirname, file);
// 开放dist目录
app.use(koaStatic(resolve('./dist')))

// 第 2 步:获得一个createBundleRenderer
const { createBundleRenderer } = require("vue-server-renderer");
const bundle = require("./dist/vue-ssr-server-bundle.json");
const clientManifest = require("./dist/vue-ssr-client-manifest.json");

const renderer = createBundleRenderer(bundle, {
 runInNewContext: false,
 template: fs.readFileSync(resolve("./src/index.temp.html"), "utf-8"),
 clientManifest: clientManifest
});

function renderToString(context) {
 return new Promise((resolve, reject) => {
  renderer.renderToString(context, (err, html) => {
   err ? reject(err) : resolve(html);
  });
 });
}
// 第 3 步:添加一个中间件来处理所有请求
app.use(async (ctx, next) => {
 const context = {
  title: "ssr test",
  url: ctx.url
 };
 // 将 context 数据渲染为 HTML
 const html = await renderToString(context);
 ctx.body = html;
});

const port = 3000;
app.listen(port, function() {
 console.log(`server started at localhost:${port}`);
});

3.5 运行server.js

node server.js

访问localhost:3000,可以看到页面的数据都是又服务端渲染完成后返回的。到这一步已经基本算完成了SSR的构建了。

如果有问题的话,可以把dist目录下的index.html文件删了。避免直接访问到了该html文件。

通过vue-cli3构建一个SSR应用程序的方法 

4、集成vuex

修改store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export function createStore() {
 return new Vuex.Store({
  state: {

  },
  mutations: {

  },
  actions: {

  }
 });
}

修改main.js

import Vue from "vue";
import App from "./App.vue";
import { createRouter } from "@/router";
import { createStore } from "@/store";
export function createApp() {
 const router = createRouter();
 const store = createStore() // +
 const app = new Vue({
  router,
  store,   // +
  render: h => h(App)
 });
 return { app, router };
}

再次运行脚本构建

npm run build:win
node server.js

5、案例代码

附上案例源码 https://github.com/lentoo/vue-cli-ssr-example欢迎star

6、总结

到目前为止,仅仅是完成了SSR的基础部分,还有相关的 SSR热更新之类的问题还需要继续探索。如果有好的热更新方法欢迎发出了参考参考。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript编程起步(第一课)
Jan 10 Javascript
JavaScript 变量基础知识
Nov 07 Javascript
Javascript学习笔记 delete运算符
Sep 13 Javascript
js控制web打印(局部打印)方法整理
May 29 Javascript
javascript检测浏览器的缩放状态实现代码
Sep 28 Javascript
2014最热门的JavaScript代码高亮插件推荐
Nov 25 Javascript
js实现点击链接后窗口缩小并居中的方法
Mar 02 Javascript
JS实现添加,替换,删除节点元素的方法
Jun 30 Javascript
Bootstrap CSS组件之按钮组(btn-group)
Dec 17 Javascript
Angular2入门教程之模块和组件详解
May 28 Javascript
js实现多个标题吸顶效果
Jan 08 Javascript
Openlayers学习之地图比例尺控件
Sep 28 Javascript
vue.js单文件组件中非父子组件的传值实例
Sep 13 #Javascript
JavaScript数组方法的错误使用例子
Sep 13 #Javascript
vue仿element实现分页器效果
Sep 13 #Javascript
区别JavaScript函数声明与变量声明
Sep 12 #Javascript
详解js中Array的方法及技巧
Sep 12 #Javascript
Angularjs Ng_repeat中实现复选框选中并显示不同的样式方法
Sep 12 #Javascript
angularjs下ng-repeat点击元素改变样式的实现方法
Sep 12 #Javascript
You might like
在PHP中PDO解决中文乱码问题的一些补充
2010/09/06 PHP
PHP微信分享开发详解
2017/01/14 PHP
phpstudy默认不支持64位php的解决方法
2017/02/20 PHP
php求斐波那契数的两种实现方式【递归与递推】
2019/09/09 PHP
兼容多浏览器的字幕特效Marquee的通用js类
2008/07/20 Javascript
javascript网页关键字高亮代码
2008/07/30 Javascript
thinkphp 表名 大小写 窍门
2015/02/01 Javascript
bootstrap输入框组代码分享
2016/06/07 Javascript
原生js封装二级城市下拉列表的实现代码
2016/06/16 Javascript
jQuery实现标签页效果实战(4)
2017/02/08 Javascript
jQuery源码分析之sizzle选择器详解
2017/02/13 Javascript
jQuery插件echarts实现的循环生成图效果示例【附demo源码下载】
2017/03/04 Javascript
react实现pure render时bind(this)隐患需注意!
2017/03/09 Javascript
小程序日历控件使用方法详解
2018/12/29 Javascript
Vue.js实现tab切换效果
2019/07/24 Javascript
Javascript var变量删除原理及实现
2020/08/26 Javascript
Python时区设置方法与pytz查询时区教程
2013/11/27 Python
举例讲解Python设计模式编程的代理模式与抽象工厂模式
2016/01/16 Python
Python用模块pytz来转换时区
2016/08/19 Python
利用python发送和接收邮件
2016/09/27 Python
Python爬虫实现简单的爬取有道翻译功能示例
2018/07/13 Python
详解Python requests 超时和重试的方法
2018/12/18 Python
python实现从本地摄像头和网络摄像头截取图片功能
2019/07/11 Python
Django将默认的SQLite更换为MySQL的实现
2019/11/18 Python
利用Python自动化操作AutoCAD的实现
2020/04/01 Python
Python如何创建装饰器时保留函数元信息
2020/08/07 Python
CSS3实现多背景模拟动态边框的效果
2016/11/08 HTML / CSS
HTML5 Canvas标签使用收录
2009/07/07 HTML / CSS
花园仓库建筑:Garden Buildings Direct
2018/02/16 全球购物
C#里面可以避免一个类被其他类继承么?如何?
2013/09/26 面试题
模具设计与制造专业应届生求职信
2013/10/18 职场文书
群众对十八届四中全会的期盼
2014/10/17 职场文书
病假证明模板
2015/06/19 职场文书
欢送会主持词
2015/07/01 职场文书
2016关于军训的心得体会
2016/01/11 职场文书
商业计划书范文
2019/04/24 职场文书