├── .gitattributes ├── .gitignore ├── Chapter 01 └── 1-started │ ├── .DS_Store │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── client.js │ ├── components │ ├── App.js │ └── Html.js │ ├── core │ └── .gitignore │ ├── data │ └── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ └── robots.txt │ ├── routes │ └── .gitignore │ ├── server.js │ ├── test │ └── .gitignore │ ├── tools │ ├── .eslintrc │ ├── build.js │ ├── clean.js │ ├── run.js │ ├── runServer.js │ ├── serve.js │ ├── start.js │ └── webpack.config.js │ └── yarn.lock ├── Chapter 02 └── 2-react │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── client.js │ ├── components │ ├── App.js │ ├── Header │ │ ├── Header.js │ │ └── package.json │ ├── Html │ │ ├── Html.js │ │ └── package.json │ └── Layout │ │ ├── Layout.js │ │ └── package.json │ ├── core │ └── Router.js │ ├── data │ └── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ └── robots.txt │ ├── routes │ ├── .gitignore │ ├── Home │ │ ├── Hero.js │ │ ├── Home.js │ │ └── package.json │ └── NotFound │ │ ├── NotFound.js │ │ └── package.json │ ├── server.js │ ├── test │ └── .gitignore │ ├── tools │ ├── .eslintrc │ ├── build.js │ ├── clean.js │ ├── run.js │ ├── runServer.js │ ├── serve.js │ ├── start.js │ └── webpack.config.js │ └── yarn.lock ├── Chapter 04 └── 4-browsersync │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── client.js │ ├── components │ ├── App.js │ ├── Header │ │ ├── Header.js │ │ ├── header.jpg │ │ └── package.json │ ├── Html │ │ ├── Html.js │ │ └── package.json │ ├── Layout │ │ ├── Layout.js │ │ ├── Layout.scss │ │ ├── header.jpg │ │ └── package.json │ └── variables.scss │ ├── core │ └── Router.js │ ├── data │ └── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── favicon.ico │ └── robots.txt │ ├── routes │ ├── .gitignore │ ├── Home │ │ ├── Hero.js │ │ ├── Home.js │ │ └── package.json │ └── NotFound │ │ ├── NotFound.js │ │ └── package.json │ ├── server.js │ ├── test │ └── .gitignore │ ├── tools │ ├── .eslintrc │ ├── build.js │ ├── clean.js │ ├── run.js │ ├── runServer.js │ ├── serve.js │ ├── start.js │ └── webpack.config.js │ └── yarn.lock ├── Chapter 05 └── 5-react-server │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── api │ └── test.js │ ├── client.js │ ├── components │ ├── App.js │ ├── Context.js │ ├── CurrentTime │ │ ├── CurrentTime.js │ │ └── package.json │ ├── Header │ │ ├── Header.js │ │ ├── header.jpg │ │ └── package.json │ ├── Html │ │ ├── Html.js │ │ └── package.json │ ├── Layout │ │ ├── Layout.js │ │ ├── Layout.scss │ │ ├── header.jpg │ │ └── package.json │ ├── LikeButton │ │ ├── LikeButton.js │ │ └── package.json │ └── variables.scss │ ├── core │ ├── Router.js │ └── fetch │ │ ├── fetch.client.js │ │ ├── fetch.server.js │ │ └── package.json │ ├── data │ └── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── Thumbs.db │ ├── favicon.ico │ └── robots.txt │ ├── routes │ ├── .gitignore │ ├── Home │ │ ├── Hero.js │ │ ├── Home.js │ │ └── package.json │ ├── NotFound │ │ ├── NotFound.js │ │ └── package.json │ └── Test │ │ ├── Test.js │ │ └── package.json │ ├── server.js │ ├── test │ └── .gitignore │ ├── tools │ ├── .eslintrc │ ├── build.js │ ├── clean.js │ ├── run.js │ ├── runServer.js │ ├── serve.js │ ├── start.js │ └── webpack.config.js │ └── yarn.lock ├── Chapter 06 └── 6-graphql │ ├── .DS_Store │ ├── .babelrc │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── client.js │ ├── components │ ├── .DS_Store │ ├── App.js │ ├── Context.js │ ├── CurrentTime │ │ ├── CurrentTime.js │ │ └── package.json │ ├── Header │ │ ├── Header.js │ │ └── header.jpg │ ├── Html │ │ ├── Html.js │ │ └── package.json │ ├── Layout │ │ ├── Layout.js │ │ ├── Layout.scss │ │ ├── header.jpg │ │ └── package.json │ ├── LikeButton │ │ ├── LikeButton.js │ │ └── package.json │ └── variables.scss │ ├── core │ ├── .DS_Store │ ├── Router.js │ └── fetch │ │ ├── fetch.client.js │ │ ├── fetch.server.js │ │ └── package.json │ ├── data │ ├── models │ │ ├── .eslintrc │ │ ├── Offer.js │ │ ├── Tag.js │ │ ├── User.js │ │ └── index.js │ ├── queries │ │ ├── greeting.js │ │ ├── offer.js │ │ └── viewer.js │ ├── schema.js │ ├── sequelize.js │ └── types │ │ ├── OfferType.js │ │ └── UserType.js │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ └── robots.txt │ ├── routes │ ├── .DS_Store │ ├── Home │ │ ├── Hero.js │ │ ├── Home.js │ │ └── package.json │ ├── NotFound │ │ ├── NotFound.js │ │ └── package.json │ └── Test │ │ ├── Test.js │ │ └── package.json │ ├── server.js │ ├── test │ └── .gitignore │ ├── tools │ ├── .eslintrc │ ├── build.js │ ├── clean.js │ ├── run.js │ ├── runServer.js │ ├── serve.js │ ├── start.js │ └── webpack.config.js │ └── yarn.lock ├── Chapter 07 └── 7-routing │ ├── .babelrc │ ├── README.md │ ├── index.js │ ├── modules │ ├── About.js │ ├── App.js │ ├── Home.js │ ├── Repo.js │ ├── Repos.js │ ├── ReposWithRouter.js │ ├── Root.js │ └── RouteWithSubRoutes.js │ ├── package.json │ ├── public │ ├── bundle.js │ └── bundle.js.map │ ├── routesConfig.js │ ├── server.js │ ├── serverWithInitialData.js │ ├── webpack.config.js │ └── yarn.lock ├── Chapter 08 ├── 8-auth │ ├── .DS_Store │ ├── .babelrc │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── bundle.js │ │ └── bundle.js.map │ ├── routesConfig.js │ ├── serverWithInitialData.js │ ├── src │ │ ├── .DS_Store │ │ ├── actions │ │ │ └── types.js │ │ ├── components │ │ │ ├── About.js │ │ │ ├── App.js │ │ │ ├── Home.js │ │ │ ├── Repo.js │ │ │ ├── Repos.js │ │ │ ├── ReposWithRouter.js │ │ │ ├── Root.js │ │ │ ├── RouteWithSubRoutes.js │ │ │ └── hoc │ │ │ │ └── require_auth.js │ │ ├── index.js │ │ └── reducers │ │ │ ├── auth_reducer.js │ │ │ └── index.js │ ├── webpack.config.js │ └── yarn.lock └── __MACOSX │ └── 8-auth │ └── ._.DS_Store ├── Chapter 09 └── B05226_09_Code │ ├── .DS_Store │ ├── .babelrc │ ├── README.md │ ├── nightwatch.json │ ├── package.json │ ├── public │ ├── bundle.js │ └── bundle.js.map │ ├── routesConfig.js │ ├── selenium-debug.log │ ├── serverWithInitialData.js │ ├── src │ ├── .DS_Store │ ├── actions │ │ └── types.js │ ├── components │ │ ├── About.js │ │ ├── App.js │ │ ├── Home.js │ │ ├── Repo.js │ │ ├── Repos.js │ │ ├── ReposWithRouter.js │ │ ├── Root.js │ │ ├── RouteWithSubRoutes.js │ │ └── hoc │ │ │ └── require_auth.js │ ├── index.js │ ├── reducers │ │ ├── auth_reducer.js │ │ └── index.js │ └── tests │ │ ├── EnzymeTest.js │ │ ├── PlainTest.js │ │ ├── it │ │ └── NightwatchTest.js │ │ └── tests_helper.js │ ├── webpack.config.js │ └── yarn.lock ├── LICENSE ├── README.md └── Software and Hardware List.pdf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /Chapter 01/1-started/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 01/1-started/.DS_Store -------------------------------------------------------------------------------- /Chapter 01/1-started/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-2", "react"], 3 | "plugins": [ 4 | "transform-runtime" 5 | ] 6 | } -------------------------------------------------------------------------------- /Chapter 01/1-started/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /Chapter 01/1-started/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "react/jsx-quotes": 0, 5 | "jsx-quotes": [2, "prefer-double"], 6 | "comma-dangle": [2, "never"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 01/1-started/.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | 4 | build 5 | node_modules 6 | ncp-debug.log 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /Chapter 01/1-started/README.md: -------------------------------------------------------------------------------- 1 | ## Learning Isomorphic Web Application Development 2 | 3 | > Example source code accompanying "Learning Isomorphic Web Application Development" book 4 | 5 | ![book](https://dl.dropboxusercontent.com/u/16006521/learning-isomorphic-web-application-development.png) 6 | 7 | ### Table of Contents 8 | 9 |   > **Chapter 01: Getting Started with Isomorphic Web Apps**
10 |      Chapter 1: Getting Started with Isomorphic Web Apps
11 |      Chapter 2: How to Compose Web UIs by Using React
12 |      Chapter 3: Working with CSS Styles and Media Assets
13 |      Chapter 4: Configuring Server-side Rendering
14 |      Chapter 5: Creating an API Backend with GraphQL
15 |      Chapter 6: Fetching Data with Relay
16 |      Chapter 7: Implementing Routing and Navigation
17 |      Chapter 8: Authentication and authorization
18 |      Chapter 9: Testing and Deploying
19 | 20 | ### Prerequisites 21 | 22 | * Node.js v4.0, NPM v3.0 and newer ([download](https://nodejs.org/en/download/)) 23 | * Text editor or IDE with ES6/ES2015 and JSX support 24 | * React Developer Tools ([download](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)) 25 | 26 | ### Getting Started 27 | 28 | Before you can launch the app, install project's dependencies by running: 29 | 30 | ```sh 31 | $ npm install 32 | ``` 33 | 34 | To build the project and start a development web server run: 35 | 36 | ```sh 37 | $ npm run serve 38 | ``` 39 | 40 | Then open [http://localhost:3000/](http://localhost:3000/) in your browser to test the app. 41 | 42 | ### Copyright 43 | 44 | © 2015 by Konstantin Tarkus ([@koistya](https://twitter.com/koistya)), Packt Publishing. All rights reserved. 45 | -------------------------------------------------------------------------------- /Chapter 01/1-started/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2015 Konstantin Tarkus, Packt Publishing 4 | * All rights reserved. 5 | */ 6 | 7 | import 'babel-core/register'; 8 | import React from 'react'; 9 | import ReactDOM from 'react-dom'; 10 | import App from './components/App'; 11 | 12 | function run() { 13 | ReactDOM.hydrate(, document.getElementById('app')); 14 | } 15 | 16 | const loadedStates = ['complete', 'loaded', 'interactive']; 17 | 18 | if (loadedStates.includes(document.readyState) && document.body) { 19 | run(); 20 | } else { 21 | window.addEventListener('DOMContentLoaded', run, false); 22 | } 23 | -------------------------------------------------------------------------------- /Chapter 01/1-started/components/App.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2015 Konstantin Tarkus, Packt Publishing 4 | * All rights reserved. 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import moment from 'moment'; 9 | 10 | class App extends Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | this.state = { time: null }; 15 | } 16 | 17 | componentDidMount() { 18 | this.tick(); 19 | this.interval = setInterval(this.tick.bind(this), 200); 20 | } 21 | 22 | componentWillUnmount() { 23 | clearInterval(this.interval); 24 | } 25 | 26 | tick() { 27 | this.setState({ time: new Date() }); 28 | } 29 | 30 | render() { 31 | const time = this.state.time; 32 | const timeString = time && moment(time).format('h:mm:ss a'); 33 | return ( 34 |
35 |

Sample Application

36 |

Current date and time is {timeString}

37 |
38 | ); 39 | } 40 | 41 | } 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /Chapter 01/1-started/components/Html.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2015 Konstantin Tarkus, Packt Publishing 4 | * All rights reserved. 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | 10 | function Html({ title, description, body }) { 11 | return ( 12 | 13 | 14 | 15 | 16 | {title} 17 | 18 | 19 | 23 | 24 | 25 | ); 26 | } 27 | 28 | Html.propTypes = { 29 | title: PropTypes.string.isRequired, 30 | description: PropTypes.string.isRequired, 31 | body: PropTypes.string.isRequired, 32 | state: PropTypes.object.isRequired 33 | }; 34 | 35 | export default Html; 36 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/Html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "Html", 4 | "main": "./Html.js" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import s from './Layout.scss'; 9 | import Header from '../Header'; 10 | 11 | function Layout({ hero, children }) { 12 | return ( 13 |
14 |
15 |
16 | My App 17 | { 18 | !hero && 19 |
20 | } 21 |
22 | Username 23 | 24 |
25 |
26 | {hero} 27 |
28 |
29 | {children} 30 |
31 |
32 | © Company Name 33 |
34 |
35 | ); 36 | } 37 | 38 | Layout.propTypes = { 39 | hero: PropTypes.element, 40 | children: PropTypes.element.isRequired 41 | }; 42 | 43 | export default Layout; 44 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/Layout/Layout.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | @import '../variables.scss'; 7 | 8 | .root { 9 | position: relative; 10 | display: flex; 11 | width: 100%; 12 | height: 100%; 13 | overflow-x: hidden; 14 | overflow-y: auto; 15 | -webkit-overflow-scrolling: touch; 16 | flex-direction: column; 17 | } 18 | 19 | .header { 20 | z-index: 3; 21 | display: flex; 22 | width: 100%; 23 | min-height: 64px; 24 | max-height: 1000px; 25 | box-sizing: border-box; 26 | padding: 0; 27 | margin: 0; 28 | color: white; 29 | flex-direction: column; 30 | background: url(./header.jpg) center / cover; 31 | border: none; 32 | box-shadow: $shadow-2dp; 33 | transition-timing-function: $animation-curve-default; 34 | transition-duration: $animation-duration-default; 35 | 36 | flex-wrap: nowrap; 37 | justify-content: flex-start; 38 | flex-shrink: 0; 39 | } 40 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/Layout/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 05/5-react-server/components/Layout/header.jpg -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/Layout/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "Layout", 4 | "main": "./Layout.js" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/LikeButton/LikeButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | class LikeButton extends Component { 10 | 11 | static propTypes = { 12 | likes: PropTypes.number.isRequired, 13 | liked: PropTypes.bool.isRequired 14 | }; 15 | 16 | state = { liked: this.props.liked }; 17 | 18 | handleClick = (event) => { 19 | event.preventDefault(); 20 | this.setState({ liked: !this.state.liked }); 21 | }; 22 | 23 | render() { 24 | const { likes, liked, ...other } = this.props; 25 | const color = this.state.liked ? 'inherit' : 'grey'; 26 | const count = likes + (this.state.liked ? 1 : 0); 27 | 28 | return ( 29 | 32 | ); 33 | } 34 | 35 | } 36 | 37 | export default LikeButton; 38 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/LikeButton/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LikeButton", 3 | "main": "./LikeButton.js" 4 | } 5 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/components/variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | /* //// Colors ////////////////////////////////////////////////////////////// */ 7 | 8 | $color-primary: #0275d8; 9 | $color-success: #5cb85c; 10 | $color-info: #5bc0de; 11 | $color-warning: #f0ad4e; 12 | $color-danger: #d9534f; 13 | 14 | /* //// Shadows ///////////////////////////////////////////////////////////// */ 15 | 16 | $shadow-2dp: 0 2px 2px 0 rgba(0, 0, 0, .14), 17 | 0 3px 1px -2px rgba(0, 0, 0, .2), 18 | 0 1px 5px 0 rgba(0, 0, 0, .12); 19 | 20 | /* //// Animations ////////////////////////////////////////////////////////// */ 21 | 22 | $animation-duration-default: .2s; 23 | $animation-curve-default: cubic-bezier(.4, 0, .2, 1); 24 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/core/Router.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React from 'react'; 7 | import Context from '../components/Context'; 8 | 9 | const routes = [ 10 | require('../routes/Home').default, 11 | require('../routes/Test').default, 12 | require('../routes/NotFound').default 13 | ]; 14 | 15 | const router = { 16 | match(location, state) { 17 | let component; 18 | const page = { 19 | title: 'My Application', 20 | description: 'Isomorphic web application sample', 21 | status: 200 22 | }; 23 | const route = routes.find(x => x.path === location.path); 24 | 25 | if (route) { 26 | try { 27 | component = route.action(); 28 | } catch (err) { 29 | component = routes.find(x => x.path === '/500').action(); 30 | page.status = 500; 31 | } 32 | } else { 33 | component = routes.find(x => x.path === '/404').action(); 34 | page.status = 404; 35 | } 36 | 37 | return [{component}, page]; 38 | } 39 | }; 40 | 41 | export default router; 42 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/core/fetch/fetch.client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import 'whatwg-fetch'; 7 | 8 | export default self.fetch.bind(self); 9 | export const Headers = self.Headers; 10 | export const Request = self.Request; 11 | export const Response = self.Response; 12 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/core/fetch/fetch.server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import fetch, { Request, Headers, Response } from 'node-fetch'; 7 | 8 | function localFetch(url, options) { 9 | return fetch(url.startsWith('http') ? 10 | url : 'http://localhost:3000' + url, options); 11 | } 12 | 13 | export { localFetch as default, Request, Headers, Response }; 14 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/core/fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "fetch", 4 | "main": "./fetch.server.js", 5 | "browser": "./fetch.client.js" 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/data/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 05/5-react-server/data/.gitignore -------------------------------------------------------------------------------- /Chapter 05/5-react-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "react-chapter", 4 | "eslintConfig": { 5 | "parser": "babel-eslint", 6 | "extends": "airbnb", 7 | "rules": { 8 | "comma-dangle": 0, 9 | "id-length": [ 10 | 2, 11 | { 12 | "exceptions": [ 13 | "i", 14 | "_", 15 | "x", 16 | "s" 17 | ] 18 | } 19 | ] 20 | } 21 | }, 22 | "dependencies": { 23 | "bluebird": "3.5.0", 24 | "express": "4.15.4", 25 | "moment": "2.18.1", 26 | "node-fetch": "1.7.2", 27 | "react": "16.0.0", 28 | "react-dom": "16.0.0" 29 | }, 30 | "devDependencies": { 31 | "autoprefixer": "7.1.4", 32 | "babel-cli": "6.7.5", 33 | "babel-core": "6.26.0", 34 | "babel-eslint": "8.0.0", 35 | "babel-loader": "7.1.2", 36 | "babel-plugin-transform-runtime": "6.7.5", 37 | "babel-preset-env": "1.6.0", 38 | "babel-preset-react": "6.5.0", 39 | "babel-preset-stage-0": "6.5.0", 40 | "css-loader": "0.28.5", 41 | "del": "3.0.0", 42 | "eslint": "4.6.1", 43 | "eslint-config-airbnb": "15.1.0", 44 | "eslint-plugin-react": "7.3.0", 45 | "extend": "3.0.0", 46 | "file-loader": "0.11.2", 47 | "gaze": "1.1.2", 48 | "ncp": "2.0.0", 49 | "node-style-loader": "0.0.1-alpha", 50 | "postcss-import": "10.0.0", 51 | "postcss-loader": "2.0.6", 52 | "precss": "2.0.0", 53 | "style-loader": "0.18.2", 54 | "url-loader": "0.5.9", 55 | "webpack-dev-middleware": "2.0.4", 56 | "webpack": "3.5.6" 57 | }, 58 | "scripts": { 59 | "lint": "eslint components core data routes test tools", 60 | "build": "babel-node tools/run build", 61 | "serve": "node build/server.js" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /Chapter 05/5-react-server/public/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 05/5-react-server/public/Thumbs.db -------------------------------------------------------------------------------- /Chapter 05/5-react-server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 05/5-react-server/public/favicon.ico -------------------------------------------------------------------------------- /Chapter 05/5-react-server/public/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 05/5-react-server/routes/.gitignore -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/Home/Hero.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React from 'react'; 7 | 8 | function Hero() { 9 | return ( 10 |
11 |

Rent Anything You Want

12 |

From people around you

13 |
14 | 17 | 18 |
19 |
20 | ); 21 | } 22 | 23 | export default Hero; 24 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/Home/Home.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import Layout from '../../components/Layout'; 8 | import Hero from './Hero'; 9 | 10 | const path = '/'; 11 | const action = () => }>; 12 | 13 | class Home extends Component { 14 | handleClick(event) { 15 | event.preventDefault(); 16 | window.location = event.currentTarget.pathname; 17 | } 18 | render() { 19 | return ( 20 |
21 |

Popular things to rent

22 |
23 | 24 | Tools 25 | 26 | 27 | Books 28 | 29 | ... 30 |
31 |
32 | ); 33 | } 34 | } 35 | 36 | export default { path, action }; 37 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/Home/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "Home", 4 | "main": "./Home.js" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/NotFound/NotFound.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React from 'react'; 7 | 8 | const path = '/404'; 9 | const action = () => ; 10 | 11 | function NotFound() { 12 | return ( 13 |
14 |

Page Not Found

15 |

Sorry, but the page you were trying to view does not exist.

16 |
17 | ); 18 | } 19 | 20 | export default { path, action }; 21 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/NotFound/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "NotFound", 4 | "main": "./NotFound.js" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/Test/Test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import fetch from '../../core/fetch'; 9 | import CurrentTime from '../../components/CurrentTime'; 10 | import LikeButton from '../../components/LikeButton'; 11 | import Layout from '../../components/Layout'; 12 | 13 | const path = '/test'; 14 | const action = () => ; 15 | 16 | class Test extends Component { 17 | 18 | static contextTypes = { 19 | page: PropTypes.shape({ 20 | title: PropTypes.string 21 | }), 22 | user: PropTypes.shape({ 23 | name: PropTypes.string.isRequired 24 | }), 25 | }; 26 | 27 | state = { data: 'loading...' }; 28 | 29 | componentDidMount2() { 30 | fetch('/api/test').then(x => x.json()).then(data => this.setState({ data })); 31 | } 32 | async componentWillMount() { 33 | try { 34 | const response = await fetch('/api/test'); 35 | const data = await response.text(); 36 | console.log('>>> data:', data); 37 | this.setState({ data }); 38 | } catch (err) { 39 | console.error(err); 40 | this.setState({ data: 'Error: ' + err.message }); 41 | } 42 | } 43 | 44 | render() { 45 | const { page, user } = this.context; 46 | page.title = 'Test Page'; 47 | 48 | return ( 49 |
50 |

{page.title}

51 |
52 |
53 |

Welcome, {user ? user.name : 'Guest'}!

54 | 55 |

Server response: {this.state.data}

56 |
57 | ); 58 | } 59 | } 60 | 61 | export default { path, action }; 62 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/routes/Test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "Test", 4 | "main": "./Test.js" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import 'babel-core/register'; 7 | import path from 'path'; 8 | import express from 'express'; 9 | import React from 'react'; 10 | import ReactDOM from 'react-dom/server'; 11 | import Router from './core/Router'; 12 | import Html from './components/Html/Html'; 13 | 14 | const server = express(); 15 | const port = process.env.PORT || 3000; 16 | 17 | server.use(express.static(path.join(__dirname, 'public'))); 18 | 19 | server.use((req, res, next) => { 20 | if (typeof req.query.admin !== 'undefined') { 21 | req.user = { name: 'Tarkus '}; 22 | } else { 23 | req.user = null; 24 | } 25 | next(); 26 | }); 27 | 28 | server.use('/api', require('./api/test').default); 29 | 30 | server.get('*', (req, res) => { 31 | const state = { user: req.user }; 32 | const [component, page] = Router.match(req, state); 33 | const body = ReactDOM.renderToString(component); 34 | const html = ReactDOM.renderToStaticMarkup(); 39 | res.status(page.status).send('\n' + html); 40 | }); 41 | 42 | server.listen(port, () => console.log( 43 | `Node.js server is listening at http://localhost:${port}/` 44 | )); 45 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/test/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 05/5-react-server/test/.gitignore -------------------------------------------------------------------------------- /Chapter 05/5-react-server/tools/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": 0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/tools/build.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import fs from 'fs'; 7 | import del from 'del'; 8 | import webpack from 'webpack'; 9 | import Promise from 'bluebird'; 10 | import run from './run'; 11 | import webpackConfig from './webpack.config'; 12 | 13 | async function clean() { 14 | await del(['build/*']); 15 | } 16 | 17 | async function copy() { 18 | const ncp = Promise.promisify(require('ncp')); 19 | if (!fs.existsSync('build')) fs.mkdirSync('build'); 20 | await ncp('public', 'build/public'); 21 | await ncp('package.json', 'build/package.json'); 22 | } 23 | 24 | function bundle({ watch }) { 25 | return new Promise((resolve, reject) => { 26 | let bundlerRunCount = 0; 27 | const bundler = webpack(webpackConfig); 28 | const cb = (err, stats) => { 29 | if (err) { 30 | reject(err); 31 | return; 32 | } 33 | 34 | console.log(stats.toString(webpackConfig[0].stats)); 35 | 36 | if (++bundlerRunCount === (watch ? webpackConfig.length : 1)) { 37 | resolve(); 38 | } 39 | }; 40 | 41 | if (watch) { 42 | bundler.watch(200, cb); 43 | } else { 44 | bundler.run(cb); 45 | } 46 | }); 47 | } 48 | 49 | async function build(options = { watch: false }) { 50 | await run(clean); 51 | await run(copy); 52 | await run(bundle, options); 53 | } 54 | 55 | export default build; 56 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/tools/clean.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import del from 'del'; 7 | 8 | async function clean() { 9 | await del(['build/*']); 10 | } 11 | 12 | export default clean; 13 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/tools/run.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | function format(time) { 7 | return time.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1'); 8 | } 9 | 10 | function run(fn, options) { 11 | const task = typeof fn.default === 'undefined' ? fn : fn.default; 12 | const start = new Date(); 13 | console.log( 14 | `[${format(start)}] Starting '${task.name}${options ? `(${options})` : ''}'...` 15 | ); 16 | return task(options).then(() => { 17 | const end = new Date(); 18 | const time = end.getTime() - start.getTime(); 19 | console.log( 20 | `[${format(end)}] Finished '${task.name}${options ? `(${options})` : ''}' after ${time} ms` 21 | ); 22 | }); 23 | } 24 | 25 | if (process.mainModule.children.length === 0 && process.argv.length > 2) { 26 | delete require.cache[__filename]; 27 | const module = require(`./${process.argv[2]}.js`).default; 28 | run(module).catch(err => console.error(err.stack)); 29 | } 30 | 31 | export default run; 32 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/tools/runServer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import path from 'path'; 7 | import cp from 'child_process'; 8 | import webpackConfig from './webpack.config'; 9 | 10 | // Should match the text string used in `server.js/server.listen(...)` 11 | const RUNNING_REGEXP = /Node\.js server is listening at http:\/\/(.*?)\//; 12 | 13 | let server; 14 | const { output } = webpackConfig.find(x => x.target === 'node'); 15 | const serverPath = path.join(output.path, output.filename); 16 | 17 | // Launch or restart the Node.js server 18 | function runServer(cb) { 19 | function onStdOut(data) { 20 | const time = new Date().toTimeString(); 21 | const match = data.toString('utf8').match(RUNNING_REGEXP); 22 | 23 | process.stdout.write(time.replace(/.*(\d{2}:\d{2}:\d{2}).*/, '[$1] ')); 24 | process.stdout.write(data); 25 | 26 | if (match) { 27 | server.stdout.removeListener('data', onStdOut); 28 | server.stdout.on('data', message => process.stdout.write(message)); 29 | if (cb) { 30 | cb(null, match[1]); 31 | } 32 | } 33 | } 34 | 35 | if (server) { 36 | server.kill('SIGTERM'); 37 | } 38 | 39 | server = cp.spawn('node', [serverPath], { 40 | env: Object.assign({ NODE_ENV: 'development' }, process.env), 41 | silent: false 42 | }); 43 | 44 | server.stdout.on('data', onStdOut); 45 | server.stderr.on('data', x => process.stderr.write(x)); 46 | } 47 | 48 | process.on('exit', () => { 49 | if (server) { 50 | server.kill('SIGTERM'); 51 | } 52 | }); 53 | 54 | export default runServer; 55 | 56 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/tools/serve.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import path from 'path'; 7 | import cp from 'child_process'; 8 | import Promise from 'bluebird'; 9 | import build from './build'; 10 | import run from './run'; 11 | 12 | async function serve() { 13 | const watch = true; 14 | const gaze = Promise.promisify(require('gaze')); 15 | await run(build, { watch }); 16 | await new Promise((resolve, reject) => { 17 | function start() { 18 | const server = cp.spawn( 19 | 'node', 20 | [path.join(__dirname, '../build/server.js')], 21 | { 22 | env: Object.assign({ NODE_ENV: 'development' }, process.env), 23 | silent: false 24 | } 25 | ); 26 | 27 | server.stdout.on('data', (data) => { 28 | process.stdout.write(new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '[$1] ')); 29 | process.stdout.write(data); 30 | if (data.toString('utf8').includes('Node.js server is listening at')) { 31 | resolve(); 32 | } 33 | }); 34 | server.stderr.on('data', data => process.stderr.write(data)); 35 | server.once('error', err => reject(err)); 36 | process.on('exit', () => server.kill('SIGTERM')); 37 | return server; 38 | } 39 | 40 | let server = start(); 41 | 42 | if (watch) { 43 | gaze('build/server.js').then((watcher) => { 44 | watcher.on('changed', () => { 45 | server.kill('SIGTERM'); 46 | server = start(); 47 | }); 48 | }); 49 | } 50 | }); 51 | } 52 | 53 | export default serve; 54 | -------------------------------------------------------------------------------- /Chapter 05/5-react-server/tools/start.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import path from 'path'; 7 | import Browsersync from 'browser-sync'; 8 | import webpack from 'webpack'; 9 | import webpackDevMiddleware from 'webpack-dev-middleware'; 10 | import webpackHotMiddleware from 'webpack-hot-middleware'; 11 | import run from './run'; 12 | import runServer from './runServer'; 13 | import webpackConfig from './webpack.config'; 14 | 15 | // Launch the development web server with Browsersync and HMR 16 | async function start() { 17 | await run(require('./clean')); 18 | await new Promise((resolve) => { 19 | // Inject HMR functionality into client-side bundle configurations 20 | /* eslint-disable no-param-reassign */ 21 | webpackConfig.filter(x => x.target !== 'node').forEach((x) => { 22 | x.entry = [x.entry, 'webpack-hot-middleware/client']; 23 | x.plugins.push(new webpack.HotModuleReplacementPlugin()); 24 | x.plugins.push(new webpack.NoErrorsPlugin()); 25 | }); 26 | /* eslint-enable no-param-reassign */ 27 | 28 | const bundler = webpack(webpackConfig); 29 | const middleware = [ 30 | webpackDevMiddleware(bundler, { 31 | stats: webpackConfig[0].stats 32 | }), 33 | ...(bundler.compilers 34 | .filter(compiler => compiler.options.target !== 'node') 35 | .map(compiler => webpackHotMiddleware(compiler))) 36 | ]; 37 | let handleServerBundleComplete = () => { 38 | runServer((err, host) => { 39 | if (!err) { 40 | const bs = Browsersync.create(); 41 | bs.init({ 42 | proxy: { target: host, middleware }, 43 | serveStatic: [ 44 | path.join(__dirname, '../public'), 45 | path.join(__dirname, '../node_modules/graphiql'), 46 | path.join(__dirname, '../node_modules/react/dist'), 47 | path.join(__dirname, '../node_modules/react-dom/dist'), 48 | path.join(__dirname, '../node_modules/whatwg-fetch') 49 | ] 50 | }, resolve); 51 | handleServerBundleComplete = runServer; 52 | } 53 | }); 54 | }; 55 | 56 | bundler.plugin('done', () => handleServerBundleComplete()); 57 | }); 58 | } 59 | 60 | export default start; 61 | 62 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 06/6-graphql/.DS_Store -------------------------------------------------------------------------------- /Chapter 06/6-graphql/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-2", "react"], 3 | "plugins": [ 4 | "transform-runtime" 5 | ] 6 | } -------------------------------------------------------------------------------- /Chapter 06/6-graphql/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | 4 | build 5 | database.sqlite 6 | node_modules 7 | ncp-debug.log 8 | npm-debug.log 9 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/README.md: -------------------------------------------------------------------------------- 1 | ## Learning Isomorphic Web Application Development 2 | 3 | > Example source code accompanying "Learning Isomorphic Web Application Development" book 4 | 5 | ![book](https://dl.dropboxusercontent.com/u/16006521/learning-isomorphic-web-application-development.png) 6 | 7 | ### Table of Contents 8 | 9 |      Chapter 1: Getting Started with Isomorphic Web Apps
10 |      Chapter 2: Creating a web UI with React**
11 |      Chapter 3: Working with CSS Styles and Media Assets
12 |      Chapter 4: Working with Browsersync and Hot Module Replacement
13 |   > **Chapter 5: Rendering React components on the server**
14 |      Chapter 6: Creating an API Backend with GraphQL
15 |      Chapter 7: Fetching Data with Relay
16 |      Chapter 8: Implementing Routing and Navigation
17 |      Chapter 9: Authentication and authorization
18 |      Chapter 10: Testing and Deploying
19 | 20 | ### Prerequisites 21 | 22 | * Node.js v4.0, NPM v3.0 and newer ([download](https://nodejs.org/en/download/)) 23 | * Text editor or IDE with ES6/ES2015 and JSX support 24 | * React Developer Tools ([download](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)) 25 | 26 | ### Getting Started 27 | 28 | Before you can launch the app, install project's dependencies by running: 29 | 30 | ```sh 31 | $ npm install 32 | ``` 33 | 34 | To build the project and start a development web server run: 35 | 36 | ```sh 37 | $ npm run serve 38 | ``` 39 | 40 | Then open [http://localhost:3000/](http://localhost:3000/) in your browser to test the app. 41 | 42 | ### Copyright 43 | 44 | © 2015 by Konstantin Tarkus ([@koistya](https://twitter.com/koistya)), Packt Publishing. All rights reserved. 45 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import 'babel-core/register'; 7 | import ReactDOM from 'react-dom'; 8 | import Router from './core/Router'; 9 | 10 | function run() { 11 | const location = { path: window.location.pathname }; 12 | const [component, page] = Router.match(location, window.AppState); 13 | ReactDOM.hydrate(component, document.getElementById('app'), () => { 14 | document.title = page.title; 15 | }); 16 | } 17 | 18 | const loadedStates = ['complete', 'loaded', 'interactive']; 19 | 20 | if (loadedStates.includes(document.readyState) && document.body) { 21 | run(); 22 | } else { 23 | window.addEventListener('DOMContentLoaded', run, false); 24 | } 25 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 06/6-graphql/components/.DS_Store -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/App.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import moment from 'moment'; 8 | 9 | class App extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { time: null }; 13 | } 14 | 15 | componentDidMount() { 16 | this.tick(); 17 | this.interval = setInterval(this.tick.bind(this), 200); 18 | } 19 | 20 | componentWillUnmount() { 21 | clearInterval(this.interval); 22 | } 23 | 24 | tick() { 25 | this.setState({ time: new Date() }); 26 | } 27 | 28 | render() { 29 | const time = this.state.time; 30 | const timeString = time && moment(time).format('h:mm:ss a'); 31 | return ( 32 |
33 |

Sample Application

34 |

Current date and time is {timeString}

35 |
36 | ); 37 | } 38 | } 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/Context.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | class Context extends Component { 10 | static propTypes = { 11 | page: PropTypes.any, 12 | user: PropTypes.any, 13 | children: PropTypes.element 14 | }; 15 | 16 | static childContextTypes = { 17 | page: PropTypes.shape({ 18 | title: PropTypes.string, 19 | description: PropTypes.string, 20 | status: PropTypes.number 21 | }), 22 | user: PropTypes.shape({ 23 | name: PropTypes.string.isRequired 24 | }) 25 | }; 26 | 27 | getChildContext() { 28 | return { 29 | page: this.props.page, 30 | user: this.props.user 31 | }; 32 | } 33 | 34 | render() { 35 | return React.Children.only(this.props.children); 36 | } 37 | } 38 | 39 | export default Context; 40 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/CurrentTime/CurrentTime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React from 'react'; 7 | import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; 8 | 9 | function CurrentTime() { 10 | const elem = canUseDOM && document.querySelector('.time[data-time]'); 11 | const time = elem ? +elem.dataset.time : Date.now(); 12 | return ( 13 |

14 | Current time (timestamp in ms): {time} 15 |

16 | ); 17 | } 18 | 19 | export default CurrentTime; 20 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/CurrentTime/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "CurrentTime", 4 | "main": "./CurrentTime.js" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | function Header({ children }) { 10 | return ( 11 |
12 |
13 | My App 14 | { 15 | !children && 16 |
17 | } 18 |
19 | Username 20 | 21 |
22 |
23 | {children} 24 |
25 | ); 26 | } 27 | 28 | Header.propTypes = { 29 | children: PropTypes.element 30 | }; 31 | 32 | export default Header; 33 | -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/Header/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 06/6-graphql/components/Header/header.jpg -------------------------------------------------------------------------------- /Chapter 06/6-graphql/components/Html/Html.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Learning Isomorphic Web Application Development 3 | * Copyright © 2016 Konstantin Tarkus, Packt Publishing 4 | */ 5 | 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | function Html({ title, description, body, state }) { 10 | return ( 11 | 12 | 13 | 14 | 15 | {title} 16 | 17 | 18 | 48 | 51 | ` 52 | } 53 | 54 | const PORT = process.env.PORT || 8080; 55 | app.listen(PORT, function () { 56 | console.log('Production Express server running at localhost:' + PORT) 57 | }); -------------------------------------------------------------------------------- /Chapter 07/7-routing/serverWithInitialData.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import express from 'express' 3 | // we'll use this to render our app to an html string 4 | import { renderToString } from 'react-dom/server' 5 | // and these to match the url to routes and then render 6 | import { StaticRouter } from 'react-router' 7 | import Root from './modules/Root' 8 | 9 | import path from 'path'; 10 | import { matchRoutes } from 'react-router-config' 11 | import routes from './routesConfig' 12 | import 'source-map-support/register'; 13 | 14 | const app = express(); 15 | 16 | // serve our static stuff like index.css 17 | app.use(express.static(path.join(__dirname, 'public'))); 18 | 19 | const loadBranchData = (location) => { 20 | const branch = matchRoutes(routes, location); 21 | 22 | const promises = branch.map(({ route, match }) => { 23 | return route.loadData 24 | ? route.loadData(match) 25 | : Promise.resolve(null) 26 | }); 27 | 28 | return Promise.all(promises) 29 | }; 30 | 31 | app.get('*', (req, res) => { 32 | 33 | loadBranchData(req.url).then((initialData) => { 34 | const context = {}; 35 | const html = renderToString( 36 | 39 | 40 | 41 | 42 | 43 | ); 44 | 45 | if (context.url) { 46 | // Somewhere a `` was rendered 47 | res.redirect(302, context.url); 48 | } else { 49 | res.set('content-type', 'text/html'); 50 | console.log(initialData); 51 | res.send(renderPage(html, initialData)); 52 | //res.sendFile('index.html'); // -> without initial data 53 | } 54 | }); 55 | 56 | }); 57 | 58 | function renderPage(appHtml, initialState) { 59 | return ` 60 | 61 | 62 | 63 | My First React Router App 64 | 65 |
${appHtml}
66 | 70 | 71 | ` 72 | } 73 | 74 | const PORT = process.env.PORT || 8080; 75 | app.listen(PORT, function () { 76 | console.log('Production Express server running at localhost:' + PORT) 77 | }); -------------------------------------------------------------------------------- /Chapter 07/7-routing/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: './index.js', 6 | 7 | output: { 8 | path: path.resolve('public'), 9 | filename: 'bundle.js', 10 | publicPath: '/' 11 | }, 12 | 13 | plugins: process.env.NODE_ENV === 'production' ? [ 14 | new webpack.optimize.DedupePlugin(), 15 | new webpack.optimize.OccurrenceOrderPlugin(), 16 | new webpack.optimize.UglifyJsPlugin() 17 | ] : [], 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /(node_modules)/, 24 | use: { 25 | loader: 'babel-loader' 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | devtool: 'source-map' 32 | }; 33 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 08/8-auth/.DS_Store -------------------------------------------------------------------------------- /Chapter 08/8-auth/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-2", "react"] 3 | } -------------------------------------------------------------------------------- /Chapter 08/8-auth/README.md: -------------------------------------------------------------------------------- 1 | # Instructions 2 | 3 | 1. For development: run `npm run start:dev:server` in one terminal, and `npm run start:dev:client` and connect to `http://localhost:8080/` 4 | 1. For development: run `npm run start:prod:server` in one terminal, and `npm run start:prod:client` and connect to `http://localhost:8080/` 5 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth-chapter", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start:dev:client": "webpack-dev-server --inline --content-base public/ --history-api-fallback", 6 | "start:dev:server": "webpack && babel-node serverWithInitialData.js", 7 | "start:prod:client": "webpack -p", 8 | "start:prod:server": "webpack && babel-node serverWithInitialData.js" 9 | }, 10 | "dependencies": { 11 | "express": "4.15.4", 12 | "jquery": "3.2.1", 13 | "react": "16.0.0", 14 | "react-dom": "16.0.0", 15 | "react-redux": "5.0.6", 16 | "react-router": "4.2.0", 17 | "react-router-config": "1.0.0-beta.4", 18 | "react-router-dom": "4.2.2", 19 | "redux": "3.7.2" 20 | }, 21 | "devDependencies": { 22 | "babel-cli": "6.26.0", 23 | "babel-core": "6.26.0", 24 | "babel-loader": "7.1.2", 25 | "babel-preset-env": "1.6.0", 26 | "babel-preset-react": "6.24.1", 27 | "babel-preset-stage-2": "6.24.1", 28 | "http-server": "0.10.0", 29 | "source-map-support": "0.4.18", 30 | "webpack": "3.5.6", 31 | "webpack-dev-server": "2.7.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/routesConfig.js: -------------------------------------------------------------------------------- 1 | import ReposWithRouter from './src/components/ReposWithRouter' 2 | import About from './src/components/About' 3 | import Repo from './src/components/Repo' 4 | import App from './src/components/App' 5 | import RequireAuth from './src/components/hoc/require_auth'; 6 | 7 | const routeConfig = [ 8 | { 9 | path: '/', 10 | component: App, 11 | loadData: App.loadData, 12 | routes: [ 13 | { 14 | path: '/repos', 15 | component: ReposWithRouter, 16 | loadData: About.loadData, 17 | routes: [ 18 | { 19 | path: '/repos/:userName/:repoName', 20 | component: Repo, 21 | loadData: About.loadData 22 | } 23 | ] 24 | }, 25 | { 26 | path: '/about', 27 | component: RequireAuth(About), 28 | loadData: About.loadData 29 | } 30 | ] 31 | } 32 | ]; 33 | 34 | export default routeConfig; 35 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/serverWithInitialData.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import express from 'express' 3 | // we'll use this to render our app to an html string 4 | import {renderToString} from 'react-dom/server' 5 | // and these to match the url to routes and then render 6 | import {StaticRouter} from 'react-router' 7 | import Root from './src/components/Root' 8 | 9 | import path from 'path'; 10 | import {matchRoutes} from 'react-router-config' 11 | import routes from './routesConfig' 12 | import {Provider} from 'react-redux' 13 | import { createStore } from 'redux'; 14 | import reducers from './src/reducers'; 15 | 16 | const app = express(); 17 | 18 | // serve our static stuff like index.css 19 | app.use(express.static(path.join(__dirname, 'public'))); 20 | 21 | const loadBranchData = (location) => { 22 | const branch = matchRoutes(routes, location); 23 | 24 | const promises = branch.map(({route, match}) => { 25 | return route.loadData 26 | ? route.loadData(match) 27 | : Promise.resolve(null) 28 | }); 29 | 30 | return Promise.all(promises) 31 | }; 32 | 33 | app.get('*', (req, res) => { 34 | 35 | loadBranchData(req.url).then((initialData) => { 36 | const context = {}; 37 | 38 | initialData = { auth: { authenticated : false}}; 39 | 40 | const store = createStore(reducers, initialData); 41 | 42 | const html = renderToString( 43 | 44 | 47 | 48 | 49 | 50 | ); 51 | 52 | if (context.url) { 53 | // Somewhere a `` was rendered 54 | console.log(`REDIRECTED! to ${context.url}`); 55 | res.redirect(302, context.url); 56 | } else { 57 | res.set('content-type', 'text/html'); 58 | console.log("initial data from server"); 59 | console.log(initialData); 60 | res.send(renderPage(html, initialData)); 61 | //res.sendFile('index.html'); // -> without initial data 62 | } 63 | }); 64 | 65 | }); 66 | 67 | function renderPage(appHtml, initialState) { 68 | return ` 69 | 70 | 71 | 72 | My First React Router App 73 | 74 |
${appHtml}
75 | 79 | 80 | ` 81 | } 82 | 83 | const PORT = process.env.PORT || 8080; 84 | app.listen(PORT, function () { 85 | console.log('Production Express server running at localhost:' + PORT) 86 | }); -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 08/8-auth/src/.DS_Store -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const AUTH_USER = 'auth_user'; 2 | export const UNAUTH_USER = 'unauth_user'; 3 | export const AUTH_ERROR = 'auth_error'; 4 | export const FETCH_MESSAGE = 'fetch_message'; 5 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class About extends React.Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | this.state = { 8 | msg: 'Hello' 9 | } 10 | } 11 | 12 | static loadData(){ 13 | return Promise.resolve({ 14 | data: 'Hello About' 15 | }); 16 | } 17 | 18 | changeMsg(){ 19 | this.setState({ 20 | msg: 'World' 21 | }) 22 | } 23 | 24 | render() { 25 | return
26 | {this.state.msg} 27 | 28 |
29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | import RouteWithSubRoutes from './RouteWithSubRoutes' 4 | 5 | export default class App extends React.Component { 6 | 7 | constructor(props){ 8 | super(props); 9 | this.state = { 10 | msg: "Loading..." 11 | }; 12 | } 13 | 14 | static loadData() { 15 | return Promise.resolve({ 16 | data: 'Hello App' 17 | }); 18 | } 19 | 20 | componentDidMount() { 21 | if(!this.props.initialData){ 22 | this.constructor.loadData().then((data) => { 23 | this.setState({msg: data.data}); 24 | }); 25 | } else { 26 | this.setState({msg: this.props.initialData[0].data}); 27 | console.log(`I already have the data: ${this.props.initialData}`); 28 | } 29 | } 30 | 31 | render() { 32 | return ( 33 |
34 |

React Router Tutorial

35 |
    36 |
  • Home
  • 37 |
  • About
  • 38 |
  • Repos
  • 39 |
40 | 41 |
{this.state.msg}
42 | { 43 | this.props.routes.map((route, i) => ( 44 | 45 | )) 46 | } 47 |
48 | ) 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class Home extends React.Component{ 4 | 5 | constructor(props){ 6 | super(props); 7 | } 8 | 9 | render() { 10 | return
Home
11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/Repo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class Repo extends React.Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | } 8 | 9 | render() { 10 | const { userName, repoName } = this.props.params; 11 | return ( 12 |
13 |

{userName} / {repoName}

14 |
15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/Repos.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | 4 | export default React.createClass({ 5 | contextTypes: { 6 | router: React.PropTypes.object 7 | }, 8 | 9 | handleSubmit(event) { 10 | event.preventDefault(); 11 | const userName = event.target.elements[0].value; 12 | const repo = event.target.elements[1].value; 13 | const path = `/repos/${userName}/${repo}`; 14 | this.context.router.history.push(path); 15 | }, 16 | 17 | render() { 18 | return ( 19 |
20 |

Repos

21 |
    22 |
  • React Router
  • 23 |
  • React
  • 24 |
  • 25 |
    26 | / {' '} 27 | {' '} 28 | 29 |
    30 |
  • 31 |
32 |
33 | ) 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/ReposWithRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | import {withRouter} from 'react-router' 4 | 5 | class ReposWithRouter extends React.Component{ 6 | 7 | constructor(props){ 8 | super(props); 9 | } 10 | 11 | handleSubmit(event) { 12 | event.preventDefault(); 13 | const userName = event.target.elements[0].value; 14 | const repo = event.target.elements[1].value; 15 | const path = `/repos/${userName}/${repo}`; 16 | this.props.history.push(path); 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 |

Repos

23 |
    24 |
  • React Router
  • 25 |
  • React
  • 26 |
  • 27 |
    28 | / {' '} 29 | {' '} 30 | 31 |
    32 |
  • 33 |
34 |
35 | ) 36 | } 37 | } 38 | 39 | export default withRouter(ReposWithRouter); -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import routes from '../../routesConfig' 3 | import RouteWithSubRoutes from './RouteWithSubRoutes' 4 | 5 | export default class Root extends React.Component { 6 | 7 | render() { 8 | return ( 9 |
10 | { 11 | routes.map((route, i) => ( 12 | 13 | )) 14 | } 15 |
16 | ) 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/RouteWithSubRoutes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route} from 'react-router-dom' 3 | 4 | export default (route, initialData) => ( 5 | ( 6 | // pass the sub-routes down to keep nesting 7 | 8 | )}/> 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/components/hoc/require_auth.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import {Redirect} from 'react-router-dom' 4 | import PropTypes from 'prop-types'; 5 | 6 | export default function(ComposedComponent) { 7 | class Authentication extends Component { 8 | static contextTypes = { 9 | router: PropTypes.object 10 | }; 11 | 12 | render() { 13 | if (!this.props.authenticated) { 14 | return 15 | } else{ 16 | return 17 | } 18 | } 19 | } 20 | 21 | function mapStateToProps(state) { 22 | return { authenticated: state.auth.authenticated }; 23 | } 24 | 25 | return connect(mapStateToProps)(Authentication); 26 | } -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import {Provider} from 'react-redux' 5 | import {createStore} from 'redux' 6 | import reducers from './reducers'; 7 | 8 | import Root from './components/Root' 9 | 10 | const initialData = JSON.parse(window.__INITIAL_STATE__); 11 | 12 | const store = createStore(reducers, initialData); 13 | 14 | render(( 15 | 16 | 17 | 18 | 19 | 20 | ), document.getElementById('app')); -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/reducers/auth_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | AUTH_USER, 3 | UNAUTH_USER, 4 | AUTH_ERROR, 5 | FETCH_MESSAGE 6 | } from '../actions/types'; 7 | 8 | export default function(state = {}, action) { 9 | switch(action.type) { 10 | case AUTH_USER: 11 | return { ...state, error: '', authenticated: true }; 12 | case UNAUTH_USER: 13 | return { ...state, authenticated: false }; 14 | case AUTH_ERROR: 15 | return { ...state, error: action.payload }; 16 | case FETCH_MESSAGE: 17 | return { ...state, message: action.payload }; 18 | } 19 | 20 | return state; 21 | } -------------------------------------------------------------------------------- /Chapter 08/8-auth/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import authReducer from './auth_reducer'; 3 | 4 | const rootReducer = combineReducers({ 5 | auth: authReducer 6 | }); 7 | 8 | export default rootReducer; -------------------------------------------------------------------------------- /Chapter 08/8-auth/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | 7 | output: { 8 | path: path.resolve('public'), 9 | filename: 'bundle.js', 10 | publicPath: '/' 11 | }, 12 | 13 | plugins: process.env.NODE_ENV === 'production' ? [ 14 | new webpack.optimize.DedupePlugin(), 15 | new webpack.optimize.OccurrenceOrderPlugin(), 16 | new webpack.optimize.UglifyJsPlugin() 17 | ] : [], 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /(node_modules)/, 24 | use: { 25 | loader: 'babel-loader' 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | devtool: 'source-map' 32 | }; 33 | -------------------------------------------------------------------------------- /Chapter 08/__MACOSX/8-auth/._.DS_Store: -------------------------------------------------------------------------------- 1 | Mac OS X  2Fx @ATTRxx -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 09/B05226_09_Code/.DS_Store -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-2", "react"] 3 | } -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/README.md: -------------------------------------------------------------------------------- 1 | # Instructions 2 | 3 | ## Unit Tests 4 | 1. npm run test 5 | 6 | ## Integration Tests 7 | 1. npm run start:prod:server 8 | 2. npm run it -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/nightwatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_folders" : ["src/tests/it/"], 3 | 4 | "selenium" : { 5 | "start_process" : true, 6 | "server_path" : "/Users/talabes/Documents/MyProjects/selenium-server-standalone-3.4.0.jar" 7 | }, 8 | 9 | "test_runner" : "mocha", 10 | 11 | "test_settings" : { 12 | "default" : { 13 | "launch_url" : "http://localhost", 14 | "selenium_port" : 4444, 15 | "selenium_host" : "localhost", 16 | "silent": true, 17 | "screenshots" : { 18 | "enabled" : false, 19 | "path" : "" 20 | }, 21 | "desiredCapabilities": { 22 | "browserName": "chrome" 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tutorial", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start:dev:client": "webpack-dev-server --inline --content-base public/ --history-api-fallback", 8 | "start:dev:server": "webpack && babel-node serverWithInitialData.js", 9 | "start:prod:client": "webpack -p", 10 | "start:prod:server": "webpack && babel-node serverWithInitialData.js", 11 | "test": "mocha --ui tdd --compilers js:babel-register src/tests/*Test.js", 12 | "it": "nightwatch --config nightwatch.json" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "express": "4.15.4", 18 | "jquery": "3.2.1", 19 | "react": "16.0.0", 20 | "react-dom": "16.0.0", 21 | "react-redux": "5.0.6", 22 | "react-router": "4.2.0", 23 | "react-router-config": "1.0.0-beta.4", 24 | "react-router-dom": "4.2.2", 25 | "redux": "3.7.2" 26 | }, 27 | "devDependencies": { 28 | "babel-cli": "6.26.0", 29 | "babel-core": "6.26.0", 30 | "babel-loader": "7.1.2", 31 | "babel-preset-env": "1.6.0", 32 | "babel-preset-react": "6.24.1", 33 | "babel-preset-stage-2": "6.24.1", 34 | "babel-register": "^6.26.0", 35 | "chai": "4.1.2", 36 | "chai-enzyme": "1.0.0-beta.0", 37 | "chai-jquery": "2.0.0", 38 | "enzyme": "^3.1.0", 39 | "enzyme-adapter-react-16": "^1.0.1", 40 | "http-server": "0.10.0", 41 | "jsdom": "11.3.0", 42 | "mocha": "3.5.3", 43 | "nightwatch": "0.9.16", 44 | "react-test-renderer": "16.0.0", 45 | "source-map-support": "0.4.18", 46 | "webpack": "3.5.6", 47 | "webpack-dev-server": "2.7.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/routesConfig.js: -------------------------------------------------------------------------------- 1 | import ReposWithRouter from './src/components/ReposWithRouter' 2 | import About from './src/components/About' 3 | import Repo from './src/components/Repo' 4 | import App from './src/components/App' 5 | import RequireAuth from './src/components/hoc/require_auth'; 6 | 7 | const routeConfig = [ 8 | { 9 | path: '/', 10 | component: App, 11 | loadData: App.loadData, 12 | routes: [ 13 | { 14 | path: '/repos', 15 | component: ReposWithRouter, 16 | loadData: About.loadData, 17 | routes: [ 18 | { 19 | path: '/repos/:userName/:repoName', 20 | component: Repo, 21 | loadData: About.loadData 22 | } 23 | ] 24 | }, 25 | { 26 | path: '/about', 27 | component: RequireAuth(About), 28 | loadData: About.loadData 29 | } 30 | ] 31 | } 32 | ]; 33 | 34 | export default routeConfig; 35 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Chapter 09/B05226_09_Code/src/.DS_Store -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const AUTH_USER = 'auth_user'; 2 | export const UNAUTH_USER = 'unauth_user'; 3 | export const AUTH_ERROR = 'auth_error'; 4 | export const FETCH_MESSAGE = 'fetch_message'; 5 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class About extends React.Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | this.state = { 8 | msg: 'Hello' 9 | } 10 | } 11 | 12 | static loadData(){ 13 | return Promise.resolve({ 14 | data: 'Hello About' 15 | }); 16 | } 17 | 18 | changeMsg(){ 19 | this.setState({ 20 | msg: 'World' 21 | }) 22 | } 23 | 24 | render() { 25 | return
26 | {this.state.msg} 27 | 28 |
29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | import RouteWithSubRoutes from './RouteWithSubRoutes' 4 | 5 | export default class App extends React.Component { 6 | 7 | constructor(props){ 8 | super(props); 9 | this.state = { 10 | msg: "Loading..." 11 | }; 12 | } 13 | 14 | static loadData() { 15 | return Promise.resolve({ 16 | data: 'Hello App' 17 | }); 18 | } 19 | 20 | componentDidMount() { 21 | if(!this.props.initialData){ 22 | this.constructor.loadData().then((data) => { 23 | this.setState({msg: data.data}); 24 | }); 25 | } else { 26 | this.setState({msg: this.props.initialData[0].data}); 27 | console.log(`I already have the data: ${this.props.initialData}`); 28 | } 29 | } 30 | 31 | render() { 32 | return ( 33 |
34 |

React Router Tutorial

35 |
    36 |
  • Home
  • 37 |
  • About
  • 38 |
  • Repos
  • 39 |
40 | 41 |
{this.state.msg}
42 | { 43 | this.props.routes.map((route, i) => ( 44 | 45 | )) 46 | } 47 |
48 | ) 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class Home extends React.Component{ 4 | 5 | constructor(props){ 6 | super(props); 7 | } 8 | 9 | render() { 10 | return
Home
11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/Repo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class Repo extends React.Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | } 8 | 9 | render() { 10 | const { userName, repoName } = this.props.params; 11 | return ( 12 |
13 |

{userName} / {repoName}

14 |
15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/Repos.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | 4 | export default React.createClass({ 5 | contextTypes: { 6 | router: React.PropTypes.object 7 | }, 8 | 9 | handleSubmit(event) { 10 | event.preventDefault(); 11 | const userName = event.target.elements[0].value; 12 | const repo = event.target.elements[1].value; 13 | const path = `/repos/${userName}/${repo}`; 14 | this.context.router.history.push(path); 15 | }, 16 | 17 | render() { 18 | return ( 19 |
20 |

Repos

21 |
    22 |
  • React Router
  • 23 |
  • React
  • 24 |
  • 25 |
    26 | / {' '} 27 | {' '} 28 | 29 |
    30 |
  • 31 |
32 |
33 | ) 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/ReposWithRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | import {withRouter} from 'react-router' 4 | 5 | class ReposWithRouter extends React.Component{ 6 | 7 | constructor(props){ 8 | super(props); 9 | } 10 | 11 | handleSubmit(event) { 12 | event.preventDefault(); 13 | const userName = event.target.elements[0].value; 14 | const repo = event.target.elements[1].value; 15 | const path = `/repos/${userName}/${repo}`; 16 | this.props.history.push(path); 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 |

Repos

23 |
    24 |
  • React Router
  • 25 |
  • React
  • 26 |
  • 27 |
    28 | / {' '} 29 | {' '} 30 | 31 |
    32 |
  • 33 |
34 |
35 | ) 36 | } 37 | } 38 | 39 | export default withRouter(ReposWithRouter); -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import routes from '../../routesConfig' 3 | import RouteWithSubRoutes from './RouteWithSubRoutes' 4 | 5 | export default class Root extends React.Component { 6 | 7 | render() { 8 | return ( 9 |
10 | { 11 | routes.map((route, i) => ( 12 | 13 | )) 14 | } 15 |
16 | ) 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/RouteWithSubRoutes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route} from 'react-router-dom' 3 | 4 | export default (route, initialData) => ( 5 | ( 6 | // pass the sub-routes down to keep nesting 7 | 8 | )}/> 9 | ); 10 | -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/components/hoc/require_auth.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import {Redirect} from 'react-router-dom' 4 | import PropTypes from 'prop-types'; 5 | 6 | export default function(ComposedComponent) { 7 | class Authentication extends Component { 8 | static contextTypes = { 9 | router: PropTypes.object 10 | }; 11 | 12 | render() { 13 | if (!this.props.authenticated) { 14 | return 15 | } else{ 16 | return 17 | } 18 | } 19 | } 20 | 21 | function mapStateToProps(state) { 22 | return { authenticated: state.auth.authenticated }; 23 | } 24 | 25 | return connect(mapStateToProps)(Authentication); 26 | } -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import {Provider} from 'react-redux' 5 | import {createStore} from 'redux' 6 | import reducers from './reducers'; 7 | 8 | import Root from './components/Root' 9 | 10 | const initialData = JSON.parse(window.__INITIAL_STATE__); 11 | 12 | const store = createStore(reducers, initialData); 13 | 14 | render(( 15 | 16 | 17 | 18 | 19 | 20 | ), document.getElementById('app')); -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/reducers/auth_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | AUTH_USER, 3 | UNAUTH_USER, 4 | AUTH_ERROR, 5 | FETCH_MESSAGE 6 | } from '../actions/types'; 7 | 8 | export default function(state = {}, action) { 9 | switch(action.type) { 10 | case AUTH_USER: 11 | return { ...state, error: '', authenticated: true }; 12 | case UNAUTH_USER: 13 | return { ...state, authenticated: false }; 14 | case AUTH_ERROR: 15 | return { ...state, error: action.payload }; 16 | case FETCH_MESSAGE: 17 | return { ...state, message: action.payload }; 18 | } 19 | 20 | return state; 21 | } -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import authReducer from './auth_reducer'; 3 | 4 | const rootReducer = combineReducers({ 5 | auth: authReducer 6 | }); 7 | 8 | export default rootReducer; -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/tests/EnzymeTest.js: -------------------------------------------------------------------------------- 1 | import { MemoryRouter } from 'react-router-dom' 2 | import About from '../components/About' 3 | import chai, {expect} from 'chai'; 4 | 5 | import {Provider} from 'react-redux' 6 | import {createStore} from 'redux' 7 | import reducers from './../reducers'; 8 | 9 | import React from 'react'; 10 | 11 | import { configure, mount, shallow, render } from 'enzyme'; 12 | import chaiEnzyme from 'chai-enzyme' 13 | import Adapter from 'enzyme-adapter-react-16'; 14 | 15 | configure({ adapter: new Adapter() }); 16 | 17 | chai.use(chaiEnzyme()); 18 | 19 | suite('Enzyme test suite', function () { 20 | 21 | test('simple dom test', function () { 22 | const component = render( 23 | 24 | ); 25 | expect(component.find('.about-msg')).to.have.text('Hello'); 26 | }); 27 | 28 | test('simple test', function () { 29 | const $node = shallow( 30 | 31 | ); 32 | expect($node.find('.about-msg')).to.have.text('Hello'); 33 | }); 34 | 35 | test('test router', function () { 36 | const $node = mount( 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | 44 | $node.find('.about-btn').simulate('click'); 45 | 46 | expect($node.find('.about-msg').text()).to.equal('World'); 47 | }); 48 | 49 | 50 | 51 | }); -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/tests/PlainTest.js: -------------------------------------------------------------------------------- 1 | import TestUtils from 'react-dom/test-utils'; 2 | import { Route, Link, MemoryRouter } from 'react-router-dom' 3 | import About from '../components/About' 4 | import chai, {expect} from 'chai'; 5 | import chaiJquery from 'chai-jquery'; 6 | import {JSDOM} from 'jsdom'; 7 | import jquery from 'jquery'; 8 | 9 | import {Provider} from 'react-redux' 10 | import {createStore} from 'redux' 11 | import reducers from './../reducers'; 12 | 13 | import React from 'react'; 14 | import ReactDOM from 'react-dom'; 15 | 16 | 17 | function setupTests() { 18 | const dom = new JSDOM(''); 19 | global.window = dom.window; 20 | global.document = dom.window.document; 21 | const $ = jquery(global.window); 22 | // Set up chai-jquery 23 | chaiJquery(chai, chai.util, $); 24 | 25 | return $; 26 | } 27 | 28 | require('fbjs/lib/ExecutionEnvironment').canUseDOM = true; 29 | 30 | suite('test suite', function () { 31 | 32 | const $ = setupTests(); 33 | 34 | test('simple dom test', function () { 35 | const component = TestUtils.renderIntoDocument( 36 | 37 | ); 38 | const renderedComponent = ReactDOM.findDOMNode(component); 39 | expect(renderedComponent.querySelector('.about-msg').textContent).to.equal('Hello'); 40 | }); 41 | 42 | test('simple test', function () { 43 | const $node = $(ReactDOM.findDOMNode(TestUtils.renderIntoDocument( 44 | 45 | ))); 46 | expect($node.find('.about-msg')).to.have.text('Hello'); 47 | }); 48 | 49 | test('test router', function () { 50 | const $node = $(ReactDOM.findDOMNode(TestUtils.renderIntoDocument( 51 | 52 | 53 | 54 | 55 | 56 | ))); 57 | 58 | TestUtils.Simulate.click($node.find('.about-btn')[0], { 59 | button: 0 60 | }); 61 | 62 | expect($node.find('.about-msg').text()).to.equal('World'); 63 | }); 64 | 65 | 66 | 67 | }); -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/tests/it/NightwatchTest.js: -------------------------------------------------------------------------------- 1 | describe('Testing our app', function() { 2 | 3 | describe('with Nightwatch', function() { 4 | 5 | before(function(client, done) { 6 | done(); 7 | }); 8 | 9 | after(function(client, done) { 10 | client.end(function() { 11 | done(); 12 | }); 13 | }); 14 | 15 | afterEach(function(client, done) { 16 | done(); 17 | }); 18 | 19 | beforeEach(function(client, done) { 20 | done(); 21 | }); 22 | 23 | it('testing navigation to repos', function(client) { 24 | client 25 | .url('http://localhost:8080') 26 | .expect.element('body').to.be.present.before(1000); 27 | 28 | client.click('.repos-link', function () { 29 | client.expect.element('.repos-title').text.to.equal('Repos'); 30 | client.assert.urlEquals('http://localhost:8080/repos'); 31 | }) 32 | }); 33 | 34 | it('testing url navigation to repos', function(client) { 35 | client 36 | .url('http://localhost:8080/repos'); 37 | 38 | // now we navigate to repos directly 39 | client.expect.element('.repos-title').text.to.equal('Repos'); 40 | }); 41 | 42 | }); 43 | }); -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/src/tests/tests_helper.js: -------------------------------------------------------------------------------- 1 | import {JSDOM} from 'jsdom'; 2 | import jquery from 'jquery'; 3 | import TestUtils from 'react-dom/test-utils'; // ES6 4 | import ReactDOM from 'react-dom'; 5 | import chai, {expect} from 'chai'; 6 | import React from 'react'; 7 | import {Provider} from 'react-redux'; 8 | import {createStore} from 'redux'; 9 | import chaiJquery from 'chai-jquery'; 10 | 11 | // Set up testing environment to run like a browser in the command line 12 | const dom = new JSDOM(''); 13 | global.window = dom.window; 14 | global.document = dom.window.document; 15 | const $ = jquery(global.window); 16 | 17 | // build 'renderComponent' helper that should render a given react class 18 | function renderComponent(ComponentClass, props, state) { 19 | const componentInstance = TestUtils.renderIntoDocument( 20 | state, state)}> 21 | 22 | 23 | ); 24 | 25 | return $(ReactDOM.findDOMNode(componentInstance)); // produces HTML 26 | } 27 | 28 | // Build helper for simulating events 29 | $.fn.simulate = function (eventName, value) { 30 | if (value) { 31 | this.val(value); 32 | } 33 | TestUtils.Simulate[eventName](this[0]); 34 | }; 35 | 36 | // Set up chai-jquery 37 | chaiJquery(chai, chai.util, $); 38 | 39 | export {renderComponent, expect}; -------------------------------------------------------------------------------- /Chapter 09/B05226_09_Code/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | 7 | output: { 8 | path: path.resolve('public'), 9 | filename: 'bundle.js', 10 | publicPath: '/' 11 | }, 12 | 13 | plugins: process.env.NODE_ENV === 'production' ? [ 14 | new webpack.optimize.DedupePlugin(), 15 | new webpack.optimize.OccurrenceOrderPlugin(), 16 | new webpack.optimize.UglifyJsPlugin() 17 | ] : [], 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /(node_modules|bower_components)/, 24 | use: { 25 | loader: 'babel-loader', 26 | options: { 27 | presets: ["env", "stage-2", "react"] 28 | } 29 | } 30 | } 31 | ] 32 | }, 33 | 34 | devtool: 'source-map' 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Isomorphic JavaScript Web Development 2 | This is the code repository for [Isomorphic JavaScript Web Development](https://www.packtpub.com/web-development/isomorphic-javascript-web-development?utm_source=github&utm_medium=repository&utm_campaign=9781785889769), published by [Packt](https://www.packtpub.com/). It contains all the supporting project files necessary to work through the book from start to finish. 3 | ## About the Book 4 | The latest trend in web development, Isomorphic JavaScript, allows developers to overcome some of the shortcomings of single-page applications by running the same code on the server as well as on the client. Leading this trend is React, which, when coupled with Node, allows developers to build JavaScript apps that are much faster and more SEO-friendly than single-page applications. 5 | ### Instructions and Navigations 6 | All of the codes are organized as per the chapters, each folder has the codes related to that chapter or appendix. 7 | For example: Isomorphic-JavaScript-Web-Development/Chapter 04/4-browsersync/components/App.js 8 | 9 | The code will look like the following: 10 | ``` 11 | constructor(props) { 12 | super(props); 13 | this.state = { time: null }; 14 | } 15 | ``` 16 | 17 | ## Related Products 18 | 19 | 20 | * [Mastering MEAN Web Development: Expert Full Stack JavaScript [Video]](https://www.packtpub.com/web-development/mastering-mean-web-development-expert-full-stack-javascript-video?utm_source=github&utm_medium=repository&utm_campaign=9781785882159) 21 | 22 | 23 | * [Isomorphic Go](https://www.packtpub.com/web-development/isomorphic-go?utm_source=github&utm_medium=repository&utm_campaign=9781788394185) 24 | 25 | 26 | * [JavaScript by Example](https://www.packtpub.com/web-development/javascript-example?utm_source=github&utm_medium=repository&utm_campaign=9781788293969) 27 | 28 | -------------------------------------------------------------------------------- /Software and Hardware List.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Isomorphic-JavaScript-Web-Development/c3589be168397c7594090b9fa1a15bf8b25dd188/Software and Hardware List.pdf --------------------------------------------------------------------------------