Usage
The basic usage pattern of feature-u is to:
Organize your app into features.
Each feature should be located in it's own directory, typically within a
features/
parent 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 characteristics through a
Feature
object (usingcreateFeature()
).A
features/index.js
module will accumulate and promote all of theFeatures
that make up your entire application.
Choose the
Aspects
that you will need, based on your selected frameworks (i.e. your run-time stack).Typically these
Aspects
are packaged separately in NPM, although you can create your own (if needed).Each
Aspect
will extend the properties accepted by the Feature object (for example:Feature.reducer
forredux
, orFeature.logic
forredux-logic
).A best practice is to organize an
aspects/
directory, mimicking the same pattern as yourfeatures/
directory.An
aspects/index.js
module will accumulate and promote all of the aspects used by your application.
Your mainline will start the app by invoking
launchApp()
, passing allFeatures
andAspects
.
Easy Peasy!!
Directory Structure
Here is a sample directory structure of an app that uses feature-u:
src/
app.js ... launches app using launchApp()
aspects/
index.js ... accumulate/promote all Aspect objects (used by the app)
... NOTE: the aspects/ dir can contain local Aspects, however
because most Aspects are pulled from external
NPM packages, this directory is typically empty!
features/
index.js ... accumulate/promote all Feature objects (for the entire 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 the feature object
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 the feature object reference ... for example:
src/features/featureA/index.js
// redirect parent dir import to the feature reference
export {default} from './feature';
Feature Accumulation
All Features
are accumulated in a single index.js
module, allowing them to be promoted through a single import.
src/features/index.js
import featureA from './featureA';
import featureB from './featureB';
// promote ALL app features through a single import (accumulated in an array)
export default [
featureA,
featureB,
];
Note: While this represents a complete list of all app features,
some of them may be disabled (i.e. logically removed) ... see:
Feature Enablement
.
Aspect Accumulation
A best practice is to accumulate all Aspects
in a single
aspects/index.js
module, allowing them to be promoted through a
single import.
src/aspects/index.js
import React from 'react';
import {createReducerAspect} from 'feature-redux';
import {createLogicAspect} from 'feature-redux-logic';
import {createRouteAspect} from 'feature-router';
import SplashScreen from 'util/SplashScreen';
// define/configure the aspects representing the app's run-time stack
// ... redux - extending: Feature.reducer
const reducerAspect = createReducerAspect();
// ... redux-logic - extending: Feature.logic
const logicAspect = createLogicAspect();
// ... Feature Routes - extending: Feature.route
const routeAspect = createRouteAspect();
// ... CONFIG: define fallback screen (used when no routes are in effect)
routeAspect.config.fallbackElm$ = <SplashScreen msg="I'm trying to think but it hurts!"/>;
// promote the aspects representing the app's run-time stack
export default [
reducerAspect,
logicAspect,
routeAspect,
];
These Aspects
(pulled from external npm packages)
reflect the frameworks of the app's run-time stack (in this example
redux
, redux-logic
, and
feature-router
) and extend the acceptable Feature
properties (Feature.reducer
, Feature.logic
, and Feature.route
respectively) ... see: Extendable aspects
Note: The main difference in this module (vs. features/index.js
)
is that it is typically pulling/configuring resources from external
NPM packages, rather than locally defined within the project
(although you can create your own if needed).
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
Features
and Aspects
, and starts the app by
invoking launchApp()
:
src/app.js
import ReactDOM from 'react-dom';
import {launchApp} from 'feature-u';
import features from 'features';
import aspects from 'aspects';
// launch our app, exposing the Fassets object (facilitating cross-feature-communication)
export default launchApp({ // *4*
features, // *1*
aspects, // *2*
registerRootAppElm(rootAppElm) { // *3*
ReactDOM.render(rootAppElm,
document.getElementById('root'));
},
});
Here are some important points of interest (match the numbers to
*n*
in the code above):
all app features are supplied (accumulated from the
features/
directory) ... see:Feature Accumulation
the app aspects (i.e. the run-time stack) are supplied (accumulated from the
aspects/
directory) ... see:Aspect Accumulation
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 (in 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-w
is the application where feature-u was
conceived. It is a PWA
, 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-w
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-w
provides the spinning
wheel!