npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

santi

v0.0.0-beta.43

Published

Isomorphic framework for base on create-react-app and jsdom

Readme

Santi

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

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

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

具体可参考压测表现


特性

  • [x] 自动内联关键样式,快速呈现首屏(对比 Nextjs)
  • [x] SSR 无需关注服务端 / 客户端差异
  • [x] SSR 使用沙盒渲染,无需关注同构时的内存泄漏问题
  • [x] SPA、预渲染、SSR 功能渐进式开启或关闭
  • [x] 性能尚可(搭配合理的缓存,单核心 500QPS + 20ms/AVG 响应)
  • [x] 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

  • [x] ~~useState~~
  • [x] ~~getInitialProps~~
  • [x] ~~SSR 页面级缓存~~
  • [ ] SSR 组件级缓存
  • [ ] SSR 分片支持

FP/FMP/TTI Compare with Nextjs

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

| | Network | CPU | First Contentful Paint(s) | First Meaningful Paint(s) | Time to Interactive(s) | | ------ | ------- | ------------------------ | ------------------------- | ------------------------- | ---------------------- | | Nextjs | Fast 3G | Mobile (PC 4x Slowndown) | 1.8 | 1.8 | 2.0 | | Santi | Fast 3G | Mobile (PC 4x Slowndown) | 0.6 | 0.6 | 1.4 | | Nextjs | Wifi | PC | 0.2 | 0.2 | 0.2 | | Santi | Wifi | PC | 0.1 | 0.1 | 0.1 |

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

| | Network | CPU | First Contentful Paint(s) | First Meaningful Paint(s) | Time to Interactive(s) | | ------ | ------- | ------------------------ | ------------------------- | ------------------------- | ---------------------- | | Nextjs | Fast 3G | Mobile (PC 4x Slowndown) | 3.1 | 3.1 | 3.4 | | Santi | Fast 3G | Mobile (PC 4x Slowndown) | 0.8 | 3.2 | 3.2 | | Nextjs | Wifi | PC | 0.2 | 0.2 | 0.2 | | Santi | Wifi | PC | 0.6 | 0.9 | 0.9 |


SSR Benchmark Compare with Nextjs

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

并发数 1

| | 可用性 | 集群数 | 完成时间(秒) | QPS | 平均响应时间(秒) | | ------ | ---------- | ------ | -------------- | ------ | ------------------ | | Nextjs | 全量计算 | 1 | 5.436 | 369.37 | 0.002707 | | Santi | 全量计算 | 1 | 105.966 | 18.88 | 0.052970 | | Santi | 缓存 100ms | 1 | 5.906 | 339.54 | 0.002945 | | Nextjs | 全量计算 | 6 | 5.705 | 352.00 | 0.002841 | | Santi | 全量计算 | 6 | 107.991 | 18.52 | 0.053981 | | Santi | 缓存 100ms | 6 | 7.315 | 274.09 | 0.003648 |

并发数 10

| | 可用性 | 集群数 | 完成时间(秒) | QPS | 平均响应时间(秒) | | ------ | ---------- | ------ | -------------- | ------ | ------------------ | | Nextjs | 全量计算 | 1 | 2.316 | 874.68 | 0.011433 | | Santi | 全量计算 | 1 | 31.884 | 62.87 | 0.159047 | | Santi | 缓存 100ms | 1 | 3.569 | 564.61 | 0.017711 | | Nextjs | 全量计算 | 6 | 2.679 | 755.05 | 0.013244 | | Santi | 全量计算 | 6 | 19.419 | 103.37 | 0.096742 | | Santi | 缓存 100ms | 6 | 3.972 | 507.34 | 0.019711 |

并发数 50

| | 可用性 | 集群数 | 完成时间(秒) | QPS | 平均响应时间(秒) | | ------ | ---------- | ------ | -------------- | ------ | ------------------ | | Nextjs | 全量计算 | 1 | 2.113 | 967.81 | 0.051663 | | Santi | 全量计算 | 1 | 30.899 | 65.04 | 0.768779 | | Santi | 缓存 100ms | 1 | 2.446 | 829.93 | 0.060246 | | Nextjs | 全量计算 | 6 | 2.24 | 910.84 | 0.054894 | | Santi | 全量计算 | 6 | 12.956 | 155.70 | 0.321122 | | Santi | 缓存 100ms | 6 | 2.523 | 809.01 | 0.061804 |

并发数 100

| | 可用性 | 集群数 | 完成时间(秒) | QPS | 平均响应时间(秒) | | ------ | ---------- | ------ | -------------- | -------- | ------------------ | | Nextjs | 全量计算 | 1 | 2.174 | 949.62 | 0.105305 | | Santi | 全量计算 | 1 | 31.02 | 65.08 | 1.536474 | | Santi | 缓存 100ms | 1 | 2.157 | 953.79 | 0.104845 | | Nextjs | 全量计算 | 6 | 2.224 | 927.44 | 0.107824 | | Santi | 全量计算 | 6 | 11.975 | 170.42 | 0.586769 | | Santi | 缓存 100ms | 6 | 2.061 | 1,015.84 | 0.098441 |

并发数 300

| | 可用性 | 集群数 | 完成时间(秒) | QPS | 平均响应时间(秒) | | ------ | ---------- | ------ | -------------- | -------- | ------------------ | | Nextjs | 全量计算 | 1 | 2.152 | 978.44 | 0.306611 | | Santi | 全量计算 | 1 | 30.409 | 68.90 | 4.354413 | | Santi | 缓存 100ms | 1 | 1.557 | 1,367.29 | 0.219412 | | Nextjs | 全量计算 | 6 | 2.139 | 966.40 | 0.310432 | | Santi | 全量计算 | 6 | 12.361 | 195.62 | 1.533604 | | Santi | 缓存 100ms | 6 | 1.812 | 1,184.90 | 0.253185 |

并发数 500

| | 可用性 | 集群数 | 完成时间(秒) | QPS | 平均响应时间(秒) | | ------ | ---------- | ------ | -------------- | -------- | ------------------ | | Nextjs | 全量计算 | 1 | 2.423 | 987.80 | 0.506178 | | Santi | 全量计算 | 1 | 31.313 | 70.84 | 7.058347 | | Santi | 缓存 100ms | 1 | 1.571 | 1,381.07 | 0.362038 | | Nextjs | 全量计算 | 6 | 2.225 | 981.13 | 0.509618 | | Santi | 全量计算 | 6 | 10.724 | 212.19 | 2.356404 | | Santi | 缓存 100ms | 6 | 1.692 | 1,273.01 | 0.392771 |

并发数 1000

| | 可用性 | 集群数 | 完成时间(秒) | QPS | 平均响应时间(秒) | | ------ | ---------- | ------ | -------------- | -------- | ------------------ | | Nextjs | 全量计算 | 1 | 2.197 | 993.31 | 1.006733 | | Santi | 全量计算 | 1 | 31.05 | 82.12 | 12.176888 | | Santi | 缓存 100ms | 1 | 1.497 | 1,572.22 | 0.636043 | | Nextjs | 全量计算 | 6 | 2.184 | 1,015.39 | 0.984847 | | Santi | 全量计算 | 6 | 11.51 | 225.60 | 4.432673 | | Santi | 缓存 100ms | 6 | 1.591 | 1,495.70 | 0.668581 |