zhangyuang / egg-react-ssr

最小而美的Egg + React + SSR 服务端渲染应用骨架,同时支持JS和TS
http://doc.ssr-fc.com/
MIT License
1.91k stars 212 forks source link

使用这个框架,如果想实现相同 url,pc mobile 使用两套不同页面,最佳实践是怎样的 #123

Closed YaoKaiLun closed 4 years ago

zhangyuang commented 4 years ago

module.exports = {
  type: 'ssr', // 指定运行类型可设置为csr切换为客户端渲染
  routes: [
    {
      path: '/',
      exact: true,
      Component: () => (require('@/page/index').default), // 这里使用一个function包裹为了让它延迟require
      controller: 'page',
      handler: 'index'
    },
    {
      path: '/news/:id',
      exact: true,
      Component: () => (require('@/page/news').default),
      controller: 'page',
      handler: 'index'
    }
  ],
  mobileRoutes: [
    {
      path: '/',
      exact: true,
      Component: () => (require('@/page/mobile-index').default), // 这里使用一个function包裹为了让它延迟require
      controller: 'page',
      handler: 'index'
    }
  ],
  baseDir: resolvePath('../'),
  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`)
}
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, StaticRouter, Route, Switch } from 'react-router-dom'
import defaultLayout from '@/layout'
import { getWrappedComponent, getComponent } from 'ykfe-utils'
import { routes as Routes, mobileRoutes } from '../config/config.ssr'

const isMobile = ua => /android|iphone/i.test(ua)
const clientRender = async () => {
  const _Routes = isMobile(navigator.userAgent) ? mobileRoutes : Routes
  // 客户端渲染||hydrate
  ReactDOM[window.__USE_SSR__ ? 'hydrate' : 'render'](
    <BrowserRouter>
      <Switch>
        {
        // 使用高阶组件getWrappedComponent使得csr首次进入页面以及csr/ssr切换路由时调用getInitialProps
        _Routes.map(({ path, exact, Component }) => {
            const ActiveComponent = Component()
            const Layout = ActiveComponent.Layout || defaultLayout
            const WrappedComponent = getWrappedComponent(ActiveComponent)
            return <Route exact={exact} key={path} path={path} render={() => <Layout><WrappedComponent /></Layout>} />
          })
        }
      </Switch>
    </BrowserRouter>
    , document.getElementById('app'))

  if (process.env.NODE_ENV === 'development' && module.hot) {
    module.hot.accept()
  }
}

const serverRender = async (ctx) => {
  const _Routes = isMobile(ctx.header['user-agent']) ? mobileRoutes : Routes
  console.log( isMobile(ctx.header['user-agent']))
  // 服务端渲染 根据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 <StaticRouter location={ctx.req.url} context={serverData}>
    <Layout layoutData={ctx}>
      <ActiveComponent {...serverData} />
    </Layout>
  </StaticRouter>
}

export default __isBrowser__ ? clientRender() : serverRender
YaoKaiLun commented 4 years ago

感谢。不过这样要维护两套路由,有点麻烦,我想到一种,可以加个 mobileComponent

{
      path: '/',
      exact: true,
      Component: () => (require('@/page/pc/index').default), 
      mobileComponent: () => (require('@/page/mobile/index').default), 
      controller: 'page',
      handler: 'index'
    },
zhangyuang commented 4 years ago

都行,这块的配置控制代码不涉及底层库的改动,你觉得怎么方便怎么写

YaoKaiLun commented 4 years ago

如何分开打包 PC 和 mobile 文件?

chenqincheng commented 4 years ago

@YaoKaiLun 你是想实现 SEO 中的“代码适配”吧(https://ziyuan.baidu.com/college/articleinfo?id=446) 通过设置 Vary,根据UA不同,同一URL返回不同的HTML内容?