Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
import { Atom, atom, react } from 'signia'
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// --- 1. DEFINE ---
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
//
|
|
|
|
// Define your debug values and feature flags here. Use `createDebugValue` to
|
|
|
|
// create an arbitrary value with defaults for production, staging, and
|
|
|
|
// development. Use `createFeatureFlag` to create a boolean flag which will be
|
|
|
|
// `true` by default in development and staging, and `false` in production.
|
|
|
|
/** @internal */
|
|
|
|
export const featureFlags = {
|
|
|
|
// todo: remove this. it's not used, but we only have one feature flag and i
|
|
|
|
// wanted an example :(
|
|
|
|
peopleMenu: createFeatureFlag('peopleMenu'),
|
2023-06-01 12:46:13 +00:00
|
|
|
highlighterTool: createFeatureFlag('highlighterTool'),
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
} satisfies Record<string, DebugFlag<boolean>>
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
export const debugFlags = {
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
// --- DEBUG VALUES ---
|
|
|
|
preventDefaultLogging: createDebugValue('preventDefaultLogging', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
|
|
|
pointerCaptureLogging: createDebugValue('pointerCaptureLogging', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
|
|
|
pointerCaptureTracking: createDebugValue('pointerCaptureTracking', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
2023-04-25 11:01:25 +00:00
|
|
|
pointerCaptureTrackingObject: createDebugValue(
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
'pointerCaptureTrackingObject',
|
2023-04-25 11:01:25 +00:00
|
|
|
// ideally we wouldn't store this mutable value in an atom but it's not
|
|
|
|
// a big deal for debug values
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
{
|
|
|
|
defaults: { all: new Map<Element, number>() },
|
|
|
|
shouldStoreForSession: false,
|
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
),
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
elementRemovalLogging: createDebugValue('elementRemovalLogging', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
|
|
|
debugSvg: createDebugValue('debugSvg', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
|
|
|
throwToBlob: createDebugValue('throwToBlob', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
|
|
|
logMessages: createDebugValue('uiLog', { defaults: { all: [] } }),
|
|
|
|
resetConnectionEveryPing: createDebugValue('resetConnectionEveryPing', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
|
|
|
debugCursors: createDebugValue('debugCursors', {
|
|
|
|
defaults: { all: false },
|
|
|
|
}),
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
interface Window {
|
|
|
|
tldrawLog: (message: any) => void
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
window.tldrawLog = (message: any) => {
|
|
|
|
debugFlags.logMessages.set(debugFlags.logMessages.value.concat(message))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- 2. USE ---
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
// In normal code, read from debug flags directly by calling .value on them:
|
|
|
|
// if (debugFlags.preventDefaultLogging.value) { ... }
|
2023-04-25 11:01:25 +00:00
|
|
|
//
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
// In react, wrap your reads in `useValue` (or your component in `track`)
|
|
|
|
// so they react to changes:
|
|
|
|
// const shouldLog = useValue(debugFlags.preventDefaultLogging)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// --- 3. GET FUNKY ---
|
|
|
|
// If you need to do fun stuff like monkey-patching in response to flag changes,
|
|
|
|
// add that here. Make sure you wrap your code in `react` so it runs
|
|
|
|
// automatically when values change!
|
|
|
|
|
|
|
|
if (typeof Element !== 'undefined') {
|
|
|
|
const nativeElementRemoveChild = Element.prototype.removeChild
|
|
|
|
react('element removal logging', () => {
|
|
|
|
if (debugFlags.elementRemovalLogging.value) {
|
|
|
|
Element.prototype.removeChild = function <T extends Node>(this: any, child: Node): T {
|
|
|
|
console.warn('[tldraw] removing child:', child)
|
|
|
|
return nativeElementRemoveChild.call(this, child) as T
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Element.prototype.removeChild = nativeElementRemoveChild
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- IMPLEMENTATION ---
|
|
|
|
// you probably don't need to read this if you're just using the debug values system
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
function createDebugValue<T>(
|
|
|
|
name: string,
|
|
|
|
{
|
|
|
|
defaults,
|
|
|
|
shouldStoreForSession = true,
|
|
|
|
}: { defaults: Defaults<T>; shouldStoreForSession?: boolean }
|
|
|
|
) {
|
|
|
|
return createDebugValueBase({
|
|
|
|
name,
|
|
|
|
defaults,
|
|
|
|
shouldStoreForSession,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
function createFeatureFlag(
|
|
|
|
name: string,
|
|
|
|
defaults: Defaults<boolean> = { all: true, production: false }
|
|
|
|
) {
|
|
|
|
return createDebugValueBase({
|
|
|
|
name,
|
|
|
|
defaults,
|
|
|
|
shouldStoreForSession: true,
|
|
|
|
})
|
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
function createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {
|
|
|
|
const defaultValue = getDefaultValue(def)
|
|
|
|
const storedValue = def.shouldStoreForSession
|
|
|
|
? (getStoredInitialValue(def.name) as T | null)
|
|
|
|
: null
|
|
|
|
const valueAtom = atom(`debug:${def.name}`, storedValue ?? defaultValue)
|
|
|
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
if (def.shouldStoreForSession) {
|
|
|
|
react(`debug:${def.name}`, () => {
|
|
|
|
const currentValue = valueAtom.value
|
|
|
|
try {
|
|
|
|
if (currentValue === defaultValue) {
|
|
|
|
window.sessionStorage.removeItem(`tldraw_debug:${def.name}`)
|
|
|
|
} else {
|
|
|
|
window.sessionStorage.setItem(`tldraw_debug:${def.name}`, JSON.stringify(currentValue))
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
// not a big deal
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.defineProperty(window, `tldraw${def.name.replace(/^[a-z]/, (l) => l.toUpperCase())}`, {
|
|
|
|
get() {
|
|
|
|
return valueAtom.value
|
|
|
|
},
|
|
|
|
set(newValue) {
|
|
|
|
valueAtom.set(newValue)
|
|
|
|
},
|
|
|
|
configurable: true,
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
return Object.assign(valueAtom, def)
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getStoredInitialValue(name: string) {
|
|
|
|
try {
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
return JSON.parse(window?.sessionStorage.getItem(`tldraw_debug:${name}`) ?? 'null')
|
2023-04-25 11:01:25 +00:00
|
|
|
} catch (err) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
Feature flags rework (#1474)
This diff tweaks our `debugFlags` framework to support setting different
default value for different environments, makes it easier to define
feature flags, and makes feature flags show up in the debug menu by
default. With this change, feature flags will default to being enabled
in dev and preview environments, but disabled in production.
Specify a feature flag like this:
```ts
const featureFlags = {
myCoolNewFeature: createFeatureFlag('myCoolNewFeature')
}
```
optionally, pass a second value to control its defaults:
```ts
const featureFlags = {
featureEnabledInProduction: createFeatureFlag('someFeature', { all: true }),
customEnabled: createFeatureFlag('otherFeature', {development: true, staging: false, production: false}),
}
```
In code, the value can be read using `featureFlags.myFeature.value`.
Remember to wrap reading it in a reactive context!
### Change Type
- [x] `patch` — Bug Fix
### Test Plan
-
### Release Notes
[internal only change]
2023-05-30 13:06:15 +00:00
|
|
|
|
|
|
|
// process.env might not be defined, but we can't access it using optional
|
|
|
|
// chaining because some bundlers search for `process.env.SOMETHING` as a string
|
|
|
|
// and replace it with its value.
|
|
|
|
function readEnv(fn: () => string | undefined) {
|
|
|
|
try {
|
|
|
|
return fn()
|
|
|
|
} catch {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDefaultValue<T>(def: DebugFlagDef<T>): T {
|
|
|
|
const env =
|
|
|
|
readEnv(() => process.env.TLDRAW_ENV) ??
|
|
|
|
readEnv(() => process.env.VERCEL_PUBLIC_TLDRAW_ENV) ??
|
|
|
|
readEnv(() => process.env.NEXT_PUBLIC_TLDRAW_ENV) ??
|
|
|
|
// default to production because if we don't have one of these, this is probably a library use
|
|
|
|
'production'
|
|
|
|
|
|
|
|
switch (env) {
|
|
|
|
case 'production':
|
|
|
|
return def.defaults.production ?? def.defaults.all
|
|
|
|
case 'preview':
|
|
|
|
case 'staging':
|
|
|
|
return def.defaults.staging ?? def.defaults.all
|
|
|
|
default:
|
|
|
|
return def.defaults.development ?? def.defaults.all
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Defaults<T> {
|
|
|
|
development?: T
|
|
|
|
staging?: T
|
|
|
|
production?: T
|
|
|
|
all: T
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
export interface DebugFlagDef<T> {
|
|
|
|
name: string
|
|
|
|
defaults: Defaults<T>
|
|
|
|
shouldStoreForSession: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
export type DebugFlag<T> = DebugFlagDef<T> & Atom<T>
|