|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT license. |
| 3 | + |
| 4 | +import React from 'react'; |
| 5 | + |
| 6 | +/** |
| 7 | + * This folder contains some simple examples using conditional compilation directive. |
| 8 | + * |
| 9 | + * They are intended to be a cheet-sheat when you're trying to figure out how to make conditional compilation work for you. |
| 10 | + * |
| 11 | + * Just like the rest of the code in this repository, this module can be built for both flavored builds: |
| 12 | + * |
| 13 | + * To build for beta (i.e. without any code removed by conditional compilation): |
| 14 | + * |
| 15 | + * ```bash |
| 16 | + * rush switch-flavor:beta # This is also the default |
| 17 | + * rush build -t . |
| 18 | + * `` |
| 19 | + * |
| 20 | + * To build for stable (i.e. marked code conditionally removed): |
| 21 | + * |
| 22 | + * ```bash |
| 23 | + * rush switch-flavor:stable |
| 24 | + * rush build -t . |
| 25 | + * ``` |
| 26 | + * |
| 27 | + * In the latter case, you can see the transformed code in `packages/acs-ui-common/preprocessed/conditional-compilation-sample/`. |
| 28 | + * |
| 29 | + * General guidelines: |
| 30 | + * 1. Keep conditional compilation as small as possible. |
| 31 | + * 2. Keep conditional compilation encapsulated. |
| 32 | + * 3. Keep conditional compilation near package boundary. |
| 33 | + * a. If the conditional compilation is in our exported API, try to quickly convert to a type that is not conditional. |
| 34 | + * This will often simplify implementation. |
| 35 | + * b. If the conditional compilation is to deal with a dependency, try to quickly fill in a default value so that you |
| 36 | + * don't need conditional compilation at point-of-use. |
| 37 | + * |
| 38 | + * (1) is not always possible (depends on the feature you're working on), but (3) can help you make the footprint smaller, and |
| 39 | + * (2) can help you keep the footprint sane. |
| 40 | + * |
| 41 | + * Organization of this file: |
| 42 | + * - First let's talk types |
| 43 | + * - Then let's talk conditional business logic |
| 44 | + * - Finally, let's bring those together in some repeated patterns we see in this code-base. |
| 45 | + */ |
| 46 | + |
| 47 | +/* eslint-disable jsdoc/require-jsdoc */ |
| 48 | + |
| 49 | +/****************************************************************************** |
| 50 | + * |
| 51 | + * Conditional types |
| 52 | + * |
| 53 | + */ |
| 54 | + |
| 55 | +/** |
| 56 | + * Conditionally define a type or interface. |
| 57 | + */ |
| 58 | +/* @conditional-compile-remove-from(stable) */ |
| 59 | +type A = number; |
| 60 | + |
| 61 | +/* @conditional-compile-remove-from(stable) */ |
| 62 | +interface B { |
| 63 | + c: number; |
| 64 | +} |
| 65 | + |
| 66 | +/** |
| 67 | + * Conditionally import from a package. |
| 68 | + */ |
| 69 | +/* @conditional-compile-remove-from(stable) */ |
| 70 | +import { Dir } from 'fs'; |
| 71 | + |
| 72 | +/** |
| 73 | + * Conditionally export from a module. |
| 74 | + */ |
| 75 | +/* @conditional-compile-remove-from(stable) */ |
| 76 | +export interface C { |
| 77 | + a: A; |
| 78 | + b: B; |
| 79 | +} |
| 80 | + |
| 81 | +/* @conditional-compile-remove-from(stable) */ |
| 82 | +export type MyDir = Dir; |
| 83 | + |
| 84 | +/** |
| 85 | + * Conditionally add fields to an interface |
| 86 | + */ |
| 87 | +export interface B2 { |
| 88 | + sameOld: number; |
| 89 | + /* @conditional-compile-remove-from(stable) */ |
| 90 | + somethingNew: number; |
| 91 | +} |
| 92 | + |
| 93 | +/** |
| 94 | + * Conditionally add variants to a type union |
| 95 | + * |
| 96 | + * Watchout: A common pitfall here is adding the conditional directive before the binary operator. |
| 97 | + */ |
| 98 | +export type Unionize = number | /* @conditional-compile-remove-from(stable) */ boolean; |
| 99 | +export type Impossible = number & /* @conditional-compile-remove-from(stable) */ boolean; |
| 100 | + |
| 101 | +/** |
| 102 | + * Add a parameter to an existing function |
| 103 | + * |
| 104 | + * We do not support adding a parameter to a function _implementation_ signature. |
| 105 | + * But we do support conditional parameters in function overload signature. |
| 106 | + * |
| 107 | + * So there are two ways to achieve this: |
| 108 | + * - Add a single overload signature with conditional parameter: `f` in the example. |
| 109 | + * - Add a new overload signature with conditional paramter: `f` and `g` in the example. |
| 110 | + * |
| 111 | + * In each case, you must modify the implementation signature to satisfy all the overloads. |
| 112 | + * The example does this by adding `f` and `g` as optional parameters. |
| 113 | + * |
| 114 | + * cf: https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads. |
| 115 | + */ |
| 116 | +export function d(e: number, /* @conditional-compile-remove-from(stable) */ f: number): void; |
| 117 | +/* @conditional-compile-remove-from(stable) */ |
| 118 | +export function d(e: number, f: number, g: number): void; |
| 119 | +export function d(e: number, f?: number, g?: number) { |
| 120 | + console.log(e); |
| 121 | + /* @conditional-compile-remove-from(stable) */ |
| 122 | + console.log(f, g); |
| 123 | +} |
| 124 | + |
| 125 | +/****************************************************************************** |
| 126 | + * |
| 127 | + * Conditional business logic |
| 128 | + * |
| 129 | + */ |
| 130 | + |
| 131 | +/** |
| 132 | + * Call a function with conditional parameters. |
| 133 | + */ |
| 134 | +export function dCaller() { |
| 135 | + d(1, /* @conditional-compile-remove-from(stable) */ 2); |
| 136 | + d(1, /* @conditional-compile-remove-from(stable) */ 2, /* @conditional-compile-remove-from(stable) */ 3); |
| 137 | + |
| 138 | + // The following would stable flavor build because the function overload signature for `d` only allows one |
| 139 | + // argument in stable flavor. |
| 140 | + // d(1, 2, 3); |
| 141 | +} |
| 142 | + |
| 143 | +/** |
| 144 | + * Conditional inclusion of JSX components can be a bit tricky, because we must remove partial expressions. |
| 145 | + */ |
| 146 | +export function GottaHaveAnExtraStackItem(): JSX.Element { |
| 147 | + return ( |
| 148 | + <ul> |
| 149 | + <li>Old kid</li> |
| 150 | + {/* @conditional-compile-remove-from(stable) */ <li>New Kid</li>} |
| 151 | + </ul> |
| 152 | + ); |
| 153 | +} |
| 154 | + |
| 155 | +/** |
| 156 | + * Conditionally modify props passed to JSX components. |
| 157 | + * |
| 158 | + * It's best to recast this as a conditional variable assignment. |
| 159 | + */ |
| 160 | +export function OverrideSomePropInBeta(): JSX.Element { |
| 161 | + const flavorDependentProp = propTrampoline(); |
| 162 | + return <h1 className={flavorDependentProp}>Nothing to see here!</h1>; |
| 163 | +} |
| 164 | +function propTrampoline() { |
| 165 | + let propValue = 'general'; |
| 166 | + /* @conditional-compile-remove-from(stable) */ |
| 167 | + propValue = 'II class'; |
| 168 | + return propValue; |
| 169 | +} |
| 170 | + |
| 171 | +/****************************************************************************** |
| 172 | + * |
| 173 | + * Common complex patterns |
| 174 | + * |
| 175 | + */ |
| 176 | + |
| 177 | +/** |
| 178 | + * A common example where a combination of some of the examples above is required is extending a selector. |
| 179 | + * |
| 180 | + * Note how the selector has a conditional field in the *return type* and the *implementation*, but does not have a conditional |
| 181 | + * dependency (and hence no conditional argument). This follows from the principle that conditional compilation should be restricted |
| 182 | + * to be as close to the API boundary as possible -- we require the selector's type to not include the new field in stable flavored builds, |
| 183 | + * but there is no reason that the internal argument list can't contain the extra (and unused) dependency on a new selector. |
| 184 | + */ |
| 185 | +export type MyExtensibleSelector = ( |
| 186 | + state: DummyState, |
| 187 | + props: DummyProps |
| 188 | +) => { |
| 189 | + memoizedA: boolean; |
| 190 | + memoizedB: boolean; |
| 191 | + /* @conditional-compile-remove-from(stable) */ |
| 192 | + memoizedC: boolean; |
| 193 | +}; |
| 194 | + |
| 195 | +export const myExtensibleSelector: MyExtensibleSelector = dummyCreateSelector( |
| 196 | + [memoizedBoolean, memoizedBoolean, memoizedBoolean], |
| 197 | + (a, b, c) => { |
| 198 | + return { |
| 199 | + memoizedA: a, |
| 200 | + memoizedB: b, |
| 201 | + /* @conditional-compile-remove-from(stable) */ |
| 202 | + memoizedC: c |
| 203 | + }; |
| 204 | + } |
| 205 | +); |
| 206 | + |
| 207 | +/****************************************************************************** |
| 208 | + * |
| 209 | + * Some helpers needed for examples above. |
| 210 | + * |
| 211 | + * No conditional compilation examples below this. |
| 212 | + */ |
| 213 | + |
| 214 | +type DummyState = unknown; |
| 215 | +type DummyProps = unknown; |
| 216 | +function memoizedBoolean(state: DummyState, props: DummyProps) { |
| 217 | + console.log(state, props); |
| 218 | + return true; |
| 219 | +} |
| 220 | + |
| 221 | +function dummyCreateSelector( |
| 222 | + dependencySelectors: [ |
| 223 | + (state: DummyState, props: DummyProps) => boolean, |
| 224 | + (state: DummyState, props: DummyProps) => boolean, |
| 225 | + (state: DummyState, props: DummyProps) => boolean |
| 226 | + ], |
| 227 | + func: (a: boolean, b: boolean, c: boolean) => { memoizedA: boolean; memoizedB: boolean; memoizedC: boolean } |
| 228 | +): (state: DummyState, props: DummyProps) => { memoizedA: boolean; memoizedB: boolean; memoizedC: boolean }; |
| 229 | +function dummyCreateSelector( |
| 230 | + dependencySelectors: [ |
| 231 | + (state: DummyState, props: DummyProps) => boolean, |
| 232 | + (state: DummyState, props: DummyProps) => boolean, |
| 233 | + (state: DummyState, props: DummyProps) => boolean |
| 234 | + ], |
| 235 | + func: (a: boolean, b: boolean, c: boolean) => { memoizedA: boolean; memoizedB: boolean } |
| 236 | +): (state: DummyState, props: DummyProps) => { memoizedA: boolean; memoizedB: boolean }; |
| 237 | +function dummyCreateSelector( |
| 238 | + dependencySelectors: [ |
| 239 | + (state: DummyState, props: DummyProps) => boolean, |
| 240 | + (state: DummyState, props: DummyProps) => boolean, |
| 241 | + (state: DummyState, props: DummyProps) => boolean |
| 242 | + ], |
| 243 | + func: (a: boolean, b: boolean, c: boolean) => unknown |
| 244 | +) { |
| 245 | + return (state: DummyState, props: DummyProps) => { |
| 246 | + return func( |
| 247 | + dependencySelectors[0](state, props), |
| 248 | + dependencySelectors[1](state, props), |
| 249 | + dependencySelectors[2](state, props) |
| 250 | + ); |
| 251 | + }; |
| 252 | +} |
0 commit comments