Universal JavaScript - Frontend United Athens 2017

Universal JavaScript - Frontend United Athens 2017

Since we started to see JS on the server side, the dream of developers has been to reduce the gap and the cost of switch between frontend/backend. Today with Node.js, React and a whole ecosystem of tools, this dream is becoming true! In this talk, I am going to discuss Universal (a.k.a. Isomorphic) JS and present some practical example regarding the major patterns related to routing, data retrieval and rendering. I will use Node, React, Webpack, Babel and React Router and give you a series of example to get you started easily with this new technology trend.

F3a6662b3cd161c3c2f13604965ed0f2?s=128

Luciano Mammino

May 29, 2017
Tweet

Transcript

  1. LUCIANO MAMMINO UNIVERSAL JAVASCRIPT UNIVERSAL JAVASCRIPT LUCIANO MAMMINO loige.link/athens-2017 ATHENS,

    27 MAY 2017 1
  2. WHO IS LUCIANO lmammino loige loige.co mt. Etna Νάξος 2

  3. NeBK20FUA -20% eBook NpBK15FUA -15% Print loige.link/node-book ⚡ ⚡CONTAINS A

    WHOLE CHAPTER ABOUT UNIVERSAL JAVASCRIPT ⚡ ⚡ GET A DISCOUNT ​ 3
  4. fullstackbulletin.com 4

  5. </shameless-self-promotion> 5

  6. AGENDA 1. The term "Universal" JS 2. Who & Why

    3. Common problems and technologies 4. Building a frontend only Single Page App 5. Making it "Universal" 6
  7. ISOMORPHIC loige.link/universal-js-story UNIVERSAL... WAIT, WHAT? 7

  8. NOT ONLY FOR THE WEB... Desktop applications Mobile applications Hardware!

    8
  9. ADVANTAGES OF UNIVERSAL JAVASCRIPT "JavaScript-only" development Maintainability Better SEO Faster

    "perceived" load time 9
  10. ADVANTAGES... MOAR Keep using React/JS paradigms also for "static" websites

    Speed up content loading with linkprefetch loige.link/universal-react-made-easy-talk 10
  11. IN THE WILD 11

  12. IT LOOKS GREAT BUT... 12

  13. MODULE SHARING Use Node.js modules in the browser UMD 13

  14. UNIVERSAL RENDERING Render the views of the application from the

    server (first request) and then in the browser (next requests) 14
  15. UNIVERSAL ROUTING Recognise the view associated to the current route

    from both the server and the browser. 15
  16. UNIVERSAL DATA RETRIEVAL Access data (and APIs) from both the

    server and the browser. AXIOS UNIVERSAL FETCH 16
  17. UNIVERSAL STATE MANAGEMENT Manage changes on the state tree both

    on the server and the client... 17
  18. FUTURISTIC/ALTERNATIVE JS?! 18

  19. 19

  20. OK... LET'S STOP COMPLAINING AND BUILD SOMETHING! 20

  21. WHAT ARE WE GOING TO BUILD? loige.link/judo-heroes-app​ loige.link/judo-heroes-tutorial v 2.0

    21
  22. 22

  23. 23

  24. 24

  25. curl -sS "http://localhost:3000/athlete/teddy-riner" 25

  26. WHAT TOOLS ARE WE GOING TO USE? v2 v15.4 v4

    v5-alpha 26
  27. DEPENDENCIES yarn add \ babel-cli@6.18.0 \ babel-core@6.18.2 \ babel-preset-es2015@6.18.0 \

    babel-preset-react@6.16.0 \ ejs@2.5.2 \ express@5.0.0-alpha.5 \ react@15.4.2 \ react-dom@15.4.2 \ react-router-dom@4.0.0 \ webpack@2.2.1 27
  28. 28

  29. The data set // src/data/athletes.js const athletes = [ {

    id: 'driulis-gonzalez', name: 'Driulis González', country: { id: 'cu', name: 'Cuba', icon: 'flag-cu.png', }, birth: '1973', image: 'driulis-gonzalez.jpg', cover: 'driulis-gonzalez-cover.jpg', link: 'https://en.wikipedia.org/wiki/Driulis_González', medals: [ { id: 1, year: '1992', type: 'B', city: 'Barcelona', event: 'Olympic Games', category: '-57kg' }, { id: 2, year: '1993', type: 'B', city: 'Hamilton', event: 'World Championships', category: '-57kg' }, { id: 3, year: '1995', type: 'G', city: 'Chiba', event: 'World Championships', category: '-57kg' }, { id: 4, year: '1995', type: 'G', city: 'Mar del Plata', event: 'Pan American Games', category: '-57kg' }, { id: 5, year: '1996', type: 'G', city: 'Atlanta', event: 'Olympic Games', category: '-57kg' }, // ... ], }, // ... ]; export default athletes; 29
  30. REACT COMPONENTS 30

  31. Layout component 31

  32. IndexPage component 32

  33. AthletePage component 33

  34. NotFoundPage component 34

  35. AthletePreview component 35

  36. AthletesMenu component 36

  37. Flag component 37

  38. Medal component 38

  39. // src/components/Layout.js import React from 'react'; import { Link }

    from 'react-router-dom'; export const Layout = props => ( <div className="app-container"> <header> <Link to="/"> <img className="logo" src="/img/logo-judo-heroes.png" /> </Link> </header> <div className="app-content">{props.children}</div> <footer> <p> This is a demo app to showcase <strong>universal Javascript</strong> with <strong>React</strong> and <strong>Express</strong>. </p> </footer> </div> ); export default Layout; 39
  40. // src/components/IndexPage.js import React from 'react'; import { AthletePreview }

    from './AthletePreview'; export const IndexPage = ({ athletes }) => ( <div className="home"> <div className="athletes-selector"> { athletes.map( athleteData => <AthletePreview key={athleteData.id} {...athleteData} /> ) } </div> </div> ); export default IndexPage; 40
  41. // src/components/AthletePreview.js import React from 'react'; import { Link }

    from 'react-router'; export const AthletePreview = (props) => ( <Link to={`/athlete/${props.id}`}> <div className="athlete-preview"> <img src={`img/${props.image}`}/> <h2 className="name">{props.name}</h2> <span className="medals-count"> <img src="/img/medal.png"/> {props.medals.length} </span> </div> </Link> ); export default AthletePreview; 41
  42. ROUTING 42

  43. 2 ROUTES Index Page: / Athlete Page: /athlete/:id 43

  44. // src/components/App.js import React from 'react'; import { Route, Switch

    } from 'react-router-dom'; import { Layout } from './Layout'; import { IndexPage } from './IndexPage'; import { AthletePage } from './AthletePage'; import { NotFoundPage } from './NotFoundPage'; import athletes from '../data/athletes'; // ... export const App = () => ( <Layout> <Switch> <Route exact path="/" render={renderIndex} /> <Route exact path="/athlete/:id" render={renderAthlete} /> <Route component={NotFoundPage} /> </Switch> </Layout> ); export default App; 44
  45. // src/components/App.js // ... const renderIndex = () => <IndexPage

    athletes={athletes} />; const renderAthlete = ({ match, staticContext }) => { const id = match.params.id; const athlete = athletes.find(current => current.id === id); if (!athlete) { return <NotFoundPage staticContext={staticContext} />; } return <AthletePage athlete={athlete} athletes={athletes} />; }; 45
  46. CLIENT APP 46

  47. // src/app-client.js import React from 'react'; import { render }

    from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; import { App } from './components/App'; const AppClient = () => ( <Router> <App /> </Router> ); window.onload = () => { render( <AppClient />, document.getElementById('main') ); }; 47
  48. HTML TEMPLATE 48

  49. // src/views/index.ejs <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta

    name="viewport" content="width=device-width, initial-scale=1.0"> <title> Judo Heroes - A Universal JavaScript demo application with React </title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <div id="main"><%- markup -%></div> <script src="/js/bundle.js"></script> </body> </html> 49
  50. BUILD CONFIG (BABEL + WEBPACK) 50

  51. .babelrc import path from 'path'; const config = { entry:

    { js: './src/app-client.js', }, output: { path: path.join(__dirname, 'src', 'static', 'js'), filename: 'bundle.js', }, module: { rules: [ { test: path.join(__dirname, 'src'), use: { loader: 'babel-loader' }, }, ], }, }; export default config; .webpack.config.babel.js { "presets": ["react", "es2015"] } 51
  52. LET'S BUILD IT! 52

  53. // src/server.js import path from 'path'; import { Server }

    from 'http'; import Express from 'express'; const app = new Express(); const server = new Server(app); // use ejs templates app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); // define the folder that will be used for static assets app.use(Express.static(path.join(__dirname, 'static'))); // render the index for every non-matched route app.get('*', (req, res) => { let markup = ''; let status = 200; return res.status(status).render('index', { markup }); }); // start the server const port = process.env.PORT || 3000; const env = process.env.NODE_ENV || 'production'; server.listen(port); "Static" Express server 53
  54. READY... LET'S TEST IT 54

  55. RECAP What we learned so far 1. Define views combining

    React components 2. Add routing using React Router 3. Compile the client bundle with Babel and Webpack 4. Run the app with a static Express server 55
  56. SERVER SIDE RENDERING AND ROUTING 56

  57. Updating the server app // ... import { renderToString }

    from 'react-dom/server'; import { StaticRouter as Router } from 'react-router-dom'; import { App } from './components/App'; // ... app.get('*', (req, res) => { let markup = ''; let status = 200; const context = {}; markup = renderToString( <Router location={req.url} context={context}> <App /> </Router>, ); // context.url will contain the URL to // redirect to if a <Redirect> was used if (context.url) { return res.redirect(302, context.url); } if (context.is404) { status = 404; } return res.status(status).render('index', { markup }); }); 57
  58. THAT'S IT! LET'S TEST AGAIN 58

  59. RECAP What we learned so far 1. Create a Single

    Page Application with React and React Router 2. Add server side routing and rendering using React and React Router libraries in the Express app 59
  60. UNIVERSAL DATA RETRIEVAL api-proxy & async-props (COMPLETE CHAPTER in )

    UNIVERSAL STATE MANAGEMENT Redux PROGRESSIVE WEB APPS (PWA) + Node.js Design Patterns @addyosmani's tutorial Create React App WHERE DO WE GO from here... Code: loige.link/judo-heroes-2 60
  61. THANKS! loige loige.co lmammino (Special thanks to , , Aleksandar

    Čambas & ) @cirpo @andreaman87 @quasi_modal loige.link/athens-2017​ 61