vue-ssr
Umajs-vue-ssr
是由@umajs/plugin-vue-ssr在新窗口中打开 搭配Srejs在新窗口中打开构建的轻量级,使用简单,灵活的vue
服务端渲染解决方案;可以在controller
和middleware
中灵活使用,通过模板引擎式的语法对vue
页面组件进行服务端渲染。
1、插件介绍
plugin-vue-ssr
插件扩展了Umajs
中提供的统一返回处理Result
对象,新增了Result.vue
页面组件渲染方法,可在controller
自由调用,使用类似传统模板引擎;也同时将方法挂载到了 koa 中间件中的ctx
对象上;当一些公关的页面组件,比如 404、异常提示页面、登录或者需要在中间件中拦截跳转时可以在middleware
中调用。
2、特性
- 不默认路由,不需区分前端路由和后端路由概念,且支持页面级组件 AB 测;
灵活
- 页面组件中没有
__isBrowser__
之类变量对ssr
和csr
模式进行特殊区分处理;统一
。 - 自定义
HTML
采用htmlWebpackPlugin
,没有runtime
,页面响应速度更高;高性能
。 - 支持
html
中使用nunjucks
类模板引擎语法实现SEO
;易上手
。 - 页面开发不依赖框架包装的任何模块,保持原生的
vue
开发体验;友好,易升级
。 - 数据获取提供服务端注入方式,页面视图渲染和数据加工分开处理;
逻辑更清晰
。 - 支持
SSR
和CSR
动态调整,支持SSR
缓存,降级。高可用
。 - 支持其他
koa
开发框架使用。可扩展
。 - 支持 MPA,各页面组件可单独构建,
可页面级更新
。
3、脚手架快速初始化项目[推荐]
在 cli 中支持快速创建
umajs-vue-ssr
模板工程。
$ npm i @umajs/cli -g // 安装cli工具
$ uma project umajs-vue-demo //通过uma初始化工程,选择vue2.0模板工程
✔ loading template
? please choise a template to create project (Use arrow keys)
all,全套模板,包含部分代码示例
mini,极简模板,仅有核心代码
standard,标准模板,不包含示例代码
react,umajs-react-ssr模板,包含示例代码
❯ vue2.0,umajs-vue-ssr模板,包含示例代码
2
3
4
5
6
7
8
9
10
依赖安装和启动
cd umajs-vue-demo
yarn install
yarn start
2
3
4
4、手动安装集成插件
插件安装
yarn add @umajs/plugin-vue-ssr --save
打开 package.json
文件并添加 scripts
配置段:
"scripts": {
"start":'node app.js',
"build":"npx ssr build",
"analyzer": "npx ssr analyzer",
},
2
3
4
5
start
启动你的 node 项目build
运行npx ssr build
构建用于生产环境的应用程序,Srejs 为多项目工程目录结构,可通过指定页面标识单独构建或者启动特定页面,命令为:npm run build xxx
analyzer
运行npx ssr analyzer
用于分析页面组件打包依赖分析 可通过npm run analyzer xxx
xxx 为页面组件标识,可指定分析某个页面组件打包结果
插件配置
// plugin.config.ts
export default <{ [key: string]: TPluginConfig }>{
vue: {
enable: true,
options: {
rootDir: 'web', // 客户端页面组件根文件夹
rootNode: 'app', // 客户端页面挂载根元素ID
ssr: true, // 全局开启服务端渲染
cache: false, // 全局使用服务端渲染缓存 开发环境设置true无效
prefixCDN: '/', // 客户端代码部署CDN前缀
},
},
}
2
3
4
5
6
7
8
9
10
11
12
13
目录结构
框架默认配置属性rootDir
默认为根目录下web
,pages 下是页面组件入口,比如list
页面,vue 主入口文件为list/index.js
,页面组件为list/App.vue
└── web
└── pages
└── list
├── App.vue
├── index.js
2
3
4
5
页面组件(App.vue)
<template>
<!--vue2.0版本APP.vue必须要设置根元素-->
<div id="app">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
msg: 'hi vue ssr!',
}
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
页面主入口文件(index.js)
import App from './App.vue'
export default {
App, // 必须导出App
Router, //如使用vue-router 导出路由配置对象
Store, //如使用vuex 导出store对象
}
2
3
4
5
6
Pages
下按照文件夹名称定义 vue 页面组件,每一个页面组件必须包含inde.js
主入口文件,文件必须导出组件App
。如果使用vue-router
,则将路由配置导出为Router
对象;当使用vuex
时,则将初始化配置导出为Store
。
5、插件使用
import { BaseController, Path } from '@umajs/core'
import { Result } from '../plugins/vue-ssr'
export default class Index extends BaseController {
@Path('/')
index() {
return Result.vue('index', { title: 'umajs-vue-ssr' })
}
}
2
3
4
5
6
7
8
9
6、插件 API
插件扩展了
Umajs
中提供的统一返回处理Result
方法,新增了vue
页面组件可在controller
自由调用,方式类似传统模板引擎使用方法;也同时将方法挂载到了 koa 中间件中的ctx
对象上;当一些公关的页面组件,比如 404、异常提示页面、登录或者需要在中间件中拦截跳转时可以在middleware
中调用。
interface TviewOptions{
ssr?: boolean, // 全局开启服务端渲染
cache?: boolean, // 全局使用服务端渲染缓存
}
Result.vue(viewName:string,initProps?:object,options?:TviewOptions);
ctx.vue(viewName:string,initProps?:object,options?:TviewOptions);
2
3
4
5
6
如果 options 参数传递为空 则默认会使用全局配置属性,全局配置采用插件集成时传递的 options 参数
注意 cache
只在生产环境
开启有效。
7、数据获取
在
Pages
页面中,vue 页面组件获取数据有两种形式;我们分为服务端直出数据(Props 和 State)和 vue 组件静态方法asyncData
获取两种形式。我们可以通过这两种方式对服务端渲染时首次页面渲染进行数据填充,使得服务端渲染时能返回完整的 DOM 结构,提高用户体验和更利于SEO
.
服务端直出 Props
框架在服务端提供了页面组件的渲染函数
Result.vue
,在调用函数渲染时我们可以在 initProps 参数中传递初始化的数据对象;这些数据可以在创建 vue 实例时会注册为组件实例的 Props 参数。在页面组件中我们可以将其定义为 Props。 了解 vue 组件 Props在新窗口中打开
Result.vue('list', {
title: 'xxx',
keywords: 'xxx',
description: 'xxxx',
say: 'hi!',
})
2
3
4
5
6
在页面组件中我们可以直接通过 Props 将服务端直出的数据在 vue 模板中使用。
<template>
<!--vue2.0版本APP.vue必须要设置根元素-->
<div id="app">
<h1>{{ title }}</h1>
<p>{{ say }}</p>
</div>
</template>
<script>
export default {
name: 'app',
props: ['say', 'title'],
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
**特别说明:**在initProps
参数中,title,keywords,description
还会默认被解析为 web 网页头中的 标题,关键字,描述填充。
服务端初直出 State
在 initProps 对象中,框架会默认将 State 属性初始化为 store 实例。从根组件“注入”到每一个子组件中。
Result.vue('list', {
title: 'xxx',
keywords: 'xxx',
description: 'xxxx',
state: {
say: 'hi!',
},
})
2
3
4
5
6
7
8
我们可以直接在 Vue
任意组件中直接获得 Vuex
状态。获取方式为this.$store.state
。
<template>
<!--vue2.0版本APP.vue必须要设置根元素-->
<div id="app">
<h1>{{ title }}</h1>
<p>{{ say }}</p>
</div>
</template>
<script>
export default {
name: 'app',
props: ['title'],
data: () => {
return {
say: this.$store.state.say,
}
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
由于 Vuex 的状态存储是响应式的,从 store 实例中可以在计算属性中返回某个状态。
<script>
export default {
name: 'app',
props: ['title'],
computed: {
say() {
return this.$store.state.say
},
},
}
</script>
2
3
4
5
6
7
8
9
10
11
更多获取 Vuex 状态的使用方法可查看官方文档Vuex在新窗口中打开
asyncData 静态方法
asyncData
是页面组件数据获取的钩子,只能作用于页面
。其接收对象参数默认是vuex
的store
和当前router
,通过router
可以获取到当前路由的参数等数据,然后调用异步请求获取 http 类型的数据,然后通过store
触发状态管理的更新,也可直接改写操作store.state
属性。框架会合并到store
数据上下文state
中。
<script>
export default {
name: 'app',
data() {
return {
msg: this.$store.state.msg,
}
},
async asyncData({ store, route }) {
// store.state.msg = 'about来自asyncData的数据'
// 触发 action 后,会返回 Promise
return store.dispatch('fetchItem', route.params.id)
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
asyncData灵感来自官方vue-ssr 示例在新窗口中打开和nuxtjs在新窗口中打开。
8、SEO 和 HTML
在
initProps
中通过,默认接收title,keywords,description
作为页面标题,关键字,网页描述填充字段。
Result.vue('list', { title: 'xxx', keywords: 'xxx', description: 'xxxx' })
自定义 html
框架内置
HTMLWebpackPlugin
插件,开发者在页面组件同级目录下可以覆盖默认 html 模板自定义引入第三方资源和脚本。自定义 html 文件名为页面下的index.html
。
<!-- web/pages/index/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="format-detection" content="email=no" />
<meta name="format-detection" content="address=no;" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<title>vue ssr</title>
<!-- 引入第三方组件库样式 -->
</head>
<body>
<!--vue-ssr-outlet-->
</body>
<!-- 引入第三方sdk脚本 -->
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
模板插值
在 html 模板中,还可以使用模板插值。服务端渲染初始化时传递的initProps
数据在服务端渲染时会被注入为渲染上下文对象
。
<html>
<head>
<!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) -->
<title>{{ title }}</title>
<!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
{{{ meta }}}
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
支持全局 HTML
为满足业务引入第三方脚本也提供了以下方式自定义 html 模板。
web/pages/xxx/index.html
(局部页面生效)web/index.html
(全局生效)
优先级web/pages/xxx/index.html
> web/index.html
9、vuex
当项目比较复杂时,我们可以在页面组件入口文件中导出 Vuex store 对象。框架会自动从根页面组件注入 vue 子组件中,通过$store 访问到实例化后的 store。
import App from './App.vue'
export default {
App,
Store: {
state: {
count: 100,
},
mutations: {
increment: (state) => state.count++,
decrement: (state) => state.count--,
},
},
}
2
3
4
5
6
7
8
9
10
11
12
13
框架接收到 Store 对象后,会实例化 vuex。并且初始化到 vue 实例中,通过 vue.use 注入到全局。对于初始化的 state 数据,如果在 initProps 中也传入同名的属性,则 initProps.state 将会覆盖主入口文件传入 store.state 中的属性值。
服务端初始化 state
Result.vue(ctx, 'vuex', { state: { count: 200 } })
页面组件获取状态
<template>
<div id="app">
<p>{{ count }}</p>
<p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</p>
</div>
</template>
<script>
export default {
name: 'vuex',
computed: {
count() {
return this.$store.state.count
},
},
methods: {
increment() {
this.$store.commit('increment')
},
decrement() {
this.$store.commit('decrement')
},
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
最后展示到页面上的count
初始值为:200
10、vue-router
当 vue 页面组件需要和 vue-router 结合开发单页面应用时,在入口文件中我们也可以通过 Router 属性导出路由配置。
- 客户端页面组件入口文件路由配置
import App from './App.vue'
import About from './about.vue'
import Home from './home.vue'
export default {
App,
Router: {
mode: 'history',
fallback: false,
base: '/index/',
scrollBehavior: () => ({ y: 0 }),
routes: [
{ path: '/about', props: true, component: About },
{ path: '/home', props: true, component: Home },
{ path: '/', redirect: '/home' },
],
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
注意:服务端渲染模式下不支持使用 import 动态导入组件方式进行路由懒加载
- 服务端路由
对于服务端我们需要保持客户端和服务端路由一致,否则会出现子路由页面刷新
404
现象。
- 页面路由获取数据
在页面组件中页面数据获取方式和普通页面组件保持一致,我们可以使用服务端渲染 props 直出和 asyncData 钩子函数两种方式获取,具体使用参考
数据获取
章节。
11、内置 css 支持
框架内置了
vue-style-loader
以及 css 预处理器 loader,支持*.vue
单个文件组件内的<style>
提取为单独 css 样式文件。也支持 CSS Modules。以下示例均开箱即用,无需额外配置。
Scoped CSS
<template>
<p class="scoped">style scoped</p>
</template>
<style scoped>
.scoped {
color: red;
font-size: 18px;
}
</style>
2
3
4
5
6
7
8
9
10
预处理器 less/scss
<template>
<p class="scoped">style scoped</p>
</template>
<style lang="less" scoped>
.scoped {
color: red;
font-size: 18px;
}
</style>
<style lang="scss" scoped>
.scoped_scss {
color: red;
font-size: 18px;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
当我们采用.less 或者.scss 文件编写样式时,也可以从 JavaScript 中导入 CSS,例如,import './foo.css'**
12、CSS Modules
- 使用 style module
在你的
<style>
上添加 module 特性,这个 module 特性指引 Vue Loader 作为名为 $style 的计算属性,向组件注入 CSS Modules 局部对象。然后你就可以在模板中通过一个动态类绑定来使用它了。
<template>
<p :class="$style.red">This should be red</p>
</template>
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
详细原理查看官方文档vue-loader在新窗口中打开
- 使用预处理器样
如果你的样式是从 JavaScript 中导入的,那么你只需要将把文件命名为
*.module.(less|scss|css)
。
<template>
<p :class="styles.module">
对于外部样式文件,框架默认支持以文件命名: xxx.module.(less|scss|css)
开启使用css module
</p>
</template>
<script>
import styles from './index.module.less'
export default {
data() {
styles
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
13、案例
14、源码
如果你觉得有用,请 star 支持下我们~谢谢!