Comparing version 5.0.0-alpha.0 to 5.0.0
{ | ||
"name": "flag", | ||
"version": "5.0.0-alpha.0", | ||
"version": "5.0.0", | ||
"description": "Strictly typed feature flagging for React", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
232
README.md
# Flag | ||
:caution: v5 is a work in progress and is not yet published. :caution: | ||
This library aims to offer a best-in-class interface for working with feature flags in TypeScript-based React applications. | ||
@@ -38,12 +36,12 @@ | ||
### FlagBackendProvider | ||
### `FlagBackendProvider` | ||
_Returned as part of `createFlags<T>()`._ | ||
This React component provides a `Backend<T>` (see below) as a data source for `Flag` and `useFlag`. | ||
This React component provides a `Backend<T>` ([see below](https://github.com/garbles/flag/tree/master/packages/flag#backends)) as a data source for `Flag` and `useFlag`. | ||
| Props | Type | Required | Description | | ||
| ---------- | ------------------ | -------- | ---------------------- | | ||
| `backend` | `Types.Backend<T>` | `true` | All pre-computed flags | | ||
| `children` | `ReactNode` | `true` | React children | | ||
| Props | Type | Required | Description | | ||
| ---------- | ------------------ | -------- | ------------------------- | | ||
| `backend` | `Types.Backend<T>` | `true` | The data source for flags | | ||
| `children` | `ReactNode` | `true` | React children | | ||
@@ -53,4 +51,6 @@ ```tsx | ||
import React from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { NullBackend } from "flag"; | ||
import { MyApplication } from "./app"; | ||
import { App } from "./app"; | ||
import { FlagBackendProvider } from "./flags"; | ||
@@ -60,12 +60,12 @@ | ||
const instance = ( | ||
const root = ReactDOM.createRoot(document.querySelector("#app")); | ||
root.render( | ||
<FlagBackendProvider backend={backend}> | ||
<MyApplication /> | ||
<App /> | ||
</FlagBackendProvider> | ||
); | ||
React.render(instance, document.querySelector("#app")); | ||
``` | ||
### useFlag | ||
### `useFlag` | ||
@@ -98,3 +98,3 @@ _Returned as part of `createFlags<T>()`._ | ||
### Flag | ||
### `Flag` | ||
@@ -123,6 +123,208 @@ _Returned as part of `createFlags<T>()`._ | ||
`FlagBackendProvider` requires that you pass a `Backend<T>` which is responsible for retreiving flags to your application. | ||
`flag` bundles with several useful backends, but you can also roll your own. | ||
### `StaticBackend<T>` | ||
Accepts a JSON object that matches the partial shape of your flags. It can be nested, but shouldn't use arrays. | ||
```tsx | ||
import React from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { StaticBackend } from "flag"; | ||
import { FlagBackendProvider, MyFlags } from "./flags"; | ||
import { App } from "./app"; | ||
const backend = new StaticBackend<MyFlags>({ | ||
features: { | ||
useMyCoolNewThing: false, | ||
}, | ||
config: { | ||
apiUrl: "https://example.com", | ||
}, | ||
cool: 100, | ||
dude: 200, | ||
coolAndDude: 300, | ||
largeCoolAndDude: 600, | ||
}); | ||
const root = ReactDOM.createRoot(document.querySelector("#app")); | ||
root.render( | ||
<FlagBackendProvider backend={backend}> | ||
<App /> | ||
</FlagBackendProvider> | ||
); | ||
``` | ||
If a partial object is provided and a requested key is missing, it will fallback to the provided default. That is, | ||
```tsx | ||
const backend = new StaticBackend<MyFlags>({ | ||
features: {}, | ||
}); | ||
// ... | ||
const SomeScreen = () => { | ||
const newThing = useFlag("features.useMyCoolNewThing", true); | ||
// => will always be `true` | ||
// ... | ||
}; | ||
``` | ||
### `ComputedBackend<T>` | ||
Similar to `StaticBackend` but if a function is used as a value, it will pass in the object `T` as an | ||
argument. (Yes, this means you can end up with a stack overflow if you're not careful.) Useful when you need to have composite flags outside of the React render loop. | ||
```tsx | ||
import React from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { ComputedBackend } from "flag"; | ||
import { FlagBackendProvider, MyFlags } from "./flags"; | ||
import { App } from "./app"; | ||
const backend = new ComputedBackend<MyFlags>({ | ||
features: { | ||
useMyCoolNewThing: false, | ||
}, | ||
config: { | ||
apiUrl: "https://example.com", | ||
}, | ||
cool: 100, | ||
dude: 200, | ||
coolAndDude: (flags) => flags.cool + flags.dude, | ||
largeCoolAndDude: (flags) => flags.coolAndDude * 2, | ||
}); | ||
const root = ReactDOM.createRoot(document.querySelector("#app")); | ||
root.render( | ||
<FlagBackendProvider backend={backend}> | ||
<App /> | ||
</FlagBackendProvider> | ||
); | ||
``` | ||
### `AlwaysBackend` | ||
Given a partial mapping of `{ boolean: boolean; string: string; number: number; }` will always yield the mapping value for a given type. If a type is missing from the mapping, it will fallback to the default value given to `useFlag`. Useful for testing. | ||
```tsx | ||
import React from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { AlwaysBackend } from "flag"; | ||
import { FlagBackendProvider } from "./flags"; | ||
import { App } from "./app"; | ||
const backend = new AlwaysBackend({ | ||
boolean: false, | ||
string: "some string", | ||
number: 1000, | ||
}); | ||
const root = ReactDOM.createRoot(document.querySelector("#app")); | ||
root.render( | ||
<FlagBackendProvider backend={backend}> | ||
<App /> | ||
</FlagBackendProvider> | ||
); | ||
``` | ||
### `NullBackend` | ||
A backend that always returns the default value. Useful for testing. | ||
```tsx | ||
import React from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { NullBackend } from "flag"; | ||
import { FlagBackendProvider } from "./flags"; | ||
import { App } from "./app"; | ||
const backend = new NullBackend(); | ||
const root = ReactDOM.createRoot(document.querySelector("#app")); | ||
root.render( | ||
<FlagBackendProvider backend={backend}> | ||
<App /> | ||
</FlagBackendProvider> | ||
); | ||
``` | ||
### Rolling your own with `AbstractBackend<T>` | ||
You can roll your own backend by extending a class off of `AbstractBackend<T>`. You need only implement the `getSnapshot()` (and optionally `getServerSnapshot()`) method. | ||
```tsx | ||
import { AbstractBackend, Types } from "flag"; | ||
/** | ||
* `F` is the shape of your flags. | ||
*/ | ||
export class MyBackend<F> extends AbstractBackend<F> { | ||
/** | ||
* `KP` is a valid key path (as an array). | ||
* `T` is the type of value associated the key path. | ||
*/ | ||
getSnapshot<KP extends Types.KeyPath<F>, T extends Types.GetValueFromKeyPath<F, KP>>(keyPath: KP, defaultValue: T): T { | ||
/** | ||
* `getSnapshot` must return `T`. | ||
*/ | ||
return defaultValue; | ||
} | ||
/** | ||
* OPTIONAL: you can override `getServerSnapshot` if you need different behavior for server rendering. | ||
* Defaults to `getSnapshot`. | ||
*/ | ||
override getServerSnapshot<KP extends Types.KeyPath<F>, T extends Types.GetValueFromKeyPath<F, KP>>(keyPath: KP, defaultValue: T): T { | ||
/** | ||
* Do something different than `getSnapshot` | ||
*/ | ||
return defaultValue; | ||
} | ||
} | ||
``` | ||
If you want your backend to work with React Suspense, you can create an [async ref object](https://github.com/garbles/flag/tree/master/packages/async-ref) by calling `this.createAsyncRef()` and using it in `getSnapshot()`. | ||
```tsx | ||
import { AbstractBackend, Types } from "flag"; | ||
export class MyBackend<F> extends AbstractBackend<F> { | ||
#data: Types.AsyncMutableRefObject<T>; | ||
constructor() { | ||
this.#data = this.createAsyncRef(); | ||
fetch("/api-with-data") | ||
.then((res) => res.json()) | ||
.then((data) => { | ||
this.#data.current = data; | ||
}); | ||
} | ||
getSnapshot<KP extends Types.KeyPath<F>, T extends Types.GetValueFromKeyPath<F, KP>>(keyPath: KP, defaultValue: T): T { | ||
/** | ||
* Throws a promise if a current value has not yet been assigned. | ||
*/ | ||
const data = this.#data.current; | ||
return someGetterFn(data, keyPath); | ||
} | ||
} | ||
``` | ||
## Setting NODE_ENV | ||
While in development, you should be sure to set `process.env.NODE_ENV` to `"development"` for useful warnings when possible. Tool kits like Remix, Next and CRA do this automatically for you. | ||
## License | ||
MPL-2.0 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
94279
23
1076
1
326
0
7