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)
$ 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
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>
}
}
}
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.
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
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