前言
项目地址:github.com/ykfe/ssr
一句话介绍:A future-oriented ssr framework based on midway-faas that implemented serverless-side render specification for faas.
项目背景
2020年Serverless这个概念更加火热。Serverless 解放了端开发者(不仅仅是 Web 开发者)的生产力,让端开发者可以更快、更好、更灵活地开发各种端上应用,不需要投入太多精力关注于后端服务的实现。但是大部分Serverless框架提供的example都是在教你如何搭建一个api服务,对于页面渲染层的例子少之又少。而next.js,nuxt.js这些框架的例子对大部分用户来说理解和使用成本实在是太高了,并且在生产环境使用过程中会遇到各种问题。
由于以上背景,我们基于之前已经开源并且受到广泛好评的[最小而美的Egg + React + SSR 服务端渲染应用骨架](github.com/ykfe/egg-re…) (已受到阿里集团内部多个bu以及多个外部公司生产环境使用反馈开发体验远好于next.js等框架)进一步提炼出Serverless场景下的SSR框架。让SSR应用的开发和发布变得更加简单透明友好同样支持SSR/CSR两种渲染模式随时切换。只需5分钟便可以部署一个Faas SSR应用在公网让所有人访问。
应用架构
如何使用
开发者只需5分钟就可以快速的创建并发布一个SSR应用上线
环境准备
$ node -v # 建议版本>=v10.15.0
v12.16.1
$ yarn -v # 建议使用yarn代替npm
1.21.1
安装脚手架
$ yarn global add ssr # 全局安装ssr脚手架。等同于npm i -g ssr
创建项目
$ ssr init # 创建example,支持SPA/MPA(开发中)两种类型的应用创建
本地开发
$ yarn
$ ssr start
$ open http://localhost:3000
资源构建
$ ssr build
$ ssr build index # 可以对指定的函数进行构建(支持中)
函数发布
发布命令
$ ssr deploy # 默认发布到阿里云函数计算服务,腾讯云支持中
为了解决函数发布对应用代码包的大小有要求的问题,我们不能像之前一样随意的安装node_modules并且发布了这样会导致代码大小压缩后也极易超出50MB的限制。这里我们有以下几种方案解决该问题。
fun nas
使用阿里云提供的nas功能将node_modules作为固定依赖上传到云端。但这样有很多缺点,分别是node_modules并不是固定文件,随时有可能有新的模块依赖加入。以及fun nas上传步骤复杂且需要时间。对开发者不友好。
关闭externals选项
如果你使用next.js这些框架,你会发现在对代码大小有限制的runtime平台根本无法发布。因为next这些模块的dependencies包括babel/webpack这些开发依赖。当你安装next模块时这些开发依赖也一并被安装了。这时候你的选择只有不开启externals选项,将这里模块代码全部打包进服务端bundle。这样会导致服务端bundle特别大,执行效率大大降低。
分离核心依赖
所以这里我们特定将SSR框架分为ssr-core,ssr两个主要的库。ssr模块包含本地开发依赖,在生产环境时我们只需要安装ssr-core模块即可运行应用。使得开发环境真正上传的代码压缩后只有3MB。
ssr-spec 开发规范
注:本规范适用于绝大多数的业务场景,如需额外定制请先想清楚是否必要!
前端技术选型
- 前端框架: React
- 开发语言: TypeScript
- 代码风格: Standard
- 样式处理: less + css modules
- UI组件: 默认已对antd的使用做打包配置无需额外配置
- 前端路由: 约定式路由
- 数据管理: 待支持,暂定使用hooks
应用类型
我们支持单页面应用(SPA)和多页面应用(MPA)两种常见的应用类型的开发。 关于SPA与MPA的区别如下(本表格转载自网络,如有侵权请提issue联系)
SPA
单页面应用一个函数对应一个页面。一个页面对应多个path(即前端路由)。
目录结构
这里我们使用约定式路由。无需手动编写路由配置文件,会根据文件夹名称及路径自动生成路由配置。
.
├── build # web目录构建产物
│ └── index
│ ├── client
│ └── server
├── config.js # 定义应用的配置
├── f.yml
├── package.json
├── src # 存放faas函数的handler
│ └── index.ts
├── tsconfig.json
├── web
│ ├── components # 存放公共组件
│ │ └── header
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ └── layout # SPA应用只需要一个默认的layout
│ │ ├── index.less
│ │ └── index.tsx
│ ├── pages # pages目录下的文件夹会映射为前端路由
│ │ ├── index # index文件夹映射为根路由
│ │ │ ├── fetch.ts # 定义fetch文件用来获取数据,会自动注入到组件的props中
│ │ │ ├── index.less
│ │ │ └── render.tsx # 定义render文件用来定义页面渲染逻辑
│ │ └── news
│ │ ├── fetch.ts
│ │ ├── index.less
│ │ └── render$id.tsx # 映射为/news/:id
│ │ └── render$id$.tsx # 映射为/news/:id?
│ ├── tsconfig.json # 仅用于编辑器ts语法检测
│ └── typings.d.ts
yml文件编写规范
service:
name: serverless-ssr
provider:
name: aliyun
functions:
index:
handler: index.handler
render:
mode: ssr # 指定渲染模式
events:
- http:
path: /*
method:
- get
package:
artifact: code.zip
如何发布
$ ssr deploy # 此时只有一个函数需要发布,选择index函数发布即可
展示形式
ssr-fc.com/ -> index 函数 -> 渲染index组件
ssr-fc.com/news -> index 函数 -> 渲染news组件
MPA
多页面应用一个函数对应一个页面。一个页面对应一个path(即服务端路由)。
目录结构
这里我们的服务端路由存在多个,需要读取yml文件具体函数的配置
.
├── README.md
├── build
│ ├── mpa1
│ │ ├── client
│ │ └── server
│ └── mpa2
│ ├── client
│ └── server
├── f.yml
├── package.json
├── src
│ ├── mpa1handler.ts
│ └── mpa2handler.ts
├── tsconfig.json
├── web
│ ├── components # 存放公共组件
│ │ └── header
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ └── layout # 默认的layout
│ │ ├── index.less
│ │ └── index.tsx
│ ├── pages
│ │ ├── index
│ │ │ ├── fetch.ts
│ │ │ ├── index.less
| | | ├── layout.tsx # 每个独立的页面可以有自己的layout
│ │ │ └── render.tsx
│ │ └── news
│ │ ├── fetch.ts
│ │ ├── index.less
│ │ └── render$id.tsx
yml文件编写规范
service:
name: serverless-ssr
provider:
name: aliyun
functions:
mpa1:
handler: mpa1.handler
render:
mode: ssr
events:
- http:
path: /
method:
- get
mpa2:
handler: mpa2.handler
render:
mode: ssr
events:
- http:
path: /news
method:
- get
package:
artifact: code.zip
如何发布
$ ssr deploy # 此时需要在终端选择需要发布哪个函数
展示形式
ssr-fc.com/ -> mpa1 函数 -> 渲染mpa1文件夹下的render组件
ssr-fc.com/news -> mpa2 函数 -> 渲染mpa2文件夹下的render组件
渲染函数
1)在 FaaS 函数里
在 FaaS 函数里,只需要调用ssr-core提供的render方法传入ctx即可
import { render } from 'ssr-core'
async handler () {
try {
const htmlStr = await render(this.ctx)
return htmlStr
} catch (error) {
return error
}
}
根据f.yml或者query来判断当前渲染模式
2)在 Node.js Web 框架里
该渲染方式实现是服务端无关的,理论上可以支持任何Node.js框架只需引入render方法以及有一个web目录,用法与Faas函数保持一致。
const Koa = require('koa');
const { render } = require('ssr-core')
const app = new Koa();
// mount routes from config
app.use(ssr)
// ctx.ssrRender()
app.get('/*', async ctx => {
ctx.body = render(ctx)
});
app.listen(3000);
模式切换
// url查询参数或者头信息
conf.mode = req.query.ssr || req.headers['x-mode-ssr'];
- ssr(conf)
- cookie
- querystring
- header
此处需要考虑优先级,比如querystring第一,其次是f.yml里的render.mode。
团队介绍
我们来自
(狼叔)团队,致力于探索SSR应用在各种场景下的实践。欢迎有志之士一起加入。联系邮箱:langshu.ssl@alibaba-inc.com
答疑群
虽然我们已经尽力检查了一遍应用,但仍有可能有疏漏的地方,如果你在使用过程中发现任何问题或者建议,欢迎提issue或者PR 欢迎直接扫码加入钉钉群