├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.markdown ├── assets ├── all-platforms.png ├── apollo.svg ├── apple-1998.png ├── benchmarks.png ├── github.svg ├── haul.png ├── heart.svg ├── hermione.jpg ├── medium.svg ├── meteor.svg ├── mls-allwhite-logo.png ├── mls.png ├── native-directory.png ├── native-modules.png ├── nsync.jpg ├── peggy.jpg ├── platform-extensions.png ├── platform-primitives.png ├── react-native.png ├── react-web.png ├── react.svg ├── reactNative.svg ├── safari.png ├── sketch.png ├── starry-sky.jpeg ├── storybook-screenshot.png ├── storyshots.png ├── svgs.png ├── tired.jpg ├── tweet1.png ├── tweet2.png ├── tweet3.png ├── twitter-lite.png ├── twitter-white.svg ├── twitter.svg ├── universal-application.png ├── universal-components.png ├── wait-what.jpg ├── web-to-native.png └── webvr-sketch.png ├── index.html ├── index.js ├── package.json ├── presentation ├── code-samples │ ├── index.js │ ├── react-native-web.js │ ├── render-agnostic.js │ └── webpack.js ├── comparison.js ├── index.js ├── notes.js └── quote.js ├── server.js ├── webpack.config.js ├── webpack.config.production.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "es2015", { "loose": true, "modules" : false } ], 4 | "stage-0", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "transform-decorators-legacy", 9 | ], 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-es2015-modules-commonjs", 14 | "transform-react-remove-prop-types", 15 | "transform-react-constant-elements", 16 | "transform-react-inline-elements", 17 | "transform-runtime", 18 | "transform-decorators-legacy", 19 | "transform-class-properties" 20 | ], 21 | }, 22 | "test": { 23 | "plugins": [ 24 | "transform-es2015-modules-commonjs" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "prettier" 4 | ], 5 | "parser": "babel-eslint", 6 | "plugins": ["prettier"], 7 | "env": { 8 | "node": true, 9 | "browser": true, 10 | "jest": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Write Once, Render Anywhere (v2) 2 | 3 | # Slides 4 | Check out the slides [here](https://reactnext-universal-components.surge.sh/)! This presentation was built with [Spectacle](https://github.com/FormidableLabs/spectacle). 5 | 6 | # Video 7 | Check out the video on [Youtube](https://www.youtube.com/watch?v=HLWM2uhv2wI) & give it a 👍 if you enjoyed it! 8 | 9 | ## Questions 10 | If you have any questions about the presentation or universal components, please feel free to open an issue or start a conversation on [Twitter](https://twitter.com/peggyrayzis). 11 | 12 | ## Cross-Platform Component Libraries 13 | - [React Native Web](https://github.com/necolas/react-native-web) 14 | - [React Primitives](https://github.com/lelandrichardson/react-primitives) 15 | - [ReactXP](https://github.com/Microsoft/reactxp) 16 | - For a comparison of all three, check the slides for [v1 of my talk](http://reactnyc-universal-components.surge.sh/#/21) 17 | 18 | ## Learn More 💡 19 | - [React Native for Web & Beyond, Nicolas Gallagher](https://www.youtube.com/watch?v=tFFn39lLO-U) 20 | - [React as a Platform, Leland Richardson](https://www.youtube.com/watch?v=JaRtmgaNZos) 21 | - [Why we need an app browser, Ken Wheeler](https://www.youtube.com/watch?v=WEQx3wz8QeY) 22 | - [The Road to Universal Components at MLS, Kurt Kemple](https://labs.mlssoccer.com/the-road-to-universal-components-at-major-league-soccer-eeb7aac27e6c) 23 | 24 | ## Universal Application Starter Packs 25 | - [Create XP App](https://github.com/react-native-training/create-xp-app) 26 | - [Create RN Web App](https://github.com/react-native-training/create-rn-web-app) 27 | -------------------------------------------------------------------------------- /assets/all-platforms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/all-platforms.png -------------------------------------------------------------------------------- /assets/apollo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 11 | 12 | 13 | 16 | 20 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /assets/apple-1998.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/apple-1998.png -------------------------------------------------------------------------------- /assets/benchmarks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/benchmarks.png -------------------------------------------------------------------------------- /assets/github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/haul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/haul.png -------------------------------------------------------------------------------- /assets/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/hermione.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/hermione.jpg -------------------------------------------------------------------------------- /assets/medium.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/meteor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 25 | 28 | 30 | 32 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /assets/mls-allwhite-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/mls-allwhite-logo.png -------------------------------------------------------------------------------- /assets/mls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/mls.png -------------------------------------------------------------------------------- /assets/native-directory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/native-directory.png -------------------------------------------------------------------------------- /assets/native-modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/native-modules.png -------------------------------------------------------------------------------- /assets/nsync.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/nsync.jpg -------------------------------------------------------------------------------- /assets/peggy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/peggy.jpg -------------------------------------------------------------------------------- /assets/platform-extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/platform-extensions.png -------------------------------------------------------------------------------- /assets/platform-primitives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/platform-primitives.png -------------------------------------------------------------------------------- /assets/react-native.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/react-native.png -------------------------------------------------------------------------------- /assets/react-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/react-web.png -------------------------------------------------------------------------------- /assets/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/reactNative.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/safari.png -------------------------------------------------------------------------------- /assets/sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/sketch.png -------------------------------------------------------------------------------- /assets/starry-sky.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/starry-sky.jpeg -------------------------------------------------------------------------------- /assets/storybook-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/storybook-screenshot.png -------------------------------------------------------------------------------- /assets/storyshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/storyshots.png -------------------------------------------------------------------------------- /assets/svgs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/svgs.png -------------------------------------------------------------------------------- /assets/tired.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/tired.jpg -------------------------------------------------------------------------------- /assets/tweet1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/tweet1.png -------------------------------------------------------------------------------- /assets/tweet2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/tweet2.png -------------------------------------------------------------------------------- /assets/tweet3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/tweet3.png -------------------------------------------------------------------------------- /assets/twitter-lite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/twitter-lite.png -------------------------------------------------------------------------------- /assets/twitter-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/universal-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/universal-application.png -------------------------------------------------------------------------------- /assets/universal-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/universal-components.png -------------------------------------------------------------------------------- /assets/wait-what.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/wait-what.jpg -------------------------------------------------------------------------------- /assets/web-to-native.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/web-to-native.png -------------------------------------------------------------------------------- /assets/webvr-sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactnext-universal-components/6341795dcace0a2c4650cd5a8606084ceeab7b73/assets/webvr-sketch.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Write Once, Render Anywhere by @peggyrayzis - ReactNext 2017 8 | 9 | 10 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "react-dom"; 3 | 4 | import Presentation from "./presentation"; 5 | 6 | render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spectacle-boilerplate", 3 | "version": "1.0.1", 4 | "description": "ReactJS Powered Presentation Framework", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "build": "cross-env NODE_ENV=production webpack --config webpack.config.production.js", 9 | "lint": "eslint --ext .js,.jsx .", 10 | "deploy": "npm run clean & npm run build && surge -p .", 11 | "start": "cross-env NODE_ENV=development node server.js", 12 | "remove": "surge teardown react-conf-data-viz.surge.sh" 13 | }, 14 | "author": "", 15 | "license": "MIT", 16 | "dependencies": { 17 | "color": "^1.0.3", 18 | "normalize.css": "3.0.3", 19 | "react": "15.4.2", 20 | "react-dom": "15.4.2", 21 | "react-particles": "^0.0.3", 22 | "spectacle": "^2.0.0", 23 | "spectacle-code-slide": "git+ssh://git@github.com/peggyrayzis/spectacle-code-slide.git", 24 | "victory": "^0.17.0" 25 | }, 26 | "devDependencies": { 27 | "babel-cli": "^6.16.0", 28 | "babel-core": "^6.18.0", 29 | "babel-eslint": "^7.0.0", 30 | "babel-loader": "^6.2.5", 31 | "babel-plugin-react-transform": "^2.0.0-beta1", 32 | "babel-plugin-transform-class-properties": "^6.24.1", 33 | "babel-plugin-transform-decorators-legacy": "^1.2.0", 34 | "babel-plugin-transform-react-constant-elements": "^6.4.0", 35 | "babel-plugin-transform-react-inline-elements": "^6.4.0", 36 | "babel-plugin-transform-react-remove-prop-types": "^0.2.1", 37 | "babel-plugin-transform-runtime": "^6.4.3", 38 | "babel-polyfill": "^6.16.0", 39 | "babel-preset-es2015": "^6.18.0", 40 | "babel-preset-react": "^6.3.13", 41 | "babel-preset-stage-0": "^6.3.13", 42 | "babel-runtime": "^6.18.0", 43 | "cross-env": "^1.0.7", 44 | "css-loader": "^0.23.0", 45 | "eslint": "^3.8.0", 46 | "eslint-config-prettier": "^1.7.0", 47 | "eslint-plugin-filenames": "^1.1.0", 48 | "eslint-plugin-import": "^2.0.1", 49 | "eslint-plugin-prettier": "^2.0.1", 50 | "eslint-plugin-react": "^7.0.0", 51 | "express": "^4.13.3", 52 | "file-loader": "^0.9.0", 53 | "html-loader": "^0.4.0", 54 | "is-buffer": "^1.1.1", 55 | "markdown-loader": "^0.1.7", 56 | "node-libs-browser": "^0.5.3", 57 | "prettier": "^1.3.1", 58 | "raw-loader": "^0.5.1", 59 | "react-transform-catch-errors": "^1.0.0", 60 | "react-transform-hmr": "^1.0.4", 61 | "redbox-react": "^1.2.0", 62 | "rimraf": "^2.4.4", 63 | "style-loader": "^0.13.0", 64 | "surge": "latest", 65 | "url-loader": "^0.5.6", 66 | "webpack": "2.2.0", 67 | "webpack-dev-middleware": "^1.8.4", 68 | "webpack-hot-middleware": "^2.13.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /presentation/code-samples/index.js: -------------------------------------------------------------------------------- 1 | export { default as ReactNativeWeb } from './react-native-web'; 2 | export { default as Webpack } from './webpack'; 3 | export { default as RenderAgnostic } from './render-agnostic'; 4 | -------------------------------------------------------------------------------- /presentation/code-samples/react-native-web.js: -------------------------------------------------------------------------------- 1 | const accessibility = ` 2 | const Button = ({ onPress, style, children }) => ( 3 | 8 | {children} 9 | 10 | ); 11 | `; 12 | 13 | const renderAgnostic = ` 14 | const MovieCard = ({ movie, children }) => { 15 | return ( 16 | children && 17 | React.Children.map(children, child => 18 | React.cloneElement(child, { 19 | movie 20 | }), 21 | ) 22 | ); 23 | }; 24 | `; 25 | 26 | const renderAgnostic2 = ` 27 | render () { 28 | const { movie } = this.props; 29 | 30 | return ( 31 | 32 | 33 | 34 | <Plot /> 35 | </MovieCard> 36 | ); 37 | }; 38 | `; 39 | 40 | const apollo = ` 41 | const MovieQuery = gql\` 42 | query Movie($id: Int!) { 43 | movie(id: $id) { 44 | title 45 | poster 46 | plot 47 | } 48 | } 49 | \` 50 | 51 | export default graphql(MovieQuery, { 52 | props: ({ data }) => { 53 | if(!data.movie) { 54 | return { loading: data.loading }; 55 | } 56 | 57 | return { 58 | loading: data.loading, 59 | movie: data.movie 60 | }; 61 | }, 62 | options: ({ id }) => ({ variables: { id }}) 63 | })(MovieCard); 64 | `; 65 | 66 | const styling = ` 67 | /* 68 | pass in media & orientation via a top level HOC 69 | use react-media on web, Dimensions API & onLayout event for native 70 | */ 71 | const MovieCard = ({ media, orientation, children, movie, style }) => { 72 | const styles = styleFactory({ media, orientation }); 73 | 74 | return ( 75 | <View style={[styles.container, style]}> 76 | {children && 77 | React.Children.map(children, child => 78 | React.cloneElement(child, { 79 | movie, 80 | orientation, 81 | media 82 | }) 83 | )} 84 | </View> 85 | ); 86 | }; 87 | `; 88 | 89 | const styling2 = ` 90 | const styleFactory = ({ media, orientation }) => 91 | StyleSheet.create({ 92 | container: 93 | media === "desktop" || "tv" 94 | ? { 95 | // tip: reuse desktop styling for tv 96 | flexDirection: "row", 97 | justifyContent: "space-between", 98 | alignItems: "center" 99 | } 100 | : { 101 | // phone orientation is necessary for mobile 102 | flexDirection: orientation === "portrait" ? "column" : "row", 103 | justifyContent: "center" 104 | } 105 | }); 106 | `; 107 | 108 | export default { 109 | accessibility, 110 | renderAgnostic, 111 | renderAgnostic2, 112 | apollo, 113 | styling, 114 | styling2, 115 | }; 116 | -------------------------------------------------------------------------------- /presentation/code-samples/render-agnostic.js: -------------------------------------------------------------------------------- 1 | const renderAgnostic = { 2 | platformModule: ` 3 | import { Platform } from 'react-native'; 4 | 5 | switch (Platform.OS) { 6 | case 'web': 7 | // do something 8 | case 'ios': 9 | case 'android': 10 | // do something on native 11 | case 'vr': 12 | case 'sketch': 13 | // works for all platforms! 14 | } 15 | `, 16 | platformExtensions: ` 17 | import { Text } from 'react-native'; 18 | import { Link } from 'universal-components'; 19 | // will resolve from index.web.js on web 20 | 21 | export default ({ text }) => ( 22 | <Link> 23 | <Text>{text}</Text> 24 | </Link> 25 | ); 26 | `, 27 | }; 28 | 29 | export default renderAgnostic; 30 | -------------------------------------------------------------------------------- /presentation/code-samples/webpack.js: -------------------------------------------------------------------------------- 1 | const webpack = { 2 | alias: ` 3 | module: { 4 | resolve: { 5 | alias: { 6 | 'react-native': path.join( 7 | __dirname, 8 | 'node_modules', 9 | 'react-native-web', 10 | ), 11 | }, 12 | extensions: ['.web.js', '.js'] 13 | } 14 | }`, 15 | babel: ` 16 | { 17 | "presets": ["react-native"], 18 | "plugins": [ 19 | ["module-resolver", { 20 | "alias": { 21 | "react-native": "react-vr", 22 | } 23 | }] 24 | ] 25 | } 26 | `, 27 | babelLoader: ` 28 | module: { 29 | rules: [ 30 | { 31 | exclude: /node_modules\/(?!react-native-vector-icons)/, 32 | loader: 'babel-loader', 33 | test: /\.js$/, 34 | } 35 | ] 36 | } 37 | `, 38 | moduleAliasing: ` 39 | module: { 40 | resolve: { 41 | alias: { 42 | 'victory-chart/src': path.join( 43 | __dirname, 44 | 'node_modules', 45 | 'victory-chart', 46 | 'lib' 47 | ), 48 | } 49 | } 50 | } 51 | `, 52 | flow: ` 53 | [options] 54 | module.name_mapper='\(react-native\)' -> 'react-native-web' 55 | `, 56 | jest: ` 57 | "jest": { 58 | "moduleNameMapper": { 59 | "react-native": "<rootDir>/node_modules/react-native-web" 60 | } 61 | } 62 | `, 63 | }; 64 | 65 | export default webpack; 66 | -------------------------------------------------------------------------------- /presentation/comparison.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from 'spectacle'; 3 | 4 | const Comparison = ({ top, bottom, comparison = 'vs.' }) => ( 5 | <div> 6 | <Text textSize="2.2em" textColor="tertiary" style={{ lineHeight: '1.3em' }}> 7 | {top} 8 | </Text> 9 | <Text 10 | textSize="1.4em" 11 | textColor="secondary" 12 | style={{ lineHeight: '1.3em' }} 13 | > 14 | {comparison} 15 | </Text> 16 | <Text 17 | textSize="2.2em" 18 | textColor="quartenary" 19 | style={{ lineHeight: '1.3em' }} 20 | > 21 | {bottom} 22 | </Text> 23 | </div> 24 | ); 25 | 26 | export default Comparison; 27 | -------------------------------------------------------------------------------- /presentation/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Radium from 'radium'; 3 | import { 4 | Appear, 5 | Code, 6 | CodePane, 7 | Deck, 8 | Fill, 9 | Heading, 10 | Image, 11 | Link, 12 | List, 13 | ListItem, 14 | Slide, 15 | S, 16 | Text, 17 | } from 'spectacle'; 18 | 19 | import { ReactNativeWeb, Webpack, RenderAgnostic } from './code-samples'; 20 | import QuoteSlide from './quote'; 21 | import Comparison from './comparison'; 22 | 23 | // change to import notes if you are presenting 24 | import notes from './notes'; 25 | 26 | // comment this out and change above line if you are presenting 27 | // const notes = Object.keys(presenterNotes).reduce( 28 | // (acc, val) => ({ ...acc, [val]: 'Presenter notes coming soon 😜' }), 29 | // {}, 30 | // ); 31 | 32 | // Import image preloader util 33 | import preloader from 'spectacle/lib/utils/preloader'; 34 | 35 | // Import theme 36 | import createTheme from 'spectacle/lib/themes/default'; 37 | 38 | // Require CSS 39 | require('normalize.css'); 40 | require('spectacle/lib/themes/default/index.css'); 41 | 42 | const images = { 43 | meteor: require('../assets/meteor.svg'), 44 | apollo: require('../assets/apollo.svg'), 45 | benchmarks: require('../assets/benchmarks.png'), 46 | mls: require('../assets/mls.png'), 47 | twitterLite: require('../assets/twitter-lite.png'), 48 | reactNative: require('../assets/reactNative.svg'), 49 | twitterWhite: require('../assets/twitter-white.svg'), 50 | medium: require('../assets/medium.svg'), 51 | github: require('../assets/github.svg'), 52 | starrySky: require('../assets/starry-sky.jpeg'), 53 | reactWebChart: require('../assets/react-web.png'), 54 | reactNativeChart: require('../assets/react-native.png'), 55 | webVrSketch: require('../assets/webvr-sketch.png'), 56 | allPlatforms: require('../assets/all-platforms.png'), 57 | nsync: require('../assets/nsync.jpg'), 58 | peggy: require('../assets/peggy.jpg'), 59 | hermione: require('../assets/hermione.jpg'), 60 | platformFiles: require('../assets/platform-extensions.png'), 61 | nativeDirectory: require('../assets/native-directory.png'), 62 | universalComponents: require('../assets/universal-components.png'), 63 | universalApplication: require('../assets/universal-application.png'), 64 | platformPrimitives: require('../assets/platform-primitives.png'), 65 | nativeModules: require('../assets/native-modules.png'), 66 | storybook: require('../assets/storybook-screenshot.png'), 67 | webToNative: require('../assets/web-to-native.png'), 68 | waitWhat: require('../assets/wait-what.jpg'), 69 | apple: require('../assets/apple-1998.png'), 70 | tweet1: require('../assets/tweet1.png'), 71 | tweet2: require('../assets/tweet2.png'), 72 | tweet3: require('../assets/tweet3.png'), 73 | sketch: require('../assets/sketch.png'), 74 | safari: require('../assets/safari.png'), 75 | svgs: require('../assets/svgs.png'), 76 | storyshots: require('../assets/storyshots.png'), 77 | }; 78 | 79 | preloader(images); 80 | 81 | const colors = { 82 | primary: '#191c20', 83 | secondary: '#F3F4F4', 84 | tertiary: '#96DBE4', 85 | quartenary: '#93DAAB', 86 | dark: '#010207', 87 | }; 88 | 89 | const theme = createTheme(colors, { 90 | primary: { name: 'Poppins', googleFont: true, styles: ['400', '600'] }, 91 | secondary: 'Helvetica', 92 | }); 93 | 94 | const fontSize = { 95 | large: '3.2em', 96 | medium: '2.2em', 97 | small: '1.8em', 98 | }; 99 | 100 | @Radium 101 | export default class Presentation extends Component { 102 | render() { 103 | return ( 104 | <Deck 105 | transition={['zoom', 'slide']} 106 | transitionDuration={500} 107 | theme={theme} 108 | progress="none" 109 | controls={false} 110 | > 111 | <Slide 112 | transition={['zoom']} 113 | bgImage={images.starrySky} 114 | bgDarken={0.4} 115 | notes={notes.intro1} 116 | > 117 | <Text 118 | margin="10px 0 0" 119 | textColor="tertiary" 120 | textSize={fontSize.large} 121 | > 122 | write once, 123 | </Text> 124 | <Text 125 | margin="10px 0 0" 126 | textColor="tertiary" 127 | textSize={fontSize.large} 128 | > 129 | render anywhere! 130 | </Text> 131 | </Slide> 132 | <Slide 133 | transition={['fade']} 134 | bgImage={images.peggy} 135 | notes={notes.intro2} 136 | > 137 | <Text 138 | bold 139 | textColor="tertiary" 140 | textSize={fontSize.medium} 141 | style={{ textShadow: '2px 2px 2px #010207' }} 142 | > 143 | Shalom! 🙋 I'm Peggy. 144 | </Text> 145 | <Link href="https://twitter.com/peggyrayzis"> 146 | <Text 147 | margin="20px" 148 | textColor="secondary" 149 | textSize={fontSize.medium} 150 | style={{ textShadow: '2px 2px 2px #010207' }} 151 | > 152 | @peggyrayzis 153 | </Text> 154 | </Link> 155 | </Slide> 156 | <Slide 157 | transition={['fade']} 158 | bgColor="primary" 159 | notes={notes.apolloIntro} 160 | > 161 | <div 162 | style={{ 163 | display: 'flex', 164 | flexDirection: 'column', 165 | justifyContent: 'center', 166 | alignItems: 'center', 167 | }} 168 | > 169 | <Image width="100%" src={images.meteor} margin="0 0 20px 0" /> 170 | <Image width="100%" src={images.apollo} margin="20px 0 0 0" /> 171 | </div> 172 | </Slide> 173 | <Slide transition={['fade']} bgColor="primary" notes={notes.reactWeb}> 174 | <Image height="600px" src={images.reactWebChart} /> 175 | </Slide> 176 | <Slide 177 | transition={['fade']} 178 | bgColor="primary" 179 | notes={notes.reactNative1} 180 | > 181 | <div 182 | style={{ 183 | height: '100%', 184 | display: 'flex', 185 | flexDirection: 'column', 186 | alignItems: 'flex-start', 187 | justifyContent: 'center', 188 | }} 189 | > 190 | <Image 191 | height="540px" 192 | margin="0 auto" 193 | src={images.reactNativeChart} 194 | /> 195 | <Appear> 196 | <Image height="90px" src={images.webVrSketch} /> 197 | </Appear> 198 | </div> 199 | </Slide> 200 | <QuoteSlide 201 | textSize={fontSize.medium} 202 | quote="92% of developers who used React Native would use it again." 203 | link="http://stateofjs.com/2016/mobile/" 204 | author="stateofjs.com, 2016" 205 | colors={colors} 206 | notes={notes.reactNative2} 207 | /> 208 | <Slide 209 | transition={['fade']} 210 | bgColor="primary" 211 | notes={notes.reactNative3} 212 | > 213 | <List> 214 | <ListItem textColor="tertiary"> 215 | <span style={{ color: colors.secondary }}>PanResponder:</span> 216 | {' '} 217 | Sophisticated gestures 218 | </ListItem> 219 | <Appear> 220 | <ListItem textColor="tertiary"> 221 | <span style={{ color: colors.secondary }}>StyleSheet:</span> 222 | {' '} 223 | Flexible CSS in JS solution 224 | </ListItem> 225 | </Appear> 226 | <Appear> 227 | <ListItem textColor="tertiary"> 228 | <span style={{ color: colors.secondary }}>Animated:</span> 229 | {' '} 230 | Declarative & configurable 231 | </ListItem> 232 | </Appear> 233 | <Appear> 234 | <ListItem textColor="tertiary"> 235 | <span style={{ color: colors.secondary }}>Yoga:</span> 236 | {' '} 237 | Flexbox by default 238 | </ListItem> 239 | </Appear> 240 | </List> 241 | </Slide> 242 | <Slide 243 | transition={['fade']} 244 | bgColor="primary" 245 | notes={notes.reactNativeWeb} 246 | > 247 | <Image src={images.webToNative} width="950px" /> 248 | </Slide> 249 | <Slide transition={['fade']} bgColor="primary" notes={notes.waitWhat}> 250 | <Image src={images.waitWhat} height="650px" /> 251 | </Slide> 252 | <Slide 253 | transition={['fade']} 254 | bgColor="primary" 255 | notes={notes.primitives1} 256 | > 257 | <Image 258 | margin="0px 0px 0px 3px" 259 | height="650px" 260 | src={images.allPlatforms} 261 | /> 262 | </Slide> 263 | <Slide 264 | transition={['fade']} 265 | bgColor="primary" 266 | notes={notes.primitives2} 267 | > 268 | <Image height="650px" src={images.platformPrimitives} /> 269 | </Slide> 270 | <Slide 271 | transition={['fade']} 272 | bgColor="primary" 273 | notes={notes.primitives3} 274 | > 275 | <Text textColor="secondary" lineHeight={1.2} textSize="2.7em"> 276 | DOM primitives were introduced in 1998... 277 | </Text> 278 | </Slide> 279 | <Slide 280 | transition={['fade']} 281 | bgDarken={0.5} 282 | bgImage={images.nsync} 283 | notes={notes.primitives4} 284 | > 285 | <Appear> 286 | <Text textColor="secondary" lineHeight={1.3} textSize="2.6em"> 287 | <p style={{ textShadow: '2px 2px 3px #010207' }}> 288 | Why are we building modern applications with primitives stuck in the 90s? 289 | </p> 290 | </Text> 291 | </Appear> 292 | </Slide> 293 | <Slide 294 | transition={['fade']} 295 | bgColor="primary" 296 | notes={notes.primitives5} 297 | > 298 | <Image src={images.apple} height="650px" /> 299 | </Slide> 300 | <QuoteSlide 301 | quote="I hope that there are plans for further unifying React Native and React for mobile web. At any given time there’s a handful of people (either at Facebook, or other companies/startups) exploring ways to bring RN style mobile app development to the web, and React Native seems like a great place to start." 302 | link="https://www.reactiflux.com/transcripts/jordan-walke/" 303 | author="Jordan Walke, creator of React" 304 | colors={colors} 305 | notes={notes.community1} 306 | /> 307 | <Slide 308 | transition={['fade']} 309 | bgColor="primary" 310 | notes={notes.crossPlatform} 311 | > 312 | <Text textColor="tertiary" lineHeight={1.2} textSize="2em"> 313 | How do we bring 314 | {' '} 315 | <span style={{ color: colors.secondary }}>React Native</span> 316 | {' '} 317 | style application development to the 318 | {' '} 319 | <span style={{ color: colors.secondary }}>web</span> 320 | ? 321 | </Text> 322 | </Slide> 323 | <Slide 324 | transition={['fade']} 325 | bgColor="primary" 326 | notes={notes.universal} 327 | margin="0px" 328 | > 329 | <div style={{ width: '100%' }}> 330 | <Image width="50%" src={images.universalComponents} /> 331 | <Image width="50%" src={images.universalApplication} /> 332 | </div> 333 | </Slide> 334 | <Slide margin="0px" transition={['fade']} notes={notes.universal1}> 335 | <div 336 | style={{ 337 | display: 'flex', 338 | justifyContent: 'space-between', 339 | alignItems: 'center', 340 | }} 341 | > 342 | <Image width="500px" src={images.universalComponents} /> 343 | <List style={{ width: '400px' }}> 344 | {[ 345 | 'Platform agnostic components made with React Native primitives', 346 | 'Published via shared NPM package', 347 | '"Write once, render anywhere"', 348 | ].map(item => ( 349 | <ListItem key={item} textSize="1.2em">{item}</ListItem> 350 | ))} 351 | </List> 352 | </div> 353 | </Slide> 354 | <Slide margin="0px" transition={['fade']} notes={notes.universal2}> 355 | <div 356 | style={{ 357 | display: 'flex', 358 | justifyContent: 'space-between', 359 | alignItems: 'center', 360 | }} 361 | > 362 | <List style={{ width: '400px', textAlign: 'right' }}> 363 | {[ 364 | 'Business logic is shared between platforms', 365 | 'Comprised of universal components', 366 | '"Write once, run anywhere"', 367 | ].map(item => ( 368 | <ListItem key={item} textSize="1.2em">{item}</ListItem> 369 | ))} 370 | </List> 371 | <Image width="500px" src={images.universalApplication} /> 372 | </div> 373 | </Slide> 374 | <Slide transition={['fade']} bgColor="primary" notes={notes.libraries1}> 375 | <div 376 | style={{ 377 | height: '650px', 378 | display: 'flex', 379 | flexDirection: 'column', 380 | justifyContent: 'space-between', 381 | alignItems: 'center', 382 | }} 383 | > 384 | <Link href="https://github.com/necolas/react-native-web"> 385 | <Text textColor="secondary" lineHeight={1.6} textSize="2.6em"> 386 | react-native-web 387 | </Text> 388 | </Link> 389 | <Link href="https://github.com/lelandrichardson/react-primitives"> 390 | <Text textColor="tertiary" lineHeight={1.6} textSize="2.6em"> 391 | react-primitives 392 | </Text> 393 | </Link> 394 | <Link href="https://microsoft.github.io/reactxp/"> 395 | <Text textColor="quartenary" lineHeight={1.6} textSize="2.6em"> 396 | ReactXP 397 | </Text> 398 | </Link> 399 | <Link href="https://reactnyc-universal-components.surge.sh/"> 400 | <Text textColor="secondary" lineHeight={1.6} textSize="1.3em"> 401 | *for a comparison of all 3, check out the first iteration of this talk 402 | </Text> 403 | </Link> 404 | </div> 405 | </Slide> 406 | <Slide transition={['fade']} bgColor="primary" notes={notes.libraries2}> 407 | <div 408 | style={{ 409 | height: '650px', 410 | display: 'flex', 411 | flexDirection: 'column', 412 | justifyContent: 'space-between', 413 | alignItems: 'center', 414 | }} 415 | > 416 | <Link href="https://github.com/necolas/react-native-web"> 417 | <Text textColor="secondary" lineHeight={1.6} textSize="2.6em"> 418 | react-native-web 419 | </Text> 420 | </Link> 421 | <Text textColor="tertiary" lineHeight={1.6} textSize="1.7em"> 422 | <span style={{ color: colors.secondary }}>APIs: 21/43</span> 423 | {' '} 424 | (9 missing are iOS/Android specific) 425 | </Text> 426 | <Text textColor="tertiary" lineHeight={1.6} textSize="1.7em"> 427 | <span style={{ color: colors.secondary }}>Components: 22/36</span> 428 | {' '} 429 | (13 missing are iOS/Android specific) 430 | </Text> 431 | <Text textColor="secondary" lineHeight={1.6} textSize="1.7em"> 432 | Platform parity: ~75% 433 | </Text> 434 | </div> 435 | </Slide> 436 | <Slide 437 | transition={['fade']} 438 | bgColor="primary" 439 | notes={notes.productionReady1} 440 | > 441 | <Text 442 | textColor="tertiary" 443 | lineHeight={1.2} 444 | textSize={fontSize.medium} 445 | > 446 | Is react-native-web 447 | {' '} 448 | <span style={{ color: colors.secondary }}>production ready</span> 449 | ? 450 | </Text> 451 | </Slide> 452 | <Slide 453 | transition={['fade']} 454 | bgColor="primary" 455 | notes={notes.productionReady2} 456 | margin="0px" 457 | > 458 | <div 459 | style={{ 460 | display: 'flex', 461 | justifyContent: 'space-between', 462 | alignItems: 'center', 463 | }} 464 | > 465 | <Text 466 | lineHeight={1.2} 467 | textSize="1.5em" 468 | padding="0 0 25px 0" 469 | textColor="secondary" 470 | > 471 | Twitter Lite 472 | </Text> 473 | <Image src={images.twitterLite} height="700px" /> 474 | </div> 475 | </Slide> 476 | <Slide 477 | transition={['fade']} 478 | bgColor="primary" 479 | notes={notes.productionReady3} 480 | margin="0px" 481 | > 482 | <Text 483 | lineHeight={1.2} 484 | textSize="1.5em" 485 | padding="0 0 25px 0" 486 | textColor="secondary" 487 | > 488 | Major League Soccer Schedule & Scores 489 | </Text> 490 | <Image src={images.mls} width="1000px" /> 491 | </Slide> 492 | <Slide 493 | transition={['fade']} 494 | bgColor="primary" 495 | notes={notes.accessibility} 496 | margin="0px" 497 | > 498 | <Text 499 | lineHeight={1.2} 500 | textSize="1.5em" 501 | padding="0 0 25px 0" 502 | textColor="secondary" 503 | > 504 | Accessibility APIs: Add ARIA roles 🙌 505 | </Text> 506 | <CodePane 507 | textSize=".8em" 508 | style={{ minWidth: 0 }} 509 | lang="jsx" 510 | source={ReactNativeWeb.accessibility} 511 | /> 512 | </Slide> 513 | <Slide transition={['fade']} bgColor="primary" notes={notes.perf}> 514 | <Text 515 | lineHeight={1.2} 516 | textSize="1.5em" 517 | padding="0 0 25px 0" 518 | textColor="secondary" 519 | > 520 | StyleSheet performance: Faster than most CSS-in-JS libraries 🚀 521 | </Text> 522 | <Image height="500px" src={images.benchmarks} /> 523 | <Link href="https://www.youtube.com/watch?v=tFFn39lLO-U"> 524 | <Text 525 | lineHeight={1.2} 526 | textSize="1.2em" 527 | padding="0 0 25px 0" 528 | textColor="tertiary" 529 | > 530 | *to learn more, check out @necolas' talk! 531 | </Text> 532 | </Link> 533 | </Slide> 534 | <Slide transition={['fade']} bgColor="primary" notes={notes.codeReuse}> 535 | <Comparison 536 | top="More code reuse" 537 | bottom="Less duplication" 538 | comparison="&" 539 | /> 540 | </Slide> 541 | <Slide 542 | transition={['fade']} 543 | bgImage={images.hermione} 544 | bgDarken={0.5} 545 | notes={notes.webpack4} 546 | > 547 | <Text 548 | style={{ textShadow: '2px 2px 2px #010207' }} 549 | lineHeight={1.2} 550 | textSize={fontSize.medium} 551 | textColor="secondary" 552 | > 553 | With some Webpack wizardry, you can use React Native libraries on the web! 554 | </Text> 555 | </Slide> 556 | <Slide transition={['fade']} notes={notes.rnModules}> 557 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 558 | Alias react-native-web 559 | </Text> 560 | <CodePane 561 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.7em' }} 562 | lang="jsx" 563 | source={Webpack.alias} 564 | /> 565 | </Slide> 566 | <Slide transition={['fade']} notes={notes.rnModules1}> 567 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 568 | Compile library to ES5 with Babel 569 | </Text> 570 | <CodePane 571 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.7em' }} 572 | lang="jsx" 573 | source={Webpack.babelLoader} 574 | /> 575 | </Slide> 576 | <Slide transition={['fade']} notes={notes.rnModules2}> 577 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 578 | If the module already includes ES5 code, point to that instead 579 | </Text> 580 | <CodePane 581 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.7em' }} 582 | lang="jsx" 583 | source={Webpack.moduleAliasing} 584 | /> 585 | </Slide> 586 | <Slide 587 | transition={['fade']} 588 | bgColor="primary" 589 | notes={notes.webCompatible} 590 | > 591 | <Text 592 | textColor="tertiary" 593 | lineHeight={1.2} 594 | textSize={fontSize.medium} 595 | > 596 | How do I know if a React Native library is 597 | {' '} 598 | <span style={{ color: colors.secondary }}> 599 | web compatible 600 | </span> 601 | ? 602 | </Text> 603 | </Slide> 604 | <Slide 605 | transition={['fade']} 606 | bgColor="primary" 607 | notes={notes.webCompatible2} 608 | > 609 | <Link href="https://native.directory/"> 610 | <Text lineHeight={1.2} textSize="1.2em" textColor="quartenary"> 611 | native.directory: Your one stop shop 612 | </Text> 613 | </Link> 614 | <Image width="100%" src={images.nativeDirectory} /> 615 | </Slide> 616 | <Slide transition={['fade']} bgColor="primary" notes={notes.tweet}> 617 | <Image src={images.tweet2} width="800px" /> 618 | </Slide> 619 | <Slide 620 | transition={['fade']} 621 | bgColor="primary" 622 | notes={notes.renderAgnostic} 623 | > 624 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 625 | Keep your components lightweight 626 | </Text> 627 | <CodePane 628 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.7em' }} 629 | lang="jsx" 630 | source={ReactNativeWeb.renderAgnostic} 631 | /> 632 | </Slide> 633 | <Slide 634 | transition={['fade']} 635 | bgColor="primary" 636 | notes={notes.renderAgnostic2} 637 | > 638 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 639 | Your universal components are your application's primitives! 640 | </Text> 641 | <CodePane 642 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.7em' }} 643 | lang="jsx" 644 | source={ReactNativeWeb.renderAgnostic2} 645 | /> 646 | </Slide> 647 | <Slide 648 | transition={['fade']} 649 | bgColor="primary" 650 | notes={notes.platformExt1} 651 | > 652 | <Text textColor="tertiary" lineHeight={1.2} textSize="2em"> 653 | Not all of your components have to be completely universal! 654 | </Text> 655 | </Slide> 656 | <Slide transition={['fade']} notes={notes.platformExt2}> 657 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 658 | Platform module 659 | </Text> 660 | <CodePane 661 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.7em' }} 662 | lang="jsx" 663 | source={RenderAgnostic.platformModule} 664 | /> 665 | </Slide> 666 | <Slide transition={['fade']} notes={notes.platformExt3}> 667 | <Text 668 | lineHeight={1.2} 669 | textSize="1.5em" 670 | style={{ paddingBottom: '25px' }} 671 | textColor="secondary" 672 | > 673 | Platform extensions 674 | </Text> 675 | <div 676 | style={{ 677 | display: 'flex', 678 | flexDirection: 'column', 679 | alignItems: 'flex-start', 680 | justifyContent: 'flex-start', 681 | }} 682 | > 683 | <Image 684 | width="35%" 685 | style={{ margin: '0', paddingLeft: '8px' }} 686 | src={images.platformFiles} 687 | /> 688 | <CodePane 689 | style={{ 690 | minWidth: 0, 691 | maxWidth: 900, 692 | fontSize: '0.7em', 693 | margin: '0', 694 | padding: '0', 695 | }} 696 | lang="jsx" 697 | source={RenderAgnostic.platformExtensions} 698 | /> 699 | </div> 700 | </Slide> 701 | <Slide transition={['fade']} bgColor="primary" notes={notes.storybook1}> 702 | <Text textColor="tertiary" lineHeight={1.2} textSize="2em"> 703 | How do I 704 | {' '} 705 | <span style={{ color: colors.secondary }}> 706 | test 707 | </span> 708 | {' '}universal components? 709 | </Text> 710 | </Slide> 711 | <Slide transition={['fade']} notes={notes.storybook2}> 712 | <Text lineHeight={1.2} textSize="1.3em" textColor="tertiary"> 713 | Storybook: Develop in isolation 714 | </Text> 715 | <Image 716 | width="80%" 717 | style={{ marginTop: '50px' }} 718 | src={images.storybook} 719 | /> 720 | </Slide> 721 | <Slide transition={['fade']} notes={notes.storybook3}> 722 | <Text lineHeight={1.2} textSize="1.2em" textColor="tertiary"> 723 | Storyshots: Automatically convert your stories to Jest snapshots ✨ 724 | </Text> 725 | <Image 726 | width="80%" 727 | style={{ marginTop: '50px' }} 728 | src={images.storyshots} 729 | /> 730 | </Slide> 731 | <Slide transition={['fade']} bgColor="primary" notes={notes.styling}> 732 | <Image src={images.tweet1} width="800px" /> 733 | </Slide> 734 | <Slide 735 | transition={['fade']} 736 | margin="0px" 737 | bgColor="primary" 738 | notes={notes.styling2} 739 | > 740 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 741 | Be mindful of responsive design 742 | </Text> 743 | <CodePane 744 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.65em' }} 745 | lang="jsx" 746 | source={ReactNativeWeb.styling} 747 | /> 748 | </Slide> 749 | <Slide 750 | transition={['fade']} 751 | margin="0px" 752 | bgColor="primary" 753 | notes={notes.styling3} 754 | > 755 | <Text lineHeight={1.2} textSize="1.5em" textColor="secondary"> 756 | Pass props into a StyleSheet factory 757 | </Text> 758 | <CodePane 759 | style={{ minWidth: 0, maxWidth: 900, fontSize: '0.7em' }} 760 | lang="jsx" 761 | source={ReactNativeWeb.styling2} 762 | /> 763 | </Slide> 764 | <Slide transition={['fade']} bgColor="primary" notes={notes.apollo}> 765 | <Image src={images.tweet3} width="800px" /> 766 | </Slide> 767 | <Slide transition={['fade']} bgColor="primary" notes={notes.apollo1}> 768 | <Link href="http://dev.apollodata.com/"> 769 | <Image src={images.apollo} width="800px" /> 770 | </Link> 771 | <Text 772 | lineHeight={1.2} 773 | textColor="tertiary" 774 | textSize={fontSize.medium} 775 | > 776 | A flexible & powerful GraphQL client 777 | </Text> 778 | </Slide> 779 | <Slide transition={['fade']} bgColor="primary" notes={notes.apollo2}> 780 | <Link href="http://dev.apollodata.com/"> 781 | <Image src={images.apollo} width="800px" /> 782 | </Link> 783 | <Text 784 | lineHeight={1.2} 785 | textColor="tertiary" 786 | textSize={fontSize.medium} 787 | > 788 | <span style={{ color: colors.secondary }}>Universal data</span> 789 | {' '} 790 | for your universal components 😍 791 | </Text> 792 | </Slide> 793 | <Slide bgColor="primary" transition={['fade']} notes={notes.apollo3}> 794 | <Text lineHeight={1.2} textSize="1.4em" textColor="secondary"> 795 | Compatible with any client, including React Native & Sketch 796 | </Text> 797 | <Image width="80%" src={images.sketch} /> 798 | </Slide> 799 | <Slide 800 | bgColor="primary" 801 | margin="0px" 802 | transition={['fade']} 803 | notes={notes.apollo4} 804 | > 805 | <Text lineHeight={1.2} textSize="1.4em" textColor="secondary"> 806 | Write your query once, run it anywhere! 807 | </Text> 808 | <CodePane 809 | style={{ 810 | minWidth: 0, 811 | maxWidth: 900, 812 | fontSize: '0.6em', 813 | margin: '0', 814 | padding: '0', 815 | }} 816 | lang="jsx" 817 | source={ReactNativeWeb.apollo} 818 | /> 819 | </Slide> 820 | <Slide 821 | transition={['fade']} 822 | margin="0px" 823 | bgColor="primary" 824 | notes={notes.challenges1} 825 | > 826 | <Text textSize="2em" lineHeight={1.6} textColor="secondary"> 827 | Challenges yet to be solved: 828 | </Text> 829 | <Text textSize="2em" lineHeight={1.6} textColor="tertiary"> 830 | Webkit Flexbox performance 831 | </Text> 832 | <Image width="80%" src={images.safari} /> 833 | </Slide> 834 | <Slide 835 | transition={['fade']} 836 | margin="0px" 837 | bgColor="primary" 838 | notes={notes.challenges2} 839 | > 840 | <Text textSize="2em" lineHeight={1.6} textColor="tertiary"> 841 | Cross-platform SVGs 842 | </Text> 843 | <Image width="60%" src={images.svgs} /> 844 | <Link href="https://github.com/godaddy/svgs"> 845 | <Text textSize="1.2em" lineHeight={1.6} textColor="secondary"> 846 | *Check out svgs, a compat layer between browser SVG elements & react-native-svg 847 | </Text> 848 | </Link> 849 | </Slide> 850 | <Slide 851 | transition={['fade']} 852 | margin="0px" 853 | bgColor="primary" 854 | notes={notes.challenges3} 855 | > 856 | <Text textSize="2em" lineHeight={1.6} textColor="tertiary"> 857 | VR 858 | </Text> 859 | <List textColor="secondary"> 860 | {[ 861 | 'Units are in meters, not pixels', 862 | 'Wide variety of input events to account for', 863 | 'Cross-platform components are limited to View, Image, & Text', 864 | ].map(item => <ListItem key={item}>{item}</ListItem>)} 865 | </List> 866 | </Slide> 867 | <Slide transition={['fade']} bgColor="primary" notes={notes.takeaway}> 868 | <Text textColor="tertiary" lineHeight={1.2} textSize="2em"> 869 | Open your mind to 870 | {' '} 871 | <span style={{ color: colors.secondary }}>new</span> 872 | {' '} 873 | platforms and possibilities. 874 | </Text> 875 | </Slide> 876 | <Slide 877 | transition={['fade']} 878 | bgImage={images.starrySky} 879 | bgDarken={0.4} 880 | notes={notes.thanks} 881 | > 882 | <Text textSize="1.9em" lineHeight={1.2} textColor="tertiary"> 883 | @peggyrayzis 884 | </Text> 885 | <div 886 | style={{ 887 | display: 'flex', 888 | flexDirection: 'row', 889 | justifyContent: 'center', 890 | alignItems: 'center', 891 | width: '100%', 892 | }} 893 | > 894 | 895 | <Link href="https://github.com/peggyrayzis"> 896 | <Image margin="30px" src={images.github} width="100px" /> 897 | </Link> 898 | <Link href="https://twitter.com/peggyrayzis"> 899 | <Image margin="30px" src={images.twitterWhite} width="100px" /> 900 | </Link> 901 | <Link href="https://medium.com/@peggyrayzis"> 902 | <Image margin="30px" src={images.medium} width="100px" /> 903 | </Link> 904 | </div> 905 | </Slide> 906 | </Deck> 907 | ); 908 | } 909 | } 910 | -------------------------------------------------------------------------------- /presentation/notes.js: -------------------------------------------------------------------------------- 1 | const notes = { 2 | intro1: ` 3 | Hi everyone! Today we’re going to talk about something really exciting that’s generating a lot of buzz in the community. 4 | Over the next 25 minutes, you’re going to learn how to write a component once & render it anywhere - 5 | i’m talking web, native, VR, sketch, & more -- without changing any lines of code. 6 | `, 7 | intro2: ` 8 | Before we get started, I wanted to introduce myself. 9 | Shalom! My name is Peggy Rayzis. You can find me on Twitter, Github, and Medium at my handle there. 10 | <br /> 11 | <br /> 12 | As you can see, I've had an awesome time in Israel so far! I even made some new friends on the tour. 13 | `, 14 | apolloIntro: ` 15 | I work at Meteor Development Group on the Apollo team building open source GraphQL tools. Previously, I was working at Major League Soccer as an engineer on the UI team. 16 | My talk actually isn't about GraphQL today at all, but I think Apollo client complements a universal components architecture very nicely. 17 | I'll explain more about that later in my talk. 18 | `, 19 | reactWeb: ` 20 | Before I tell you what cross-platform component libs are and the problems they solve, I want to give you some context first 21 | <br /> 22 | <br /> 23 | In the beginning, we had React on the web 24 | We all thought it was awesome - clearly judging by the amazing international community we have here today! 25 | React allowed us to build beautiful UIs and iterate upon them quickly 26 | `, 27 | reactNative1: ` 28 | Then came the custom renderers that brought React's success on the web to native platforms 29 | <br /> 30 | <br /> 31 | The first of which was React native 32 | React Native was so great that it inspired several forks 33 | More recently, we also have custom renderers for WebVR & Sketch. 34 | `, 35 | reactNative2: ` 36 | Needless to say React Native was a hit! 37 | Who here has used React Native? 38 | <br /> 39 | <br /> 40 | Check out these stats from the last state of JavaScript survey, over 10,000 devs responded 41 | <br /> 42 | <br /> 43 | 92% satisfaction rating for React Native! 44 | I can't think of the last time that 92% of developers agreed upon anything! 45 | `, 46 | reactNative3: ` 47 | <p style="font-size:22px;padding-left:40px;"> 48 | There are a lot of reasons why developers are continuing to bet on React Native. 49 | <br /> 50 | <br /> 51 | You have an intuitive way to handle sophisticated multi touch gestures with PanResponder, 52 | <br /> 53 | You have StyleSheet, a built in CSS in JS solution that allows you to colocate your styles with your React components 54 | <br /> 55 | It's easy to create performant, declarative animations to enhance your UI with the Animated API 56 | <br /> 57 | You also have Yoga, Facebook's cross-platform layout engine that uses flexbox by default for positioning your UI. 58 | </p> 59 | `, 60 | reactNativeWeb: ` 61 | All of this was really exciting! Not too long after React Native was officially released, 62 | Nicolas Gallagher at Twitter released react-native-web to bring these APIs & the building blocks of RN over to web development 63 | `, 64 | waitWhat: ` 65 | When I first heard about this, I thought it was weird too. You bring React from the web to native with React Native and now you’re trying to bring React Native over to the web? 66 | In reality, he was ahead of his time. 67 | `, 68 | primitives1: ` 69 | <p style="font-size:20px;padding-left:40px;"> 70 | When you look at the React ecosystem in totality and where it seems to be heading, it doesn’t seem that strange 71 | <br /> 72 | <br /> 73 | At the most granular level, all of the building blocks, or “primitives,” for the platforms on the right are the same. 74 | They also share the same APIs for styling, animation, and gesture handling. 75 | Theoretically, you can write a pretty basic component using RN primitives and have it work on every single one of those platforms just by changing the renderer you’re importing from 76 | </p> 77 | `, 78 | primitives2: ` 79 | <p style="font-size:20px;padding-left:40px;"> 80 | The odd one out here is the web -- the web’s primitives are tied to the DOM! 81 | We’re building components with divs & h1 tags instead of Views & Text. 82 | Styling & animation are not implemented in a uniform way. 83 | </p> 84 | `, 85 | primitives3: ` 86 | DOM primitives were standardized in 1998. To put it in perspective... 87 | `, 88 | primitives4: ` 89 | That's the same year NSYNC released their debut album! DOM primitives are almost 20 years old! 90 | <br /> 91 | `, 92 | primitives5: ` 93 | Our websites don't look like this anymore!! We need a new set of primitives that better reflect modern application development for the mobile web. 94 | `, 95 | community1: ` 96 | <br /> 97 | Luckily for us, we kind of already have the primitives and APIs we need with React Native! 98 | I love this quote from Jordan Walke, the creator of React. In his interview with Reactiflux, he advocated for further unifying React & React Native for mobile app development. 99 | <br /> 100 | `, 101 | crossPlatform: ` 102 | So how do we accomplish this goal and bring the joys of React Native development to the web? 103 | I think this is a pretty difficult problem to solve and won't fully materialize without advancements from browser vendors. 104 | Who knows, maybe someday we'll get a React Native app browser. Ken Wheeler built an excellent proof of concept for one, and 105 | I highly encourage you to check out his talk if this subject interests you. 106 | Until that happens, we have to play the hand we're dealt 107 | `, 108 | universal: ` 109 | <p style="font-size:20px;padding-left:40px;"> 110 | There is a solution that you can start building today -- that's universal components. 111 | <br /> 112 | <br /> 113 | First, I want to explain what they are and also introduce the concept of a universal application 114 | universal components are built to render anywhere. They are platform agnostic components compatible with any renderer that are built with React Native primitives & APIs 115 | With universal components, you separate the business logic by platform, but compose your features with universal, shared components. 116 | </p> 117 | `, 118 | universal1: ` 119 | To share these components, you would publish them in an NPM package and consume them in each separate application. 120 | You can think of building universal components as writing once and rendering anywhere. 121 | This is what we're going to cover in detail today. 122 | `, 123 | universal2: ` 124 | in contrast, you can think of developing a universal application as writing it once, and running it anywhere. You will need some kind of intermediate layer between your UI & the platform to determine the renderer 125 | In a universal application, you share the business logic across platforms. 126 | Universal applications are built with both universal & platform specific components, so you can think of universal components as more of an incremental approach 127 | <br /> 128 | <br /> 129 | We're not going to cover universal applications today, but if you're interested, check out create-react-native-web-app & create-react-xp app by Nader Dabit. I'll post the link to his projects in my Github repo for the talk. 130 | `, 131 | libraries1: ` 132 | Three libraries have emerged to cover these needs. all of them are excellent. 133 | <br /> 134 | <br /> 135 | RNW - first cross-platform component library developed by Nicolas Gallagher at Twitter. It's been battle tested with Twitter Lite, their new progressive web app. 136 | <br /> 137 | Primitives - Developed by Leland Richardson of Airbnb. He gave an excellent talk on it at Chain React that you should check out. 138 | <br /> 139 | ReactXP - Developed by Microsoft, newest of the three. Used by the Skype team in production. 140 | <br /> 141 | <br /> 142 | If you'd like to see a comparison of these libraries, explaining how they work and some of their tradeoffs, you should check out the first version of this talk. all of my slides are clickable, so you can follow the link below. 143 | `, 144 | libraries2: ` 145 | today, we're going to focus on react native web in order to build our universal components. it has the highest platform parity of all the libraries 146 | what i mean by platform parity is how similar it is to react native. 147 | i actually ran the numbers - react native web supports 21 out of 43 react native APIs - that's things like StyleSheet and Dimensions. 148 | 9 of those 43 that are unsupported are actually iOS and Android specific 149 | for components, RNW supports over half. 150 | so if you combine them and substract the irrelevant platform specific APIs, react native web supports around 3/4 of what's available in react native. 151 | i'd like to point out that react native web works by acting as a compatibility layer on top of DOM primitives, so you can write React Native code that will ultimately render to divs and spans. 152 | `, 153 | productionReady1: ` 154 | you're probably wondering with a healthy dose of skepticism whether universal components built with RNW are production ready 155 | or if this is just another javascript fad. my answer to this question is a resounding YES! RNW is already being used in production by large companies today. 156 | `, 157 | productionReady2: `twitter uses RNW for their progressive web app, twitter lite. 158 | in order for twitter to use RNW in production, they had to solve problems like right to left layout & accessibility. 159 | i'd also like to point out that this architecture is not just for greenfield applications - twitter has been incrementally migrating their component base to RNW, 160 | so it's entirely possible for your new universal components to render beside your old ones 161 | `, 162 | productionReady3: ` 163 | we also used it in production at major league soccer for our realtime match experience 164 | everything here is built with universal components 165 | `, 166 | accessibility: ` 167 | one of the things that might have concerned you is that react native web renders to divs and spans, which is a huge concern for accessibility reasons because there is no way to preserve semantic markup for screenreaders. there are built in accessibility APIs to make sure you can apply the correct ARIA role to your DOM elements 168 | `, 169 | perf: ` 170 | react native web is also performant. RNW's StyleSheet implementation is on par or faster than most popular CSS in JS libraries today 171 | it extracts your styles into CSS, applies a class name for each unique declaration, and memoizes them at runtime to improve performance 172 | `, 173 | codeReuse: ` 174 | Adopting this architecture also increases your team's velocity. Instead of developing a feature once * the number of platforms you're supporting, 175 | you'll only have to write a universal component once, dramatically increasing your code reuse. 176 | You'll also be able to standardize your third party libraries across platforms - so instead of having a calendar library for web and a separate one for native with another API you have to learn, you can just use one, resulting in less duplication of work 177 | `, 178 | webpack4: ` 179 | just by configuring a couple things in your build process, you can use RN libraries on the web! 180 | The reason why all of this is possible is react-native-web's almost complete feature parity with React Native. let's dive into some code! 181 | `, 182 | rnModules: ` 183 | first, you will want to alias react-native-web to react-native in your project. you can achieve this with either webpack or babel-plugin-module-resolver 184 | `, 185 | rnModules1: ` 186 | since React Native modules are in ES6, you're going to have to compile them with Babel in your Webpack build. 187 | To tell the babel-loader to process the RN module, add the module's name as a negative lookahead to the exclusion regex. you'll need to do this for every RN module you use 188 | `, 189 | rnModules2: ` 190 | Sometimes your RN module will contain code that has already been compiled. In the case of victory native, victory shares a couple core modules with the web. 191 | However, the React Native version points to the uncompiled ES6 code. 192 | If this is the case, point Webpack to the compiled code instead 193 | `, 194 | webCompatible: ` 195 | you're probably asking yourself how do I know which libraries are web compatible? surely not all of them can be 196 | `, 197 | webCompatible2: ` 198 | the one stop shop for determining web compatibility is native directory. recently, i partnered with the awesome folks at expo to add web compatibility features to native.directory, their curated list of RN libraries 199 | all you need to do is check the filter to find what you're looking for! 200 | shoutout to jim lee, who was able to execute this feature super quickly before my talk :) 201 | in the upcoming weeks, i'm going to perform an audit to add more web compatible libraries to the list 202 | but i need your help! if you're using react native libraries on the web that aren't listed, PLEASE send a pull request to add it. 203 | `, 204 | tweet: ` 205 | alright, so switching gears, i know this architecture is very new, so i wanted to answer some of the questions the community has about universal components. 206 | recently i posed the question on twitter to see what people were curious about. 207 | so let's start off by briefly discussing how to write render agnostic components 208 | `, 209 | renderAgnostic: ` 210 | a lot of the best practices with react in general apply to universal components. 211 | you want to keep them as small and focused as possible 212 | i like to do this with a top level wrapper component that passes its data down to any amount of child components 213 | we're going to be using a simple moviecard example for this talk that passes down a movie prop to all of its children 214 | `, 215 | renderAgnostic2: ` 216 | Think of your universal components as your "primitives" for features in your application that you can compose based on the platform. 217 | here we're rendering a poster, a title, and a plot inside a card but these could all be standalone components used in other parts of your application. 218 | think of reusability as your developing 219 | `, 220 | platformExt1: ` 221 | it's really difficult to make all of your components universal. sometimes you're going to need an escape hatch 222 | `, 223 | platformExt2: ` 224 | that's ok because we have one! for small one off changes, you're going to want to use the Platform module. 225 | This is great for things like styling differences that don't affect the overall logic of your universal component 226 | `, 227 | platformExt3: ` 228 | for bigger, more substantial differences, you can use platform extensions. 229 | you will need to configure this in your webpack build. 230 | here's where this comes in handy. 231 | you can prefix your file extension with your platform. webpack and the RN packager will know where to resolve to depending on the platform 232 | this is great for things like linking whhere the implementation varies significantly between platforms 233 | `, 234 | storybook1: ` 235 | how do we test our universal components? 236 | a helpful tool is react storybook. 237 | storybook is an interactive development & testing environment for your react components. 238 | it removes the platform out of the equation, so you can focus on writing your components quickly 239 | `, 240 | storybook2: ` 241 | By developing our components in isolation and keeping them focused, we can iterate faster. 242 | i like to use the web version of storybook to ensure that my universal components work on the web 243 | `, 244 | storybook3: ` 245 | another cool feature is that you can automatically convert your stories to snapshots in Jest 246 | `, 247 | styling: ` 248 | ok, so we're developing our components in storybook. 249 | what about styling your universal components? 250 | personally, i stick with stylesheet, but you can use libraries like glamorous native too if you're familiar with a styled components like API. it's really up to you, but i would advise to keep the performance benchmarks in mind and run them for yourself. 251 | `, 252 | styling2: ` 253 | one of the problems you'll have to tackle right away is the variation of screen sizes. 254 | luckily, if you're already following responsive design practices, this shouldn't be too bad. 255 | if you're designing a component for desktop, you can easily port those styles over to Apple TV 256 | on the flip side, styles for the mobile web can be applied to react native. 257 | to determine the media size, on web, you can use a library like react-media for media queries and pass down a top level media prop either through context or a higher order component 258 | on native, you can use dimensions and onLayout to determine whether the phone is in portrait or landscape 259 | `, 260 | styling3: ` 261 | then you can pass props like media and orientation into a function that returns the output from stylesheet.create 262 | `, 263 | apollo: ` 264 | alright so we can style our components, but what about fetching data? 265 | `, 266 | apollo1: ` 267 | i think this is a perfect use case for apollo! and this is not just because i work there, i was a huge fan of apollo for universal components even when i was at major league soccer. 268 | apollo is a graphQL client that manages data fetching and updating your UI for you. 269 | `, 270 | apollo2: ` 271 | you can think of apollo as a universal data solution for your universal components. it's used in production by a number of large companies, ranging from ticketmaster to airbnb. 272 | it's also easy to integrate into your app incrementally so you don't have to refactor your whole application at once 273 | `, 274 | apollo3: ` 275 | the reason why it is a universal data solution is because it supports any client, including react native and even sketch, without any additional configuration. i think this sketch example is really cool because designers can fetch and design with real data. 276 | `, 277 | apollo4: ` 278 | the other cool thing about apollo is that your components only need to request the data that they need. 279 | this aligns really nicely with the modular philosophy of universal components, since the goal is to keep them small and focused. 280 | with apollo, you can write your queries once and run them on any platform. 281 | you can even publish shared graphql containers to an npm package along with your universal components 282 | `, 283 | challenges1: ` 284 | even though universal components are really useful, there are some challenges since the ecosystem is so new. 285 | flexbox performance is still an issue in webkit and older browsers, however there is a workaround. 286 | i believe this is fixed in the newest version of webkit but it's still something to keep in mind 287 | `, 288 | challenges2: ` 289 | cross platform svgs can be difficult as well for some newer, experimental platforms like sketch. 290 | luckily a solution exists for web and native. svgs by godaddy is a compat layer that allows you to use the react-native-svg API to render svg elements in the browser. 291 | this is essential for using libraries like react native vector icons and victory native on the web, which rely upon svg 292 | `, 293 | challenges3: ` 294 | VR can also be tough for a number of reasons. units of measurement in VR are meters, not pixels, unless you wrap your 2d components in a cylindricalpanel. it can also be hard to normalize user input events, since there are so many to account for -- gaze tracking, keyboard events, hand held controllers. 295 | also you're limited to view, image, and text for cross-platform components so just keep that in mind. 296 | `, 297 | takeaway: ` 298 | ok that was a lot of information to throw at you at once. if you takeaway one thing from this talk, even if you don't plan on executing a cross-platform strategy any time soon is this: 299 | be open minded to new platforms and possibilities. 300 | as our world becomes more connected, new platforms will emerge, possibly as a react custom renderer. 301 | if you're a maintainer. people may be using your libraries in ways you never expected. 302 | react is truly becoming a platform for application development, so try to think of the bigger picture 303 | `, 304 | thanks: ` 305 | thank you so much, you've been a fabulous audience. if you have any questions, please feel free to tweet at me or come find me later. 306 | `, 307 | }; 308 | 309 | export default notes; 310 | -------------------------------------------------------------------------------- /presentation/quote.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Slide, BlockQuote, Quote, Link, Cite } from 'spectacle'; 3 | 4 | export default class QuoteSlide extends Component { 5 | constructor() { 6 | super(...arguments); 7 | } 8 | 9 | render() { 10 | const { 11 | quote, 12 | author, 13 | link, 14 | colors, 15 | notes, 16 | textSize = '1.3em', 17 | } = this.props; 18 | return ( 19 | <Slide bgColor="primary" notes={notes}> 20 | <BlockQuote> 21 | <Quote 22 | lineHeight={1.6} 23 | textColor="secondary" 24 | textSize={textSize} 25 | style={{ 26 | lineHeight: 1.3, 27 | borderLeft: `2px solid ${colors.quartenary}`, 28 | fontWeight: 'none', 29 | }} 30 | > 31 | {quote} 32 | </Quote> 33 | <Link href={link}> 34 | <Cite textColor="tertiary">{author}</Cite> 35 | </Link> 36 | </BlockQuote> 37 | </Slide> 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | var path = require("path"); 4 | var express = require("express"); 5 | var webpack = require("webpack"); 6 | var config = require("./webpack.config"); 7 | 8 | var app = express(); 9 | var compiler = webpack(config); 10 | 11 | var serverPort = process.env.PORT || 3000; 12 | 13 | app.use(require("webpack-dev-middleware")(compiler, { 14 | noInfo: true, 15 | publicPath: config.output.publicPath 16 | })); 17 | 18 | app.use(require("webpack-hot-middleware")(compiler)); 19 | 20 | app.get("*", function(req, res) { 21 | res.sendFile(path.join(__dirname, "index.html")); 22 | }); 23 | 24 | app.listen(serverPort, "localhost", function (err) { 25 | if (err) { 26 | console.log(err); 27 | return; 28 | } 29 | 30 | console.log("Listening at http://localhost:" + serverPort); 31 | }); 32 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | var path = require('path'); 4 | var webpack = require('webpack'); 5 | 6 | module.exports = { 7 | devtool: 'source-map', 8 | entry: ['webpack-hot-middleware/client', 'babel-polyfill', './index'], 9 | output: { 10 | path: path.join(__dirname, 'dist'), 11 | filename: 'bundle.js', 12 | publicPath: '/dist/', 13 | }, 14 | plugins: [ 15 | new webpack.HotModuleReplacementPlugin(), 16 | new webpack.NoEmitOnErrorsPlugin(), 17 | ], 18 | module: { 19 | loaders: [ 20 | { 21 | test: /\.md$/, 22 | loader: 'html-loader!markdown-loader?gfm=false', 23 | }, 24 | { 25 | test: /\.(js|jsx)$/, 26 | exclude: /node_modules/, 27 | loader: 'babel-loader', 28 | query: { 29 | plugins: [ 30 | [ 31 | 'react-transform', 32 | { 33 | transforms: [ 34 | { 35 | transform: 'react-transform-hmr', 36 | imports: ['react'], 37 | locals: ['module'], 38 | }, 39 | { 40 | transform: 'react-transform-catch-errors', 41 | imports: ['react', 'redbox-react'], 42 | }, 43 | ], 44 | }, 45 | ], 46 | ], 47 | }, 48 | exclude: /node_modules/, 49 | include: __dirname, 50 | }, 51 | { 52 | test: /\.css$/, 53 | loaders: ['style-loader', 'raw-loader'], 54 | include: __dirname, 55 | }, 56 | { 57 | test: /\.svg$/, 58 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml', 59 | include: path.join(__dirname, 'assets'), 60 | }, 61 | { 62 | test: /\.png$/, 63 | loader: 'url-loader?mimetype=image/png', 64 | include: path.join(__dirname, 'assets'), 65 | }, 66 | { 67 | test: /\.gif$/, 68 | loader: 'url-loader?mimetype=image/gif', 69 | include: path.join(__dirname, 'assets'), 70 | }, 71 | { 72 | test: /\.jpeg$/, 73 | loader: 'url-loader?mimetype=image/jpeg', 74 | include: path.join(__dirname, 'assets'), 75 | }, 76 | { 77 | test: /\.jpg$/, 78 | loader: 'url-loader?mimetype=image/jpg', 79 | include: path.join(__dirname, 'assets'), 80 | }, 81 | ], 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | var path = require('path'); 4 | var webpack = require('webpack'); 5 | 6 | module.exports = { 7 | entry: ['babel-polyfill', './index'], 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: 'bundle.js', 11 | publicPath: '/dist/', 12 | }, 13 | plugins: [ 14 | new webpack.DefinePlugin({ 15 | 'process.env': { 16 | NODE_ENV: JSON.stringify('production'), 17 | }, 18 | }), 19 | new webpack.optimize.UglifyJsPlugin({ 20 | compressor: { 21 | warnings: false, 22 | }, 23 | }), 24 | ], 25 | module: { 26 | loaders: [ 27 | { 28 | test: /\.md$/, 29 | loader: 'html-loader!markdown-loader?gfm=false', 30 | }, 31 | { 32 | test: /\.(js|jsx)$/, 33 | exclude: /node_modules/, 34 | loader: 'babel-loader', 35 | }, 36 | { 37 | test: /\.css$/, 38 | loader: 'style-loader!css-loader', 39 | }, 40 | { 41 | test: /\.(png|jpg|gif|jpeg)$/, 42 | loader: 'url-loader?limit=8192', 43 | }, 44 | { 45 | test: /\.svg$/, 46 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml', 47 | }, 48 | ], 49 | }, 50 | }; 51 | --------------------------------------------------------------------------------