Skip to content
This repository has been archived by the owner on May 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #19 from andymikulski/multiple-app-entries
Browse files Browse the repository at this point in the history
#18: Multiple app entry points
  • Loading branch information
andymikulski committed Mar 12, 2018
2 parents 814d1e0 + a0666f9 commit 859bc1a
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 119 deletions.
3 changes: 2 additions & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

[options]
module.name_mapper.extension='less' -> '<PROJECT_ROOT>/StubModule.js'
module.name_mapper='^console\/\(.*\)$' -> '<PROJECT_ROOT>/console/\1'
module.name_mapper='^console\/\(.*\)$' -> '<PROJECT_ROOT>/src/console/\1'
module.name_mapper='^normandy\/\(.*\)$' -> '<PROJECT_ROOT>/src/normandy/\1'
emoji=true

9 changes: 9 additions & 0 deletions config-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = function(config, env) {
alias: {
...config.resolve.alias,
console: path.resolve(__dirname, "./src/console/"),
normandy: path.resolve(__dirname, "./src/normandy/"),
}
};

Expand All @@ -19,5 +20,13 @@ module.exports = function(config, env) {
config
);

// If an --app=something parameter is present when running this script,
// change the entry point to start the given app.
let selectedApp = process.argv.find(val => val.startsWith('--app'));
if(selectedApp) {
selectedApp = selectedApp.split('=')[1];
config.entry = path.resolve(__dirname, `./src/${selectedApp}/index.js`);
}

return config;
};
60 changes: 60 additions & 0 deletions docs/dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,63 @@ right away run:
Now you should be able to open ``http://localhost:3000`` and behold the
aweomeness.


Running Apps Separately
=======================

By default, ``yarn start`` will spin up the entire Delivery Console, including all of the nested apps such as Normandy. In order to stand up only one of the apps, simply pass an `--app` parameter pointing to the app's directory. Here are some examples:

.. code-block:: shell
$ yarn start --app=normandy
$ yarn start --app=delivery-dashboard
$ yarn start --app=some-other-app
This works via altering the Webpack ``entry`` configuration via ``config-overrides.js`` to point to the ``[app-dir]/src/index.js`` file. An index.js file is required for each app in order to be served on its own - see below.


Standalone App Configuration
============================

When working on an individual app, there is a need to stub out certain values/props that would normally be passed in through the delivery-console. These props include authTokens, usernames, etc. When working on an app on its own, these values are stubbed out when the component is mounted to the DOM. Consider the following example:

.. code-block:: javascript
/*
fake-app/App.js
The entry point to the app when nested in the delivery-console. This contains the basic component definition for the app, and is written as a normal React component.
*/
import React from 'react';
import './App.css';
export default class App extends React.Component {
render() {
return (
<div>
<h1>Welcome to this app!</h1>
<p>The prop value being passed in is set to "{ this.props.someProp }"</p>
</div>
);
}
}
.. code-block:: javascript
/*
fake-app/index.js
The entry point to the app when standing up alone. This is useful for stubbing out values that would be passed down from the delivery-console (such as an authToken).
*/
import React from 'react';
import ReactDOM from 'react-dom';
import FakeApp from './App';
ReactDOM.render(
<FakeApp someProp="some-value" />,
document.getElementById('root'),
);
``App.js`` outlines the actual React component for the app, while ``index.js`` mounts the app to the DOM with some fake data.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
},
"jest": {
"moduleNameMapper": {
".*\\.less$": "<rootDir>/StubModule.js"
".*\\.less$": "<rootDir>/StubModule.js",
"^console/(.*)$": "<rootDir>/src/console/$1",
"^normandy/(.*)$": "<rootDir>/src/normandy/$1"
}
}
}
113 changes: 113 additions & 0 deletions src/console/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* @flow */

import React, { Component } from 'react';

import 'console/App.less';

import LoginPage from './LoginPage';
import Normandy from 'normandy/App';
import { BrowserRouter, NavLink, Link } from 'react-router-dom';
import { Route, Redirect } from 'react-router';

import { Alert, Layout } from 'antd';
const { Header, Content } = Layout;

const Homepage = props => (
<div>
<h3>Welcome {props.authToken ? 'back' : 'home'}!</h3>
{props.authToken ? (
<p>
Go to the <Link to="/normandy">Normandy page</Link>.
</p>
) : (
<p>
You are not logged in! Go to the <Link to="/login">login page</Link>.
</p>
)}
</div>
);

type AppProps = {};
type AppState = {
authToken: ?string,
username: ?string,
};

class App extends Component<AppProps, AppState> {
state = {
authToken: null,
username: null,
};

onUserLogin = (username: string, authToken: string) => {
this.setState({
username,
authToken,
});
};

render() {
return (
<BrowserRouter>
<Layout className="app">
<Header className="app-header">
<h1>Delivery Console</h1>

<NavLink exact to="/">
Home
</NavLink>
<NavLink to="/login">Login</NavLink>
<NavLink exact to="/normandy">
Normandy
</NavLink>

{this.state.username && (
<Alert
style={{ marginLeft: '3em' }}
type="info"
showIcon
message={`You are logged in as ${this.state.username}.`}
/>
)}
</Header>
<Content className="app-content">
{/* Homepage */}
<Route
exact
path="/"
component={() => <Homepage authToken={this.state.authToken} />}
/>

{/* Login */}
<Route
exact
path="/login/"
component={() =>
this.state.username ? (
<Redirect to="/" />
) : (
<LoginPage onAuth={this.onUserLogin} />
)
}
/>

{/* Normandy App */}
<Route
exact
path="/normandy/"
component={() =>
this.state.username ? (
<Normandy authToken={this.state.authToken} />
) : (
<Redirect to="/login/?next=/normandy/" />
)
}
/>
</Content>
</Layout>
</BrowserRouter>
);
}
}

export default App;
2 changes: 1 addition & 1 deletion src/console/App.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './index';
import App from './App';

it('renders without crashing', () => {
const div = document.createElement('div');
Expand Down
119 changes: 4 additions & 115 deletions src/console/index.js
Original file line number Diff line number Diff line change
@@ -1,116 +1,5 @@
/* @flow */
import React from 'react';
import ReactDOM from 'react-dom';
import DevConsoleApp from './App';

import React, { Component } from 'react';

import 'console/App.less';

import LoginPage from './LoginPage';
import { BrowserRouter, NavLink, Link } from 'react-router-dom';
import { Route, Redirect } from 'react-router';

import { Alert, Layout } from 'antd';
const { Header, Content } = Layout;

const Homepage = props => (
<div>
<h3>Welcome {props.authToken ? 'back' : 'home'}!</h3>
{props.authToken ? (
<p>
Go to the <Link to="/normandy">Normandy page</Link>.
</p>
) : (
<p>
You are not logged in! Go to the <Link to="/login">login page</Link>.
</p>
)}
</div>
);

const MockNormandy = props => (
<div>Normandy with auth token {props.authToken}</div>
);

type AppProps = {};
type AppState = {
authToken: ?string,
username: ?string,
};

class App extends Component<AppProps, AppState> {
state = {
authToken: null,
username: null,
};

onUserLogin = (username: string, authToken: string) => {
this.setState({
username,
authToken,
});
};

render() {
return (
<BrowserRouter>
<Layout className="app">
<Header className="app-header">
<h1>Delivery Console</h1>

<NavLink exact to="/">
Home
</NavLink>
<NavLink to="/login">Login</NavLink>
<NavLink exact to="/normandy">
Normandy
</NavLink>

{this.state.username && (
<Alert
style={{ marginLeft: '3em' }}
type="info"
showIcon
message={`You are logged in as ${this.state.username}.`}
/>
)}
</Header>
<Content className="app-content">
{/* Homepage */}
<Route
exact
path="/"
component={() => <Homepage authToken={this.state.authToken} />}
/>

{/* Login */}
<Route
exact
path="/login/"
component={() =>
this.state.username ? (
<Redirect to="/" />
) : (
<LoginPage onAuth={this.onUserLogin} />
)
}
/>

{/* Normandy App */}
<Route
exact
path="/normandy/"
component={() =>
this.state.username ? (
<MockNormandy authToken={this.state.authToken} />
) : (
<Redirect to="/login/?next=/normandy/" />
)
}
/>
</Content>
</Layout>
</BrowserRouter>
);
}
}

export default App;
ReactDOM.render(<DevConsoleApp />, document.getElementById('root'));
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import ReactDOM from "react-dom";
import DevConsoleApp from "./console";
import DevConsoleApp from "./console/App";

ReactDOM.render(<DevConsoleApp />, document.getElementById("root"));
28 changes: 28 additions & 0 deletions src/normandy/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.App {
text-align: center;
}

.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}

.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}

.App-title {
font-size: 1.5em;
}

.App-intro {
font-size: large;
}

@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

0 comments on commit 859bc1a

Please sign in to comment.