Coder Social home page Coder Social logo

zhangyuang / egg-react-ssr Goto Github PK

View Code? Open in Web Editor NEW
1.9K 38.0 211.0 3.18 MB

最小而美的Egg + React + SSR 服务端渲染应用骨架,同时支持JS和TS

Home Page: http://doc.ssr-fc.com/

License: MIT License

TypeScript 100.00%
ssr getinitialprops react typescript isomorphic eggjs csr nextjs

egg-react-ssr's Introduction

Hi there 👋

I'm a front end engineer, and foucs on open source programmer.

I use TypeScript and Rust in daily development.

My work experience

I used to work at Alibaba from 2018 to 2020

I'm working at WeChat

My personal representative open source project

ssr A most advanced ssr framework support React17/React18/Vue2/Vue3 on Earth that implemented serverless-side render specification.

fe-dev-playbook A document about how to create a perfect development environment.

v8-profiler-rs Use Rust analyze V8 HeapSnapShot by visual interface ui

node-ffi-rs Implement ffi in Node.js by Rust and NAPI

memoryshare Share memory between different Node.js processes by Rust

Famous open source project i had contributed to

vite

webpack

How to sponsor me

There are two ways to sponsor me both Alipay and WeChat

Eth address: 0x87a2575a5d4dbD5f965e3e3a3d20641BC9a5d192

egg-react-ssr's People

Contributors

allcontributors[bot] avatar anthinkingcoder avatar bs32g1038 avatar dean16930 avatar dependabot[bot] avatar frankfan avatar gogogo1024 avatar i5ting avatar jerryyux avatar johannlai avatar johniexu avatar juzhiyuan avatar kingstone3 avatar lyule avatar macbesu avatar nuintun avatar stone-jin avatar tkgkn avatar xieww avatar zhangyuang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

egg-react-ssr's Issues

Windows下运行异常问题

经测试,项目在非Windows环境(Mac、Ubuntu)运行正常。但在windows环境下,由于Windows command无法正常设置NODE_ENV,会阻塞退出,导致webpack无法正常生成dist目录文件,因此无法正常运行项目。如下图:

image

解决方法:package.json脚本新增cross-env 抹平运行环境兼容问题

serverRender 使用 mobx-react 出现 hook 错误

hi,你好,可能是我使用不当出现以下问题,麻烦指导一下,感谢~
操作系统:centos7
使用语言: typescript

package.json

  "dependencies": {
    "egg-scripts": "^2.10.0",
    "midway": "^1.0.0",
    "mobx": "^5.13.0",
    "mobx-react": "^6.1.4",
    "react": "^16.10.2",
    "react-dom": "^16.10.2",
    "react-router-dom": "^5.1.2",
    "yk-cli": "^2.6.0",
    "ykfe-utils": "^2.1.2"
  },

复现步骤:

  1. 新建一个 typescript 的 ssr 项目
  2. 加入以下内容:
  3. npm start
import { Provider, useStaticRendering } from 'mobx-react';


const serverRender = async (ctx: Context): Promise<JSX.Element> => {
  useStaticRendering(true);
  // 服务端渲染 根据ctx.path获取请求的具体组件,调用getInitialProps并渲染
  const ActiveComponent = getComponent(routes, ctx.path)()
  const Layout = ActiveComponent.Layout || defaultLayout
  const serverData = ActiveComponent.getInitialProps ? await ActiveComponent.getInitialProps(ctx) : {}
  ctx.serverData = serverData
  return  <Provider Index={{test: 111}} >
            <StaticRouter location={ctx.req.url} context={serverData}>
                <Layout layoutData={ctx}>
                    <ActiveComponent {...serverData} />
                </Layout>
            </StaticRouter>
        </Provider>
}

错误日志:

019-11-22 10:39:02,015 ERROR 22235 [-/10.13.82.114/-/130ms GET /] nodejs.Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
[0] [0] 1. You might have mismatching versions of React and the renderer (such as React DOM)
[0] [0] 2. You might be breaking the Rules of Hooks
[0] [0] 3. You might have more than one copy of React in the same app
[0] [0] See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
[0] [0] 1. You might have mismatching versions of React and the renderer (such as React DOM)
[0] [0] 2. You might be breaking the Rules of Hooks
[0] [0] 3. You might have more than one copy of React in the same app
[0] [0] See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
[0] [0]     at resolveDispatcher (webpack-internal:///./node_modules/react/cjs/react.development.js:1590:13)
[0] [0]     at Object.useContext (webpack-internal:///./node_modules/react/cjs/react.development.js:1598:20)
[0] [0]     at X (webpack-internal:///./node_modules/mobx-react/dist/mobx-react.module.js:26:5804)
[0] [0]     at processChild (/data/workSpace/test-mobx-ssr/node_modules/react-dom/cjs/react-dom-server.node.development.js:3204:14)
[0] [0]     at resolve (/data/workSpace/test-mobx-ssr/node_modules/react-dom/cjs/react-dom-server.node.development.js:3124:5)
[0] [0]     at ReactDOMServerRenderer.render (/data/workSpace/test-mobx-ssr/node_modules/react-dom/cjs/react-dom-server.node.development.js:3598:22)
[0] [0]     at ReactDOMServerRenderer.read (/data/workSpace/test-mobx-ssr/node_modules/react-dom/cjs/react-dom-server.node.development.js:3536:29)
[0] [0]     at ReactMarkupReadableStream._read (/data/workSpace/test-mobx-ssr/node_modules/react-dom/cjs/react-dom-server.node.development.js:4298:38)
[0] [0]     at ReactMarkupReadableStream.Readable.read (_stream_readable.js:470:10)
[0] [0]     at resume_ (_stream_readable.js:949:12)
[0] [0] headerSent: true
[0] [0] pid: 22235
[0] [0] hostname: VM_9_43_centos
[0] [0] 

请教一下,ssr-with-ts 在 config.ssr.js 引入一个配置文件后,csr 打开空白,ssr没问题。

可能是我的打开方式不对,复现 demo 如下,麻烦帮忙看看~ 感谢🙏

https://github.com/JohannLai/ssr-with-ts/commit/00f32189dc3e4978cbb1950d0cf450606bda9825

还增加了这个 文件https://github.com/JohannLai/ssr-with-ts/blob/master/config/config.default.js

原意是想在 page 底下写路由文件,然后引用整理放到 config.ssr.js 中,使到页面和路由不分开

CSR的一些疑问

我本地yarn csr 开启客户端渲染
查看界面元素发现这些资源文件:
20190925095846
但是看代码逻辑是不包含这些资源文件的啊:
20190925095707
不知道是不是我哪里没理解清楚? @zhangyuang

支持单个小组件的getInitialProps方法的调用

目前getInitialProps只支持路由级别的大组件才会被调用,next.js也是这样。
如果可以支持定义每一个小组件也使用getInitialProps来定义获取数据的逻辑,我们就可以精细的控制一个组件的获取数据以及渲染的过程,到底要在服务端做还是在客户端做。可以根据实际需求,来让需要优先展示的模块放在服务端渲染,次级模块在客户端渲染。将性能做到极致

新版yk-cli讨论方案

通过压缩包的方式

// 发布之前将example打成一个压缩包发布到yk-cli的npm包中
prePublish: {
tar ../example/ssr-with-js    ssr-with-js.tgz
}

init: {
// 在每次用脚手架新建项目时,解压tgz包为example
tar   ssr-with-js.tgz   ssr-with-js
}

缺陷:不能保证example为远程仓库最新的代码,一旦需要用到最新的,需要强制更新cli

将example单独仓库维护

脚手架下载代码后,缓存到本地,在每次init之前,比对远程仓库版本与本地目录版本号,如果不一致重新下载
优势:能够保证用到最新的代码
缺陷:需要写获取远程版本号以及比对版本号的逻辑,且example单独分仓库,整体性有所欠缺

利用git config + mv命令

初始化.git文件,并且通过git config,使得只clone example文件夹,再将ssr-with-js文件夹利用mv命令提取到当前执行cli命令的目录。至于.git文件夹是否删除皆可
优势:无需将example分仓库维护,实现简单
缺陷:需要考虑用户设备无git命令以及mv命令的情况
业界类似方案:create-react-app

PR is Welcome

List

目前存在以下功能需要完善,欢迎PR

  • 修复 ssr-with-loadable 进入详情页面刷新的warning // fix by this commit
  • 修复 ssr-with-mobx 修改store无法热更新的bug(暂时搁置,解法使用hooks与getInitialProps冲突)
  • yk-cli新增可以创建多个不同类型的example的功能
  • 补充yk-cli 单元测试
  • 补充ykfe-utils 单元测试

关于[email protected]相关问题

现存问题及优化建议:

  1. js-sass版本在init过程,一直在“下载模版中...”,其实中断构建过程,运行项目是可以的,有待优化;

  2. 为了提高脚手架使用体验,建议在init完成后,新增初始化项目运行命令指南,例如:
    npm install npm run start
    而且注意到js与ts版本开发环境启动命令尚不同,能否统一,或者直接在脚手架初始化时做出命令指南,就不用去阅读package.json中脚本配置。只是建议,考虑下

关于 webpack output 配置的疑问

你好,看文档的时候有一个疑惑,为什么output的 filenamechunkFilename 只使用了 [name] , 而没有用 [contenthash] / [chunkhash] 等。

打包出来的名称没有变化,会不会导致因为缓存而让用户无法得到最新的文件?如果是使用协商缓存,那好像强缓存会更好一点,hash值变了就重新获取,协商还要来回通信

所以这部分的考量是什么呢?

是否支持远程加载静态资源文件

目前示例里的前端页面代码都是放在web/page目录下的。通过config.ssr.js中的这块代码
Component: () => (require('@/page/index').default)去加载。
但是现在有一种场景就是,这些前端代码都是单独部署的独立应用,是否可以提供一种机制去加载这些独立应用编译打包后的js,css文件 @zhangyuang

Got a warnning in getInitialProps with a promise.

version

{
  ykfe-util:  "^1.1.0" 
}

I do like this:

Page.getInitialProps = () => {
  console.log('👽  Page.getInitialProps');
  return api.products.list().then(data => ({
    products: data,
  }));
};

api.products.list is like this:
return new Promise((resolve, reject) => {
return fetch(
${APISERVERHOST}/api/${this.name}.json?page=${option.page}&per_page=${option.perPage}
)
.then(response => {
if (response.status >= 400) {
reject('Bad response from server');
}
return resolve(response.json());
})
.then(data => {
return data;
})
.catch(err => {
throw new Error(err);
});
});

and got warnning:

WARNING in ./node_modules/ykfe-utils/es/renderToStream.js 34:25-60
[1] Critical dependency: the request of a dependency is an expression
[1]  @ ./node_modules/ykfe-utils/es/index.js
[1]  @ ./web/entry.js
[1] Child html-webpack-plugin for "index.html":

请问原来是 class 写的页面怎么做迁移?

例如这样:

import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Carousel } from 'antd';

export default class extends Component {
    state = {};

    render() {
        return (
            <section>
              test 
            </section>
        );
    }
}

还是说必须要使用 function 的写法?

injectScript支持类似webpack的[contenthash]

现象:
目前injectScript引入script时未带url查询参数或更新文件名,造成发布后http强缓存。
<script src='/static/js/Page.chunk.js'></script>
建议:
支持类似webpack的[contenthash]
<script src='/static/js/Page.chunk.[contenthash].js'></script>

生产环境ssr与csr两种渲染模式的切换

现状:目前支持本地同时启动两个端口分别是服务端渲染和客户端渲染。但是客户端渲染应用还得手动build bundle扔到cdn发布,无法做到生产环境无缝切换两种渲染方式。当服务器压力过大时可以由服务端渲染模式切换为客户端渲染模式。
目的:希望支持生产环境两种渲染模式的无缝切换

支持嵌套路由吗?

想在 index page 里面嵌套子的 react router,发现不工作,代码如下:

     <input />
     <Link to="/home">home</Link>
      <Link to="/about">about</Link>
      <Route path="/home">
          <HomeTab />
        </Route>
        <Route path="/about">
          <AboutTab />
        </Route>
    </div>

然后尝试修改了 config.ssr.js,让多个路径匹配到相同组件,如下:

    {
      path: '/',
      exact: true,
      Component: () => (require('@/page/index').default), // 这里使用一个function包裹为了让它延迟require
      controller: 'page',
      handler: 'index'
    },
    {
      path: '/home',
      exact: true,
      Component: () => (require('@/page/index').default), // 这里使用一个function包裹为了让它延迟require
      controller: 'page',
      handler: 'index'
    },
    {
      path: '/about',
      exact: true,
      Component: () => (require('@/page/index').default), // 这里使用一个function包裹为了让它延迟require
      controller: 'page',
      handler: 'index'
    }

这样路由就能工作了,不过这样,他们共有的父节点,在每次切换路由时会更新,比如在上面的 input 组件里输入内容,再切换子路由,input 的输入会被清空

npm run csr 报错

问题

[1] start clientRender
[1]
[1] Options:
[1]   --help         Show help                                             [boolean]
[1]   --version, -v                                       [boolean] [default: false]
[1]
[1] TypeError: Property key of ObjectProperty expected node to be of a type ["Identifier","StringLiteral","NumericLiteral"] but
instead got "BooleanLiteral"
[1]     at validate (C:\admin\github.com.project\pangu.admin\node_modules\_@babel_types@7.5.5@@babel\types\lib\definitions\utils.js:131:13)
[1]     at Object.validate (C:\admin\github.com.project\pangu.admin\node_modules\_@babel_types@7.5.5@@babel\types\lib\definitions\core.js:526:11)
[1]     at validateField (C:\admin\github.com.project\pangu.admin\node_modules\_@babel_types@7.5.5@@babel\types\lib\validators\validate.js:22:9)
[1]     at Object.validate (C:\admin\github.com.project\pangu.admin\node_modules\_@babel_types@7.5.5@@babel\types\lib\validators\validate.js:16:3)
[1]     at NodePath._replaceWith (C:\admin\github.com.project\pangu.admin\node_modules\_@babel_traverse@7.5.5@@babel\traverse\lib\path\replacement.js:194:9)
[1]     at NodePath.replaceWith (C:\admin\github.com.project\pangu.admin\node_modules\_@babel_traverse@7.5.5@@babel\traverse\lib\path\replacement.js:178:8)
[1]     at replaceAndEvaluateNode (C:\admin\github.com.project\pangu.admin\node_modules\_babel-plugin-transform-define@1.3.1@babel-plugin-transform-define\lib\index.js:117:12)
[1]     at processNode (C:\Lizhicheng\github.com.project\pangu.admin\node_modules\_babel-plugin-transform-define@1.3.1@babel-plugin-transform-define\lib\index.js:143:5)
[1]     at PluginPass.Identifier (C:\admin\github.com.project\pangu.admin\node_modules\_babel-plugin-transform-define@1.3.1@babel-plugin-transform-define\lib\index.js:23:9)
[1]     at newFn (C:\admin\github.com.project\pangu.admin\node_modules\_@babel_traverse@7.5.5@@babel\traverse\lib\visitors.js:193:21)

系统环境

node:v10.15.3
win10: v1803

版本依赖

  "dependencies": {
    "egg": "^2.23.0",
    "egg-proxy": "^1.1.0",
    "egg-scripts": "^2.11.0",
    "egg-static": "^2.2.0",
    "koa-router": "^7.4.0",
    "react": "16.9.0",
    "react-dev-utils": "^9.0.3",
    "react-dom": "16.9.0",
    "react-router-dom": "^5.0.1",
    "ykfe-utils": "^2.1.1"
 },
"devDependencies": {
    "@babel/core": "^7.5.5",
    "@babel/plugin-transform-runtime": "^7.5.5",
    "@babel/polyfill": "^7.4.4",
    "@babel/preset-env": "^7.5.5",
    "@babel/preset-react": "^7.0.0",
    "@babel/register": "^7.5.5",
    "@babel/runtime": "^7.5.5",
    "babel-loader": "8.0.6",
    "browserslist": "^4.7.0",
    "caniuse-lite": "1.0.30000989",
    "concurrently": "^4.1.2",
    "cross-env": "^5.2.1",
    "css-hot-loader": "^1.4.4",
    "css-loader": "3.2.0",
    "css-modules-require-hook": "^4.2.3",
    "egg-bin": "^4.13.1",
    "file-loader": "4.2.0",
    "less": "^3.10.3",
    "less-loader": "^5.0.0",
    "memory-fs": "^0.4.1",
    "mini-css-extract-plugin": "^0.8.0",
    "nodemon": "^1.19.2",
    "optimize-css-assets-webpack-plugin": "5.0.3",
    "postcss-flexbugs-fixes": "4.1.0",
    "postcss-loader": "3.0.0",
    "postcss-preset-env": "^6.7.0",
    "postcss-safe-parser": "4.0.1",
    "rimraf": "^3.0.0",
    "terser-webpack-plugin": "^2.0.0",
    "url-loader": "2.1.0",
    "webpack": "4.39.3",
    "webpack-bundle-analyzer": "^3.4.1",
    "webpack-cli": "^3.3.8",
    "webpack-dev-server": "3.8.0",
    "webpack-manifest-plugin": "^2.0.4",
    "webpack-merge": "^4.2.2",
    "webpack-node-externals": "^1.7.2",
    "yk-cli": "^2.2.1"
 }

项目来源(官方脚手架)

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

经过上面步骤尝试 测试 项目,发现命令行报错。后升级依赖包。问题仍然没解决。错误结果一样。

使用mobx的demo时出现些问题

当我修改mobx的数据时,页面会报错,Uncaught Error: MobX Provider: The set of provided stores has changed,需要手动刷新页面

ykcli init 报错

node 版本 v10.15.1

(node:92402) UnhandledPromiseRejectionWarning: undefined (node:92402) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:92402) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

[RFC] 新版目录结构规划

目录

$ tree .
.
├── README.md
├── bin
│   └── cli.js
├── config
│   ├── env.js
│   ├── jest
│   │   ├── cssTransform.js
│   │   └── fileTransform.js
│   ├── paths.js
│   ├── util.js
│   ├── webpack.config.base.js
│   ├── webpack.config.client.js
│   └── webpack.config.server.js
├── doc
├── example
│   ├── app
│   │   ├── controller
│   │   │   └── page.js
│   │   └── router.js
│   ├── app.js
│   ├── config
│   │   ├── config.daily.js
│   │   ├── config.default.js
│   │   ├── config.local.js
│   │   ├── config.prod.js
│   │   ├── plugin.js
│   │   └── plugin.local.js
│   ├── dist
│   │   ├── Page.server.js
│   │   └── static
│   │       └── css
│   │           └── Page.css
│   ├── package.json
│   └── web
│       ├── assets
│       │   └── common.less
│       ├── entry.js
│       ├── index.html
│       ├── layout
│       │   ├── index.js
│       │   └── index.less
│       └── page
│           ├── index
│           │   ├── index.js
│           │   └── index.less
│           └── news
│               ├── index.js
│               └── index.less
├── package.json
└── src
    ├── index.js
    └── tpl
        ├── js
        │   ├── entry.js
        │   └── package.json
        └── ts
            ├── entry.js
            └── package.json

21 directories, 37 files

说明

  • bin是脚手架命令
  • example是示例,复用config目录下的webpack配置
  • doc是文档,暂定采用vuepress
  • src是脚手架相关代码,主要是src/tpl下,会提供js和ts二个版本的模板文件

webpack treeskaing 相关bug

image
这几天为了兼容serverless场景对renderToStream方法进行了一些改造,经过测试发现,renderToStream.js期望的被调用环境是在服务端,但是在打包客户端资源时,虽然我们已经启用了tree shaking, 并且打包的结果也并没有renderToStream方法相关代码,但是打包分析的时候以及本地开发模式下,webpack仍然会去分析使用未import的代码,故导致报错

目前想的解决方式是只将客户端或者双端能够通用的文件在ykfe-utils中export出来,而renderToStream不export,改为

const renderToStream = require('ykfe-utils/lib/renderToStream')

上面这种方式来引入具体的文件

如果你有更好的解决方式,欢迎评论

访问 8000 端口 csr 模式,改动 web/layout 文件夹名称会报错,找不到 /node_modules/yk-cli/dist/Layout.server.js

报错如下:

(node:24001) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open '/data/workSpace/wecard-homepage-ssr/node_modules/yk-cli/dist/Layout.server.js'
[1]     at Object.openSync (fs.js:443:3)
[1]     at Object.readFileSync (fs.js:343:35)
[1]     at Object.Module._extensions..js (internal/modules/cjs/loader.js:788:20)
[1]     at Module.load (internal/modules/cjs/loader.js:653:32)
[1]     at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
[1]     at Function.Module._load (internal/modules/cjs/loader.js:585:3)
[1]     at Module.require (internal/modules/cjs/loader.js:692:17)
[1]     at require (internal/modules/cjs/helpers.js:25:18)
[1]     at renderLayout (/data/workSpace/wecard-homepage-ssr/node_modules/yk-cli/lib/renderLayout.js:49:18)
[1] (node:24001) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 6)

新增config.ssr.js配置文件

目前配置项前后端都用到了config.default.js,这就会导致前端代码中会将config.default.js的内容打包进去,有可能会包含一些隐私配置,或者会将后端代码打进去。建议将ssr的相关通用配置统一放到单独的一个配置文件进行维护

打包结果包含无用依赖

通过npm run analyze的分析我们可以看到,vendor.chunk.js中打包进了multistream等stream相关的库的依赖。这些库在client端是没有用到的,很明显我们使用import {} from 'ykfe-utils'时,没有做tree shaking的操作。以及发现react react-dom react-router等依赖出现了多次,说明没有做重复依赖的去重操作。

image

error

Page Controller renderToStream Error TypeError: config.injectScript is not a function

ssr中使用hooks报错

image
在ssr应用中使用react hooks会报上图错误。
查了一下原因是因为出现了多个实例的react-dom。发现将renderToStream方法移动到项目中就没事了。
google了一下类似的问题挺多的。#14898 #13972

add koa/express support

  • add packages/middleware,package name:ssr-middleware
  • add koa/koa middlwares

example

const conf = require('./config/config.ssr')
const ssr = require('egg-react-ssr').koa(conf);

const Koa = require('koa');
const app = new Koa();

//  mount routes from config
app.use(ssr)

// ctx.ssrRender()
app.use(async ctx => {
  ctx.ssrRender(...);
});

app.listen(3000);

接下来的规划

现状

目前本应用能够通过脚手架新建比较稳定的运行,目前该配置可以解决大部分项目的基本需求,但为了应付灵活多变的需求,我们还需要不断的完善本应用所支持的场景,目前初步列出以下待办事项

数据流

提供结合多种流行的数据管理方式的example

  • 结合dva的example
  • 结合mobx的example

组件到达时间统计

服务端渲染应用的首屏时间是一个很难通过浏览器提供的API来直观看出的数据,我们必须在应用中手动触发时间统计埋点,如何让开发者做到无感知的给应用组件中添加时间统计的埋点也是一项有价值的任务,以上一个页面的unload时间为基准,即performance.timing.navigationStart的时间,目前只支持页面级别的组件信息统计

  • 当前应用在服务端的一些性能监控数据
  • 显示该组件在服务端被渲染完毕的时间
  • 显示该组件在浏览器中出现的时间

结合loadable

将bundle能够根据路由来分割能够进一步的加快首屏domContentLoaded的时间,不过对首屏时间的影响不大,但loadable结合SSR应用定义的getInitialProps方法来作为数据获取的方法,并没有那么简单,需要修改react-loadable源码

  • 结合loadable进行路由分割(example已发布,但仍有完善空间

更细粒度的组件获取

我们目前的getInitalProps方法只支持在路由级别的"大"组件中调用生效,如果能够支持在每一个更小粒度的组件也是通过该方法去获取数据,那我们就可以更精细的控制一个组件的渲染逻辑,究竟要放在服务端还是客户端。将首屏性能做到极致

  • getInitalProps方法支持更细粒度的组件

多页面应用

目前给的example是单页面应用,如果要做多页面应用实质上是多个entry分别打包为单独的bundle,但如何做到配置更加灵活,更加轻便是个不小的难题,以及需要讨论既然ssr已经解决了seo的问题,就解决了多页面应用的一个优势。ssr + 路由分割理论上已经足够好用了,而多页面应用的缺点却很多,所以得考虑当前应用究竟有没有必要做成多页面应用的必要。

  • 讨论是否需要支持多页面应用

添加测试

  • 单元测试
  • ci
  • e2e测试

ts化

  • ykfe-utils 使用ts重写

feat: 使用jsx 来代替模版引擎

无论是使用模版引擎还是用目前的锚点+replace方式来拼接成完整的html文档,实现起来都很别扭。需要自己来定义如何组装成完成的页面。
如果使用react jsx来做外层模版,则直接renderToStream(<layout><page /></layout>)就可以直接得到完整的页面文档了。而且jsx的写法比起模版引擎的写法无疑要舒服很多。
并且这种做法的好处是可以用使用者自由决定页面应该加入哪些额外的标签,而不用通过约定配置项来决定。之前的做法是通过约定config,再将config的值插入到锚点中,这样的做法不够灵活

目前

  baseHtml.replace('<!-- Start Server Render Head -->', config.head ? config.head.join('') : '')

  const docArr = baseHtml.split('<!-- Start Server Render Document -->')

  const beginDoc = docArr[0].trim().replace('\n', '')
  const beginDocStream = stringToStream(beginDoc.replace('<!-- Start Injecting Style Flows Up and Down -->', `${config.injectCss(chunkName).join('')}`))
  const initialData = !isCsr ? `<script>window.__USE_SSR__=true;window.__USESSR__=true;window.__INITIAL_DATA__ =${serialize(ctx.serverData || {})};</script>` : ''
  const injectScript = config.injectScript ? config.injectScript(chunkName).join('') : config.injectSrcipt(chunkName).join('')

  const endDoc = docArr[1].trim().replace('\n', '')
  const endDocStream = stringToStream(endDoc.replace('<!-- Start InitialData Script  -->', initialData).replace('<!-- Start Client Script -->', injectScript))
  const streamArr = isCsr ? [beginDocStream, endDocStream] : [beginDocStream, stream, endDocStream]
  return multiStream(streamArr)

改造后

if (__isBrowser__) {
    return <div id='app' ><div className='normal'><h1 className='title'><Link to='/'>Egg + React + SSR</Link><div className='author'>by ykfe</div></h1>{props.children}</div></div>
  } else {
    const { injectCss, injectScript, chunkName } = 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(chunkName).map(item => <link rel='stylesheet' href={item} />)
          }
        </head>
        <body>
          <div id='app' ><div className='normal'><h1 className='title'><Link to='/'>Egg + React + SSR</Link><div className='author'>by ykfe</div></h1>{props.children}</div></div>
          <div dangerouslySetInnerHTML={{
            __html: injectScript && injectScript(chunkName).join('')
          }} />
        </body>
      </html>
    )
  }

目前唯一的点是,如何使用react jsx组件来作为csr html-webpack-plugin的模版。目前的思路是在本地开发时,使用renderToString api来将组件编译成html字符串并且写入到本地index.html文件,给html-webpack-plugin用

egg-react-ssr/example/ssr-with-antd/例子中build:client和build:server写入css文件造成覆盖

现象:
egg-react-ssr/example/ssr-with-antd/项目中,

build:client
popo_2019-08-28  20-50-56
build:server
f765543acd434c81a35b33fb1416b0ec_09b651fea17fe6a03afad39a41422bfa

3.chunk.css被覆盖了

初步分析:

// package.json
{
    "build": "rimraf dist && ykcli build && npm run build:server",
}

同时调用了webpack.config.base.js先后写入css文件,造成覆盖

//  build/webpack.config.base.js    
new MiniCssExtractPlugin({
      filename: 'static/css/[name].css',
      chunkFilename: 'static/css/[name].chunk.css'
})

yk-cli v1.0.15重构说明

with this commit feat: 重构yk-cli

工具

这里我们使用yargs来作为脚手架的底层服务

代码风格

我们的应用的代码风格无论是js或是ts皆采用standardjs规范

跨平台

为了使我们的脚手架能够在windows平台和*nix平台上都能够稳定使用,这里我们使用了shelljs,该脚本使用原生的fs模块来实现一些跨平台命令例如cp mv rm等命令

callback promise化

为了防止代码中出现大量的if else 以及callback语句造成可读性低下,在这里我们将callback都用promise包一层。这里可以使用Node原生的util.promisify方法或者自己手动封装

去除css预处理器选项

这里我们默认只提供一种css预处理器使用,因为如果要提供多种预处理器会涉及到比较大量复杂的文件处理操作,一个是容易影响稳定性,另一个是容易导致执行效率不高且代码可读性差

todoList

  • ts版本的example,ts版本还在完善中,等成熟后我们会在脚手架中加入ts的选项
  • 状态管理,我们将会提供使用状态管理方案例如dva的example

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.