A Closer Look
As mentioned previously, the basic process of feature-u is that
each feature promotes a Feature
object that contains
various aspects of that feature ... things like: the feature's name,
it's Public API, whether it is enabled, initialization constructs, and
resources used to configure it's slice of the frameworks in use.
In turn, these Feature objects are supplied to launchApp()
,
which configures and starts your application, returning an
App
Object (which promotes the public API of each
feature).
Let's take a closer look at this process ...
aspects
In feature-u, "aspect" is a generalized term used to refer to the various ingredients that (when combined) constitute your application. Aspects can take on many different forms ... for example:
- UI Components and Routes,
- State Management (actions, reducers, selectors),
- Business Logic,
- Startup Initialization Code,
- etc.
Not all aspects are of interest to feature-u ... only those that are needed to setup and launch the app ... all others are considered to be an internal implementation detail of the feature. As an example, consider the redux state manager: while it uses actions, reducers, and selectors ... only reducers are needed to setup and configure redux.
feature-u provides a base set of built-in aspects (out-of-the-box) but allows additional aspects to be introduced through it's extendable API.
-
These aspects are promoted directly by the base feature-u package. They provide very rudimentary capabilities, such as feature enablement, public API, and application life-cycle hooks.
-
These aspects are promoted by external packages (or self defined in your project). They provide feature-u integration with other frameworks (for example
redux
state management, orredux-logic
business logic, or navigational routers, etc.). They are created with feature-u's extendable API, and are packaged separately, so as to not introduce unwanted dependencies.
Feature Object (relaying aspect content)
The Feature
object is merely a container that holds
aspect content that is of interest to feature-u.
Each feature within your application promotes a Feature object (using
createFeature()
) that catalogs the
AspectContent
of that feature.
Ultimately, all Feature objects are consumed by launchApp()
.
Built-In aspects
Built-in aspects are promoted directly by the base feature-u package. They provide very rudimentary capabilities, such as feature enablement, public API, and application life-cycle hooks.
Like all aspects, Built-In aspect content is relayed through Feature
object properties (via createFeature()
).
Feature.name
A string property which represents the identity of the feature. Feature names are used to index the
App
Object by feature (in support ofCross Feature Communication
), and are therefore guaranteed to be unique. Application code can also useFeature Name
in various single-source-of-truth operations.
Feature.enabled
A boolean property that determines whether the feature is enabled or not. This indicator is typically based on a dynamic expression, allowing packaged code to be dynamically enabled/disabled at run-time (please refer to:
Feature Enablement
).
Feature.publicFace
An optional resource object that is the feature's Public API, promoting cross-communication between features. This object is exposed through the App object as:
app.{featureName}.{publicFace}
(please refer to:publicFace and the App Object
).
Feature.appWillStart
An optional
Application Life Cycle Hook
invoked one time, just before the app starts up. This life-cycle hook can do any type of initialization, and/or optionally supplement the app's top-level content (using a non-null return) (please refer to:appWillStart
).
Feature.appDidStart
An optional
Application Life Cycle Hook
invoked one time, immediately after the app has started. Because the app is up-and-running at this time, you have access to the appState and the dispatch() function ... assuming you are using redux (when detected by feature-u's plugable aspects) (please refer to:appDidStart
).
Extendable aspects
feature-u is extendable! Extendable Aspects provide feature-u
integration with other frameworks (for example redux
state management, or redux-logic
business logic, etc.).
For this reason (by in large) they provide the most value,
because they fully integrate your features into your run-time
stack!
Extendable Aspects are packaged separately from feature-u, so as to not introduce unwanted dependencies (because not everyone uses the same frameworks). You pick and choose them based on the framework(s) used in your project (matching your project's run-time stack).
Extendable Aspects are created with feature-u's
Extension API
, using createAspect()
. You can
define your own Aspect (if the one you need doesn't already exist)!
For more information, please see Extending feature-u
.
Like all aspects, Extendable Aspect content is relayed through Feature
object properties (via createFeature()
).
Because Extendable Aspects are not part of the base feature-u
package, it is a bit problematic to discuss them here (they are
either in a separate npm package, or self contained in your project).
You should search the npm registry with the 'feature-u'
keyword
to find the ones that meet your requirements. With that said, we
will briefly discuss the Extendable Aspects that were created in
conjunction with the initial development of feature-u (just to give
you a feel of what is possible).
Feature.reducer
via:feature-redux
feature-redux
is the feature-u integration point toredux
!It configures
redux
through thereducerAspect
(to be supplied tolaunchApp()
), which extends the Feature object, adding support for theFeature.reducer
property, referencing feature-based reducers.Only reducers are of interest because that is all that is needed to configure [redux]. All other redux items (actions, selectors, etc.) are considered to be an internal implementation detail of the feature.
The
Feature.reducer
content must be embellished byslicedReducer()
, which provides instructions on how to combine multiple feature-based reducers in constructing the overall top-level application state tree.Because feature-redux manages redux, it also provides an integration hook to other Aspects that need to inject redux middleware.
Please refer to the
feature-redux
documentation for complete details.
Feature.logic
via:feature-redux-logic
feature-redux-logic
is the feature-u integration point toredux-logic
!It configures
redux-logic
through thelogicAspect
(to be supplied tolaunchApp()
), which extends the Feature object, adding support for theFeature.logic
property, referencing feature-based logic modules.The following article is an introduction (and motivation) for the development of redux-logic: Where do I put my business logic in a React-Redux application.
Please refer to the
feature-redux-logic
documentation for complete details.
Feature.route
via:feature-router
feature-router
is the feature-u integration point to Feature Routes!It configures Feature Router through the
routeAspect
(to be supplied tolaunchApp()
), which extends the Feature object, adding support for theFeature.route
property, referencing routes defined through thefeatureRoute()
function.Feature Routes are based on a very simple concept: allow the application state to drive the routes! It operates through a series of registered functional callback hooks, which determine the active screen based on an analysis of the the overall appState. This is particularly useful in feature-based routing, because each feature can promote their own UI components in an encapsulated and autonomous way! Because of this, feature-router is a preferred routing solution for feature-u.
Feature Routes is the recommended solution for feature-based navigation. You can find more information in the
Feature Based Routes
section.Please refer to the
feature-router
documentation for complete details.
Launching Your Application
By interpreting the set of Aspects and Features that comprise an application, feature-u can actually coordinate the launch of your application (i.e. start it running)!
This is accomplished through the launchApp()
function.
It manages the setup and configuration of the frameworks in use, such as
redux
,redux-logic
, etc. This is based on a set of supplied plugable Aspects that extend feature-u, integrating external frameworks to match your specific run-time stack.It facilitates application life-cycle hooks on the
Feature
object, allowing features to manage things like: initialization and injecting root UI elements, etc.It creates and promotes the
App
object which contains the publicFace of all features, facilitating a cross-communication between features.
As a result, your 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 React from 'react';
import ReactDOM from 'react-dom';
import {launchApp} from 'feature-u';
import {routeAspect} from 'feature-router'; // *1*
import {reducerAspect} from 'feature-redux'; // *1*
import {logicAspect} from 'feature-redux-logic'; // *1*
import SplashScreen from './util/comp/SplashScreen';
import features from './feature'; // *2*
// define our set of "plugable" feature-u Aspects, conforming to our app's run-time stack
const aspects = [ // *1*
routeAspect, // Feature Routes ... order: early - it's DOM injection does NOT support children
reducerAspect, // redux ... order: later - <Provider> DOM injection should be on top
logicAspect, // redux-logic ... order: N/A - NO DOM injection
];
// configure our Aspects (as needed)
// ... Feature Route fallback screen (when no routes are in effect)
routeAspect.config.fallbackElm$ = <SplashScreen msg="I'm trying to think but it hurts!"/>;
// launch our app, exposing the feature-u App object (facilitating cross-feature communication)!
export default launchApp({ // *4*
aspects, // *1*
features, // *2*
registerRootAppElm(rootAppElm) { // *3*
ReactDOM.render(rootAppElm,
getElementById('myAppRoot'));
}
});
The Aspect collection (see *1*
in the code snippet above) reflects
the frameworks of our run-time stack (in our example
redux
, redux-logic
, and
feature-router
) and extend the acceptable Feature
properties (Feature.reducer
, Feature.logic
, and Feature.route
respectively) ... see: Extendable aspects
.
In this case, all our Aspects were pulled from external npm packages,
however you can define your own using createAspect()
.
All of our supplied app features are accumulated from the features/
directory ... (see *2*
in the code snippet above).
React Registration
The launchApp()
function uses a
registerRootAppElm()
callback (see *3*
in the code
snippet above) to catalog the supplied rootAppElm
to the specific
React platform in use.
API: registerRootAppElm(rootAppElm): void
NOTE regarding the rootAppElm
:
Typically the supplied
rootAppElm
will have definition, based on the Aspects and Features that are in use. In this case, it is the responsibility of this callback to register this content in some way (either directly or indirectly).However, there are atypical isolated cases where the supplied
rootAppElm
can be null. This can happen when the app chooses NOT to use Aspects/Features that inject any UI content. In this case, the callback is free to register it's own content.
Because this registration is accomplished by your app-specific code, feature-u can operate in any of the React platforms, such as:
React Web
import ReactDOM from 'react-dom';
...
export default launchApp({
aspects,
features,
registerRootAppElm(rootAppElm) { // *3*
ReactDOM.render(rootAppElm,
getElementById('myAppRoot'));
}
});
React Native
import {AppRegistry} from 'react-native';
...
export default launchApp({
aspects,
features,
registerRootAppElm(rootAppElm) { // *3*
AppRegistry.registerComponent('myAppKey',
()=>rootAppElm); // convert rootAppElm to a React Component
}
});
Expo
import Expo from 'expo';
...
export default launchApp({
aspects,
features,
registerRootAppElm(rootAppElm) { // *3*
Expo.registerRootComponent(()=>rootAppElm); // convert rootAppElm to a React Component
}
});
App Object
An App
object is emitted from the launchApp()
function, which promotes the accumulated Public API of all features
(see: publicFace and the App Object
).
The App object should be exported (see *4*
in the code
snippet above) so other modules can access it (providing
Cross Feature Communication
). Please note that depending on the context,
there are various techniques by which the App object can be accessed
(see: Accessing App
).
The App object contains named feature nodes, structured as follows:
App.{featureName}.{publicFace}
The app object can be used for two distinct purposes:
- to access a feature's Public API, and
- to determine whether a feature exists.
Feature Public API
The App
object promotes the feature's Public API
(i.e. it's publicFace).
As an example, an application that has two features (featureA, and featureB) will look like this:
app: {
featureA: {
action: {
open(),
close()
}
},
featureB: {
}
}
You can see that featureA is promoting a couple of actions (open(), close()) in it's publicFace, while featureB has NO publicFace.
Does Feature Exist
The App
object can be used to determine if a feature is
present or not. If a feature does not exist, or has been disabled,
the corresponding app.{featureName}
will NOT exist.
It could be that
featureA
will conditionally usefeatureB
if it is present.if (app.featureB) { ... do something featureB related }
It could be that
featureC
unconditionally requires thatfeatureD
is present. This can be checked in theappWillStart()
Application Life Cycle Hook
.appWillStart({app, curRootAppElm}) { assert(app.featureD, '***ERROR*** I NEED featureD'); }