Browse Source

Add sample for server-side rendering of a React app (#1)

* add sample for server-side rendering of a react app

* update main Readme
master
Xin Wei 8 years ago
committed by GitHub
parent
commit
26e1468905
  1. 9
      README.md
  2. 3
      isomorphic-react-app/.gitignore
  3. 38
      isomorphic-react-app/README.md
  4. 5
      isomorphic-react-app/database.rules.json
  5. 14
      isomorphic-react-app/firebase.json
  6. 115
      isomorphic-react-app/functions/data-seed.json
  7. 59
      isomorphic-react-app/functions/firebase-database.js
  8. 60
      isomorphic-react-app/functions/index.js
  9. 13
      isomorphic-react-app/functions/package.json
  10. 47
      isomorphic-react-app/functions/template.js
  11. 134
      isomorphic-react-app/public/assets/custom.css
  12. 50
      isomorphic-react-app/src/components/Home.jsx
  13. 118
      isomorphic-react-app/src/components/User.jsx
  14. 71
      isomorphic-react-app/src/containers/App.jsx
  15. 11
      isomorphic-react-app/src/containers/ClientApp.js
  16. 20
      isomorphic-react-app/src/containers/ServerApp.jsx
  17. 30
      isomorphic-react-app/src/package.json
  18. 27
      isomorphic-react-app/src/webpack.client.config.js
  19. 37
      isomorphic-react-app/src/webpack.config.js
  20. 32
      isomorphic-react-app/src/webpack.server.config.js

9
README.md

@ -38,6 +38,9 @@ This quickstart sample demonstrates using **Cloud Functions** triggered by **Fir
This quickstart sample demonstrates using **Cloud Functions** triggered by **PubSub events**. The functions log the PubSub payload in a Hello world message.
### [Authenticated JSON API](/authenticated-json-api)
This sample shows how to authenticate access to a JSON API to only allow access to data for a specific Firebase user.
### [Authorized HTTP endpoint](/authorized-https-endpoint)
This samples shows how to restrict an HTTPS Function to only the Firebase users of your app.
@ -64,11 +67,17 @@ Uses a Realtime Database trigger.
Demonstrates how to authorize with a 3rd party sign-in mechanism (LinkedIn in this case), create a Firebase custom auth token, update the user's profile and authorize Firebase.
Uses an HTTP trigger.
### [Image Maker](/image-maker)
This sample demonstrates how to create various customized images through Cloud Functions and Hosting and serve it to the client.
### [Authorize with Instagram](/instagram-auth)
Demonstrates how to authorize with a 3rd party sign-in mechanism (Instagram in this case), create a Firebase custom auth token, update the user's profile and authorize Firebase.
Uses an HTTP trigger.
### [Isomorphic React App](/isomorphic-react-app)
This sample demonstrates how to create an isomorphic React application with server-side data fetching. This sample uses Cloud Functions, Hosting, and the Realtime Database.
### [Authorize with LINE](/line-auth)
Demonstrates how to authorize with a 3rd party sign-in mechanism (LINE in this case), create a Firebase custom auth token, update the user's profile and authorize Firebase.

3
isomorphic-react-app/.gitignore

@ -0,0 +1,3 @@
.firebaserc
node_modules
*.bundle.js

38
isomorphic-react-app/README.md

@ -0,0 +1,38 @@
# Isomorphic React App
This sample shows how to create an isomorphic React application using using Functions, Hosting, and Realtime Database.
During the initial request to your app, Hosting will proxy your request to a Node express server.
The server will then load any data that's necessary for your app (through the Realtime Database). It will also generate the markup that's needed from React. It will inject the markup with the preloaded state before passing it to the client.
This sample uses `react-router` to demonstrate simple routing logic for React.
The `src` folder contains the source code for React app. It also uses [Webpack](https://github.com/webpack/webpack) to bundle the app and generate two bundles, one for the server, and one for the client. The server bundle is required by the express server to generate the initial markup, and the client bundle is included in the `functions/template.js` - the markup that's being passed onto the client.
## Setting up the sample
1. Create a Firebase Project using the [Firebase Console](https://console.firebase.google.com).
1. Clone or download this repo and open the `isomorphic-react-app` directory.
1. You must have the Firebase CLI installed. If you don't have it install it with `npm install -g firebase-tools` and then configure it with `firebase login`.
1. Configure the CLI locally by using `firebase use --add` and select your project in the list.
1. Install dependencies locally by running: `cd functions; npm install; cd ../src; npm install`
1. Run `npm run build` within the `src` folder to start webpack, which will bundle the app. It will output `functions/build/server.bundle.js` and `public/assets/client.bundle.js`
1. Import the sample `functions/data-seed.json` to your Firebase Realtime Database. For more details, see [https://support.google.com/firebase/answer/6386780?hl=en#import](https://support.google.com/firebase/answer/6386780?hl=en#import)
## Deploy and test
This sample comes with a web-based UI for testing the function. To test it out:
1. Deploy your project using `firebase deploy`
1. Open the app using `firebase open hosting:site`, this will open a browser.
## Contributing
We'd love that you contribute to the project. Before doing so please read our [Contributor guide](../CONTRIBUTING.md).
## License
© Google, 2017. Licensed under an [Apache-2](../LICENSE) license.

5
isomorphic-react-app/database.rules.json

@ -0,0 +1,5 @@
{
"rules": {
".read": true
}
}

14
isomorphic-react-app/firebase.json

@ -0,0 +1,14 @@
{
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"function": "app"
}
]
},
"database": {
"rules": "database.rules.json"
}
}

115
isomorphic-react-app/functions/data-seed.json

@ -0,0 +1,115 @@
{
"employees" : {
"berthaye" : {
"birthday" : "November 11, 1992",
"email" : "berthaye@fakecompany.com",
"id" : "berthaye",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/berthaye.png?alt=media&token=e65a55b1-f27d-403b-ad70-8ae89a081303",
"level" : 1,
"location" : "San Francisco, CA, USA",
"name" : "Bertha Ye",
"number" : "1-570-788-4003",
"position" : "Software Engineer",
"twitter" : "@fake-bertha"
},
"georgebrown" : {
"birthday" : "April 3, 1982",
"email" : "georgebrown@fakecompany.com",
"id" : "georgebrown",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/georgebrown.png?alt=media&token=d958b887-ff50-4166-a8c0-a032304abf6f",
"level" : 5,
"location" : "San Francisco, CA, USA",
"name" : "George Brown",
"number" : "1-707-658-7561",
"position" : "President and CEO",
"reports" : {
"lydiabennet" : true,
"marcelovaldez" : true,
"tanyabearden" : true
},
"twitter" : "@fake-georgebrown"
},
"kellyelrod" : {
"birthday" : "July 8, 1989",
"email" : "kellyelrod@fakecompany.com",
"id" : "kellyelrod",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/kellyelrod.png?alt=media&token=bd2501c9-2491-4c00-9d40-53923485c836",
"level" : 2,
"location" : "Portland, CA, USA",
"name" : "Kelly Elrod",
"number" : "1-847-384-3036",
"position" : "Marketing Manager",
"twitter" : "@fake-kelly"
},
"lydiabennet" : {
"birthday" : "November 20, 1979",
"email" : "lydiabennet@fakecompany.com",
"id" : "lydiabennet",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/lydiabennet.png?alt=media&token=e022d0c2-df05-49d5-945c-d1f29e2e2b71",
"level" : 4,
"location" : "San Francisco, CA, USA",
"name" : "Lydia Bennet",
"number" : "1-309-423-4299",
"position" : "VP of Marketing",
"reports" : {
"kellyelrod" : true
},
"twitter" : "@fake-lydia"
},
"marcelovaldez" : {
"birthday" : "May 18, 1980",
"email" : "marcelovaldez@fakecompany.com",
"id" : "marcelovaldez",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/marcelovaldez.png?alt=media&token=e8ddfda2-5395-40dc-baf1-197dbae9318d",
"level" : 4,
"location" : "Boston, MA, USA",
"name" : "Marcelo Valdez",
"number" : "1-978-401-9913",
"position" : "VP of Sales",
"reports" : {
"staceyhansen" : true
},
"twitter" : "@fake-marcelo"
},
"staceyhansen" : {
"birthday" : "April 29, 1990",
"email" : "staceyhansen@fakecompany.com",
"id" : "staceyhansen",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/staceyhansen.png?alt=media&token=9100f50f-2740-4dd2-bf1d-58e066e0722b",
"level" : 1,
"location" : "San Francisco, CA, USA",
"name" : "Stacey Hansen",
"number" : "1-936-524-2834",
"position" : "Sales Representative",
"twitter" : "@fake-leslie"
},
"tanyabearden" : {
"birthday" : "December 20, 1982",
"email" : "tanyabearden@fakecompany.com",
"id" : "tanyabearden",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/tanyabearden.png?alt=media&token=bb575a43-8cef-43b0-b680-9bc400f9b181",
"level" : 4,
"location" : "Portland, OR, USA",
"name" : "Tanya Bearden",
"number" : "1-518-822-6241",
"position" : "VP of Engineering",
"reports" : {
"berthaye" : true,
"tiffanycaldwell" : true
},
"twitter" : "@fake-tanya"
},
"tiffanycaldwell" : {
"birthday" : "April 10, 1991",
"email" : "tiffanycaldwell@fakecompany.com",
"id" : "tiffanycaldwell",
"image" : "https://firebasestorage.googleapis.com/v0/b/oompa-loompa-aeb91.appspot.com/o/tiffanycaldwell.png?alt=media&token=1f6c4a8d-4567-4a8a-9673-77295e8e950f",
"level" : 2,
"location" : "Boston, CA, USA",
"name" : "Tiffany Caldwell",
"number" : "1-336-524-5631",
"position" : "Sr Software Engineer",
"twitter" : "@fake-tiffany"
}
}
}

59
isomorphic-react-app/functions/firebase-database.js

@ -0,0 +1,59 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Client and Server Data Fetching Logic
// Uses either the client firebase (initialized from hosting init script)
// Or serverside firebase
const firebase = global.firebase || require('firebase');
// Initialize Firebase SDK
// Only should be called once on the server
// the client should already be initialized from hosting init script
const initializeApp = (config) => {
if (firebase.apps.length === 0) {
firebase.initializeApp(config);
}
}
// Get and return all employees
const getAllEmployees = () => {
return firebase.database().ref('/employees').orderByChild('level').once('value').then((snap) => {
return { employees: snap.val() };
});
};
// Get and return an employee by their id number
// also fetch all of the employee's direct reports (if any)
const getEmployeeById = (employeeId) => {
return firebase.database().ref(`/employees/${employeeId}`).once('value').then((snap) => {
const promises = [];
const snapshot = snap.val();
if (snapshot.reports) {
Object.keys(snapshot.reports).forEach((userId) => {
promises.push(firebase.database().ref(`/employees/${userId}`).once('value').then((snap) => snap.val()));
})
}
return firebase.Promise.all(promises).then((resp) => {
return { currentEmployee: { employee: snapshot, reports: resp } };
});
});
};
module.exports = {
getAllEmployees,
getEmployeeById,
initializeApp
};

60
isomorphic-react-app/functions/index.js

@ -0,0 +1,60 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const functions = require('firebase-functions');
const firebase = require('firebase');
const app = require('express')();
const React = require('react');
const ReactDOMServer = require('react-dom/server');
// React App
const ServerApp = React.createFactory(require('./build/server.bundle.js').default);
const template = require('./template');
// Server-side Data Loading
const appConfig = functions.config().firebase;
const database = require('./firebase-database');
database.initializeApp(appConfig);
// Helper function to get the markup from React, inject the initial state, and
// send the server-side markup to the client
const renderApplication = (url, res, initialState) => {
const html = ReactDOMServer.renderToString(ServerApp({url: url, context: {}, initialState, appConfig }));
const templatedHtml = template({ body: html, initialState: JSON.stringify(initialState)});
res.send(templatedHtml);
};
app.get('/favicon.ico', function(req, res) {
res.send(204);
});
app.get('/:userId?', (req, res) => {
res.set('Cache-Control', 'public, max-age=60, s-maxage=180');
if (req.params.userId) {
// client is requesting user-details page with userId
// load the data for that employee and its direct reports
database.getEmployeeById(req.params.userId).then((resp) => {
renderApplication(req.url, res, resp);
});
} else {
// index page. load data for all employees
database.getAllEmployees().then((resp) => {
renderApplication(req.url, res, resp);
});
}
});
exports.app = functions.https.onRequest(app);

13
isomorphic-react-app/functions/package.json

@ -0,0 +1,13 @@
{
"name": "functions-isomorphic-react-app",
"description": "A sample isomorphic React app using Functions and Hosting",
"dependencies": {
"express": "4.15.2",
"firebase": "3.9.0",
"firebase-admin": "4.0.5",
"firebase-functions": "0.5.5",
"react": "15.5.4",
"react-dom": "15.5.4",
"request": "2.81.0"
}
}

47
isomorphic-react-app/functions/template.js

@ -0,0 +1,47 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Template file that the server will use to inject the React markup and
// initial state before sending it to the client
const template = function(opts) {
return `
<!DOCTYLE html>
<html>
<head>
<title>Employee Directory</title>
<meta name="description" content="Demonstrates how to dynamically generate images on the server">
<!-- Bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
<!-- Custom CSS -->
<link rel="stylesheet" href="/assets/custom.css" />
</head>
<body>
<div id="root">${opts.body}</div>
</body>
<script>
window.__initialState = ${opts.initialState}
</script>
<script src="/__/firebase/3.9.0/firebase-app.js"></script>
<script src="/__/firebase/3.9.0/firebase-database.js"></script>
<script src="/__/firebase/init.js"></script>
<!-- Client bundle generated by webpack -->
<script src='/assets/client.bundle.js'></script>
</html>
`;
}
module.exports = template;

134
isomorphic-react-app/public/assets/custom.css

@ -0,0 +1,134 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Custom CSS */
body {
font-family: Lato,'Helvetica Neue',Helvetica,Arial,sans-serif;
}
.navbar-custom {
background: #2C3E50;
text-transform: uppercase;
font-weight: 700;
border: none;
padding: 20px 0;
}
.navbar-custom .navbar-brand {
color: #fff;
font-size: 1.5em;
}
.navbar-custom .navbar-brand:hover {
color: gray;
}
ul.cards {
margin-top: 20px;
}
li.card-inline {
display: inline-block;
width: 300px;
height: 326px;
margin: 5px;
text-align: center;
}
img.card-image {
width: 150px;
padding-top: 20px;
}
.user-details {
margin-bottom: 40px;
}
.hero {
text-align: center;
background: #18BC9C;
padding: 20px;
color: #fff;
}
img.employee-image {
width: 350px;
margin: 10px 0 20px;
}
td.title {
text-align: right;
font-weight: 700;
}
.callout {
padding: 20px;
margin: 20px auto;
border: 1px solid #eee;
border-left-width: 5px;
border-radius: 3px;
width: 1000px;
border-left-color: #ce4844;
}
.callout h4 {
color: #ce4844;
margin-top: 0;
margin-bottom: 5px;
}
.details-table, .reports {
width: 1000px;
margin: 0 auto;
}
.reports {
margin-top: 30px;
}
img.report-img {
width: 100px;
padding-right: 10px;
}
.report-desc {
width: 230px;
}
.report-desc p {
margin: 0;
color: gray
}
.direct-report {
display: inline-block;
}
.direct-report .card {
width: 352px;
margin: 5px;
padding: 10px;
flex-direction: row;
align-items: center;
justify-content: center;
}
.loader {
color: gray;
font-size: 11px;
margin-top: 30px;
}

50
isomorphic-react-app/src/components/Home.jsx

@ -0,0 +1,50 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { Link } from 'react-router-dom';
import _ from 'lodash';
export default class Home extends React.Component {
componentDidMount() {
// if no data is present, load the employees data
if(_.isEmpty(this.props.employees)) {
this.props.getAllEmployees();
}
}
render() {
if(_.isEmpty(this.props.employees)) {
return <div className="container loader">Loading...</div>;
}
return (
<div className='container home'>
<ul className="cards">
{_.reverse(_.sortBy(this.props.employees, 'level')).map((employee) => {
return (<li className="card card-inline" key={employee.id}>
<img className="card-img-top card-image" src={employee.image}/>
<div className="card-block">
<h4 className="card-title">{employee.name}</h4>
<p className="card-text">{employee.position}</p>
<Link to={`/${employee.id}`} className="btn">Details</Link>
</div>
</li>)
})}
</ul>
</div>
);
}
}

118
isomorphic-react-app/src/components/User.jsx

@ -0,0 +1,118 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { Link } from 'react-router-dom';
import _ from 'lodash';
export default class User extends React.Component {
componentDidMount() {
// if no data is present, load the employee data from the database
if(_.isEmpty(this.props.currentEmployee) ||
this.props.currentEmployee.employee.id !== this.props.match.params.id) {
this.props.getEmployeeById(this.props.match.params.id);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.match.params.id !== this.props.match.params.id) {
// user has navigated to a new employee details page
// load data for that employee and set to state
this.props.getEmployeeById(nextProps.match.params.id);
}
}
// render function for direct reports
renderReports() {
const { reports, employee } = this.props.currentEmployee;
if (!reports || _.isEmpty(reports)) {
// Employee has no direct reports
return null;
}
return (
<div className='reports'>
<h3>Direct Reports</h3>
{ reports.map((report) => {
return (
<div className='direct-report' key={`${employee.id}-${report.id}`}>
<div className='card'>
<img className='card-img-left report-img' src={report.image} />
<div className='report-desc'>
<h3>{report.name}</h3>
<p>{report.position}</p>
<Link to={`/${report.id}`}>View {report.id}'s profile</Link>
</div>
</div>
</div>
);
}) }
</div>
);
}
render() {
if(_.isEmpty(this.props.currentEmployee)) {
return <div className="container loader">Loading...</div>;
}
const { employee } = this.props.currentEmployee;
return (
<div className='user-details'>
<div className='hero'>
<div className='container'>
<img className='employee-image' src={employee.image} />
<h1>{employee.name}</h1>
<h4>{employee.position}</h4>
</div>
</div>
<div className='callout'>
<h4>Fake Data</h4>
<p>All data displayed below are fake data and displayed for informational purposes only.</p>
</div>
<div className='container'>
<table className='table table-striped details-table'>
<tbody>
<tr>
<td className='title'>Location: </td>
<td>{employee.location}</td>
</tr>
<tr>
<td className='title'>Job Level: </td>
<td>{employee.level}</td>
</tr>
<tr>
<td className='title'>Email: </td>
<td>{employee.email}</td>
</tr>
<tr>
<td className='title'>Phone Number: </td>
<td>{employee.number}</td>
</tr>
<tr>
<td className='title'>Twitter Handle: </td>
<td>{employee.twitter}</td>
</tr>
<tr>
<td className='title'>Birthday: </td>
<td>{employee.birthday}</td>
</tr>
</tbody>
</table>
{ this.renderReports() }
</div>
</div>
)
}
}

71
isomorphic-react-app/src/containers/App.jsx

@ -0,0 +1,71 @@
import React from 'react';
import { Switch, Route } from 'react-router';
import { Link } from 'react-router-dom';
import Home from '../components/Home';
import User from '../components/User';
import database from 'firebase-database';
export default class App extends React.Component {
constructor(props) {
super(props);
// check to see if we have existing server-rendered data
// sets the state if we do, otherwise initialize it to an empty state
if (props.state) {
this.state = props.state;
} else {
this.state = {
employees: {},
currentEmployee: {}
}
}
}
// Loads an employee by its id
getEmployeeById = (employeeId) => {
this.setState({
currentEmployee: {}
});
database.getEmployeeById(employeeId).then(({ currentEmployee }) => {
this.setState({
currentEmployee
});
});
}
// Loads all employees
getAllEmployees = () => {
database.getAllEmployees().then(({employees}) => {
this.setState({
employees
});
});
}
render() {
return (
<div>
<nav id="mainNav" className="navbar navbar-custom">
<div className="container">
<div className="navbar-header">
<Link to='/' className="navbar-brand">Employee Directory</Link>
</div>
</div>
</nav>
<Switch>
<Route path='/:id' render={(props) => (
<User {...props}
currentEmployee={this.state.currentEmployee}
getEmployeeById={this.getEmployeeById}
/>
)}/>
<Route path='/' render={(props) => (
<Home {...props}
employees={this.state.employees}
getAllEmployees={this.getAllEmployees}
/>
)}/>
</Switch>
</div>
)
}
}

11
isomorphic-react-app/src/containers/ClientApp.js

@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.render((
<BrowserRouter>
<App state={window.__initialState}/>
</BrowserRouter>
), document.getElementById('root')
);

20
isomorphic-react-app/src/containers/ServerApp.jsx

@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { StaticRouter } from 'react-router';
import App from './App';
import database from 'firebase-database';
export default class ServerApp extends React.Component {
constructor(props) {
super(props);
database.initializeApp(props.appConfig);
}
render() {
return (
<StaticRouter location={this.props.url} context={this.props.context}>
<App state={this.props.initialState} />
</StaticRouter>
);
}
}

30
isomorphic-react-app/src/package.json

@ -0,0 +1,30 @@
{
"name": "employee-directory-app",
"description": "Sample employee directory app built with React and Firebase",
"scripts": {
"buildClient": "./node_modules/.bin/webpack --config ./webpack.client.config.js",
"buildServer": "./node_modules/.bin/webpack --config ./webpack.server.config.js",
"build": "npm run buildClient && npm run buildServer"
},
"dependencies": {
"firebase": "3.9.0",
"lodash": "4.17.4",
"react": "15.5.4",
"react-dom": "15.5.4",
"react-router": "4.1.1",
"react-router-dom": "4.1.1"
},
"devDependencies": {
"babel-core": "6.24.1",
"babel-loader": "7.0.0",
"babel-preset-react": "6.24.1",
"babel-preset-stage-0": "6.24.1",
"webpack": "2.5.0"
},
"babel": {
"presets": [
"react",
"stage-0"
]
}
}

27
isomorphic-react-app/src/webpack.client.config.js

@ -0,0 +1,27 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Sample Webpack Configuration for Client Bundle
const baseConfig = require('./webpack.config');
const path = require('path');
module.exports = Object.assign({}, {
entry: './containers/ClientApp.js',
output: {
filename: 'client.bundle.js',
path: path.resolve(__dirname, '../public/assets')
}
}, baseConfig);

37
isomorphic-react-app/src/webpack.config.js

@ -0,0 +1,37 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Sample Webpack Configuration
const path = require('path');
module.exports = {
resolve: {
extensions: [".js", ".jsx"],
alias: {
'firebase-database': path.resolve(__dirname, '../functions/firebase-database'),
},
},
resolveLoader: {
modules: [path.resolve(__dirname, "./node_modules")],
},
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
}]
}
};

32
isomorphic-react-app/src/webpack.server.config.js

@ -0,0 +1,32 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Sample Webpack Configuration for Server Bundle
const baseConfig = require('./webpack.config');
const path = require('path');
// Note that since this is for the server, it is important to
// set the target to node and set the libraryTarget to commonjs2
module.exports = Object.assign({}, {
target: 'node',
entry: './containers/ServerApp.jsx',
output: {
filename: 'server.bundle.js',
path: path.resolve(__dirname, '../functions/build'),
libraryTarget: 'commonjs2',
}
}, baseConfig);
Loading…
Cancel
Save