Home

Awesome

简体中文 | English

Egg + React + SSR boilerplate

<a href="https://circleci.com/gh/ykfe"><img src="https://img.shields.io/circleci/build/github/ykfe/egg-react-ssr/dev.svg" alt="Build Status"></a> <a href="https://codecov.io/gh/ykfe/egg-react-ssr"><img src="https://codecov.io/gh/ykfe/egg-react-ssr/branch/dev/graph/badge.svg" alt="Coverage Status"></a> <a href="https://npmcharts.com/compare/yk-cli"><img src="https://img.shields.io/npm/dt/yk-cli" alt="download"></a> <a href="https://standardjs.com"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="standardjs"></a> <a href="https://github.com/facebook/jest"><img src="https://img.shields.io/badge/tested_with-jest-99424f.svg" alt="License"></a> <a href="https://github.com/ykfe/egg-react-ssr"><img src="https://img.shields.io/npm/l/vue.svg" alt="License"></a> <img src="https://img.shields.io/badge/node-%3E=8-green.svg" alt="Node">

最小而美的服务端渲染应用模板,特点

正在使用这个项目的公司(应用), 如果您正在使用但名单中没有列出来的话请提issue,欢迎推广分享

<table> <tr> <td align="center"><a target="_blank" href="https://www.youku.com"><img src="https://img.alicdn.com/tfs/TB17DTuXkH0gK0jSZPiXXavapXa-680-133.svg" width="100px;" alt="优酷"/><br /><sub><b>优酷视频 </b></sub></a></td> <td align="center"><a target="_blank" href="https://campaign.vmate.com/vrbollywood"><img src="https://img.alicdn.com/tfs/TB17p6Vhbj1gK0jSZFOXXc7GpXa-512-512.png" width="100px;" alt="vmate 积分商城"/><br /> <a target="_blank" href="https://job.alibaba.com/zhaopin/position_detail.htm?trace=qrcode_share&positionCode=GP524819"><sub><b>Vmate短视频 </b></a></td> <td align="center"><a target="_blank" href="https://enjoysales.paat.com/"><img src="https://img.alicdn.com/tfs/TB1Ma0BiEY1gK0jSZFMXXaWcVXa-836-836.png" width="100px;" alt="火炽星原CRM"/><br /> <a target="_blank" href="https://enjoysales.paat.com/"><sub><b>火炽星原CRM </b></a></td> <td align="center"><a href="https://www.niuniuda.com/mall" target="_blank"><img src="https://img.alicdn.com/tfs/TB1df_8pBr0gK0jSZFnXXbRRXXa-160-60.png" width="100px;" alt="牛牛搭"/><br /> <a href="https://www.niuniuda.com/mall" target="_blank"><sub><b>牛牛搭 </b></a></td> <td align="center"><a href="https://help.seewo.com/" target="_blank"><img src="https://img.alicdn.com/tfs/TB11Xo3q4v1gK0jSZFFXXb0sXXa-350-55.png" width="100px;" alt="cvte"/><br /><a href="https://help.seewo.com/" target="_blank"><sub><b>希沃帮助中心 </b></a></td> <td align="center"><a href="https://wecard.qq.com/index/" target="_blank"><img src="https://img.alicdn.com/tfs/TB1pTZGrFT7gK0jSZFpXXaTkpXa-164-164.jpg" width="100px;" alt="腾讯微卡"/><br /> <a href="https://wecard.qq.com/index/" target="_blank"><sub><b>腾讯微卡 </b></a></td> <td align="center"><a href="https://www.myweimai.com/#sectionOne" target="_blank"><img src="https://img.alicdn.com/tfs/TB16i3VrQT2gK0jSZPcXXcKkpXa-400-400.jpg" width="100px;" alt="微脉"/><br /> <a href="https://www.myweimai.com/#sectionOne" target="_bvlank"><sub><b>微脉 </b></a></td> </tr> </table>

快速入门

这里我们提供了一个脚手架,方便你创建快速项目。

$ npm install yk-cli -g
$ ykcli init <Your Project Name>
$ cd <Your Project Name>
$ npm i
$ npm start
$ open http://localhost:7001

在执行 ykcli init 的时候,可以选择javascript或typescript语言,非常方便。

npm scripts

1)启动服务

启动监听7001端口,此端口同时用于服务端渲染以及客户端渲染,通过query或者config来指定渲染模式

$ npm start # 建议以本方式启动应用,同时启动服务端渲染 + 客户端hydrate

2)只启动服务端渲染,此时仅服务端直出html,没有与客户端混合的步骤

$ npm run ssr 

3)启动客户端渲染

仅限于本地开发使用,启动监听8000端口,只启动客户端渲染,相当于传统的cra脚手架开发模式

$ npm run csr 

4)配套的脚本

$ npm run prod    # 使用egg-scripts启动应用模拟SSR应用生产环境
$ npm run build   # 打包服务端以及客户端资源文件
$ npm run analyze # 可视化分析客户端打包的资源详情

功能/特性

该模板特色为:写法简单、功能强大、一切都是组件、支持 SSR/CSR 两种渲染模式无缝切换。

更多功能/特性如下:

写法

在写法上统一csr和ssr,采用next类似的静态的getInitialProps作为数据获取方法

function Page(props) {
  return <div> {props.name} </div>
}

Page.getInitialProps = async (ctx) => {
  return Promise.resolve({
    name: 'Egg + React + SSR'
  })
}

export default Page

具体说明如下。

在运行时,通过npm run csrnpm run ssr来进行区分,是目前最简单的同构渲染方案。当页面初始化加载时,getInitialProps只会加载在服务端。只有当路由跳转(Link组件跳转或 API 方法跳转)时,客户端才会执行getInitialProps。

getInitialProps入参对象的属性如下:

一切皆组件

我们的页面基础模版 html,meta 等标签皆使用JSX来生成,避免你去使用繁琐的模版引擎语法

const commonNode = props => (
  // 为了同时兼容ssr/csr请保留此判断,如果你的layout没有内容请使用 props.children ? { props.children } : ''
  props.children
    ? <div className='normal'><h1 className='title'><Link to='/'>Egg + React + SSR</Link><div className='author'>by ykfe</div></h1>{props.children}</div>
    : ''
)

const Layout = (props) => {
  if (__isBrowser__) {
    return commonNode(props)
  } else {
    const { serverData } = props.layoutData
    const { injectCss, injectScript } = props.layoutData.app.config
    return (
      <html lang='en'>
        <head>
          <meta charSet='utf-8' />
          <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no' />
          <meta name='theme-color' content='#000000' />
          <title>React App</title>
          {
            injectCss && injectCss.map(item => <link rel='stylesheet' href={item} key={item} />)
          }
        </head>
        <body>
          <div id='app'>{ commonNode(props) }</div>
          {
            serverData && <script dangerouslySetInnerHTML={{
              __html: `window.__USE_SSR__=true; window.__INITIAL_DATA__ =${serialize(serverData)}`
            }} />
          }
          <div dangerouslySetInnerHTML={{
            __html: injectScript && injectScript.join('')
          }} />
        </body>
      </html>
    )
  }
}

如何切换渲染模式

在本地开发时,你可以同时启动ssr/csr两种渲染模式查看区别,在生产环境时,你可以通过设置config中的type属性来切换不同的渲染模式或者通过query来切换,在流量较大时可以降级为csr渲染模式 参考文档如何切换渲染模式

$ open http://localhost:7001/          # 以SSR模式渲染应用
$ open http://localhost:7001/?csr=true # 切换为CSR模式渲染或者通过config.type来设置渲染模式

执行环境

执行流程

配置

为了足够灵活使用,这里我们将一些关键项提供可配置的选项,可根据实际需要来配置,如无特殊必要,使用默认配置即可。服务端渲染相关配置信息我们放在config.ssr.js,在这里我们建议不要将配置放在egg的配置文件当中,避免前端bundle中包含后端配置文件信息

// config/config.ssr
const resolvePath = (path) => require('path').resolve(process.cwd(), path)

module.exports = {
    type: 'ssr', // 指定运行类型可设置为csr切换为客户端渲染,此时服务端不会做获取数据生成字符串的操作以及不会使用hydrate API
    static: {
        // 设置Node应用的静态资源目录,为了生产环境读取静态资源文件
      prefix: '/',
      dir: resolvePath('dist')
    },
    routes: [
        // 前后端统一使用的路由配置文件,防止重复编写相同的路由
      {
        path: '/', // 请求的path
        exact: true, // 是否精确匹配
        Component: () => (require('@/page/index').default), // 这里使用一个function包裹为了让它延迟require, 否则Node环境无法识别前端组件中用到的import关键字会报错
        controller: 'page', // 需要调用的controller
        handler: 'index' // 需要调用的controller中具体的method
      },
      {
        path: '/news/:id',
        exact: true,
        Component: () => (require('@/page/news').default),
        controller: 'page',
        handler: 'index'
      }
    ],
    injectCss: [
    `/static/css/Page.chunk.css`
  ], // 客户端需要加载的静态样式表
  injectScript: [
    `<script src='/static/js/runtime~Page.js'></script>`,
    `<script src='/static/js/vendor.chunk.js'></script>`,
    `<script src='/static/js/Page.chunk.js'></script>`
  ], // 客户端需要加载的静态资源文件表
  serverJs: resolvePath(`dist/Page.server.js`): string|function, // 打包后的server端的bundle文件路径支持传入CDN地址, 接受直接传入require后的function
  layout: resolvePath(`dist/Layout.server.js`): string|function // 打包后的server端的bundle文件路径支持传入CDN地址, 接受直接传入require后的function
}

目录结构

目录结构保持了Egg的方式,以app和config目录为主。将前端React相关代码放到web目录下,webpack打包相关文件位于build目录。整体来看,目录不多,层级不深,属于刚刚好那种。

├── README.md
├── app # egg核心目录
│   ├── controller
│   ├── extend
│   ├── middleware
│   └── router.js # egg路由文件,无特殊需求不需要修改内容
├── app.js # egg 启动入口文件
├── build # webpack配置目录
│   ├── paths.js
│   ├── util.js
│   ├── webpack.config.base.js # 通用的webpack配置
│   ├── webpack.config.client.js # webpack客户端打包配置
│   └── webpack.config.server.js # webpack服务端打包配置
├── config # egg 配置文件目录
│   ├── config.daily.js
│   ├── config.default.js
│   ├── config.ssr.js
│   ├── config.local.js
│   ├── config.prod.js
│   ├── plugin.js
│   └── plugin.local.js
├── dist # build生成静态资源文件目录
│   ├── Page.server.js # 服务端打包后文件(即打包后的serverRender方法)
│   └── static # 前端打包后静态资源目录
└── web # 前端文件目录
    ├── assets
    │   └── common.less
    ├── entry.js # webpack打包入口文件,分环境导出不同配置
    ├── layout
    │   ├── index.js # 页面布局
    │   └── index.less
    └── page
        ├── index
        └── news

Changelog

每一个版本的详细改动请查看 release notes

与其他方案的对比

本地如何调试源码

请查看该wiki

如何向本项目贡献代码

请查看该wiki

Contributors

Thanks goes to these wonderful people (emoji key):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- prettier-ignore-start --> <!-- markdownlint-disable --> <table> <tr> <td align="center"><a href="https://github.com/zhangyuang"><img src="https://avatars3.githubusercontent.com/u/17424434?v=4" width="100px;" alt=""/><br /><sub><b>LeonCheung</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=zhangyuang" title="Code">💻</a></td> <td align="center"><a href="http://i5ting.com"><img src="https://avatars3.githubusercontent.com/u/3118295?v=4" width="100px;" alt=""/><br /><sub><b>狼叔</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=i5ting" title="Code">💻</a></td> <td align="center"><a href="http://www.lessing.online/xx-blog/"><img src="https://avatars2.githubusercontent.com/u/21156871?v=4" width="100px;" alt=""/><br /><sub><b>Xu Zhiyong</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/issues?q=author%3AJohnieXu" title="Bug reports">🐛</a></td> <td align="center"><a href="https://ivc.me"><img src="https://avatars0.githubusercontent.com/u/16490377?v=4" width="100px;" alt=""/><br /><sub><b>Menteceso</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=ivc369" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/jerryYuX"><img src="https://avatars2.githubusercontent.com/u/33367577?v=4" width="100px;" alt=""/><br /><sub><b>jerryYu</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=jerryYuX" title="Code">💻</a></td> <td align="center"><a href="https://github.com/qq1353709"><img src="https://avatars3.githubusercontent.com/u/7944687?v=4" width="100px;" alt=""/><br /><sub><b>dydong</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=qq1353709" title="Code">💻</a></td> <td align="center"><a href="https://github.com/jxycbjhc"><img src="https://avatars0.githubusercontent.com/u/16661897?v=4" width="100px;" alt=""/><br /><sub><b>snoy</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=jxycbjhc" title="Documentation">📖</a></td> <td align="center"><a href="http://zxy.im"><img src="https://avatars2.githubusercontent.com/u/15117664?v=4" width="100px;" alt=""/><br /><sub><b>zhaoxingyue</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=zhaoxingyue" title="Documentation">📖</a></td> </tr> <tr> <td align="center"><a href="http://www.puacode.com"><img src="https://avatars3.githubusercontent.com/u/48011106?v=4" width="100px;" alt=""/><br /><sub><b>九牧</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/issues?q=author%3Adeancn175" title="Bug reports">🐛</a></td> <td align="center"><a href="https://github.com/JohannLai"><img src="https://avatars0.githubusercontent.com/u/10769405?v=4" width="100px;" alt=""/><br /><sub><b>JohannLai</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/issues?q=author%3AJohannLai" title="Bug reports">🐛</a></td> <td align="center"><a href="https://github.com/robert7git"><img src="https://avatars2.githubusercontent.com/u/6889441?v=4" width="100px;" alt=""/><br /><sub><b>robert.xu</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=robert7git" title="Code">💻</a></td> <td align="center"><a href="https://github.com/zhusjfaker"><img src="https://avatars1.githubusercontent.com/u/31839470?v=4" width="100px;" alt=""/><br /><sub><b>zhushijie</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/commits?author=zhusjfaker" title="Code">💻</a></td> <td align="center"><a href="https://github.com/c690554125"><img src="https://avatars3.githubusercontent.com/u/13865568?v=4" width="100px;" alt=""/><br /><sub><b>Cheng Zhongmin</b></sub></a><br /><a href="https://github.com/ykfe/egg-react-ssr/issues?q=author%3Ac690554125" title="Bug reports">🐛</a></td> </tr> </table> <!-- markdownlint-enable --> <!-- prettier-ignore-end --> <!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the all-contributors specification. Contributions of any kind welcome!

License

MIT

NodeParty 分享

如果你想了解本应用的设计思路,欢迎下载查看本人在2020.1.11日在北京NodeParty上所做的分享PPT,其中讨论了需要关注的一些问题的设计思路和解决方案的选取

答疑群

虽然我们已经尽力检查了一遍应用,但仍有可能有疏漏的地方,如果你在使用过程中发现任何问题或者建议,欢迎提issue或者PR 欢迎直接扫码加入钉钉群 <img src="https://img.alicdn.com/tfs/TB1X1CsnET1gK0jSZFrXXcNCXXa-750-990.jpg" width="300">