@atlaskit/feature-gate-js-client

中文文档
8.0.0发布于 2026/11/02Apache-2.0TypeScriptESM + CJS近 30 天下载 568.1万

Atlassians wrapper for the Statsig js-lite client.

原始使用文档
文档目录20 节
  1. Usage
  2. What is this repository for?
  3. Client usage
  4. Installation
  5. Initialization
  6. Evaluation
  7. Subscriptions
  8. File-based persistent overrides
  9. Multiple clients on a single page
  10. Isolated Cloud support
  11. Testing
  12. Jest
  13. Cypress
  14. Playwright
  15. Storybook
  16. Development
  17. How do I get set up?
  18. Contribution guidelines
  19. Releasing
  20. Who do I talk to?

FeatureGateJsClient

Atlassians wrapper for the Statsig js-lite client.

Usage

import FeatureGateJsClient from '@atlaskit/feature-gate-js-client';

What is this repository for?

The js-client covers frontend feature gate use cases. This client is modelled around bootstrapping feature gate values from the backend and does not receive live updates.

Client usage

Installation

The client can be pulled from the Artifactory NPM repository.

yarn add @atlaskit/feature-gate-js-client

Initialization

The client must be initialized before attempted usage or it will throw an error.

There are three ways to initialize your client:

1. Default initialization mechanism

This will initialize the client by calling out to feature-flag-service (fx3), and bootstrapping the client with the returned values. If the client fails to initialize for any reason, including taking longer than 2 seconds to fetch the values, default values will be used.

import FeatureGates, {
    FeatureGateEnvironment,
    FeatureGateProducts,
    PerimeterType,
} from '@atlaskit/feature-gate-js-client';

try {
    await FeatureGates.initialize(
        {
            // This is an fx3 api key used to fetch the feature flag values.
            // Supported keys found at go/fx3/resources/api-keys
            apiKey: 'client-test',
            // This is the environment that you are operating in, targeting rules can target specific environments
            environment: FeatureGateEnvironment.Production,
            // This will be used to filter data from Statsig to only one target app.
            // View [doc](https://hello.atlassian.net/wiki/spaces/MEASURE/pages/2955970231/How-to+Use+TargetApps+in+Statsig)
            // for details on using targetApp.
            targetApp: 'jira_web',
            // [Optional] Default is 2000ms
            // This is the fetch timeout used for requests to feature-flag-service to get values to bootstrap the client.
            // The higher this value the longer your application will need to wait to render if you block on client initialization
            // Too low and your application will be more likely to fallback on default values
            fetchTimeoutMs: 1000,
            // [Optional] Default is false
            // This is a boolean to indicate whether to use the `gateway/api` url for the request to feature flag service.
            // Note that this option takes precendence over the environment and perimeter options in building the url.
            // To be used for applications with strict cross-origin policies, as it will keep all requests to the same origin.
            useGatewayUrl: true,
            // [Optional] Default is false
            // Whether to include flags served by the Feature Flag Platform (FFP). When true, values are fetched from the
            // feature-flag-service `/api/v1/evalFlags` endpoint instead of `/api/v2/frontend/experimentValues`.
            // Each flag is then served by the platform the service assigns it to. When false, FFP is treated as disabled.
            includeFfpFlags: false,
            // [Optional] Must be one of the strings from the exported enum PerimeterType.
            // If provided, will build base url for the `feature-flag-service` based on environment and perimeter type, and
            // will disable all logging to Statsig in perimeters where it is prohibited..
            perimeter: PerimeterType.FEDRAMP_MODERATE,
        },
        {
            // These are expected identifiers, you must provide them if they are relevant to your product and will be added to exposures events
            analyticsAnonymousId: '<analyticsAnonymousId>',
            atlassianAccountId: '<aaid>',
            atlassianOrgId: '<orgid>',
            tenantId: '<tenantid>',
            transactionAccountId: '<transactionAccountId>',
            trelloUserId: '<trelloUserId>',
            trelloWorkspaceId: '<trelloWorkspaceId>',
        },
        {
            // These are additional custom attributes that can be used for targeting and will be added to exposure events
            exampleCustomAttribute: '<attributeValue>',
        },
    );
} catch (err) {
    console.error('Failed to initialize FeatureGates client.', err);
}

If your application has a log-in flow or other mechanism that makes it possible for the user to change during a session, then you can use the updateUser method to apply this change. The signature of this method is almost identical to initialize, except that it only requires options that relate to the network call it will perform to fetch the new set of values.

IMPORTANT: Calling this method will completely re-initialize the client with a new set of flags. You will need to re-render the entire page after this completes to ensure everything picks up the new flag values. You should avoid using this frequently as it has implications on the user experience.

import FeatureGates, {
    FeatureGateEnvironment,
    FeatureGateProducts,
    PerimeterType,
} from '@atlaskit/feature-gate-js-client';

try {
    await FeatureGates.updateUser(
        {
            // This is an fx3 api key used to fetch the feature flag values.
            // Supported keys found at go/fx3/resources/api-keys
            apiKey: 'client-test',
            // This is the environment that you are operating in, targeting rules can target specific environments
            environment: FeatureGateEnvironment.Production,
            // [Optional] Default is 2000ms
            // This is the fetch timeout used for requests to feature-flag-service to get values to bootstrap the client.
            fetchTimeoutMs: 1000,
            // [Optional] Default is false
            // This is a boolean to indicate whether to use the `gateway/api` url for the request to feature flag service.
            // Note that this option takes precendence over the environment and perimeter options in building the url.
            // To be used for applications with strict cross-origin policies, as it will keep all requests to the same origin.
            useGatewayUrl: true,
            // [Optional] Default is false
            // Whether to include flags served by the Feature Flag Platform (FFP). When true, values are fetched from the
            // feature-flag-service `/api/v1/evalFlags` endpoint instead of `/api/v2/frontend/experimentValues`.
            // Each flag is then served by the platform the service assigns it to. When false, FFP is treated as disabled.
            includeFfpFlags: false,
            // [Optional] Default is COMMERCIAL. Must be one of the values from the exported enum PerimeterType.
            // If provided, will build base url for the `feature-flag-service` based on environment and perimeter type, and
            // will disable all logging to Statsig in perimeters where it is prohibited.
            perimeter: PerimeterType.FEDRAMP_MODERATE,
        },
        {
            // These are expected identifiers, you must provide them if they are relevant to your product and will be added to exposures events
            analyticsAnonymousId: '<analyticsAnonymousId>',
            atlassianAccountId: '<aaid>',
            atlassianOrgId: '<orgid>',
            tenantId: '<tenantid>',
            transactionAccountId: '<transactionAccountId>',
            trelloUserId: '<trelloUserId>',
            trelloWorkspaceId: '<trelloWorkspaceId>',
        },
        {
            // These are additional custom attributes that can be used for targeting and will be added to exposure events
            exampleCustomAttribute: '<attributeValue>',
        },
    );
} catch (err) {
    console.error('Failed to update the FeatureGates user.', err);
}

2. Initializing from values

You must fetch the values yourself using one of our wrapper backend clients (Also found in this repo) and providing them to this frontend client

import FeatureGates, {
    FeatureGateEnvironment,
    FeatureGateProducts,
} from '@atlaskit/feature-gate-js-client';

try {
    await FeatureGates.initializeFromValues(
        {
            // [Optional] This should come directly from a backend or service from the Statsig.getClientInitializeResponse(user) call
            // Or supplied from the statsig UI
            // Should be provided if it is available. It is optional so initialisation can still occur if the request to fetch the key fails
            // If not provided, a default string will be used but exposure event data will not be sent back to Statsig
            sdkKey: 'client-test',
            // This is the environment that you are operating in, targeting rules can target specific environments
            environment: FeatureGateEnvironment.Production,
            // This will be used to filter config data relevant to the listed products
            products: [FeatureGateProducts.Jira],
        },
        {
            // These are expected identifiers, you must provide them if they are relevant to your product and will be added to exposures events
            analyticsAnonymousId: '<analyticsAnonymousId>',
            atlassianAccountId: '<aaid>',
            atlassianOrgId: '<orgid>',
            tenantId: '<tenantid>',
            transactionAccountId: '<transactionAccountId>',
            trelloUserId: '<trelloUserId>',
            trelloWorkspaceId: '<trelloWorkspaceId>',
        },
        {
            // These are additional custom attributes that will be added to exposure events
            exampleCustomAttribute: '<attributeValue>',
        },
        // This should come directly from a backend or service from the Statsig.getClientInitializeResponse(user) call
        initializeValues,
    );
} catch (err) {
    console.error('Failed to initialize FeatureGates client.', err);
}

If your application has a log-in flow or other mechanism that makes it possible for the user to change during a session, then you can use the updateUserWithValues method to apply this change. The signature of this method is almost identical to initializeFromValues, except that it does not require any options.

IMPORTANT: Calling this method will completely re-initialize the client with a new set of flags. You will need to re-render the entire page after this completes to ensure everything picks up the new flag values. You should avoid using this frequently as it has implications on the user experience.

import FeatureGates, {
    FeatureGateEnvironment,
    FeatureGateProducts,
} from '@atlaskit/feature-gate-js-client';

try {
    await FeatureGates.updateWithValues(
        {
            // These are expected identifiers, you must provide them if they are relevant to your product and will be added to exposures events
            analyticsAnonymousId: '<analyticsAnonymousId>',
            atlassianAccountId: '<aaid>',
            atlassianOrgId: '<orgid>',
            tenantId: '<tenantid>',
            transactionAccountId: '<transactionAccountId>',
            trelloUserId: '<trelloUserId>',
            trelloWorkspaceId: '<trelloWorkspaceId>',
        },
        {
            // These are additional custom attributes that will be added to exposure events
            exampleCustomAttribute: '<attributeValue>',
        },
        // This should come directly from a backend or service from the Statsig.getClientInitializeResponse(user) call
        initializeValues,
    );
} catch (err) {
    console.error('Failed to update FeatureGates user.', err);
}

If there are any issues during initialization, then the client will be put in a mode which always returns default values, and a rejected promise will be returned. You can catch this rejected promise if you wish to record your own logs and metrics, or if you wish to stop your application from loading with the defaults.

There is only once instance of the FeatureGates client, so only the first initialize call will start the initialization. Any subsequent calls will return the existing Promise for the first initialization, and the argument values will be ignored. In order to confirm whether the client has started to initialize already you can call FeatureGates.initializeCalled().

3. Initializing using a Provider

This initialization is done using an implementation of the Provider in order to fetch the client sdk key and experiment values needed.

Supported providers are:

  • @atlaskit/feature-gate-single-fetch-provider
  • @atlaskit/feature-gate-polling-provider
import FeatureGates, {
    FeatureGateEnvironment,
    FeatureGateProducts,
    PerimeterType,
} from '@atlaskit/feature-gate-js-client';

try {
    await FeatureGates.initializeWithProvider(
        {
            // This is an fx3 api key used to fetch the feature flag values.
            // Supported keys found at go/fx3/resources/api-keys
            apiKey: 'client-test',
            // This is the environment that you are operating in, targeting rules can target specific environments
            environment: FeatureGateEnvironment.Production,
            // This will be used to filter data from Statsig to only one target app.
            // View [doc](https://hello.atlassian.net/wiki/spaces/MEASURE/pages/2955970231/How-to+Use+TargetApps+in+Statsig)
            // for details on using targetApp.
            targetApp: 'jira_web',
            // [Optional] Must be one of the strings from the exported enum PerimeterType.
            // If provided, will build base url for the `feature-flag-service` based on environment and perimeter type, and
            // will disable all logging to Statsig in perimeters where it is prohibited..
            perimeter: PerimeterType.FEDRAMP_MODERATE,
        },
        new Provider(/* ... */),
        {
            // These are expected identifiers, you must provide them if they are relevant to your product and will be added to exposures events
            analyticsAnonymousId: '<analyticsAnonymousId>',
            atlassianAccountId: '<aaid>',
            atlassianOrgId: '<orgid>',
            tenantId: '<tenantid>',
            transactionAccountId: '<transactionAccountId>',
            trelloUserId: '<trelloUserId>',
            trelloWorkspaceId: '<trelloWorkspaceId>',
        },
        {
            // These are additional custom attributes that can be used for targeting and will be added to exposure events
            exampleCustomAttribute: '<attributeValue>',
        },
    );
} catch (err) {
    console.error('Failed to initialize FeatureGates client.', err);
}

If your application has a log-in flow or other mechanism that makes it possible for the user to change during a session, then you can use the updateUserWithProvider method to apply this change. This method will use the same provider and options provided in initializeWithProvider. It takes the identifiers and custom attributes of the new user. that relate to the network call it will perform to fetch the new set of values.

IMPORTANT: Calling this method will completely re-initialize the client with a new set of flags. You will need to re-render the entire page after this completes to ensure everything picks up the new flag values. You should avoid using this frequently as it has implications on the user experience.

import FeatureGates, {
    FeatureGateEnvironment,
    FeatureGateProducts,
} from '@atlaskit/feature-gate-js-client';

try {
    await FeatureGates.updateUserWithProvider(
        {
            // These are expected identifiers, you must provide them if they are relevant to your product and will be added to exposures events
            analyticsAnonymousId: '<analyticsAnonymousId>',
            atlassianAccountId: '<aaid>',
            atlassianOrgId: '<orgid>',
            tenantId: '<tenantid>',
            transactionAccountId: '<transactionAccountId>',
            trelloUserId: '<trelloUserId>',
            trelloWorkspaceId: '<trelloWorkspaceId>',
        },
        {
            // These are additional custom attributes that can be used for targeting and will be added to exposure events
            exampleCustomAttribute: '<attributeValue>',
        },
    );
} catch (err) {
    console.error('Failed to update the FeatureGates user.', err);
}

Feature Flag Platform (FFP) values

During the migration from Statsig to the Feature Flag Platform, flags are served by one platform or the other. With includeFfpFlags: true, initialize and updateUser fetch the Feature Flag Service /api/v1/evalFlags endpoint, which partitions them and returns { statsig, ffp }, so every flag key appears under exactly one platform. Without it, only Statsig values are fetched and FFP is treated as disabled. The client reads a key from FFP (via @atlaskit/flag-client) when the latest FFP values contain it, and from Statsig otherwise; the evaluation APIs do not change:

  • checkGate reads FFP boolean flags.
  • getExperiment / getExperimentValue read FFP JSON object flags as experiment parameters. Any other FFP flag is a flattened Statsig dynamic config, so read it with the value parameter, for example getExperimentValue('example.flag', 'value', 'default').
  • Local overrides still take precedence, and layers are always served by Statsig.
  • FFP exposure telemetry is disabled in FedRAMP and when disableAllLogging is set.

When you initialize or update from values, pass the ffp member of the service response as the optional last argument of initializeFromValues / updateUserWithValues, or as ffp in a Provider's getExperimentValues result. When it is omitted, Statsig serves every flag. Pass it unchanged: its requestContext is required, and without it FFP serves nothing for the user.

The response also has a status for each platform: OK, DISABLED (deliberately not used) or UNAVAILABLE (could not be evaluated). The client uses a platform's values only when it is OK, so flags served only by an unhealthy platform return their code defaults; when FFP is unavailable, the service serves flags that are still being migrated from Statsig. initialize and updateUser fetch once. While at least one platform is OK, or neither is used, they resolve; when a platform is unavailable (or neither is used), the client logs a warning outside production and sends a degraded featureGatesClientValues operational event with statsigStatus, ffpStatus and trigger attributes. When no platform is OK and one is UNAVAILABLE (for example Statsig is unavailable and FFP is disabled), there are no values to use, so they reject as for any failed fetch: initialize still starts Statsig without values, so evaluations return their defaults, and updateUser keeps the previous user's values. Providers can report the status of the values they return through the status field of their result. The polling provider calls the service again while a platform is UNAVAILABLE (after about 30 seconds, backing off up to its polling interval), but not for DISABLED, and keeps the last OK values of an unavailable platform until it recovers. It retries a response with no usable platform with its usual backoff, as for a 5xx. It does not retry a 4xx response other than 408 and 429 until the user changes.

Evaluation

In order to evaluate a gate:

import FeatureGates from '@atlaskit/feature-gate-js-client';

// Note: this checkGate call will automatically fire an exposure event
if (FeatureGates.checkGate('gateName')) {
    // do something here
}

In order to evaluate an experiment:

import FeatureGates from '@atlaskit/feature-gate-js-client';

// Note: this call will automatically fire an exposure event
if (FeatureGates.getExperimentValue('myExperiment', 'myBooleanParameter', false)) {
    // do something here
}

In order to use more complex experiment configuration:

import FeatureGates from '@atlaskit/feature-gate-js-client';

// You can provide an optional function like this to reject any incoming values that don't meet your expectations.
// If this function does not pass, the default value will be returned instead.
const isHexCode = (value: unknown) =>
    typeof value === 'string' && value.startsWith('#') && value.length === 7;

// Note: this call will automatically fire an exposure event. You can provide "fireExposureEvent: false" in the options if you wish to suppress it.
const buttonColor: string = FeatureGates.getExperimentValue(
    'myExperiment',
    'myButtonColorStringParameter',
    '#000000',
    {
        typeGuard: isHexCode,
    },
);

New FFP-only flags vs migrating flags

Flags fall into two groups while products move from Statsig to the Feature Flag Platform (FFP):

FlagRead it withWhy
A new flag, created only in FFPFeatureGates.ffpClientIt does not exist in Statsig.
A pre-existing Statsig flag, including one being migrated to FFPcheckGate, getExperiment, getExperimentValueThey serve each key from whichever platform the Feature Flag Service assigns it to.

Read a new FFP-only flag with the typed methods of FeatureGates.ffpClient, which is the FlagClient of @atlaskit/flag-client that this package manages:

import FeatureGates from '@atlaskit/feature-gate-js-client';
import type { EvaluationDetails } from '@atlaskit/flag-client/types';

const enabled = FeatureGates.ffpClient?.getBooleanValue('my-new-flag', false) ?? false;
const limit = FeatureGates.ffpClient?.getNumberValue('my-new-limit', 10) ?? 10;
const theme = FeatureGates.ffpClient?.getObjectValue('my-new-theme', { color: 'blue' }) ?? {
    color: 'blue',
};

// To log the exposure later, for example when the user sees the feature:
const details: EvaluationDetails<boolean> | undefined = FeatureGates.ffpClient?.getBooleanDetails(
    'my-new-flag',
    false,
    { disableExposureLogging: true },
);
if (details) {
    FeatureGates.ffpClient?.logExposure(details);
}
  • ffpClient is undefined until an initialize method is called. After that it is the same instance for the life of the page, including across updateUser calls.
  • Until FFP values arrive, and when the service does not use FFP for the user, reads return the default you pass.
  • Local overrides (overrideGate, overrideConfig, setOverrides) do not apply to it.
  • FFP exposure telemetry is disabled in FedRAMP and when disableAllLogging is set.
  • FfpEvaluationClient, the type of ffpClient, is exported from @atlaskit/feature-gate-js-client/ffp/types. The types its methods use (EvaluationDetails, EvaluationOptions and the rest) come from @atlaskit/flag-client/types, so add @atlaskit/flag-client as a dependency to import them.

Keep reading a pre-existing Statsig flag with checkGate or getExperimentValue while it is being migrated. Do not switch it to ffpClient: ffpClient serves only FFP values, so it returns the default for any user Statsig still serves the flag to. Conversely, do not read an FFP-only flag with checkGate or getExperimentValue: they map FFP values onto Statsig shapes (see Feature Flag Platform (FFP) values) and are being retired with Statsig.

The methods of ffpClient match those of FlagClient, so once the migration is complete, code that reads ffpClient can move to a FlagClient from @atlaskit/flag-client without changing its reads.

Exposure Event Logging

Exposure events are batched and sent to Statsig every 10 seconds. Statsig's domain for their event logging API is blocked by some ad blockers, so by default we are proxying these requests through xp.atlassian.com to reduce exposure loss.

Subscriptions

To subscribe to changes to gates. The callback will be called when the check gate value changes.

import FeatureGates from '@atlaskit/feature-gate-js-client';

const unsubscribe = FeatureGates.onGateUpdated('gateName', () => {});

// To unsubscribe
unsubscribe();

To subscribe to changes to experiment values. The callback will be called when the experiment value changes.

import FeatureGates from '@atlaskit/feature-gate-js-client';

const unsubscribe = FeatureGates.onExperimentValueUpdated(
    'myExperiment',
    'myButtonColorStringParameter',
    '#000000',
    () => {},
    {
        typeGuard: isHexCode,
    },
);

// To unsubscribe
unsubscribe();

To subscribe to whenever a new set of values is updated on the client, no matter if the underlying values have changed.

import FeatureGates from '@atlaskit/feature-gate-js-client';

// Note: The callback will be called whenever a new set of values is set even if none of the values in the set have changed.
const unsubscribe = FeatureGates.onAnyUpdated(() => {});

// To unsubscribe
unsubscribe();

File-based persistent overrides

For CLI tools and local development environments where the browser's localStorage is not available, you can use the FilePersistentOverrideAdapter. This allows you to persist feature flag overrides to a file on the file system.

⚠️ DO NOT USE THIS PACKAGE IN A BROWSER CONTEXT - The FilePersistentOverrideAdapter is designed for CLI tools and local development environments only. It will not work properly in browser environments and may cause errors.

import { FilePersistentOverrideAdapter } from '@atlaskit/feature-gate-js-client/file-persistent-override-adapter';
import { FeatureGateClient } from '@atlaskit/feature-gate-js-client/client';

// Create the file override adapter with a path where overrides will be stored
const overrideAdapter = new FilePersistentOverrideAdapter('/path/to/overrides.json');

// Create a client instance with the adapter
const client = new FeatureGateClient({
  overrideAdapter: overrideAdapter
});


await client.initialize({
  // ... standard initialization options
});

Unlike the browser localStorage adapter, the file adapter will create any necessary directories to store the file, and will properly handle file system errors.

Multiple clients on a single page

Typically we don't allow multiple usages of the feature gate client on a single page because the client makes a lot of heavy network calls which could have a drastic performance impact for customers if many clients were to exist on a single page. However there are some cases where a seperate client to the product is absolutely necessary. We expose a way to instantiate a new client instead of using the static methods for this case.

Due to the performance implications please ask us in #help-statsig-switcheroo before using the standalone client so that we can check if there are any alternative solutions that won't impact customers and make us aware of the cases where separate clients are necessary.

import FeatureGateClient from '@atlaskit/feature-gate-js-client/client';

const featureGates = new FeatureGateClient();

// Usage is the same as the static API
await featureGates.initialize({
    // ...
});
featureGates.checkGate('my-gate');

Isolated Cloud support

The client supports isolated cloud environments and will be able to init using FFS in IC environments. The SDK automatically resolves the right stargate api url based on the environment provided by the user and the window location.

If users are running this client as part of server code and as a backend SDK and want to talk to FFS directly, they need to pass isolationContextId which is an optional field in ClientOptions. The SDK will construct the FFS URL based on the isolationContextId from which SDK will do the initialization.

import FeatureGates, {
    FeatureGateEnvironment,
    FeatureGateProducts,
    PerimeterType,
} from '@atlaskit/feature-gate-js-client';

try {
    await FeatureGates.initialize(
        {
            // This is an fx3 api key used to fetch the feature flag values.
            // Supported keys found at go/fx3/resources/api-keys
            apiKey: 'client-test',
            isolationContextId: 'ic-7bf',
        });
} catch (err) {
    console.error('Failed to initialize FeatureGates client.', err);
}

Testing

Jest

Testing initialization states

You can test the various initialization states by mocking the return values for initialize and updateUser. Note that you will also need to mock initializeCalled, as this is usually updated by the real initialize function.

import FeatureGates from '@atlaskit/feature-gate-js-client';

jest.mock('@atlaskit/feature-gate-js-client', () => ({
    ...jest.requireActual('@atlaskit/feature-gate-js-client'),
    initializeCalled: jest.fn(),
    initialize: jest.fn(),
    updateUser: jest.fn(),
}));

const MockedFeatureGates = jest.mocked(FeatureGates);

describe('with successful initialization', () => {
    beforeEach(() => {
        MockedFeatureGates.initializeCalled.mockReturnValue(true);
        MockedFeatureGates.initialize.mockResolvedValue();
        MockedFeatureGates.updateUser.mockResolvedValue();
    });

    afterEach(() => jest.resetAllMocks());
});

describe('with failed initialization', () => {
    beforeEach(() => {
        MockedFeatureGates.initializeCalled.mockReturnValue(true);
        MockedFeatureGates.initialize.mockRejectedValue();
        MockedFeatureGates.updateUser.mockRejectedValue();
    });

    afterEach(() => jest.resetAllMocks());
});

describe('with pending initialization', () => {
    // These can be called within your tests transition from the pending
    // initialization state into a successful/failed state.
    let resolveInitPromise;
    let rejectInitPromise;
    beforeEach(() => {
        const initPromise = new Promise((resolve, reject) => {
            resolveInitPromise = resolve;
            rejectInitPromise = reject;
        });
        MockedFeatureGates.initializeCalled.mockReturnValue(true);
        MockedFeatureGates.initialize.mockReturnValue(initPromise);
        MockedFeatureGates.updateUser.mockReturnValue(initPromise);
    });

    afterEach(() => jest.resetAllMocks());
});

Overriding values

There are two ways that you can override values in Jest tests:

  1. Using mocks
  2. Using the built-in override methods

Using mocks

import FeatureGates, { DynamicConfig, EvaluationReason } from '@atlaskit/feature-gate-js-client';

jest.mock('@atlaskit/feature-gate-js-client', () => ({
    ...jest.requireActual('@atlaskit/feature-gate-js-client'),
    getExperiment: jest.fn(),
    checkGate: jest.fn(),
}));

const MockedFeatureGates = jest.mocked(FeatureGates);

describe('with mocked experiments and gates', () => {
    beforeEach(() => {
        const overrides = {
            configs: {
                'example-experiment': {
                    cohort: 'variation',
                },
            },
            gates: {
                'example-gate': true,
            },
        };

        MockedFeatureGates.getExperiment.mockImplementation((experimentName) => {
            const values = overrides.configs[experimentName] || {};
            return new DynamicConfig(experimentName, values, 'rule-id', {
                time: Date.now(),
                reason: EvaluationReason.LocalOverride,
            });
        });

        MockedFeatureGates.checkGate.mockImplementation((gateName, defaultValue) => {
            return overrides.gates[gateName] || defaultValue;
        });
    });

    afterEach(() => jest.resetAllMocks());
});

Using overrides methods

import FeatureGates, { FeatureGateEnvironment } from '@atlaskit/feature-gate-js-client';

describe('with overridden gates and experiments', () => {
    beforeAll(async () => {
        // setOverrides can only be called if the client is _actually_ initialized. You can't mock the initialization, you will have invoke it properly.
        await FeatureGates.initializeWithValues(
            {
                environment: FeatureGateEnvironment.Development,
                sdkKey: 'client-default-key',
                localMode: true,
            },
            {},
            {},
        );
    });

    beforeEach(() => {
        const overrides = {
            configs: {
                'example-experiment': {
                    cohort: 'variation',
                },
            },
            gates: {
                'example-gate': true,
            },
        };

        FeatureGates.setOverrides(overrides);
    });

    afterEach(() => FeatureGates.clearAllOverrides());
});

Cypress

Overriding values

The .visit command in Cypress creates a new window with its own instance of FeatureGates, so you will not be able to simply import the module and apply stubs to it.

// ❌ This will not work!

import FeatureGates, { LocalOverrides } from '@atlaskit/feature-gate-js-client';

const overrides: LocalOverrides = {
    configs: {
        'example-experiment': {
            cohort: 'variation',
        },
    },
    gates: {
        'example-gate': true,
    },
};

// These interact with the FeatureGates instance that your test framework is running in
cy.stub(
    FeatureGates,
    'checkGate',
    (gateName, defaultValue) => overrides.gates[gateName] || defaultValue,
);

cy.stub(FeatureGates, 'getExperiment', (experimentName) => {
    const values = overrides.configs[experimentName] || {};
    return new DynamicConfig(experimentName, values, 'rule-id', {
        time: Date.now(),
        reason: EvaluationReason.LocalOverride,
    });
});

// This will creates a new window, with its own FeatureGates instance.
cy.visit('http://localhost:3000/');

// The test feature will not exist since the stubs don't exist on the visited window.
cy.get('#test-feature').dblclick();

Instead, you will need to obtain a reference to the client that exists on the generated window, and apply your overrides to that instead.

We have exposed a window.__FEATUREGATES_JS__ variable which will contain the instance attached to the window.

// ✅ Do this instead!

import { LocalOverrides } from '@atlaskit/feature-gate-js-client';

const overrides: LocalOverrides = {
    configs: {
        'example-experiment': {
            cohort: 'variation',
        },
    },
    gates: {
        'example-gate': true,
    },
};

cy.visit('http://localhost:3001', {
    // onLoad provides a reference to the generated window, and it is also only invoked when all scripts have finished loading, so the __FEATUREGATES_JS__
    // variable will be available at this point.
    onLoad: (contentWindow) => {
        const FeatureGates = contentWindow.__FEATUREGATES_JS__;

        // Note that the client would not have been initialized by this point, so we can't use the override* or setOverrides methods.
        // We also don't want to wait until the initialization completes, because then the page may have already started to render without these overrides.
        cy.stub(
            FeatureGates,
            'checkGate',
            (gateName, defaultValue) => overrides.gates[gateName] || defaultValue,
        );

        cy.stub(FeatureGates, 'getExperiment', (experimentName) => {
            const values = overrides.configs[experimentName] || {};
            return new DynamicConfig(experimentName, values, {
                time: Date.now(),
                reason: EvaluationReason.LocalOverride,
            });
        });
    },
});

You can also set up your own custom command which listens to the next window:load event to do the same thing, which you can invoke before any page visit:

import { LocalOverrides } from '@atlaskit/feature-gate-js-client';

Cypress.Commands.add('featureGateOverrides', (overrides: LocalOverrides) => {
    // Use cy.once instead of cy.on so that this only affects the next visit.
    cy.once('window:load', (contentWindow) => {
        const FeatureGates = contentWindow.__FEATUREGATES_JS__;

        cy.stub(
            FeatureGates,
            'checkGate',
            (gateName, defaultValue) => overrides?.gates?.[gateName] || defaultValue,
        );

        cy.stub(FeatureGates, 'getExperiment', (experimentName) => {
            const values = overrides?.configs?.[experimentName] || {};

            return new DynamicConfig(experimentName, values, {
                time: Date.now(),
                reason: EvaluationReason.LocalOverride,
            });
        });
    });
});

// Once the command has been added, you can use it in any of your tests like this
cy.featureGateOverrides({
    configs: {
        'example-experiment': {
            cohort: 'variation',
        },
    },
    gates: {
        'example-gate': true,
    },
}).visit('http://localhost:3001');

Playwright

Similar to Cypress, Playwright requires you to apply your stubs after a page has loaded. We can utilise page.addInitScript to hook into this lifecycle in order to stub window.__FEATUREGATES_JS__.

Overriding values

import {
  LocalOverrides,
  DynamicConfig,
  EvaluationReason,
} from '@atlaskit/feature-gate-js-client';
import { Page } from '@playwright/test';

export async function withFeatureGateOverrides(
  page: Page,
  overrides: LocalOverrides,
) {
  await page.addInitScript(
    ({ overridesSerialized, localOverrideReason }) => {
      const FeatureGates = {
        initialize: () => Promise.resolve(),
        checkGate: (gateName: string) => {
          return overridesSerialized.gates?.[gateName] ?? false;
        },

        getExperiment: (experimentName: string) => {
          const values = overridesSerialized.configs?.[experimentName] || {};
          return new DynamicConfig(experimentName, values, 'rule-id', {
            time: Date.now(),
            reason: localOverrideReason,
          });
        },
      };

      window.__FEATUREGATES_JS__ = {
        ...window.__FEATUREGATES_JS__,
        ...FeatureGates,
      };
    },
    {
      overridesSerialized: overrides,
      localOverrideReason: EvaluationReason.LocalOverride,
    },
  );
}

Below is an example of consuming this

import { expect, test } from '@playwright/test';
import { withFeatureGateOverrides } from './utils/statsig';

test.describe('Playwright test suite', () => {
  test.beforeEach(async ({ page }) => {
    await withFeatureGateOverrides(page, {
      configs: {},
      gates: {
        your_feature_gate: true,
      },
      layers: {},
    });
  });

  test('should pass', async ({ page }) => {
    await expect(1).toEqual(1);
  });
});

Storybook

Overriding values

Storybook does not have any mocking or stubbing APIs, but you can use the override* and setOverrides methods on this client as a replacement.

Please note that the client must be initialized before these methods can be called, and that the overrides will need to be cleared after each storybook is unmounted, since they are persisted to localStorage.

The easiest way to get set up is to use the FeatureGatesInitializationWithDefaults component in our React SDK (@atlassian/feature-gates-react) with the overrides prop set, since this manages the initialization and clean-up for you. Please see the component documentation for more information.

Development

How do I get set up?

  • Summary of set up
    • This repo package uses yarn
    • Run yarn install in the root directory to set up your git hooks.
    • In order to get started run yarn to install the dependencies
  • How to run tests
    • In order to run all tests simply run yarn test packages/measurement/feature-gate-js-client from the platform directory
    • In order to run jest tests in watch mode while doing development run yarn test:jest --watch
    • NOTE: You may need to run yarn build @atlaskit/feature-gate-js-client to create a version.ts file thats required for some tests

Contribution guidelines

  • All new logic must be tested
    • There is no need to test direct pass through of Statsig APIs
    • Transformation of arguments counts as new logic
  • Code review
    • Changes must go through a pull request to be merged
  • Other guidelines

Releasing

This package is part of the AFP monorepo. Create a changeset using yarn changeset and commit. Documentation

Who do I talk to?

This repo is owned by the experimentation platform team, reach out to !disturbed in #help-switcheroo-statsig if you need a hand.

版本与发布记录

@atlaskit/feature-gate-js-client 共发布 65 个版本,首次发布于 2024/03/01;过去 12 个月发布了 16 个版本。

版本发布日期距今
8.0.0latest2026/11/025 天前
7.1.02026/10/299 天前
7.0.02026/09/241 个月前
6.0.22026/08/033 个月前
6.0.12026/07/153 个月前
6.0.02026/06/164 个月前

关于 @atlaskit/feature-gate-js-client 的常见问题

@atlaskit/feature-gate-js-client 是什么?

Atlassians wrapper for the Statsig js-lite client.

如何安装 @atlaskit/feature-gate-js-client?

在项目目录运行 npm install @atlaskit/feature-gate-js-client,或使用 pnpm add / yarn add / bun add。

@atlaskit/feature-gate-js-client 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@atlaskit/feature-gate-js-client 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@atlaskit/feature-gate-js-client 可以商用吗?

@atlaskit/feature-gate-js-client 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@atlaskit/feature-gate-js-client 的下载量有多少?

根据 npm 官方下载统计,@atlaskit/feature-gate-js-client 在 2026/10/07 – 2026/11/05 这 30 天内被下载 5,681,460 次,日均约 189,382 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@atlaskit/feature-gate-js-client 还在维护吗?

最新版本 8.0.0 发布于 2026/11/02(5 天前);至今共发布 65 个版本,其中过去 12 个月发布了 16 个;首次发布于 2024/03/01。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。