Skip to content

Commit fe84397

Browse files
authored
[compiler][playground] (4/N) Config override panel (#34436)
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn test --debug --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary Removed the old `OVERRIDE` pragma to make the source of truth for config overrides in the left-hand pane. Now, it will automatically update the output pane each time there is an edit to the config. The old pragma format is still supported, but it will be overwritten by the config pane if they are modifying the same flags. Removed the gating on the config panel so now all users will automatically be able to view it, but it will be initially collapsed. <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> ## How did you test this change? https://github.com/user-attachments/assets/9d4512b9-e203-4ce0-ae95-dd96ff03bbc1 <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. -->
1 parent b1c519f commit fe84397

File tree

7 files changed

+80
-355
lines changed

7 files changed

+80
-355
lines changed

compiler/apps/playground/components/Editor/ConfigEditor.tsx

Lines changed: 35 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,8 @@ import type {editor} from 'monaco-editor';
1010
import * as monaco from 'monaco-editor';
1111
import React, {useState, useCallback} from 'react';
1212
import {Resizable} from 're-resizable';
13-
import {useSnackbar} from 'notistack';
1413
import {useStore, useStoreDispatch} from '../StoreContext';
1514
import {monacoOptions} from './monacoOptions';
16-
import {
17-
ConfigError,
18-
generateOverridePragmaFromConfig,
19-
updateSourceWithOverridePragma,
20-
} from '../../lib/configUtils';
2115

2216
// @ts-expect-error - webpack asset/source loader handles .d.ts files as strings
2317
import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts';
@@ -28,61 +22,17 @@ export default function ConfigEditor(): React.ReactElement {
2822
const [isExpanded, setIsExpanded] = useState(false);
2923
const store = useStore();
3024
const dispatchStore = useStoreDispatch();
31-
const {enqueueSnackbar} = useSnackbar();
3225

3326
const toggleExpanded = useCallback(() => {
3427
setIsExpanded(prev => !prev);
3528
}, []);
3629

37-
const handleApplyConfig: () => Promise<void> = async () => {
38-
try {
39-
const config = store.config || '';
40-
41-
if (!config.trim()) {
42-
enqueueSnackbar(
43-
'Config is empty. Please add configuration options first.',
44-
{
45-
variant: 'warning',
46-
},
47-
);
48-
return;
49-
}
50-
const newPragma = await generateOverridePragmaFromConfig(config);
51-
const updatedSource = updateSourceWithOverridePragma(
52-
store.source,
53-
newPragma,
54-
);
55-
56-
dispatchStore({
57-
type: 'updateFile',
58-
payload: {
59-
source: updatedSource,
60-
config: config,
61-
},
62-
});
63-
} catch (error) {
64-
console.error('Failed to apply config:', error);
65-
66-
if (error instanceof ConfigError && error.message.trim()) {
67-
enqueueSnackbar(error.message, {
68-
variant: 'error',
69-
});
70-
} else {
71-
enqueueSnackbar('Unexpected error: failed to apply config.', {
72-
variant: 'error',
73-
});
74-
}
75-
}
76-
};
77-
7830
const handleChange: (value: string | undefined) => void = value => {
7931
if (value === undefined) return;
8032

81-
// Only update the config
8233
dispatchStore({
83-
type: 'updateFile',
34+
type: 'updateConfig',
8435
payload: {
85-
source: store.source,
8636
config: value,
8737
},
8838
});
@@ -120,49 +70,40 @@ export default function ConfigEditor(): React.ReactElement {
12070
return (
12171
<div className="flex flex-row relative">
12272
{isExpanded ? (
123-
<>
124-
<Resizable
125-
className="border-r"
126-
minWidth={300}
127-
maxWidth={600}
128-
defaultSize={{width: 350, height: 'auto'}}
129-
enable={{right: true}}>
130-
<h2
131-
title="Minimize config editor"
132-
aria-label="Minimize config editor"
133-
onClick={toggleExpanded}
134-
className="p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 font-light text-secondary hover:text-link">
135-
- Config Overrides
136-
</h2>
137-
<div className="h-[calc(100vh_-_3.5rem_-_4rem)]">
138-
<MonacoEditor
139-
path={'config.ts'}
140-
language={'typescript'}
141-
value={store.config}
142-
onMount={handleMount}
143-
onChange={handleChange}
144-
options={{
145-
...monacoOptions,
146-
lineNumbers: 'off',
147-
folding: false,
148-
renderLineHighlight: 'none',
149-
scrollBeyondLastLine: false,
150-
hideCursorInOverviewRuler: true,
151-
overviewRulerBorder: false,
152-
overviewRulerLanes: 0,
153-
fontSize: 12,
154-
}}
155-
/>
156-
</div>
157-
</Resizable>
158-
<button
159-
onClick={handleApplyConfig}
160-
title="Apply config overrides to input"
161-
aria-label="Apply config overrides to input"
162-
className="absolute right-0 top-1/2 transform -translate-y-1/2 translate-x-1/2 z-10 w-8 h-8 bg-blue-500 hover:bg-blue-600 text-white rounded-full border-2 border-white shadow-lg flex items-center justify-center text-sm font-medium transition-colors duration-150">
163-
164-
</button>
165-
</>
73+
<Resizable
74+
className="border-r"
75+
minWidth={300}
76+
maxWidth={600}
77+
defaultSize={{width: 350, height: 'auto'}}
78+
enable={{right: true}}>
79+
<h2
80+
title="Minimize config editor"
81+
aria-label="Minimize config editor"
82+
onClick={toggleExpanded}
83+
className="p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 font-light text-secondary hover:text-link">
84+
- Config Overrides
85+
</h2>
86+
<div className="h-[calc(100vh_-_3.5rem_-_4rem)]">
87+
<MonacoEditor
88+
path={'config.ts'}
89+
language={'typescript'}
90+
value={store.config}
91+
onMount={handleMount}
92+
onChange={handleChange}
93+
options={{
94+
...monacoOptions,
95+
lineNumbers: 'off',
96+
folding: false,
97+
renderLineHighlight: 'none',
98+
scrollBeyondLastLine: false,
99+
hideCursorInOverviewRuler: true,
100+
overviewRulerBorder: false,
101+
overviewRulerLanes: 0,
102+
fontSize: 12,
103+
}}
104+
/>
105+
</div>
106+
</Resizable>
166107
) : (
167108
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
168109
<button

compiler/apps/playground/components/Editor/EditorImpl.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import BabelPluginReactCompiler, {
2222
parsePluginOptions,
2323
printReactiveFunctionWithOutlined,
2424
printFunctionWithOutlined,
25+
type LoggerEvent,
2526
} from 'babel-plugin-react-compiler';
2627
import clsx from 'clsx';
2728
import invariant from 'invariant';
@@ -46,7 +47,6 @@ import {
4647
PrintedCompilerPipelineValue,
4748
} from './Output';
4849
import {transformFromAstSync} from '@babel/core';
49-
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
5050
import {useSearchParams} from 'next/navigation';
5151

5252
function parseInput(
@@ -147,6 +147,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
147147
function compile(
148148
source: string,
149149
mode: 'compiler' | 'linter',
150+
configOverrides: string,
150151
): [CompilerOutput, 'flow' | 'typescript'] {
151152
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
152153
const error = new CompilerError();
@@ -207,7 +208,7 @@ function compile(
207208
}
208209
}
209210
};
210-
const parsedOptions = parseConfigPragmaForTests(pragma, {
211+
const parsedPragmaOptions = parseConfigPragmaForTests(pragma, {
211212
compilationMode: 'infer',
212213
environment:
213214
mode === 'linter'
@@ -227,10 +228,26 @@ function compile(
227228
/* use defaults for compiler mode */
228229
},
229230
});
231+
232+
// Parse config overrides from config editor
233+
let configOverrideOptions: any = {};
234+
const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s);
235+
// TODO: initialize store with URL params, not empty store
236+
if (configOverrides.trim()) {
237+
if (configMatch && configMatch[1]) {
238+
const configString = configMatch[1].replace(/satisfies.*$/, '').trim();
239+
configOverrideOptions = new Function(`return (${configString})`)();
240+
} else {
241+
throw new Error('Invalid config overrides');
242+
}
243+
}
244+
230245
const opts: PluginOptions = parsePluginOptions({
231-
...parsedOptions,
246+
...parsedPragmaOptions,
247+
...configOverrideOptions,
232248
environment: {
233-
...parsedOptions.environment,
249+
...parsedPragmaOptions.environment,
250+
...configOverrideOptions.environment,
234251
customHooks: new Map([...COMMON_HOOKS]),
235252
},
236253
logger: {
@@ -285,19 +302,14 @@ export default function Editor(): JSX.Element {
285302
const dispatchStore = useStoreDispatch();
286303
const {enqueueSnackbar} = useSnackbar();
287304
const [compilerOutput, language] = useMemo(
288-
() => compile(deferredStore.source, 'compiler'),
289-
[deferredStore.source],
305+
() => compile(deferredStore.source, 'compiler', deferredStore.config),
306+
[deferredStore.source, deferredStore.config],
290307
);
291308
const [linterOutput] = useMemo(
292-
() => compile(deferredStore.source, 'linter'),
293-
[deferredStore.source],
309+
() => compile(deferredStore.source, 'linter', deferredStore.config),
310+
[deferredStore.source, deferredStore.config],
294311
);
295312

296-
// TODO: Remove this once the config editor is more stable
297-
const searchParams = useSearchParams();
298-
const search = searchParams.get('showConfig');
299-
const shouldShowConfig = search === 'true';
300-
301313
useMountEffect(() => {
302314
// Initialize store
303315
let mountStore: Store;
@@ -339,7 +351,7 @@ export default function Editor(): JSX.Element {
339351
return (
340352
<>
341353
<div className="relative flex basis top-14">
342-
{shouldShowConfig && <ConfigEditor />}
354+
<ConfigEditor />
343355
<div className={clsx('relative sm:basis-1/4')}>
344356
<Input language={language} errors={errors} />
345357
</div>

compiler/apps/playground/components/Editor/Input.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {useStore, useStoreDispatch} from '../StoreContext';
1717
import {monacoOptions} from './monacoOptions';
1818
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
1919
import React$Types from '../../node_modules/@types/react/index.d.ts';
20-
import {parseAndFormatConfig} from '../../lib/configUtils.ts';
2120

2221
loader.config({monaco});
2322

@@ -83,14 +82,10 @@ export default function Input({errors, language}: Props): JSX.Element {
8382
const handleChange: (value: string | undefined) => void = async value => {
8483
if (!value) return;
8584

86-
// Parse and format the config
87-
const config = await parseAndFormatConfig(value);
88-
8985
dispatchStore({
90-
type: 'updateFile',
86+
type: 'updateSource',
9187
payload: {
9288
source: value,
93-
config,
9489
},
9590
});
9691
};

compiler/apps/playground/components/StoreContext.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,14 @@ type ReducerAction =
5353
};
5454
}
5555
| {
56-
type: 'updateFile';
56+
type: 'updateSource';
5757
payload: {
5858
source: string;
59+
};
60+
}
61+
| {
62+
type: 'updateConfig';
63+
payload: {
5964
config: string;
6065
};
6166
}
@@ -69,11 +74,18 @@ function storeReducer(store: Store, action: ReducerAction): Store {
6974
const newStore = action.payload.store;
7075
return newStore;
7176
}
72-
case 'updateFile': {
73-
const {source, config} = action.payload;
77+
case 'updateSource': {
78+
const source = action.payload.source;
7479
const newStore = {
7580
...store,
7681
source,
82+
};
83+
return newStore;
84+
}
85+
case 'updateConfig': {
86+
const config = action.payload.config;
87+
const newStore = {
88+
...store,
7789
config,
7890
};
7991
return newStore;

0 commit comments

Comments
 (0)