Skip to content

feat: combile SSR in prerender to prevent shake problem #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 14, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -30,11 +30,9 @@ abbrlink: s31w9gd1

* 建站理念: `文件即站点` (Files as a Site)。
* 开箱即用: 通过指定目录或文档, 一键生成文档、博客站点, 无需关心站点环境配置信息。
* 性能: 通过`预渲染``懒加载`大幅提升站点加载速度
* 流畅的用户体验: 内置 SSR 首屏直出方案(基于 gp-pages 服务),以提升用户体验
* 基于 mdx: 支持在 markdown 中`书写 React 组件`、数学公式等。
* 搜索引擎优化:
* 支持 SEO, 让文档更易被搜索。
* 支持短链,以让链接持久化。
* 搜索引擎优化: 支持 SEO, 让文档更易被搜索。
* 个性化: 支持[自定义主题](https://muyunyun.cn/create-react-doc/9f41fc98)
* 工作流: 集成 Github action, 支持自动化打包、发布站点。

@@ -49,12 +47,10 @@ create-react-doc 提供了官方默认主题 [crd-seed](https://github.com/MuYun
* 文档支持内嵌 codepen、codesandbox。
* GitHub 联动。

使用该主题搭建的项目有:
[笔者博客](http://muyunyun.cn/blog) 就是使用默认主题搭建的。

* [blog](https://github.com/MuYunyun/blog), [站点](http://muyunyun.cn/blog)
* ![](http://with.muyunyun.cn/ec330b8ac2175c828be41f446f9f9619.jpg)
* ![](http://with.muyunyun.cn/2e7440e4256debda2d73a4e6392c7146.jpg-300)
* [diana](https://github.com/MuYunyun/diana)
![](http://with.muyunyun.cn/ec330b8ac2175c828be41f446f9f9619.jpg)
![](http://with.muyunyun.cn/2e7440e4256debda2d73a4e6392c7146.jpg-300)

如果您想定制化或者分享个人主题, 可以参考[自定义主题](https://muyunyun.cn/create-react-doc/9f41fc98)

9 changes: 9 additions & 0 deletions docs/更新日志.md
Original file line number Diff line number Diff line change
@@ -6,6 +6,15 @@ abbrlink: 179nqpxt

`create-react-doc` 严格遵循 [Semantic Versioning 2.0.0](http://semver.org/lang/zh-CN/) 语义化版本规范。

### 1.8.0

`2022-01-15`

- **Feature**

- 🚀 支持 SSR 首屏直出方案(基于 gp-pages 服务)以避免预渲染带来的二次刷新产生页面抖动的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/103)
- 🚀 新增 crd-client-utils 包以收录公用方法。

### 1.7.0

`2022-01-02`
10 changes: 10 additions & 0 deletions packages/crd-client-utils/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# .npmrc

registry=https://registry.npmjs.org/

# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
3 changes: 3 additions & 0 deletions packages/crd-client-utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### crd-client-utils

[create-react-doc](https://github.com/MuYunyun/create-react-doc) 的工具包。
16 changes: 16 additions & 0 deletions packages/crd-client-utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useLayoutEffect, useEffect } from 'react'

const ifDev = env === 'dev'
const ifProd = env === 'prod'
const ifPrerender = window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.prerender

const useEnhancedEffect = typeof window !== 'undefined'
? useLayoutEffect
: useEffect

export {
ifDev,
ifProd,
ifPrerender,
useEnhancedEffect
}
19 changes: 19 additions & 0 deletions packages/crd-client-utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "crd-client-utils",
"version": "1.5.0",
"description": "Utils with create react doc",
"main": "index.js",
"dependencies": {},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/utils"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199"
}
23 changes: 19 additions & 4 deletions packages/crd-scripts/src/web/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import '@babel/polyfill'
import { ifDev, ifPrerender } from 'crd-client-utils'
import RouterRoot from './Router'

ReactDOM.render(
<RouterRoot />,
document.getElementById('root'),
)
if (ifDev) {
// dev render
document.getElementById('root').innerHTML = ReactDOMServer.renderToString(<RouterRoot />)
ReactDOM.hydrate(
<RouterRoot />,
document.getElementById('root'),
)
} else if (ifPrerender) {
// prerender
document.getElementById('root').innerHTML = ReactDOMServer.renderToString(<RouterRoot />)
} else {
// prod render:
ReactDOM.hydrate(
<RouterRoot />,
document.getElementById('root'),
)
}
35 changes: 18 additions & 17 deletions packages/crd-seed/component/Affix/affix.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useLayoutEffect, useRef } from 'react'
import { useState, useEffect, useRef } from 'react'
import { throttle } from './utils'

const Affix = ({
@@ -22,14 +22,29 @@ const Affix = ({
// 是否是绝对布局模式
const fixedRef = useRef(false)
const [fixed, setFixed] = useState(fixedRef.current)
useLayoutEffect(() => {

useEffect(() => {
widthRef.current = width
}, [width])

useEffect(() => {
// 在子节点移开父节点后保持原来占位
setWrapperDimension()
}, [fixed, width])

useEffect(() => {
if (target) scrollElm = target()
scrollElm.addEventListener('scroll', scroll)
return () => {
if (target) scrollElm = target()
scrollElm.removeEventListener('scroll', scroll)
}
}, [offsetTop, offsetBottom])

const validValue = (value) => {
return typeof value === 'number'
}
const setWrapperDimension = () => {
// eslint-disable-next-line no-shadow
const { width: wrapperRefWidth, height: wrapperRefHeight } = wrapperRef.current
? wrapperRef.current.getBoundingClientRect()
: {}
@@ -98,20 +113,6 @@ const Affix = ({

const scroll = throttle(handleScroll, 20)

useLayoutEffect(() => {
// 在子节点移开父节点后保持原来占位
setWrapperDimension()
}, [fixed, width])

useLayoutEffect(() => {
if (target) scrollElm = target()
scrollElm.addEventListener('scroll', scroll)
return () => {
if (target) scrollElm = target()
scrollElm.removeEventListener('scroll', scroll)
}
}, [offsetTop, offsetBottom])

return (
<div ref={placeholderRef} style={style} className={className}>
<div
7 changes: 4 additions & 3 deletions packages/crd-seed/component/Menu/SubMenu.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useRef, useLayoutEffect, Fragment, Children, cloneElement } from 'react'
import { useState, useRef, Fragment, Children, cloneElement } from 'react'
import cx from 'classnames'
import { useEnhancedEffect } from 'crd-client-utils'
import Transition from './transition'
import { getMenuStyle } from './util'
import { useMenuContext } from './context'
@@ -41,8 +42,8 @@ function SubMenu({
const [getParentMenuHover, setParentMenuHover] = useCurrent(false)

const gapDistance = 4
/** 使用 useLayoutEffect 可以避免 useEffect 产生可见的位移痕迹 */
useLayoutEffect(() => {

useEnhancedEffect(() => {
if (popupSubMenu.current && curSubmenu.current) {
popupSubMenu.current.style.left = `${curSubmenu.current.getBoundingClientRect().right +
gapDistance}px`
5 changes: 2 additions & 3 deletions packages/crd-seed/component/Menu/transition.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useLayoutEffect, useRef, useCallback } from 'react'
import { useEffect, useRef, useCallback } from 'react'
import styles from './style/index.less'

const ANIMATION_DURATION = 200
@@ -59,7 +59,6 @@ export default function Transition({
}, [afterLeave])

const triggerChange = useCallback(
// eslint-disable-next-line no-shadow
(isShow) => {
clearTimeout(timer.current.enterTimer)
clearTimeout(timer.current.leaveTimer)
@@ -74,7 +73,7 @@ export default function Transition({
[beforeLeave, enter, leave]
)

useLayoutEffect(() => {
useEffect(() => {
if (!mounted.current) {
mounted.current = true
beforeEnter()
4 changes: 3 additions & 1 deletion packages/crd-seed/layout/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as React from 'react'
import { Switch, Link, Route, Redirect } from 'react-router-dom'
import cx from 'classnames'
import { ifDev, ifProd, ifPrerender } from 'crd-client-utils'
import Menu from '../component/Menu'
import Icon from '../component/Icon'
import Affix from '../component/Affix'
import Header from '../component/Header'
import Footer from '../component/Footer'
import languageMap from '../language'
import { isMobile, ifAddPrefix, ifProd, ifPrerender } from '../utils'
import { isMobile, ifAddPrefix } from '../utils'
import { getOpenSubMenuKeys } from './utils'
import logo from '../crd.logo.svg'
import styles from './index.less'
@@ -261,6 +262,7 @@ function BasicLayout({
</div>
)
}

return (
<div className={styles.wrapper}>
<Header
1 change: 1 addition & 0 deletions packages/crd-seed/layout/index.less
Original file line number Diff line number Diff line change
@@ -89,6 +89,7 @@
.affixWrapper {
border-right: 1px solid rgb(233, 233, 233);
transition: width .2s linear;
width: 0px;
}
}

6 changes: 2 additions & 4 deletions packages/crd-seed/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import isClient from 'diana/lib/isClient'
import { ifProd, ifPrerender } from 'crd-client-utils'

/** judge if is in mobile */
const isMobile = isClient() ? 'ontouchend' in window : false

// eslint-disable-next-line no-undef
const ifProd = env === 'prod'
const ifPrerender = window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.prerender
// decide to if add prefix for path, eg: '/' or '/${repo}'
const ifAddPrefix = ifProd && !ifPrerender

export { isMobile, ifPrerender, ifAddPrefix, ifProd }
export { isMobile, ifAddPrefix }
12 changes: 1 addition & 11 deletions packages/crd-theme/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
// import Loading from './component/Loading'
import Markdown from './routes/Pages'
// import { ifProd, ifPrerender } from './utils'
import Markdown from './markdown'
import './index.less'

export default function (Lazyload, props) {
/** todo: how to avoid the extra logic */
// if (ifProd && !ifPrerender) return null
// const LoadableComponent = Lazyload({
// component: () => import('./routes/Pages'),
// LoadingComponent: Loading,
// })

// routing load component
if (props.routeData && props.routeData.length > 0) {
props.routeData.map((item) => {
// item.component = LoadableComponent
item.component = Markdown
return item
})
38 changes: 25 additions & 13 deletions packages/crd-theme/markdown/index.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import { MDXProvider } from '@mdx-js/react'
import { Helmet } from 'react-helmet'
import CodeBlock from './codeBlock'
import Link from './Link'
import Loading from '../component/Loading'
import styles from './style/index.less'

const { useState, useEffect, useRef } = React
@@ -17,16 +16,27 @@ const components = {
function Markdown(markdownProps) {
const { props } = markdownProps
const { relative, name } = props
const [MarkdownCP, setMarkdownCP] = useState(null)
const markdownWrapperRef = useRef(null)

const renderMarkdown = () => {
const getRmFirstSlashMarkdownName = () => {
const relativeMd = relative
if (!relativeMd) return null
return relative.slice(1, relative.length - 3)
}

const getInitMarkdownCP = () => {
const markdownName = getRmFirstSlashMarkdownName()
if (!markdownName) return
return () => require(`__project_root__/${markdownName}.md`).default
}

const [MarkdownCP, setMarkdownCP] = useState(getInitMarkdownCP())
const markdownWrapperRef = useRef(null)

const rmFirstSlash = relative.slice(1, relative.length - 3)
const renderMarkdown = () => {
const markdownName = getRmFirstSlashMarkdownName()
if (!markdownName) return
// it must be writen with / & .md in dynamic import
import(`__project_root__/${rmFirstSlash}.md`).then((data) => {
import(`__project_root__/${markdownName}.md`).then((data) => {
// data.default is a function, so we should write () => data.default in setState here.
setMarkdownCP(() => (data.default || data))
})
@@ -46,18 +56,20 @@ function Markdown(markdownProps) {
<title>{getName()}</title>
<meta name={getName()} content={getName()} />
</Helmet>
<div className={cx('markdown', styles.markdown, styles.markdownwrapper)} ref={markdownWrapperRef}>
{
MarkdownCP
?
{
MarkdownCP
? <div
className={cx('markdown', styles.markdown, styles.markdownwrapper)}
ref={markdownWrapperRef}
>
<MDXProvider
components={components}
>
<MarkdownCP />
</MDXProvider>
: <Loading />
}
</div>
</div>
: null
}
</>
)
}
3 changes: 0 additions & 3 deletions packages/crd-theme/routes/Pages/index.js

This file was deleted.

5 changes: 0 additions & 5 deletions packages/crd-theme/utils/index.js

This file was deleted.