Featurevisor

Advanced

Code Generation

For additional compile-time and runtime safety including autocompletion, we can generate code from our already defined features for improved developer experience.

Why generate code?

This is an optional step that we may wish to adopt in our workflow. If we do, it will help us avoid some common mistakes:

  • any unintentional spelling mistakes in feature and variable keys
  • worrying about the types of our variables
  • worrying about passing attributes in wrong types in context

All of it done being code-driven, thus reducing overall cognitive load of multiple teams.

Supported languages

Currently only TypeScript is supported.

Support for other languages is planned in future, as Featurevisor SDK becomes available in more languages.

Generate code

From the root of our Featurevisor project directory, use the CLI for generating code in a specified directory:

Command
$ npx featurevisor generate-code \
--language typescript \
--out-dir ./src \
--react

The generated files can be found in ./src directory.

Some of the key generated files are:

  • context.ts: typed Context interface for SDK evaluation
  • attributes.ts: generated attribute types, such as CountryAttribute or AccountAttribute
  • schemas.ts: generated reusable schema types
  • features.ts: feature keys, variation values, feature variable keys, and feature variable value types
  • variables.ts: global variable keys and value types
  • functions.ts: typed isEnabled, getVariation, and overloaded getVariable functions
  • instance.ts: SDK instance wiring through setInstance
  • react.ts: optional typed React hooks when --react is used
  • index.ts: exports the generated modules

Generated TypeScript uses FeatureVariableKey and FeatureVariableType for variables owned by features, and GlobalVariableKey and GlobalVariableType for independently defined variables. Regenerating code created by an older generator can therefore require import updates where the former VariableKey or VariableType names were used directly.

Optional flags:

  • --tag=<tag>: generate code for features and global variables with the given tag
  • --target=<target>: generate code for features and global variables selected by the target
  • --react: also generate typed React hooks in react.ts

Both --tag and --target can be repeated. Every supplied tag and target forms one union across features and global variables. This is useful when one generated package serves several datafiles:

Command
$ npx featurevisor generate-code \
--language typescript \
--out-dir ./src \
--tag=shared \
--target=web \
--target=mobile

A target uses tag or tags to select both features and global variables. includeFeatures and excludeFeatures filter features. includeVariables and excludeVariables filter global variables. These selectors match datafile builds. Build-time context does not remove generated types because code generation is not tied to one environment or one specialized datafile result.

Publishing the generated code

We are free to use the generated code in any way we want.

We can choose to either:

  • copy/paste the code in our applications, or
  • publish the generated code as a private npm package and use it in multiple applications, like as @yourorg/features package

The publishing part can be done in the same deployment process right after deploying our generated datafiles.

Consuming the generated code

Assuming we published the generated code as a private npm package @yourorg/features, we can consume it in our applications as follows.

Initialize Featurevisor SDK as usual, and make our newly created package aware of the SDK instance:

your-app/index.js
import { createFeaturevisor } from '@featurevisor/sdk'
import { setInstance } from '@yourorg/features'
const f = createFeaturevisor({
// ...
})
setInstance(f)

Afterwards, we can import a common set of functions which are already aware of which feature keys we are allowed to use including their variable keys.

Importing functions

Similar to JavaScript SDK's methods isEnabled, getVariation, and getVariable, we can import these functions with the same names:

import { isEnabled, getVariation, getVariable } from '@yourorg/features'
const featureIsEnabled = isEnabled('featureKey')
const featureVariation = getVariation('featureKey')
const featureVariable = getVariable('featureKey', 'variableKey')
const sharedVariable = getVariable('supportEmail')

We can optionally pass additional context as the last argument:

const context = {
userId: '123',
}
const featureIsEnabled = isEnabled('featureKey', context)
const featureVariation = getVariation('featureKey', context)
const featureVariable = getVariable('featureKey', 'variableKey', context)
const sharedVariable = getVariable('supportEmail', context)

Everything here is typed as per our defined features.

If we pass a wrong feature key, or a variable key that does not belong to the same feature, we will get a TypeScript error.

Importing React hooks

If we passed --react in CLI, we can import React hooks with the same names as the original package @featurevisor/react:

import { useFlag, useVariation, useVariable } from '@yourorg/features'
const isEnabled = useFlag('featureKey')
const variation = useVariation('featureKey')
const variable = useVariable('featureKey', 'variableKey')
const sharedVariable = useVariable('supportEmail')

Passing any wrong feature key or variable key combination will result in a TypeScript error.

The generated functions and React hooks pass their schema derived result types through the generic APIs of @featurevisor/sdk and @featurevisor/react. This keeps hand written generic calls and generated calls consistent, while generated code also validates feature and variable keys for the selected project.

We can optionally pass additional context as the last argument:

const context = {
userId: '123',
}
const isEnabled = useFlag('featureKey', context)
const variation = useVariation('featureKey', context)
const variable = useVariable('featureKey', 'variableKey', context)

Suggestions for package publishing

You are advised to publish the generated code as a private npm package, with support for ES Modules (ESM).

When published as ES Modules, it will enable tree-shaking in your applications, thus reducing the bundle size.

Previous
Datasource