Skip to content

Instantly share code, notes, and snippets.

@chadfurman
Last active May 1, 2017 09:28
Show Gist options
  • Save chadfurman/4cad2d84d1b33a0bf8d4d22f347fba8e to your computer and use it in GitHub Desktop.
Save chadfurman/4cad2d84d1b33a0bf8d4d22f347fba8e to your computer and use it in GitHub Desktop.
5-1.md

Separating the Client and Server code

  • server.js and app.js
  • app.js is run both on the client and the server
  • we need to separate browser concerns from universal concerns
  • We extract our Client.js into app.js (everything but the call to render)
  • BrowserRouter also gets extracted from the App.js code (stays in client)
  • Brian is using a templating engine on the server

Implementing Server-Side Rendering

  • We need babel to run on the server
  • Node doesn't speak JSX, import / all of es6
  • we need the transform-es215-modules-commonjs plugin on the server in babelrc
require('babel-register')
// note that this server doesn't use babel

const express = require('express')
const React  require('react')
const ReactDOMServer = require('react-dom/server')
const ReactRouter = require('react-router')
const ServerRouter = ReactRouter.serverRouter
const _ = require('lodash')
const fs = require('fs')
const POST = 5050
const baseTemplate = fs.readFileSync('./index.html')
const template = _.template(baseTemplate)
const App = require('./js/App').default

const server = express()

server.use('/public', express.static('/public'))

server.use((req, res) => {
  const context = ReactRouter.createServerRenderContext()
  let body = ReactDOMServer.renderToString(
    React.createElement(ServerRouter, {
      location, req.url, 
      context; context
      React.createElement(App)
    },
  )
  
  res.write(template({body; body})
  res.end()
})

console.log(('listening on port', PORT)
server.listen(PORT)

Running the Node Server

$ NODE_ENv=server node server.js
  • note that importing your styles in react will break. You can just put the styles in your html
  • With isomorphic javascript, we can browse the whole app w/o javascript
  • Any DOM API calls need to be used very carefully or we blow up our node server
  • If your server render and client render are different (i.e. Math.random()) then React will simply re-render on the frontend (bad)

Code Splitting with Webpack

  • When we load the page, we get all the code for the other pages tooroute
  • Webpack can "cut" along dependency boundaries
  • System.import must use a string, not a var, otherwise webpack can't do code splitting.
  • splitting code into multiple bundles -- have to specify publicPath inside output webpack config object pointing to bundles

Async Routing

  • asyncRoute.js -- show loading state until component ready, then show component (higher order component)
import React from 'react'
const { object } = React.PropTypes

const AsyncRoute = React.createClass({
  propTypes: {
    props: object,
    loadingPromise: object
  },
  getInitialState () {
    return { lloaded: false }
  },
  componentDidMount () {
    this.props.loadingPromise.then((module) => { 
    this.component = module
    this.setState({loaded: true})
  },
  render () {
    if (this.state.loaded) {
      return <this.component {...this.props.props} />
    } else {
      return <h1>loading...</h1>
    }
  }
}
  • App.js
import AsyncRoute from './AsyncRoute' // replace import Landing call

if (global) {
  global.System = { import () {} } // don't fail when this function is called
}

<Match exactly pattern='/' 
  component={(props) => <Asyncroute props={props} loadingPromise={System.import('./Landing')} />}

Creating a Landing Bundle

  • es6 and commonjs module different -- don't forget modules.default
  • create your node script to set the env
  • nodemon kills/restarts server for us

Creating Search and Details Bundles

<Match pattern='/search'
  component={(props) => <AsyncRoute props={Object.assign({shows; preload.shows}, props)}
  loadingPromise={System.import('./Search')}
  />
<Match pattern='/details/:id'
  component={(props) => {
    const shows = preload.shows.filter((show) => props.params.id == show.imdbID)
    return <AsyncRoute props={object.assign({show: show[0]}, props)) loadingPromise={system.import('./Details')} />
  • Won't need browser router anymore
  • System.import must use a string, not a var, otherwise webpack can't do code splitting.

Building for Production

NODE_ENV=production webpack -p
  • Note that bundle.js is still very big. We need to uglify
/* don't forget "uglify"*/
// inside webpack config
const webpack = require('webpack')

plugins: [
  new webpack.optimize.UglifyJsPlugin({compress: {warnings: true}})
]
  • also, in webpack config, disable devtool
  • don't ship more than 300k in a bundle

Preact

  • Drop-in compatible library replacement for react that's 3k
// webpack config
// inside of resolves, next to extensions
alias: {
  react: 'preact-compat',
  'react-dom': 'preact-compat'
}
  
// also need to run preact through babel
include: [ path.resolve('node_modules/preact-compat/src')
  • not using it at netflix yet
  • faster than react
  • doesn't support mixins
  • checkout preact docs to get serverside rendering working

Final QA: Starter Kits, Organizing Code, Best Practices

  • npm install --global create-react-app
  • npm run eject
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment