1 | <p align="center">
|
2 | <b style="font-size: 25px">🎛 flopflip - Feature Toggling 🎚</b><br />
|
3 | <i>flip or flop a feature in LaunchDarkly with real-time updates through a redux store.</i>
|
4 | </p>
|
5 |
|
6 | <p align="center">
|
7 | <img alt="Logo" src="https://raw.githubusercontent.com/tdeekens/flopflip/master/logo.png" /><br /><br />
|
8 | <i>Toggle features in LaunchDarkly with their state being maintained in a redux state slice being accessible through a set of Higher-Order Components in React (via recompose).</i><br />
|
9 | </p>
|
10 |
|
11 | <details>
|
12 | <summary>Want to see a demo?</summary>
|
13 |
|
14 | <img alt="Logo" src="https://raw.githubusercontent.com/tdeekens/flopflip/master/demo.gif" />
|
15 | </details>
|
16 |
|
17 | ### Status
|
18 |
|
19 | [![Travis](https://img.shields.io/travis/tdeekens/flopflip.svg?style=flat-square)](https://travis-ci.org/tdeekens/flopflip) 💎 [![npm](https://img.shields.io/tdeekens/v/flopflip.svg?style=flat-square)]() 💎 [![David](https://img.shields.io/david/tdeekens/flopflip.svg?style=flat-square)]()
|
20 |
|
21 | ## Installation
|
22 |
|
23 | `yarn add flopflip` or `npm i flopflip --save`
|
24 |
|
25 | ## Demo
|
26 |
|
27 | A minimal [demo](/demo) exists and can adjusted to point to a [custom](https://github.com/tdeekens/flopflip/blob/master/demo/src/App.js#L108) LaunchDarkly account. You would have to create feature toggles according to the existing [flags](https://github.com/tdeekens/flopflip/blob/master/demo/src/flags.js) too.
|
28 |
|
29 | Then simply run:
|
30 |
|
31 | 1. From the repositories root: `yarn build:watch`
|
32 | 2. From `/demo`: first `yarn` and then `yarn start`
|
33 |
|
34 | A browser window should open and the network tab should show feature flags being loaded from LaunchDarkly.
|
35 |
|
36 | ## Documentation
|
37 |
|
38 | Flopflip allows you to manage feature flags through [LaunchDarkly](https://launchdarkly.com/) within an application written using React and Redux.
|
39 |
|
40 | ### API & exports
|
41 |
|
42 | The `modules/index.js` exports:
|
43 |
|
44 | - `createFlopFlipEnhancer` a redux store enhancer to configure LaunchDarkly and add feature toggle state to your redux store
|
45 | - `ConfigureFlopFlip` a component to configure LaunchDarkly (alternative to the store enhancer)
|
46 | - `reducer` and `STATE_SLICE` a reducer and the state slice for the feature toggle state
|
47 | - `withFeatureToggle` a Higher-Order Component (HoC) to conditionally render components depending on feature toggle state
|
48 | - `injectFeatureToggles` a HoC to inject requested feature toggles from existing feature toggles onto the `props` of a component
|
49 | - `FeatureToggled` a component conditionally rendering its `children` based on the status of a passed feature flag
|
50 |
|
51 | #### `createFlopFlipEnhancer`
|
52 |
|
53 | Requires arguments of `clientSideId:string`, `user:object`.
|
54 |
|
55 | - The `clientSideId` is your LaunchDarkly ID.
|
56 | - The `user` object needs at least a `key` attribute. An anonymous `key` will be generated using `uuid4` when nothing is specified. The user object can contain additional data.
|
57 |
|
58 | #### `reducer` & `STATE_SLICE`
|
59 |
|
60 | The flopflop reducer should be wired up with a `combineReducers` within your application in coordination with the `STATE_SLICE` which is used internally too to manage the location of the feature toggle states.
|
61 |
|
62 | In context this configuration could look like:
|
63 |
|
64 | ```js
|
65 | import { createStore, compose, applyMiddleware } from 'redux';
|
66 | import {
|
67 | createFlopFlipEnhancer,
|
68 | flopflipReducer,
|
69 |
|
70 | // We refer to this state slice in the `injectFeatureToggles`
|
71 | // HoC and currently do not support a custom state slice.
|
72 | FLOPFLIP_STATE_SLICE
|
73 | } from 'flopflip';
|
74 |
|
75 | // Maintained somewhere within your application
|
76 | import user from './user';
|
77 | import appReducer from './reducer';
|
78 |
|
79 | const store = createStore(
|
80 | combineReducers({
|
81 | appReducer,
|
82 | [FLOPFLIP_STATE_SLICE]: featureToggleReducer,
|
83 | }),
|
84 | initialState,
|
85 | compose(
|
86 | applyMiddleware(...),
|
87 | createFlopFlipEnhancer(
|
88 | // NOTE:
|
89 | // This clientId is not secret to you and can be found
|
90 | // within your settings on LaunchDarkly.
|
91 | window.application.env.LD_CLIENT_ID,
|
92 | user
|
93 | )
|
94 | )
|
95 | )
|
96 | ```
|
97 |
|
98 | Whenever setup is not preferred via the store enhance the same can be achieved using the `ConfigureFlopFlip` component.
|
99 |
|
100 | It takes the `props`:
|
101 |
|
102 | - The `clientSideId` is your LaunchDarkly ID.
|
103 | - The `user` object needs at least a `key` attribute. An anonymous `key` will be generated using `uuid4` when nothing is specified. The user object can contain additional data.
|
104 |
|
105 | ```js
|
106 | import { createStore, compose, applyMiddleware } from 'redux';
|
107 | import {
|
108 | ConfigureFlopFlip,
|
109 | flopflipReducer,
|
110 | FLOPFLIP_STATE_SLICE
|
111 | } from 'flopflip';
|
112 |
|
113 | // Maintained somewhere within your application
|
114 | import user from './user';
|
115 | import appReducer from './reducer';
|
116 |
|
117 | const store = createStore(
|
118 | combineReducers({
|
119 | appReducer,
|
120 | [FLOPFLIP_STATE_SLICE]: featureToggleReducer,
|
121 | }),
|
122 | initialState,
|
123 | compose(
|
124 | applyMiddleware(...),
|
125 | )
|
126 | )
|
127 |
|
128 | // Somewhere where your <App /> is rendered
|
129 |
|
130 | <ConfigureFlopFlip user={user} clientSideId={clientSideId}>
|
131 | <App />
|
132 | </ConfigureFlopFlip>
|
133 | ```
|
134 |
|
135 | #### `FeatureToggled`
|
136 |
|
137 | The component renders its `children` depending on the state of a given feature flag. It also allows passing an optional `untoggledComponent` which will be rendered whenever the feature is disabled instead of `null`.
|
138 |
|
139 | ```js
|
140 | import React, { Component } from 'react';
|
141 |
|
142 | import { FeatureToggled } from 'flopflip';
|
143 | import flagsNames from './feature-flags';
|
144 |
|
145 | export default (
|
146 | <FeatureToggled
|
147 | flag={flagsNames.THE_FEATURE_TOGGLE}
|
148 | untoggledComponent={<h3>At least there is a fallback!</h3>}
|
149 | >
|
150 | <h3>I might be gone or there!</h3>
|
151 | </FeatureToggled>
|
152 | );
|
153 | ```
|
154 |
|
155 | #### `withFeatureToggle`
|
156 |
|
157 | A HoC to conditionally render a component based on a feature toggle's state. It accepts the feature toggle name and an optional component to be rendered in case the feature is disabled.
|
158 |
|
159 | Without a component rendered in place of the `ComponentToBeToggled`:
|
160 |
|
161 | ```js
|
162 | import { withFeatureToggle } from 'flopflip';
|
163 | import flagsNames from './feature-flags';
|
164 |
|
165 | const ComponentToBeToggled = () => <h3>I might be gone or there!</h3>;
|
166 |
|
167 | export default withFeatureToggle(flagsNames.THE_FEATURE_TOGGLE)(
|
168 | ComponentToBeToggled
|
169 | );
|
170 | ```
|
171 |
|
172 | With a component rendered in place of the `ComponentToBeToggled`:
|
173 |
|
174 | ```js
|
175 | import { withFeatureToggle } from 'flopflip';
|
176 | import flagsNames from './feature-flags';
|
177 |
|
178 | const ComponentToBeToggled = () => <h3>I might be gone or there!</h3>;
|
179 | const ComponentToBeRenderedInstead = () =>
|
180 | <h3>At least there is a fallback!</h3>;
|
181 |
|
182 | export default withFeatureToggle(flagsNames.THE_FEATURE_TOGGLE)(
|
183 | ComponentToBeToggled,
|
184 | ComponentToBeRenderedInstead
|
185 | );
|
186 | ```
|
187 |
|
188 | #### `injectFeatureToggles`
|
189 |
|
190 | This HoC matches feature toggles given against configured ones and injects the matching result. `withFeatureToggle` uses this to conditionally render a component.
|
191 |
|
192 | ```js
|
193 | import { injectFeatureToggles } from 'flopflip';
|
194 | import flagsNames from './feature-flags';
|
195 |
|
196 | const Component = props => {
|
197 | if (props.featureToggles[flagsNames.TOGGLE_A])
|
198 | return <h3>Something to render!</h3>;
|
199 | else if (props.featureToggles[flagsNames.TOGGLE_B])
|
200 | return <h3>Something else to render!</h3>;
|
201 |
|
202 | return <h3>Something different to render!</h3>;
|
203 | };
|
204 |
|
205 | export default injectFeatureToggles([flagsNames.TOGGLE_A, flagsNames.TOGGLE_B])(
|
206 | Component
|
207 | );
|
208 | ```
|
209 |
|
210 | The feature flags will be available as `props` within the component allowing some custom decisions based on their value.
|
211 |
|
212 | ### Module formats
|
213 |
|
214 | `Flopflip` is built as a UMD module using [`rollup`](https://github.com/tdeekens/flopflip/blob/master/rollup.config.js). The distribution version is not added to `git` but created as a `preversion` [script](https://github.com/tdeekens/flopflip/blob/master/package.json).
|
215 |
|
216 | - ...ESM just import the `dist/flopflip.es.js` within your app.
|
217 | - ...it's a transpiled version accessible via the `pkg.module`
|
218 | - ...CommonJS use the `dist/flopflip.umd.js`
|
219 | - ...AMD use the `dist/flopflip.umd.js`
|
220 | - ...`<script />` link it to `dist/flopflip.umd.js` or `dist/flopflip.umd.min.js`
|
221 |
|
222 | All build files are part of the npm distribution using the [`files`](https://github.com/tdeekens/flopflip/blob/master/package.json) array to keep install time short.
|
223 |
|
224 | Also feel free to use [unpkg.com](https://unpkg.com/flopflip@latest/dist/flopflip.umd.min.js) as a CDN to the [dist](https://unpkg.com/flopflip@latest/dist/) files.
|