Skip to content
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

SSR functionality #258

Open
wants to merge 2 commits into
base: ssr
Choose a base branch
from
Open

SSR functionality #258

wants to merge 2 commits into from

Conversation

legendar
Copy link
Contributor

@legendar legendar commented Nov 17, 2020

commands

yarn start now starts webpack-dev-server + ssr server
yarn start:web legacy mode to run only webpack-dev-server (useful for slow machines)
yarn ssr use this to run SSR node server on the production
yarn ssr:dev dev mode for ssr server, allow to test SSR without webpack

misc notes

  • WriteFilePlugin was removed (webpack-dev-server supports this functionality out of the box: Support Webpack 4.29.0 breaking change gajus/write-file-webpack-plugin#74 (comment))
  • CleanWebpackPlugin currently is disabled for SSR builds due to incompatible issues (you need to periodically manually clear dist folder)
  • CSS Modules are now enabled for all modes and can't be disabled
  • @ds-frontend/resource still has no SSR support (need separate PR)
  • smoothscroll-polyfill was removed as outdated
  • react-helmet is now in dependencies and is supported in SSR out of the box
  • i18n is partially support (need separate PR for i18n)
  • you can use yarn start:web to disable SSR in development mode on slow computers. but this is not recommended. you need to check SSR build on pre-commit hook in this case
  • there is a hidden feature for QA that allow to easy testing SSR localStorage.setItem('_SSR_DEBUG_', true) (do not forgot to remove it localStorage.removeItem('_SSR_DEBUG_'))

code restrictions

  • no globals (window, navigator, etc). use context where possible, or check the environment (e.g. process.env.SSR ? (mock) : window.navigator)
  • all data that is necessary for SSR should be stored in redux.
  • @ds-frontend resource (not yet) and apollo-client has SSR support, but for custom queries you need additional code
  • use componentDidMount in browser but constructor for SSR env (see examples below)
  • for functional container you can use useSSREffect hook

TODO before merge

TODO non-critical

  • make separate App.js to avoid a lot of extra conditions in code
  • add example for pages with API requests (via resource or API)
  • use template processor for index.html on server-side
  • add "disable js" mode to simplification SSR testing (disable rendering in index.js)

CI notes

  • yarn build - no changes
  • yarn ssr - run nodejs server that build and serves static.
  • all urls that was previously redirected to dist folder should be proxied to the node.js server (usually all site urls except django static and client/assets)
  • it's strongly recommended to add yarn build to CI tests (to prevent developers use browser-specific features in shared code)

examples

class CategoriesListContainer extends Component {
  constructor(props) {
    super(props)

    if(process.env.SSR) {
      // this.props.categories - is not-prefetched resource
      // fetch data on the server
      this.props.categories.data || this.fetchData()
    }
  }

  componentDidMount() {
    // fetch data on the client side
    // componentDidMount has no effect on server-side
    this.fetchData()
  }

  fetchData() {
    const { staticUrl, acceptLanguage } = this.props

    this.props.categories.fetch()
  }

  render() {
    if(!this.props.categories.data) {
      return null
    }

    return (
      <CategoriesListView items={this.props.categories.data} />
    )
  }
}

the same thing, but using hooks:

function CategoriesListContainer({ categories }) {
  useSSREffect(categories.fetch)

  if(!categories.data) {
    return null
  }

  return (
    <CategoriesListView items={categories.data} />
  )
}

in case if you need custom request you can use resource custom requests or use api module. you need to set data to redux store manually in last case. use own requests via fetch/xhr or other modules doesn't supported and need a lot of custom code.

function CategoriesListContainer({ setData }) {
  useSSREffect(function() {
    api.request({ endpoint: 'categories' }).then(categories.setData)
  })

  if(!categories.data) {
    return null
  }

  return (
    <CategoriesListView items={categories.data} />
  )
}

}

handleResponseCallback(response) {
if(response.status === 401) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch(response.status) {
case 401:
case 204:
case 500:
...
}

redirect: routerContext.url,
// TODO templating
content: contents
.split('<div id="root"></div>').join(`<div id="root">${html}</div>`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.split('<div id="root"></div>').join(<div id="root" data-initial-state="${state}">${html}</div>) ?`

.split('</head>').join(`${helmet.title.toString()}${helmet.meta.toString()}${helmet.link.toString()}<style>${css}</style></head>`)
.split('<html ').join(`<html ${helmet.htmlAttributes.toString()} `)
.split('<body ').join(`<body ${helmet.bodyAttributes.toString()} `)
.split('<div id="root"').join(`<div id="root" data-initial-state="${state}"`),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can pass this data attribute in first iteration

# ssr bundle and server will be available in dist/ssr
# to disable just remove the value, e.g `SSR=`
# WARNING! This will not work for non-SPA applications.
SSR=true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need to make SPA=true and SSR=?

return next()
}

let accept = String(get(req, 'headers.accept', ''))
Copy link
Contributor

@NikitaMazur NikitaMazur Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do you use this variable?


let accept = String(get(req, 'headers.accept', ''))

let isWebpackAsset = getFilenameFromUrl(
Copy link
Contributor

@NikitaMazur NikitaMazur Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const

req.url
)

let isProxyMatched = proxyMatch(
Copy link
Contributor

@NikitaMazur NikitaMazur Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const

let lastIndex = 0
let alreadyEmited = new Set()

export default function usuSSREffect(func) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usu -> use

import App from './App'


render(
<App store={store} history={history} />,
let needHydrate = Boolean(document.getElementById('root').dataset.initialState)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const

@legendar legendar linked an issue Dec 23, 2020 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

SSR support
2 participants