0.0.0-beta.42 • Published 2 years ago

santi v0.0.0-beta.42

Weekly downloads
14
License
ISC
Repository
github
Last release
2 years ago

Santi

npm.io

基于 create-react-appjsdom 的同构方案,SPA/预渲染/SSR 三位一体

SSR 功能基于 jsdom,每次渲染需启动 jsdom 沙盒,相对于 React 官方 renderToString 方案存在较大性能差异,因此使用 santi 时需要考虑使用缓存控制

目前主要针对低并发高可用性或高并发低可用性等低计算量场景

具体可参考压测表现


特性

  • 自动内联关键样式,快速呈现首屏(对比 Nextjs
  • SSR 无需关注服务端 / 客户端差异
  • SSR 使用沙盒渲染,无需关注同构时的内存泄漏问题
  • SPA、预渲染、SSR 功能渐进式开启或关闭
  • 性能尚可(搭配合理的缓存,单核心 500QPS + 20ms/AVG 响应)
  • SSR 页面级缓存,自由控制
  • SSR 组件级缓存
  • SSR 分片支持

在线示例

https://codesandbox.io/s/santi-demo-bl1f9

兼容性

React v16.8.0+

Preact v10+

需要 Hooks 支持

node v8+

需要 async/await 语法支持


起步

需要先使用 create-react-app 生成项目

或者直接参考完整示例项目 example

  1. 安装 santi 依赖

    npm install santi --save
    # or
    yarn add santi
  2. package.json 中增加 "config-overrides-path" 项,并替换 scripts 执行部分

    serve 命令用于启动构建模式后的 SSR 或静态资源代理服务

    NOTE:暂不支持 test 命令,请勿使用 react-scripts eject 命令

    /* package.json */
    {
      ...
      "scripts": {
        ...
    -   "start": "react-scripts start",
    +   "start": "santi start",
    -   "build": "react-scripts build",
    +   "build": "santi build"
    +   "serve": "santi serve",
        ...
       },
    + "config-overrides-path": "node_modules/santi/config",
      ...
    }
  3. 使用 santi.render 替代 ReactDOM.render

    - import { render } from 'react-dom
    + import { render } from 'santi'
    
    render(<App />, document.getElementById('root'))

主动声明渲染完成

santi 中,每一次 SSR 并不会像 nextjs 那样会在 getInitialProps 后自动完成,需要触发一个自定义事件 ssr-ready 来通知 jsdom 完成了渲染

若一次 SSR 渲染迟迟未收到 ssr-ready 事件,则默认在 1000ms 后强制采集结果并返回,超时时间可在配置文件中的 ssr.timeout 项更改

document.dispatchEvent(new Event('ssr-ready'))

这样做可以自由地确定完成 ssr 渲染的时机,以实现一些有趣的功能,例如让 ssr 允许处理异步载入的模块

在 santi 中,可以不需要手动触发这个自定义事件,触发过程被封装为了一些直接可用的方法:

  • api 形式的 ready 方法,它可以延迟触发
  • 组件形式的 Ready 组件,以及在组件 onMount 后立即发起 ready 的 Ready.OnMount 组件

    import { ready, Ready } from 'santi'
    
    ready() // 立即触发 ssr-ready 事件
    ready(1000) // 1s 后触发 ssr-ready 事件
    
    function TestReady() {
      const [ready, setReady] = useState(false)
    
      useEffect(() => {
        doSomething.then(() => {
          setReady(true)
        })
      }, [])
    
      return <Ready when={ready}>...</Ready>
    }
    
    function TestReadyOnMount() {
      return <Ready.OnMount>...</Ready.OnMount>
    }

在服务端准备数据

useState

santi 允许使用 hook 方式的 santi.useState 方法来初始化服务端数据,这个 hook 将会在 ssr 阶段触发,并同步到 csr 阶段,csr 阶段初始化时直接使用 ssr 阶段获得的数据

一般情况下,为了让 ssr 阶段的数据和 csr 阶段可以一一对应,需要给 useState 一个 key 值,用来做两侧数据的交接

import { useState, Ready } from 'santi'

function App() {
  const [value, setValue] = useState(undefined, 'App_ssrState')
  const [ready, setReady] = React.useState(false)

  React.useEffect(() => {
    doSomeAsyncWork().then(value => {
      setValue(value)
      setReady(true)
    })
  }, [])

  return <Ready when={ready}>State from SSR: {value}</Ready>
}

如果不希望主动声明这个 key 值,可以使用 withSanti HOC 包裹组件

import { withSanti, useState, Ready } from 'santi'

const App = withSanti(function App() {
  const [random, setRandom] = useState(Math.random())

  return <Ready.OnMount>State from SSR: {random}</Ready.OnMount>
})

getInitialProps

santi 也允许类似于 nextjs 中 getInitialProps 的操作,让数据在服务端就准备好,但使用方式上于 nextjs 有所不同

santi 中的 getInitialProps 使用的是 HOC 形式,渲染方式类似于 React.Suspence,将在异步任务完成后才加载组件,因此可以配合 Ready.OnMount 使用

getInitialProps 中包含了 withSanti 功能,因此其内部使用 santi.useState 不需要声明 key 值

import { getInitialProps, Ready } from 'santi'

const delay = time => new Promise(resolve => setTimeout(resolve, time))

const App = getInitialProps(async () => {
  await delay(200) // 模拟异步任务延时

  return {
    random: `ssrProp ${Math.random()}`
  }
})(function App({ random }) {
  return <Ready.OnMount>Prop from SSR: {random}</Ready.OnMount>
})

Santi 配置(页面缓存配置)

合法的 santi 配置文件为以下路径

  • santi.config.js 或 santi.config.ts
  • .santirc.js 或 .santirc.ts
  • config/index.js 或 config/index.ts

配置文件内容如下

const { addWebpackAlias } = require('customize-cra')

module.exports = {
  mode: 'ssr' // santi 模式,可选值为 'csr' | 'ssr'

  prerender: ['/', '/list'] // 构建阶段需要预渲染的路由

  ssr: {
    timeout: 1000, // 每个 ssr 任务等待 ssr-ready 的最长超时时间
    renderConfig: [ // 每个渲染请求的行为配置,如缓存等
      [
        // 使用 micromatch 进行路径匹配
        // https://github.com/micromatch/micromatch
        ['/', '/?**'],
        req => ({
          key: `${req.path}:${req.cookie.uid}`, // 缓存将受 cookie 中 uid 影响,不同 uid 缓存不同
          cache: {
            maxAge: 1000 // 每次对 / 路由的请求都将缓存 1s
          }
        })
      ],
      [
        '/list',
        {
          key: '/list',
          timeout: 2000, // 单独配置该请求的 ssr-ready 超时
          cache: true // 仅渲染一次后长期缓存
        }
      ],
      [
        '**', // 默认渲染配置
        {
          ssr: false // 不使用 ssr
        }
      ]
    ],
    cacheEngine: { // 可选自定义缓存引擎,接入 redis 缓存等,默认为 lru-cache 内存缓存
      get(key) {...},
      set(key, value maxAge) {...}
    }
  },

  // 代理部分参考 http-proxy-middleware
  // https://github.com/chimurai/http-proxy-middleware
  proxy: {
    '/api': 'http://www.somewhere.com'
  }

  // 基于 create-react-app,并使用 react-app-rewired 和 customize-cra 进行无 inject 定制 webpack
  // https://github.com/timarney/react-app-rewired
  // https://github.com/arackaf/customize-cra
  // webpack 部分对应 customize-cra 中的 override 函数
  webpack: [
    addWebpackAlias({
      // 使用 preact 代替 react
      // https://preactjs.com/guide/v10/switching-to-preact
      react: 'preact/compat',
      'react-dom': 'preact/compat'
    })
  ],

  // webpack 部分对应 customize-cra 中的 overrideDevServer 函数
  devServer: []
}

TODO

  • useState
  • getInitialProps
  • SSR 页面级缓存
  • SSR 组件级缓存
  • SSR 分片支持

FP/FMP/TTI Compare with Nextjs

55 节点数,无异步任务场景

NetworkCPUFirst Contentful Paint(s)First Meaningful Paint(s)Time to Interactive(s)
NextjsFast 3GMobile (PC 4x Slowndown)1.81.82.0
SantiFast 3GMobile (PC 4x Slowndown)0.60.61.4
NextjsWifiPC0.20.20.2
SantiWifiPC0.10.10.1

4710 节点数(55 节点 repeat 100x),无异步任务场景

NetworkCPUFirst Contentful Paint(s)First Meaningful Paint(s)Time to Interactive(s)
NextjsFast 3GMobile (PC 4x Slowndown)3.13.13.4
SantiFast 3GMobile (PC 4x Slowndown)0.83.23.2
NextjsWifiPC0.20.20.2
SantiWifiPC0.60.90.9

SSR Benchmark Compare with Nextjs

55 节点数,无异步任务场景,限定 2000 样本、i7-4790 基准测试

并发数 1

可用性集群数完成时间(秒)QPS平均响应时间(秒)
Nextjs全量计算15.436369.370.002707
Santi全量计算1105.96618.880.052970
Santi缓存 100ms15.906339.540.002945
Nextjs全量计算65.705352.000.002841
Santi全量计算6107.99118.520.053981
Santi缓存 100ms67.315274.090.003648

并发数 10

可用性集群数完成时间(秒)QPS平均响应时间(秒)
Nextjs全量计算12.316874.680.011433
Santi全量计算131.88462.870.159047
Santi缓存 100ms13.569564.610.017711
Nextjs全量计算62.679755.050.013244
Santi全量计算619.419103.370.096742
Santi缓存 100ms63.972507.340.019711

并发数 50

可用性集群数完成时间(秒)QPS平均响应时间(秒)
Nextjs全量计算12.113967.810.051663
Santi全量计算130.89965.040.768779
Santi缓存 100ms12.446829.930.060246
Nextjs全量计算62.24910.840.054894
Santi全量计算612.956155.700.321122
Santi缓存 100ms62.523809.010.061804

并发数 100

可用性集群数完成时间(秒)QPS平均响应时间(秒)
Nextjs全量计算12.174949.620.105305
Santi全量计算131.0265.081.536474
Santi缓存 100ms12.157953.790.104845
Nextjs全量计算62.224927.440.107824
Santi全量计算611.975170.420.586769
Santi缓存 100ms62.0611,015.840.098441

并发数 300

可用性集群数完成时间(秒)QPS平均响应时间(秒)
Nextjs全量计算12.152978.440.306611
Santi全量计算130.40968.904.354413
Santi缓存 100ms11.5571,367.290.219412
Nextjs全量计算62.139966.400.310432
Santi全量计算612.361195.621.533604
Santi缓存 100ms61.8121,184.900.253185

并发数 500

可用性集群数完成时间(秒)QPS平均响应时间(秒)
Nextjs全量计算12.423987.800.506178
Santi全量计算131.31370.847.058347
Santi缓存 100ms11.5711,381.070.362038
Nextjs全量计算62.225981.130.509618
Santi全量计算610.724212.192.356404
Santi缓存 100ms61.6921,273.010.392771

并发数 1000

可用性集群数完成时间(秒)QPS平均响应时间(秒)
Nextjs全量计算12.197993.311.006733
Santi全量计算131.0582.1212.176888
Santi缓存 100ms11.4971,572.220.636043
Nextjs全量计算62.1841,015.390.984847
Santi全量计算611.51225.604.432673
Santi缓存 100ms61.5911,495.700.668581
0.0.0-beta.42

2 years ago

0.0.0-beta.41

3 years ago

0.0.0-beta.40

3 years ago

0.0.0-beta.39

3 years ago

0.0.0-beta.38

3 years ago

0.0.0-beta.37

3 years ago

0.0.0-beta.36

3 years ago

0.0.0-beta.35

4 years ago

0.0.0-beta.34

4 years ago

0.0.0-beta.33

4 years ago

0.0.0-beta.31

4 years ago

0.0.0-beta.32

4 years ago

0.0.0-beta.30

4 years ago

0.0.0-beta.28

4 years ago

0.0.0-beta.29

4 years ago

0.0.0-beta.27

4 years ago

0.0.0-beta.26

4 years ago

0.0.0-beta.25

4 years ago

0.0.0-beta.24

4 years ago

0.0.0-beta.23

4 years ago

0.0.0-beta.22

4 years ago

0.0.0-beta.21

4 years ago

0.0.0-beta.19

4 years ago

0.0.0-beta.20

4 years ago

0.0.0-beta.18

4 years ago

0.0.0-beta.17

4 years ago

0.0.0-beta.16

4 years ago

0.0.0-beta.15

4 years ago

0.0.0-beta.14

4 years ago

0.0.0-beta.13

4 years ago

0.0.0-beta.12

4 years ago

0.0.0-beta.11

4 years ago

0.0.0-beta.10

4 years ago

0.0.0-beta.9

4 years ago

0.0.0-beta.8

4 years ago

0.0.0-beta.6

4 years ago

0.0.0-beta.5

4 years ago

0.0.0-beta.4

4 years ago

0.0.0-beta.3

4 years ago

0.0.0-beta.2

4 years ago

0.0.0-beta.1

4 years ago