Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimagine how the babel transform works #53

Merged
merged 6 commits into from
Aug 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 114 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,36 @@ module.exports = function(environment) {

## Usage

### Launch Darkly Service

ember-launch-darkly automatically injects the launch darkly service, as `launchDarkly` in to the following Ember objects:

- all `route`s
- all `controller`s
- all `component`s
- `router:main`

This means that it can be accessed without an explicit injection, like so:

```js
// /app/application/route.js

import Route from '@ember/routing/route';

export default Route.extend({
model() {
let user = {
key: 'aa0ceb',
anonymous: true
};

return this.launchDarkly.initialize(user);
}
});
```

Due to Ember not allowing auto injection of a service in to another service, we are currently unable to auto inject `launchDarkly` in to other services. This means that if you would like to check for Launch Darkly flags in your service, you will need to explicitly inject the `launchDarkly` service yourself.

### Initialize

Before being used, Launch Darkly must be initialized. This should happen early so choose an appropriate place to make the call such as an application initializer or the application route.
Expand All @@ -84,11 +114,8 @@ The user `key` is the only required attribute, see the [Launch Darkly documentat
// /app/application/route.js

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
launchDarkly: service(),

model() {
let user = {
key: 'aa0ceb',
Expand All @@ -102,17 +129,15 @@ export default Route.extend({

### Identify

If you initialized Launch Darkly with an anonymous user and want to re-initialize it for a specific user to receive the flags for that user, you can use the `identify`. This can only be called after `initialize` has been called.
If you initialized Launch Darkly with an anonymous user and want to re-initialize it for a specific user to receive the flags for that user, you can use `identify`. This can only be called after `initialize` has been called.

```js
// /app/session/route.js

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
session: service(),
launchDarkly: service(),

model() {
return this.session.getSession();
Expand All @@ -132,7 +157,7 @@ export default Route.extend({

### Templates

ember-launch-darkly provides a `variation` template helper to check your feature flags.
ember-launch-darkly provides a `variation` helper to check your feature flags in your handlebars templates.

If your feature flag is a boolean based flag, you might use it in an `{{if}}` like so:

Expand Down Expand Up @@ -167,11 +192,8 @@ If your feature flag is a boolean based flag, you might use it in a function lik

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({
launchDarkly: service(),

actions: {
getPrice() {
if (this.launchDarkly.variation('new-price-plan')) {
Expand All @@ -191,11 +213,8 @@ If your feature flag is a multivariate based flag, you might use it in a functio

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({
launchDarkly: service(),

actions: {
getPrice() {
switch (this.launchDarkly.variation('new-pricing-plan')) {
Expand All @@ -220,11 +239,8 @@ And if you want to check a flag in a computed property, and have it recompute wh

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({
launchDarkly: service(),

price: computed('launchDarkly.new-price-plan', function() {
if (this.launchDarkly['new-price-plan']) {
return 99.00;
Expand All @@ -237,15 +253,15 @@ export default Component.extend({

### [EXPERIMENTAL] `variation` Javascript helper

ember-launch-darkly provides a special, experimental, `variation` import that can be used in Javascript files such as Components, to make the invocation and checking of feature flags a little nicer in the JS code.
ember-launch-darkly provides a special, _experimental_, `variation` import that can be used in Javascript files such as Components, to make the invocation and checking of feature flags a little nicer in the JS code.

This helper is backed by a Babel transform that essentially replaces the helper with the code above in the [Javascript](#javascript) section. The main benefits this helper provides is:

- Automatically adds feature flags as dependent keys to computed properties so they are recalculated when flags change
- Removes boiler plate launch darkly code (injection of service, referencing service in functions, etc)
- Provide a syntax that is parallel to the `variation` helper used in templates

The babel transform that replaces this `variation` helper in the JS code is experimental. Your mileage may vary.
The babel transform that replaces this `variation` helper in the JS code is experimental. YMMV.

If you would like to try it out, simply enable it in your `ember-cli-build.js` as follows:

Expand All @@ -257,7 +273,7 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
let app = new EmberApp(defaults, {
babel: {
plugins: [ require.resolve('ember-launch-darkly/launch-darkly-variation-helper') ]
plugins: [ require.resolve('ember-launch-darkly/babel-plugin') ]
}
});

Expand All @@ -271,10 +287,29 @@ Then import the helper from `ember-launch-darkly` and use it as follows:
// /app/components/login-page/component.js

import Component from '@ember/component';
import { computed } from '@ember/object';

import { variation } from 'ember-launch-darkly';

export default Component.extend({
price() {
if (variation('new-pricing-plan')) {
return 99.00;
}

return 199.00;
}
});
```

If you would like the feature flag to recompute a computed property when it
changes, you will need to also use the `computedWithVariation` import, like so:

```js
// /app/components/login-page/component.js

import Component from '@ember/component';
import { variation, computedWithVariation as computed } from 'ember-launch-darkly';

export default Component.extend({
price: computed(function() {
if (variation('new-pricing-plan')) {
Expand All @@ -286,20 +321,19 @@ export default Component.extend({
});
```

For reference, the babel transform should transform the above code in to the following:
The `computedWithvariation` import is literally a direct re-export of the `@ember/object` computed. We need to re-export it so that we can signal to the babel transform where to auto insert the feature flag dependent keys. Because `computedWithVariation` is a re-export you can alias it as `computed` (like above) and use it anywhere you would use a normal `computed`.

For reference, the babel transform should transform the above code in to, _roughly_, the following:

```js
// /app/components/login-page/component.js

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({
ldService: service(),

price: computed('ldService.new-pricing-plan', function() {
if (this.ldService['new-pricing-plan']) {
price: computed('launchDarkly.new-pricing-plan', function() {
if (this.launchDarkly['new-pricing-plan']) {
return 99.00;
}

Expand All @@ -308,6 +342,59 @@ export default Component.extend({
});
```

It's important to note that the `variation` helper will only work when used
inside objects that have an `owner`. This is because the helper is transformed
in to a call to `this.launchDarkly` which is an injected service. Therefore, the
`variation` helper will not work in a computed returned from, say, a function.
Like so:

```js
export default function myCoolFn() {
if (variation('foo')) {
return 'I love cheese';
}

return 'I love bacon';
}
```

For the reason above, if you would like to use the variation helper in one of your own objects that
extends `EmberObject`, you will need to create the `EmberObject` instance with
an owner injection, like so:

```js
// /app/components/login-page/component.js

import Component from '@ember/component';
import EmberObject from '@ember/object';
import { getOwner } from '@ember/application';

const MyFoo = EmberObject.extend({
price() {
return variation('my-price');
}
});

export default Component.extend({
createFoo() {
let owner = getOwner(this);

return MyFoo.create(owner.ownerInjection(), {
someProp: 'bar'
});
}
});

```

As this babel transform is experimental, it does not currently support the
following:

- Native classes
- Decorators

We will endeavour to add support for these things in the future.

## Local feature flags

When `local: true` is set in the Launch Darkly configuration, ember-launch-darkly will retrieve the feature flags and their values from `config/environment.js` instead of the Launch Darkly service. This is useful for development purposes so you don't need to set up a new environment in Launch Darkly, your app doesn't need to make a request for the flags, and you can easily change the value of the flags from the browser console.
Expand Down Expand Up @@ -432,7 +519,7 @@ module('Acceptance | Homepage', function(hooks) {

### Integration Tests

Use the test client and `withVariation` helper in component tests to control feature flags as well.
Use the `setupLaunchDarkly` hook and `withVariation` helper in component tests to control feature flags as well.

```js
import { module, test } from 'qunit';
Expand Down
19 changes: 19 additions & 0 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DEBUG } from '@glimmer/env';

let variation;
if (DEBUG) {
variation = () => {
throw new Error(
`

In order to use the "variation" Javascript helper, you must include the ember-launch-darkly babel plugin. See the README (link below) for more details on how this works and some considerations when using it.

https://github.com/ember-launch-darkly/ember-launch-darkly#experimental-variation-javascript-helper
`
);
};
}

export { variation };

export { computed as computedWithVariation } from '@ember/object';
4 changes: 4 additions & 0 deletions addon/initializers/launch-darkly.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export function initialize(application) {
let Factory = config.local ? LocalClient : RemoteClient;

application.register('service:launch-darkly-client', Factory);

['route', 'controller', 'component', 'router:main'].forEach(type => {
application.inject(type, 'launchDarkly', 'service:launch-darkly');
});
}

export default {
Expand Down
Loading