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

Client Side Feature Flags #10706

Open
keithamus opened this issue Oct 16, 2024 · 3 comments
Open

Client Side Feature Flags #10706

keithamus opened this issue Oct 16, 2024 · 3 comments
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest

Comments

@keithamus
Copy link
Contributor

keithamus commented Oct 16, 2024

What problem are you trying to solve?

Complex websites often use feature flags to gradually roll-out changes to users. These features can be easily toggled on-or-off per-request, or tweaked by engineers in realtime without changing code and going through a whole deploy/upgrade cycle. On the server it's quite straightforward to run with a solution that makes database queries but on the client there are more considerations and complexities. The biggest problem - in my opinion - is that without a standard third party libraries have no way to coordinate feature flags and so many libraries will expose a config instead, this means more JS required to "initialize" the library with a set of features. But also the solutions that exist today usually have to trade-off between which parts of the platform they get exposed to (JS, CSS, ServiceWorkers) and/or efficiency.

What solutions exist today?

Myriad ways exist, each with their own trade-offs:

  • Expose an API endpoint on the server and make API requests every time you want to check a feature (or make a request for a list of all the client side features). The problem with this is it involves an additional HTTP round trip for data the server likely had (or had easy access to) in the initial response. This can also be tricky because it's asynchronous - introducing accidental complexity of now requiring some code to lazy-load in order to accommodate the asynchronous timing. This also prevents use of these features in CSS as CSS cannot request and read JSON APIs. My rating: D tier solution.
  • Use the cookie header and make the values exposed to script. This solves the synchronicity problem but raises new issues; one of which is that the flags (cookie) are then sent back with every request which is useless for the server and a waste of bandwidth in general. The other is that you've still got to write a bunch of code to parse document.cookie and extract the relevant feature flags. Finally, Set-Cookie is a forbidden Response header name meaning if you have a ServiceWorker, you're unable intercept responses and read feature flags within the service worker context. My rating: E tier solution.
  • Abuse the server-timing header! server-timing can have arbitrary data (server-timing: my_flag;desc="on"), it can store multiple values, it is not a forbidden header which means ServiceWorkers can read it, and also it has a JS API via performance.getEntriesByType('navigation')?.[0]?.serverTiming. However this is not exposed to CSS, and is obviously a gnarly hack and I frankly feel bad for mentioning it. My rating: F tier solution.
  • Set a bunch of classnames/attributes on the html or body element, or stuff them in a <meta> tag etc. This can solve the synchronicity problem, and these would be exposed to CSS, but it also increases the difficulty of reading these from the service worker, as you'll need to pull out and parse the response body (related; Proposal: DOM APIs in web workers? dom#1217). My rating: C tier solution.
  • Dump a blob of JSON in a <script> tag or similar: This seems to be quite common with React apps, where JSON will be embedded somewhere in the HTML, that can then be read from JS and fed into the framework. This isn't CSS exposed and it's difficult to get at via ServiceWorker without parsing the response body as DOM. My rating: E tier solution.

How would you solve it?

I think what I'd like to see is a new HTTP response header (let's call it features) which looks a little like server-timing, allowing arbitrary read-only keys (with optional values) to be extracted by CSS/JS. This flag would be parsed and become part of the page context, with complementary DOM & CSS APIs. Importantly, while this looks a bit like Set-Cookie, the values are never sent back to the server, and there would be no concept of "third party features".

features: use-new-widgets, llm-temp=0.4, cookie-compliance=gdpr, color-contrast=aaa
<!-- features can also be defined with an http-equiv meta tag -->
<meta http-equiv="features" content="use-new-widgets, llm-temp=0.4, cookie-compliance=gdpr, color-contrast=aaa">
interface Features {
  bool has(DOMString feature);
  DOMString get(DOMString feature);
}
partial interface Document {
  readonly attribute Features features;
}
// .has can be used to check for presence of a feature
if (document.features.has('use-new-widgets')) {
  await import('widgets-v4');
}

// While get retrieves the string
respondToPromopt({ temperature: Number(document.features.get('llm-temp')) || 0.7 })

switch (document.features.get('cookie-compliance')) {
  case 'gdpr': return showGDPRCookieBanner();
  case 'ccpa': return showCCPACookieBanner();
}
/* the @feature rule can check for presence of a flag also */
@feature (use-new-widgets) {
  @import "./widgets-v4.css";
}

:root {
  --global-contrast: wcag2(aa);
}
/* but can also check equality against the string value */
@feature (color-contrast: "aaa") {
  --global-contrast: wcag2(aaa);
}

This header would be a safe header for ServiceWorkers to read, and given the simplicity of the format would be straightforward to parse with a couple of lines of JS in the ServiceWorker.

Anything else?

I realise this might not be the ideal venue to discuss this as it crosses many working groups, but as HTML defines Document this seemed as good a place as any.

@keithamus keithamus added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest labels Oct 16, 2024
@ydaniv
Copy link

ydaniv commented Oct 16, 2024

That sounds great for simple stateless toggles. Would there be a way to make this sticky somehow to also enable the A/B use-case?

@keithamus
Copy link
Contributor Author

I think how the flags are set depends on how your server applies them, so stickiness would be part of the server, no? For multivariant you could set a feature like cart-button-color="green",cart-button-color="blue" and so on. Does that answer the A/B use-case you're thinking of?

@ydaniv
Copy link

ydaniv commented Oct 16, 2024

I think this currently achieved with cookies. You want the server to be stateless, but the client needs to remember it's variant and stick to it.
I guess that's exactly the problem you're trying to avoid. So IIUC, unless another solution is found, this is strictly out of scope, which is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest
Development

No branches or pull requests

2 participants