Usage
The basic usage pattern of feature-u is to:
Choose the
Aspects
that you will need, based on your selected frameworks (i.e. your run-time stack). This extends the aspect properties accepted by the Feature object (for example:Feature.reducer
forredux
, orFeature.logic
forredux-logic
).Typically these Aspects are packaged separately in NPM, although you can create your own Aspects (if needed).
Organize your app into features.
Each feature should be located in it's own directory.
How you break your app up into features will take some time and thought. There are many ways to approach this from a design perspective.
Each feature will promote it's aspect content through a
Feature
object (usingcreateFeature()
).
Your mainline starts the app by invoking
launchApp()
, passing allAspects
andFeatures
.
Easy Peasy!!
Directory Structure
Here is a sample directory structure of an app that uses feature-u:
src/
app.js ... launches app using launchApp()
features/
index.js ... accumulate/promote all Feature objects (within the app)
featureA/ ... a feature (within the app)
actions.js
appDidStart.js
appWillStart.js
comp/
ScreenA1.js
ScreenA2.js
feature.js ... promotes featureA object using createFeature()
index.js ... redirect parent dir import to our feature reference
logic.js
reducer.js
route.js
featureB/ ... another feature
...
util/ ... common utilities used across all features
...
Each feature is located in it's own directory, containing it's aspects (actions, reducers, components, routes, logic, etc.).
Feature Object
Each feature promotes it's aspect content through a
Feature
object (using createFeature()
).
src/features/featureA/feature.js
import {createFeature} from 'feature-u';
import reducer from './state';
import logic from './logic';
import route from './route';
import appWillStart from './appWillStart';
import appDidStart from './appDidStart';
export default createFeature({
name: 'featureA',
enabled: true,
fassets: {
define: {
'api.openA': () => ... implementation omitted,
'api.closeA': () => ... implementation omitted,
},
},
reducer,
logic,
route,
appWillStart,
appDidStart,
});
We will fill in more detail a bit later, but for now notice that the
feature is conveying reducers, logic modules, routes, and does some
type of initialization (appWillStart/appDidStart). It also promotes
something called fassets
(feature assets - the Public Face of a
feature) with openA()
and closeA()
functions which will be publicly
promoted to other features.
Note: Feature directory imports are redirected to our feature object reference ... for example:
src/features/featureA/index.js
// redirect parent dir import to our feature reference
export {default} from './feature';
Feature Accumulation
All features are accumulated through a single es6 module, allowing them to be pulled in through a single array import.
src/features/index.js
import featureA from './featureA';
import featureB from './featureB';
// promote ALL our features through a single import (accumulated in an array)
export default [
featureA,
featureB,
];
Note: While this represents a complete list of all our features,
some of them may be disabled (i.e. logically removed) ... see:
Feature Enablement
.
launchApp()
In feature-u the application mainline is very simple and generic.
There is no real app-specific code in it ... not even any global
initialization! That is because each feature can inject their own
app-specific constructs!! The mainline merely accumulates the
Aspects
and Features
, and starts the app by
invoking launchApp()
:
src/app.js
import ReactDOM from 'react-dom';
import {launchApp} from 'feature-u';
import {createRouteAspect} from 'feature-router';
import {createReducerAspect} from 'feature-redux';
import {createLogicAspect} from 'feature-redux-logic';
import features from './features';
// launch our app, exposing the Fassets object (facilitating cross-feature communication)
export default launchApp({ // *4*
aspects: [ // *1*
createRouteAspect(), // Feature Routes ... extending: Feature.route
createReducerAspect(), // redux ... extending: Feature.reducer
createLogicAspect(), // redux-logic ... extending: Feature.logic
],
features, // *2*
registerRootAppElm(rootAppElm) { // *3*
ReactDOM.render(rootAppElm,
getElementById('myAppRoot'));
}
});
Here are some important points of interest (match the numbers to
*n*
in the code above):
the supplied
Aspects
(pulled from separate npm packages) reflect the frameworks of our run-time stack (in our exampleredux
,redux-logic
, andfeature-router
) and extend the acceptable Feature properties (Feature.reducer
,Feature.logic
, andFeature.route
respectively) ... see:Extendable aspects
all of our app features are supplied (accumulated from the
features/
directory)a
registerRootAppElm()
callback is used to catalog the suppliedrootAppElm
to the specific React platform in use. Because this registration is accomplished by your app-specific code, feature-u can operate in any of the React platforms, such as:react-web
,react-native
, andexpo
... see:React Registration
as a bit of a preview, the return value of
launchApp()
is aFassets object
, which promotes the accumulated Public Face of all features, and is exported to provideCross Feature Communication
... here is what thefassets
looks like (for this example):fassets: { api: { openA(), closeA(), }, }
Hopefully this gives you a basic feel of how feature-u operates. The subsequent sections will develop a more thorough understanding!
Real Example
Want to see a real feature-u app?
eatery-nod
is the application where feature-u was
conceived. It is a react-native
expo
mobile
app, and is one of my sandbox applications that I use to test
frameworks. I like to develop apps that I can use, but have enough
real-world requirements to make it interesting.
eatery-nod
randomly selects a "date night" restaurant
from a pool of favorites. My wife and I have a steady "date night",
and we are always indecisive on which of our favorite restaurants to
frequent :-) So eatery-nod
provides the spinning
wheel!