Slide 1

Slide 1 text

Universal JavaScript

Slide 2

Slide 2 text

@Jack_Franklin, Pusher

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Why?

Slide 9

Slide 9 text

Source: GDS Blog, 2013

Slide 10

Slide 10 text

"Surprisingly, the proportion of people that have explicitly disabled JavaScript or use a browser that doesn't support JavaScript, only makes up a small slice of people that don't run JavaScript."

Slide 11

Slide 11 text

"Progressive enhancement has never been about users who've turned JavaScript off, or least it wasn't for me." Jake Archibald, "Progressive Enhancement Still Important"

Slide 12

Slide 12 text

Everyone has JS, right? • On a train / in a tunnel / etc • HTTP request hangs • Firewalls • ISP is interfering • A browser addon is messing with you • Your CDN is down Stuart Langridge's Flowchart

Slide 13

Slide 13 text

Think about if this is right for you!

Slide 14

Slide 14 text

Cutting Edge! We're really still at the early stages of figuring out how this stuff works. Some of the code shown here isn't the easiest, or the APIs aren't that straight forward. This will change as we learn more. Don't expect this to be 100% smooth!

Slide 15

Slide 15 text

React paved the way

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Code and demos: https://github.com/jackfranklin/ universal-react-talk/tree/fluent-conf-version

Slide 19

Slide 19 text

A standard React app: class MyApp extends React.Component { render() { ... } } ReactDOM.render( , document.getElementById('app') )

Slide 20

Slide 20 text

Server side: class MyApp extends React.Component { render() { ... } } ReactDOM.renderToString()

Slide 21

Slide 21 text

// some imports left out to save space import React from 'react'; import MyApp from './component'; import { renderToString } from 'react-dom/server'; const app = express(); app.get('*', (req, res) => { const markup = renderToString(); res.render('index', { markup }); });

Slide 22

Slide 22 text

renderToString When your HTML is going to be picked up by React on the client renderToStaticMarkup When your HTML is never going to be edited by client-side React

Slide 23

Slide 23 text

My App

Hello from React

Slide 24

Slide 24 text

Going Client side • Shared set of components that are environment agnostic • A server rendering step (like we just saw) • A client rendering step • A bundler to generate our client side JavaScript

Slide 25

Slide 25 text

There's no requirement to actually go client side A server side generated React app with no client side JS is perfectly fine.

Slide 26

Slide 26 text

Agnostic Components

Slide 27

Slide 27 text

Webpack A JavaScript application bundler that will generate our client side build.

Slide 28

Slide 28 text

Rendering on the Client

Slide 29

Slide 29 text

Updating our server template.
<%- markup %>

Slide 30

Slide 30 text

Creating client.js: import React from 'react'; import ReactDOM from 'react-dom'; import MyApp from './component'; ReactDOM.render( , document.getElementById('app') );

Slide 31

Slide 31 text

Same Components on client and server

Slide 32

Slide 32 text

npm install --save-dev webpack \ babel-loader \ babel-preset-es2015 \ babel-preset-react \ babel-yet-another-thing-lol-jk

Slide 33

Slide 33 text

Create .babelrc { "presets": ["es2015", "react"] }

Slide 34

Slide 34 text

Create webpack.config.js var path = require('path'); module.exports = { entry: path.join(process.cwd(), 'client.js'), output: { path: './public/', filename: 'build.js' }, module: { loaders: [ { test: /.js$/, exclude: /node_modules/, loader: 'babel' } ] } }

Slide 35

Slide 35 text

Run webpack: (PS: in production we'd minify, etc!) > webpack Hash: 78c865d5593fe910f823 Version: webpack 1.12.12 Time: 4948ms Asset Size Chunks Chunk Names build.js 690 kB 0 [emitted] main + 160 hidden modules (Tip: webpack -w for continuous rebuilds)

Slide 36

Slide 36 text

An interactive component export default class MyApp extends React.Component { constructor() { super(); this.state = { count: 0 }; } onClick() { this.setState({ count: this.state.count + 1 }); } render() { return (
Click Me

Count: { this.state.count }

); } }

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

A read-only experience is way better than no experience.

Slide 39

Slide 39 text

Routing

Slide 40

Slide 40 text

Don't be that person who breaks the web.

Slide 41

Slide 41 text

react-router The defacto, practically standard routing solution for React. https://github.com/rackt/react-router

Slide 42

Slide 42 text

First we need some more components, starting with components/app.js: import React from 'react'; export default class AppComponent extends React.Component { render() { return (

My web 2.0 app

{ this.props.children }
); } } this.props.children are the nested routes.

Slide 43

Slide 43 text

And then components/index.js: import React from 'react'; export default class IndexComponent extends React.Component { render() { return (

This is the index page

); } }

Slide 44

Slide 44 text

Define our routes: import { Route } from 'react-router'; import React from 'react'; import AppComponent from './components/app'; import IndexComponent from './components/index'; export const routes = ( );

Slide 45

Slide 45 text

Match against the URL on the server. Gets a bit hairy, stick with me! React Router server side guide

Slide 46

Slide 46 text

// our newly defined routes import { routes } from './routes'; // match is responsible for matching routes against a URL // RouterContext renders the components in the matched routes import { match, RouterContext } from 'react-router';

Slide 47

Slide 47 text

app.get('*', (req, res) => { match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { if (error) { res.status(500).send(error.message) } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search) } else if (renderProps) { res.render('index', { markup: renderToString() }); } else { res.status(404).send('Not found') } }) });

Slide 48

Slide 48 text

Let's break that down...

Slide 49

Slide 49 text

// take our app's routes, and the URL of the request match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { // error: given if something went wrong matching a route // redirectLocation: returned if the URL matches a redirect // renderProps: given if a route was matched and we can render ... });

Slide 50

Slide 50 text

if (error) { // if there was an error, 500 with the error message // you might show a custom error HTML page here res.status(500).send(error.message) } else if (redirectLocation) { ... }

Slide 51

Slide 51 text

... } else if (redirectLocation) { // if we need to redirect, redirect to the new URL res.redirect(302, redirectLocation.pathname + redirectLocation.search) } else if (renderProps) { ... }

Slide 52

Slide 52 text

... } else if (renderProps) { // if we have renderProps that means we have a match and can render res.render('index', { // RouterContext is React Router's wrapper around our app // and renderProps contains all the info // React Router needs to render our app markup: renderToString() }); } else { ... }

Slide 53

Slide 53 text

} else { // if we get here, it's not an error, redirect or match // hence, 404! res.status(404).send('Not found') }

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

Let's add an about page!

Slide 56

Slide 56 text

components/about.js: import React from 'react'; export default class AboutComponent extends React.Component { render() { return

Rockstar developer

; } }

Slide 57

Slide 57 text

routes.js: import AppComponent from './components/app'; import IndexComponent from './components/index'; import AboutComponent from './components/about'; import React from 'react'; import { Route } from 'react-router'; export const routes = ( );

Slide 58

Slide 58 text

And some links... ... render() { return (

My web 2.0 app

Home About { this.props.children }
); } ...

Slide 59

Slide 59 text

With no client side bundle, this works perfectly:

Slide 60

Slide 60 text

Updating the client side: import React from 'react'; import ReactDOM from 'react-dom'; import { Router, browserHistory } from 'react-router'; import { routes } from './routes'; ReactDOM.render( , document.getElementById('app') ) And then rerun webpack.

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

Dealing with Data on the server and client

Slide 63

Slide 63 text

(Caveat: this area is still WIP) Lots of unknowns!

Slide 64

Slide 64 text

1.We want to be able to fetch data on the server and/or on the client. 2.If the data is loaded on the server and rendered to the client, we ideally want to avoid making the request again.

Slide 65

Slide 65 text

Async Props (also by the creator's of React Router). Not production ready, still a WIP. Check code/ with-async-data on GitHub for an example.

Slide 66

Slide 66 text

First, let's give components/index.js some data: export default class IndexComponent extends React.Component { // a stage 1 proposal for ES.next static loadProps(params, cb) { fetchGithubData('jackfranklin').then((data) => { cb(null, { github: data }); }); } render() { return (

My github repo count: { this.props.github.public_repos }

); } }

Slide 67

Slide 67 text

Then we update our server and client rendering. AsyncProps generates a script that will contain the fetched data from the server. The client can pick this data up and avoid having to make the request all over again.

Slide 68

Slide 68 text

This is one of many approaches.

Slide 69

Slide 69 text

React Resolver code/with-react-resolver has an example. class IndexComponent extends React.Component { render() { return (

This is the index page

My github repo count: { this.props.github.public_repos }

); } } export default resolve('github', (props) => { return fetch('https://api.github.com/users/jackfranklin').then((data) => { return data.json(); }); })(IndexComponent);

Slide 70

Slide 70 text

• Update the server side rendering to include window.__REACT_RESOLVER_PAYLOAD__. • Update the client side rendering to use React Resolver. • All data is resolved on the server, rendered and then used to populate data on the client.

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

This area is still being figured out - more solutions will definitely come!

Slide 73

Slide 73 text

Code and Demos These slides and all the demos are on GitHub. jackfranklin/universal-react-talk Please send me questions: @Jack_Franklin or [email protected].

Slide 74

Slide 74 text

Universal JavaScript is here to stay. The techniques, libraries and approaches will change over time. There's plenty to figure out in this space! Long term I expect frameworks to do more, and it become even easier for developers to take advantage.

Slide 75

Slide 75 text

Server side by default?

Slide 76

Slide 76 text

Further Reading • Universal React on 24ways • How we built the new gocardless.com • GoCardless.com repo • Universal React Example app

Slide 77

Slide 77 text

If you dig React I can make you dig it more! Day long React workshops in London: • 16 March • 10 June

Slide 78

Slide 78 text

Thanks! @Jack_Franklin javascriptplayground.com http://speakerdeck.com/jackfranklin [email protected]