Skip to content

Commit

Permalink
Enable redux enhancer (#1870)
Browse files Browse the repository at this point in the history
* add a test project for subapp-redux, copied from poc-subapp

* remove unneeded remote subapp settings

* add option of enhancer for subapp-reux Redux createStore

* add demo cases for subapp-redux Redux createStore enhancer

* delete an unrelated file

* refactor: make reduc enhancer name more self explanatory

Co-authored-by: Wesley Ren <[email protected]>
  • Loading branch information
WesleyRen and Wesley Ren authored Feb 4, 2022
1 parent c09d823 commit 6a4da64
Show file tree
Hide file tree
Showing 47 changed files with 2,998 additions and 4 deletions.
20 changes: 16 additions & 4 deletions packages/subapp-redux/src/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,18 @@ function createSharedStore(initialState, info, storeContainer) {
replaceReducer(info.reduxReducers, info, storeContainer);
} else {
reducerContainer = newReducerContainer();
store = createStore(
combineSharedReducers(info, reducerContainer, info.reduxReducers),
initialState
);
if (info.reduxEnhancer && info.reduxEnhancer instanceof Function) {
store = createStore(
combineSharedReducers(info, reducerContainer, info.reduxReducers),
initialState,
info.reduxEnhancer()
);
} else {
store = createStore(
combineSharedReducers(info, reducerContainer, info.reduxReducers),
initialState
);
}
store[originalReplaceReducerSym] = store.replaceReducer;
//
// TODO: better handling of a replaceReducer that takes extra params
Expand Down Expand Up @@ -136,6 +144,10 @@ function createSharedStore(initialState, info, storeContainer) {
} else {
reducer = x => x;
}

if (info.reduxEnhancer && info.reduxEnhancer instanceof Function) {
return createStore(reducer, initialState, info.reduxEnhancer());
}
return createStore(reducer, initialState);
}

Expand Down
4 changes: 4 additions & 0 deletions samples/poc-subapp-redux/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Browsers that we support
last 2 versions
ie >= 11
> 5%
11 changes: 11 additions & 0 deletions samples/poc-subapp-redux/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
11 changes: 11 additions & 0 deletions samples/poc-subapp-redux/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var path = require("path");
var archetype = require("@xarc/app/config/archetype")();
var archetypeEslint = path.join(archetype.config.eslint, ".eslintrc-react");

function dotify(p) {
return path.isAbsolute(p) ? p : "." + path.sep + p;
}

module.exports = {
extends: dotify(path.relative(__dirname, archetypeEslint))
};
1 change: 1 addition & 0 deletions samples/poc-subapp-redux/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto
13 changes: 13 additions & 0 deletions samples/poc-subapp-redux/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Copyright 2018-present @WalmartLabs

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.
209 changes: 209 additions & 0 deletions samples/poc-subapp-redux/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# subapp poc sample

## The Concept

- What is a subapp?

At its core, a subapp is just a component, and if React is used, a React component.

The goal is to not limit subapps to a framework, but at the moment React is the primary focus.

- What makes a subapp special?

Electrode provide enhancements to make subapps special:

- `Code splitting` - Automatically detect subapps and configure webpack to split your JS by subapps.
- `Composable` - Create routes/pages that are composed of multiple subapps.
- `Lazy loading` - Dynamically lazy load and create multiple instances of subapps on a page.
- `Initial Props` - Automatically retrieve initial props before rendering subapps.
- `Async data fetch` - Use React suspense to enable single pass async data fetch within components.
- `Server Side Rendering` - Independently enable server side render for each subapp.
- `Redux` - Automatically facilitate, initialize, and hydrate SSR data using Redux.
- `React Router` - Automatically setup component routing using [react-router].
- `Hot module Reload` - Automatically support Hot Module Reload during development.

## Introduction

**How do I create a subapp?**

- Electrode automatically look under the directory `src` for subapps.
- To create a subapp, create a directory for it and an entry file with `subapp-` prefix, or just `subapp.js`.
- Load your subapp with the `loadSubApp` API.

- The simplest sample of a subapp:

```js
import React from "react";
import { loadSubApp } from "subapp-web";

const Home = () => <div>Home</div>;

export default loadSubApp({
name: "Home",
Component: Home
});
```

And that's it! **TBD**: Electrode will detect your subapps and generate a route for each subapp base on its name. The `home` subapp will be used for the default route.

## Flexible Server Routing

To control server routes, you can create a `src/routes.js` file that specify the server routes.

Example:

```js
export const favicon = "static/favicon.png";

export default {
"/": {
pageTitle: "Home",
subApps: ["./home", "./demo1"]
},
about: {
pageTitle: "About us",
subApps: ["./about"]
}
};
```

## Initial Props

Your subapp can provide a `prepare` method that retrieves `props`.

Example:

```js
import React from "react";
import { loadSubApp } from "subapp-web";

const Demo1 = props => <div>Demo1 props: {JSON.stringify(props)}</div>;

export default loadSubApp({
name: "Demo1",
Component: Home,
prepare() {
return fetch("/api/demo-data");
}
});
```

## Server Side Rendering

### Enable SSR for a subapp

When you create an instance of a subapp, you can specify attributes for that instance. To enable SSR, set the `serverSideRendering` attribute.

Example in `routes.js`:

```js
export const favicon = "static/favicon.png";

export default {
"/": {
pageTitle: "Home",
subApps: [["./home", { serverSideRendering: true }], "./demo1"]
},
about: {
pageTitle: "About us",
subApps: ["./about"]
}
};
```

### Server Side Data Fetching

You can have data fetching that only runs on server side, and send that to the client for hydration if you use Redux.

To enable server side only data fetching, create a file `server.js` under your subapp's directory, and have it exports a `prepare` method.

Example:

```js
export default {
prepare: ({ request, context }) => {
return fetch("remote-service-url/api/data").then(result => {
return { initialState: result };
});
}
};
```

### Other SSR attributes

You can also control how your subapp works from server side to client side for each instance.

Some of the attributes supported:

- `hydrate` - Enable client side hydration of initial state from server. ie: Use `React.hydrate` API.
- `useStream` - Use the stream SSR APIs. ie: `ReactDomServer.renderToNodeStream`.
- `suspenseSsr` - Support suspense in SSR. No stream support.

## React Router and SPA

In addition to the basic server routes, your subapps can have its own routing using [react-router].

To enable, set the `useReactRouter` flag when loading your subapp and wrap your component with a router.

Example:

```js
import React from "react";
import { loadSubApp } from "subapp-web";
import { Router, Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";

const Home = () => {
return (
<div>
<Navigation />
<Switch>
<Route path="/" exact component={Main} {...props} />
<Route path="/products" component={About} {...props} />
<Route path="/contact" component={Contact} {...props} />
</Switch>
</div>
);
};

export default loadSubApp({
name: "Home",
Component: withRouter(Home),
useReactRouter: true
});
```

Electrode will automatically ensure your subapp is integrated with `StaticRouter` when doing SSR and `DynamicRouter` with `history` when running in the browser.

**SPA** Further, you can have other subapps on the same page, and those will stay persistent while your subapp that is enabled with [react-router] will change. When loading each of the [react-router] routes, your page will be properly server side rendered.

## Redux

Electrode enables automatic integration with Redux for your subapps. To enable, add `redux` and `react-redux` to your dependencies, and use `reduxLoadSubApp` from the module `subapp-redux`, and provide reducers or `reduxCreateStore` for the subapp.

Example:

```js
import React from "react";
import { reduxLoadSubApp } from "subapp-redux";
import { connect } from "react-redux";
import reducers from "./reducers";

const Home = props => <div>Home</div>;

const mapStateToProps = state => state;

export default reduxLoadSubApp({
name: "Home",
Component: connect(mapStateToProps, dispatch => ({ dispatch }))(Home),
reduxCreateStore(initialState) {
return createStore(reducers, initialState);
}
});
```

## License

Apache-2.0 ©

[react-router]: https://www.npmjs.com/package/react-router
5 changes: 5 additions & 0 deletions samples/poc-subapp-redux/archetype/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
options: {
subapp: true
}
};
4 changes: 4 additions & 0 deletions samples/poc-subapp-redux/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use strict";
module.exports = {
extends: "@xarc/app-dev/config/babel/babelrc.js"
};
31 changes: 31 additions & 0 deletions samples/poc-subapp-redux/config/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use strict";

const defaultListenPort = 3000;

const portFromEnv = () => {
const x = parseInt(process.env.APP_SERVER_PORT || process.env.PORT, 10);
/* istanbul ignore next */
return x !== null && !isNaN(x) ? x : defaultListenPort;
};

module.exports = {
plugins: {
"@xarc/app-dev": {
enable: process.env.WEBPACK_DEV === "true"
},
"subapp-server": { options: { insertTokenIds: true } }
},
connections: {
default: {
host: process.env.HOST,
address: process.env.HOST_IP || "0.0.0.0",
port: portFromEnv(),
routes: {
cors: false
},
state: {
ignoreErrors: true
}
}
}
};
1 change: 1 addition & 0 deletions samples/poc-subapp-redux/config/development.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
10 changes: 10 additions & 0 deletions samples/poc-subapp-redux/config/production.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
plugins: {
"subapp-server": {
options: {
insertTokenIds: false,
cdn: { enable: true }
}
}
}
};
Loading

0 comments on commit 6a4da64

Please sign in to comment.