Skip to content

Commit c6d8a57

Browse files
authored
Add a sample / cheet-sheat of conditional compilation directives (#1430)
1 parent 8eaa0b5 commit c6d8a57

3 files changed

Lines changed: 261 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "Add sample module for conditional compilation",
4+
"packageName": "@internal/acs-ui-common",
5+
"email": "82062616+prprabhu-ms@users.noreply.github.com",
6+
"dependentChangeType": "none"
7+
}

packages/acs-ui-common/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"@azure/communication-common": "1.0.0"
3232
},
3333
"peerDependencies": {
34-
"react": ">=16.8.0 <18.0.0"
34+
"react": ">=16.8.0 <18.0.0",
35+
"@types/react": ">=16.8.0 <18.0.0"
3536
},
3637
"devDependencies": {
3738
"@babel/cli": "~7.16.0",
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
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

Comments
 (0)