通过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 事件属性绑定带参数的函数
Mar 13 Javascript
异步动态加载js与css文件的js代码
Sep 15 Javascript
js对table的td进行相同内容合并示例详解
Dec 27 Javascript
JS数组排序技巧汇总(冒泡、sort、快速、希尔等排序)
Nov 24 Javascript
JavaScript实现带播放列表的音乐播放器实例分享
Mar 07 Javascript
关于Javascript回调函数的一个妙用
Aug 29 Javascript
angular bootstrap timepicker TypeError提示怎么办
Jun 13 Javascript
input 标签实现输入框带提示文字效果(两种方法)
Oct 09 Javascript
vue.js-div滚动条隐藏但有滚动效果的实现方法
Mar 03 Javascript
详解React服务端渲染从入门到精通
Mar 28 Javascript
微信小程序之数据绑定原理解析
Aug 14 Javascript
详解vue 组件注册
Nov 20 Vue.js
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面向对象的使用教程 简单数据库连接
2006/11/25 PHP
ThinkPHP模板替换与系统常量及应用实例教程
2014/08/22 PHP
PHP中auto_prepend_file与auto_append_file用法实例分析
2014/09/22 PHP
php生成excel列名超过26列大于Z时的解决方法
2014/12/29 PHP
php简单操作mysql数据库的类
2015/04/16 PHP
PHP二维数组实现去除重复项的方法【保留各个键值】
2017/12/21 PHP
thinkphp5框架调用其它控制器方法 实现自定义跳转界面功能示例
2019/07/03 PHP
jQuery学习3:操作元素属性和特性
2010/02/07 Javascript
基于JQuery的一个简单的鼠标跟随提示效果
2010/09/23 Javascript
Jquery ajax传递复杂参数给WebService的实现代码
2011/08/08 Javascript
五段实用的js高级技巧
2011/12/20 Javascript
jquery的冒泡事件的阻止与允许(三种实现方法)
2013/02/01 Javascript
jquery获取URL中参数解决中文乱码问题的两种方法
2013/12/18 Javascript
JavaScript组合拼接字符串的效率对比测试
2014/11/06 Javascript
jquery中$each()方法的使用指南
2015/04/30 Javascript
jQuery实现下拉框选择图片功能实例
2015/08/08 Javascript
jquery马赛克拼接翻转效果代码分享
2015/08/24 Javascript
详解JavaScript基于面向对象之继承实例
2015/12/16 Javascript
jQuery纵向导航菜单效果实现方法
2016/12/19 Javascript
JavaScript实现经典排序算法之插入排序
2016/12/28 Javascript
利用node.js写一个爬取知乎妹纸图的小爬虫
2017/05/03 Javascript
浅谈node中的exports与module.exports的关系
2017/08/01 Javascript
浅谈vue中使用编辑器vue-quill-editor踩过的坑
2020/08/03 Javascript
python使用openCV遍历文件夹里所有视频文件并保存成图片
2020/01/14 Python
opencv 图像滤波(均值,方框,高斯,中值)
2020/07/08 Python
英国领先的葡萄酒专家:Majestic Wine
2017/05/30 全球购物
英国时尚首饰品牌:Missoma
2020/06/29 全球购物
营销总监岗位职责范本
2014/02/26 职场文书
超市理货员岗位职责
2014/07/04 职场文书
教师暑期培训感言
2014/08/15 职场文书
交通安全横幅标语
2014/10/07 职场文书
退货证明模板
2015/06/23 职场文书
电工生产实习心得体会
2016/01/22 职场文书
护士爱岗敬业心得体会
2016/01/25 职场文书
Python 中random 库的详细使用
2021/06/03 Python
Python 读取千万级数据自动写入 MySQL 数据库
2022/06/28 Python