@nuskin/ns-feature-flags-vue

Nu Skin Feature Flagging Vue Plugin

Usage no npm install needed!

<script type="module">
  import nuskinNsFeatureFlagsVue from 'https://cdn.skypack.dev/@nuskin/ns-feature-flags-vue';
</script>

README

npm version

Nu Skin Feature Flags - Vue

This package includes Nu Skin's Vue plugin for working with feature flagging.

For more information on feature flagging at Nu Skin, like instructions about how to set up feature flags, see our internal documentation.

Note that, due to limitations of our current feature flag server, all flag names must consist of lower case letters, digits, underscores _, and dashes -. The name must start with a letter and must end with a letter or number. For more, see Feature Flag Naming.

Installation

npm i --save @nuskin/ns-feature-flags-vue
yarn add @nuskin/ns-feature-flags-vue

This package exports both CommonJS and ES Modules versions, so all build tooling which respects the 'modules' option in package.json (like Webpack and Rollup) will find the correct version for their environment.

Usage

In a Component

In an individual component, you can use the Mixin to expose feature flags to your component. This works both in applications and component libraries.


import {UsesFeatureFlags} from '@nuskin/ns-feature-flags-vue';

export default {
    mixins: [UsesFeatureFlags]
}

The mixin exposes a method named featureEnabled.

if (this.featureEnabled('my-feature-flag')) {
    // do something flag-ish
}
<div v-if="featureEnabled('my-feature-flag')">
    <!-- Feature-related markup -->
</div>

Using a Global Mixin

If you want to expose featureEnabled to all components within your application without them having to explicitly use the mixin, you can. This introduces a bit of 'magic' into the application, so be sure to document that this is what you're doing so that future developers aren't stumped by where this random method is coming from. You'll also want to disable some normally helpful warnings that will annoy you if you use this as a global mixin.

Vue CLI

In main.js:

import { UsesFeatureFlags } from '@nuskin/ns-feature-flags-vue';

Vue.mixin(UsesFeatureFlags);
UsesFeatureFlags.logMissingAppComponent = false;

Nuxt

Create a file named plugins/FeatureFlags.js.

import { UsesFeatureFlags } from '@nuskin/ns-feature-flags-vue';
import Vue from 'vue';

Vue.mixin(UsesFeatureFlags);
UsesFeatureFlags.logMissingAppComponent = false;

Now, in your Nuxt configuration (nuxt.config.js), tell Nuxt about your plugin:

module.exports = {
  //... other stuff ...
  plugins: [
    // ... other plugins ...
    '~/plugins/FeatureFlags.js'
  ]
};

In an Application

By themselves, components will always see flags as disabled. To control how flags are loaded in a consistent way across the entire application, the root node of the application should be wrapped in a feature-flag-app (exact naming to TBD) component.

If you don't know where the 'root element' is in your app, look at the Vue CLI and Nuxt instructions below, or look for a v-app tag in your application.

feature-flag-app provides two things: A single source of truth for where and how flags should be loaded, as well as deferring rendering of the application until feature flags have been loaded (which greatly simplifies the use of feature flags).

feature-flag-app contains optional configuration that controls from where it loads the flags. Note that changing any of these properties will cause all children of feature-flag-app to re-render, and thus should not be done outside of a development environment.

Prop Note
env Environment from which to load flags. If not set, infers the environment from the current page URL. Valid names are 'dev', 'test', 'stage', 'local'. Can also be an explicit URL to the feature flag service.
user User ID for user-specific feature flags
<!-- Put this in the correct place for your project. See below. -->
<template>
  <feature-flag-app>
    <!-- Your App Here -->
  </feature-flag-app>
</template>
<script>
    import {FeatureFlagApp} from '@nuskin/ns-feature-flags-vue';
    
    export default {
        components: {
            FeatureFlagApp
        },
        // Other Options
    };
</script>

You can also specify a template to be rendered while the flags are loading using the loading slot.

<feature-flag-app>
  <my-app />
  <template #loading>
    <my-loading-indicator />
  </template>
</feature-flag-app>

Usage in a component library

In a component library, you can safely use UsesFeatureFlags in any component. Do NOT register UsesFeatureFlags as a global mixin inside of a component library - mutating anything on the global Vue instance (Vue.use, Vue.mixin, etc) is highly discouraged in a component library.

When in a component library, do NOT include FeatureFlagApp in any production components. The point of FeatureFlagApp is to provide a single source of truth for what flags are loaded, so if a component library says one thing and the consuming application says another, you can get into lots of weird situations.

However, it is quite nice to be able to see feature flags turned on in your storybook. In order to do this, you can add a 'decorator' to your storybook which contains the FeatureFlagApp component.

In your Storybook preview.js (usually located in either .storybook/preview.js or config/storybook/preview.js), add a decorator that will wrap your stories in the component.

Storybook 6

import { FeatureFlagApp } from '@nuskin/ns-feature-flags-vue';

export const decorators = [
  (story) => ({
    components: { story, FeatureFlagApp },
    template: '<feature-flag-app><story /></feature-flag-app>'
  })
];

Storybook 5

import { FeatureFlagApp } from '@nuskin/ns-feature-flags-vue';
import { addDecorator } from '@storybook/vue';

addDecorator(() => ({
    components: { FeatureFlagApp },
    template: '<feature-flag-app><story /></feature-flag-app>'
}));

Vue CLI

By default, Vue CLI components have an App.vue which bootstraps the rest of the application. You can add feature-flag-app inside that component.

Nuxt

If you already have layouts in your Nuxt app (in the layouts directory), you can add this component to the root of each of your layouts. If you do not have a layouts directory, create one in the root of your application, and then create default.vue:

<template>
  <feature-flag-app>
    <nuxt />
  </feature-flag-app>
</template>
<script>
import {FeatureFlagApp} from '@nuskin/ns-feature-flags-vue';

export default {
    components: { FeatureFlagApp }
}
</script>

This will apply the feature flag element to all of the pages in your nuxt app.

Unit Testing

To test code that's behind a feature flag, you can use the mocks option on mount (in @vue/test-utils) or render (in @testing-library/vue). Alternatively, you can use the testing export in this library.

Vue Test Utils

import { mountWithFlags } from '@nuskin/ns-feature-flags-vue/testing';

test('some fancy new feature', () => {
  const wrapper = mountWithFlags(MyComponent, {
    flags: ['my-fancy-flag']
  });
  // Test your feature
});

Vue Testing Library

import { renderWithFlags } from '@nuskin/ns-feature-flags-vue/testing';

test('some fancy new feature', () => {
  const { getByText } = renderWithFlags(MyComponent, {
    flags: ['my-fancy-flag']
  });
  // Test your feature
});

Configuring Warnings

FeatureFlagApp and UsesFeatureFlags can emit helpful warnings when you're using them incorrectly.

  • FeatureFlagApp.logMultipleInstances controls whether a warning is emitted if there are multiple FeatureFlagApp instances present in the same application.
  • UsesFeatureFlags.logMissingAppComponent will log if UsesFeatureFlag is used without a FeatureFlagApp being present somewhere in its tree.

By default, these warnings are turned on. If you are in a Jest testing environment, they are turned off. You can also turn them off manually:

import { FeatureFlagApp, UsesFeatureFlags } from '@nuskin/ns-feature-flags-vue';

FeatureFlagApp.logMultipleInstances = false;
UsesFeatureFlags.logMissingAppComponent = false;

Legacy Docs

This section contains the documentation for deprecated plugin-based version of this library. It can still be used, but has a lot of sharp edges that are solved by the new mixin-and-root-component-based version.

Nuxt

Deprecated!

To use with Nuxt, install as described above, then add the following to your Nuxt configuration:

export default {
    // ... other configuration
    modules: [
        '@nuskin/ns-feature-flags-vue/nuxt',
        // ... other modules
    ]
}

Vue CLI

Deprecated!

There will be a Vue CLI plugin released soon. For now, add a file named src/plugins/ns-feature-flags.js:

import FeatureFlagPlugin from '@nuskin/ns-feature-flags-vue';

Vue.use(FeatureFlagPlugin);

Then, import this plugin in src/main.js file:

import 'plugins/ns-feature-flags.js';

Usage

Configuration

Deprecated!

You can configure the URL of the feature flag server:

Vue.use(FeatureFlagPlugin, { url: 'https://www.nuskin.com/my-fancy-flags' });

By default, the plugin uses the production feature flag server.

Consuming Flags

Deprecated!

The plugin registers a $features property on all Vue components. The keys of this object are the feature flag names.

So, to check the value of a flag named 'foo':

if (this.$features.foo) {
    // do something neat
}

Or:

<span v-if="$features.foo">Something Neat</span>

You can also use array indexing to get other styles of flag name, like Jira issue numbers:

if (this.$features['test-1234']) {
    // do something neat
}

IMPORTANT

In order to consume feature flags outside of templates and computed properties, you MUST wait for the flags to load before checking them. Otherwise, your code may not see the correct value!

await this.$features.$waitForLoad();

For example, if you want to use flags in 'mounted':

async mounted() {
  await this.$features.$waitForLoad();
  if (this.$features.my_flag) {
    // do something cool
  }
}

Triggering Reloads

Deprecated!

You may wish to trigger a reload of the flags. You can do this by invoking $forceReload():

await this.$features.$forceReload();
this.$features.foo // will be the latest value from the server

The returned promise will resolve when the reload finishes.

To wait for any loads that are currently in progress, you can call $waitForLoad():

await this.$features.$waitForLoad();
this.$features.foo // will be the latest value from the server

Overriding Flag Values

To explicitly set a flag value, like in an integration test or local development, you can set a value in the query string or in session storage.

Both the query string and session storage values use the same key names. To enable flags, the key is $enableFeatureFlags; to disable them, it is $disableFeatureFlags. Both of these values are a comma-separated list of the flags you want to enable or disable.

Examples

To enable the flags 'foo' and 'bar' and disable the flag 'baz' in the query string, add this to the URL: ?$enableFeatureFlags=foo,bar&$disableFeatureFlags=baz.

To do the same in session storage, run the following command in your devtools console:

sessionStorage.setItem('$enableFeatureFlags', 'foo,bar');
sessionStorage.setItem('$disableFeatureFlags', 'baz');

Feature Flag Users

You can set a 'userId' to be sent to the Feature Flag Server. This ID allows you to enable flags for specific users. For example, to allow QA to test a feature before general release, you could enable the flag for a user named 'qa'. Then, when browsing the application, the QA just has to set the user.

There are four supported ways to set the feature flag user:

  • When the Vue plugin is installed
  • By calling $setUser
  • By setting a query parameter named $featureFlagUser
  • By setting a session storage value named $featureFlagUser

At plugin installation

Vue.use(FeatureFlagPlugin, { user: 'qa' });

With $setUser

In a Vue component:

await this.$features.$setUser('qa');
this.$features.foo // will be the latest value for this user

Development

NPM Scripts

  • test - Runs Jest unit tests
  • lint - Runs ES Lint
  • build - Transpiles the library for use in Node environments.