@asos/web-toggle-point-features
v0.5.0
Published
toggle point features code, used to store toggle state
Downloads
17
Readme
@asos/web-toggle-point-features
This package provides utility modules for managing features state in applications.
It provides global or contextual status of the toggles that should govern relevant modules, passing methods to an appropriate decision-making toggle point (e.g. those created by withTogglePoint or withToggledHook from the react-pointcuts package).
A store should be chosen based on the requirement for global or partitioned state, and the reactivity needed, for the type of toggle.
Usage
See: JSDoc output
Exports
[!WARNING] This package uses
package.jsonexports to specify individual stores (listed below), to ensure that browser / node specific stores can be individually imported and prevent build failures where, prior to tree-shaking, incompatible APIs / globals are referenced. Due to a long-standing bug ineslint-plugin-import, users of eslint with this plugin may need to ignore animport/no-unresolvederror, or move to a modern alternative for this plugin (e.g.eslint-plugin-import-x), or use a typescript parser (which understandsexports)
The package contains the following exports:
storeFactories/globalFeaturesStoreFactory
A "global" features store factory: a thin wrapper around a singleton, this is an extension point for future plugins etc. Each invocation will create a new store, even with a toggleType matching a prior invocation.
It accepts the following parameters:
toggleType- the type of the toggle, used only for debugging.
It exports a store with:
- a
setValuefunction, that sets a current value. - a
getFeaturesfunction- designed to be passed as the
getActiveFeaturesinput of thewithTogglePointFactoryorwithToggledHookFactoryfrom thereact-pointcutspackage.
- designed to be passed as the
For protection against variation (or other) code modifying the toggle state unduly, the value passed could be deep frozen, e.g.
import { deepFreeze } from "deep-freeze-es6";
const initialValue = {};
featuresStore.setValue({ value: deepFreeze(initialValue) });For reactive values, without the need for a React or other contextual wrapper, consider wrapping an object with valtio:
import { proxy } from "valtio/vanilla";
const initialValue = {};
featuresStore.setValue({ value: proxy(initialValue) });...which can then be subscribed to in an appropriate toggle point, to re-evaluate toggled functions:
import { subscribe } from "valtio/vanilla";
subscribe(featuresStore.getFeatures(), () => { /* re-evaluate */ });If using React (e.g. react-pointcuts package), can just use the native support in Valtio:
import { proxy, useSnapshot } from "valtio";
const initialValue = {};
featuresStore.setValue({ value: proxy(initialValue) });
export const setValue = (input) => // consumed in updating code-paths
featuresStore.setValue({
value: Object.assign(featuresStore.getFeatures(), input)
});
export const getActiveFeatures = () => useSnapshot(featuresStore.getFeatures()); // passed to `withTogglePointFactory`...which will then re-render consuming components based on the parts of the toggle state they are reliant on.
storeFactories/nodeRequestScopedFeaturesStoreFactory
A "request scoped" features store factory, for use in Node.
It accepts the following parameters:
toggleType- the type of the toggle, this is keyed against a singleton referenced by a runtime-wide global symbol, to ensure it works across multi-compilation runtimes (e.g. NextJs) / throughout a realm. N.B. Each invocation with the same toggleType will return the initial store.
It exports a store with:
- a
setValuefunction that sets a current value, taking ascopeCallBack(along with avalue), under which the value is scoped.- This is using
AsyncLocalStorage.rununder the hood, which can be plugged into Express middleware thus:import express from "express"; const app = express(); const featuresStore = requestScopedFeaturesStoreFactory({ toggleType: "some type of toggle" }); app.use((request, response, next) => { const value = ?? // some value holding toggle state, either based on `request`, or scoped from outside this middleware, etc. featuresStore.setValue({ value, scopeCallBack: next }); }); app.use("/", () => { /* routes that require toggled code */ });
- This is using
- a
getFeaturesfunction- designed to be passed as the
getActiveFeaturesinput of thewithTogglePointFactoryorwithToggledHookFactoryfrom thereact-pointcutspackage.
- designed to be passed as the
[!WARNING] This will throw an error if called outside of a request scope, so care should be taken to set up the toggle point config to only toggle modules called within the call stack of the middleware. Wrap the
setValuecall in atry/catch, if prior access is expected. If this happens unexpectedly, follow the advice here.
storeFactories/reactContextFeaturesStoreFactory
It accepts the following parameters:
toggleType- the type of the toggle, forming the displayName of the react context provider
It exports a store with:
- a
providerFactoryfactory function, creating a react context provider.- appropriate parts of the react tree, that need to have toggled react components, should be wrapped by this provider. It should be passed a value representing active features state.
- a
getFeaturesfunction- this uses
useContextinternally, so should be used honouring the rules of hooks. It will make consumers reactive to any change of the toggle state. - can be passed to
getActiveFeaturesofwithTogglePointFactory/withToggledHookFactoryfrom thereact-pointcutspackage.
- this uses
storeFactories/ssrBackedReactContextFeaturesStoreFactory
It exports a store with the same signature as that exported by reactContextFeaturesStoreFactory. It utilises withJsonIsomorphism from the ssr package internally, to create "isomorphic" or "universal" contexts, for use in framework-less React applications. The value set on the server will be realised as the initial value within the browser.
It accepts the following parameters:
namespace- this becomes a prefix for the
idof theapplication/jsonscript written to the page, useful for pages running multiple react applications.
- this becomes a prefix for the
toggleType- the type of the toggle, the latter part of the
idof the aforementioned script, and becoming the prop holding the features state, and forming the displayName of the underlying react context provider
- the type of the toggle, the latter part of the
logWarning- a method to log warnings, should the serialized json somehow become malformed when hydrating the client application
- this was designed to allow modifications of markup in systems upstream of the origin, but downstream of the browser, with a view to ensure adequate telemetry is in place.
- a method to log warnings, should the serialized json somehow become malformed when hydrating the client application
[!WARNING]
Use with React 17
The react-specific stores should work with React 17 and above, but due to a bug that they are not back-filling, the use of
"type": "module"in the package means webpack will be unable to resolve the extensionless import. To fix, either upgrade to React 18+ or add the following resolve configuration to the webpack config:resolve: { alias: { "react/jsx-runtime": "react/jsx-runtime.js", "react-dom/server": "react-dom/server.js", } }
