Skip to content

Commit db24098

Browse files
committed
[playground] Decouple playground from compiler
Currently the playground is setup as a linked workspace for the compiler which complicates our yarn workspace setup and means that snap can sometimes pull in a different version of react than was otherwise specified. There's no real reason to have these workspaces combined so let's split them up. ghstack-source-id: 56ab064 Pull Request resolved: #31081
1 parent 3edc000 commit db24098

File tree

22 files changed

+4142
-2652
lines changed

22 files changed

+4142
-2652
lines changed

.github/workflows/compiler_playground.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ env:
1515

1616
defaults:
1717
run:
18-
working-directory: compiler
18+
working-directory: compiler/apps/playground
1919

2020
jobs:
2121
playground:
@@ -27,13 +27,17 @@ jobs:
2727
with:
2828
node-version-file: '.nvmrc'
2929
cache: yarn
30-
cache-dependency-path: compiler/yarn.lock
30+
cache-dependency-path: compiler/**/yarn.lock
3131
- name: Restore cached node_modules
3232
uses: actions/cache@v4
3333
id: node_modules
3434
with:
3535
path: "**/node_modules"
3636
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
37-
- run: yarn install --frozen-lockfile
37+
- name: yarn install compiler
38+
run: yarn install --frozen-lockfile
39+
working-directory: compiler
40+
- name: yarn install playground
41+
run: yarn install --frozen-lockfile
3842
- run: npx playwright install --with-deps chromium
39-
- run: yarn workspace playground test
43+
- run: yarn test

compiler/apps/playground/app/layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
import '../styles/globals.css';
99

10-
export default function RootLayout({children}: {children: React.ReactNode}) {
10+
export default function RootLayout({
11+
children,
12+
}: {
13+
children: React.ReactNode;
14+
}): JSX.Element {
1115
'use no memo';
1216
return (
1317
<html lang="en">

compiler/apps/playground/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {SnackbarProvider} from 'notistack';
1111
import {Editor, Header, StoreProvider} from '../components';
1212
import MessageSnackbar from '../components/Message';
1313

14-
export default function Hoot() {
14+
export default function Page(): JSX.Element {
1515
return (
1616
<StoreProvider>
1717
<SnackbarProvider

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
4444
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
4545

46-
function parseInput(input: string, language: 'flow' | 'typescript') {
46+
function parseInput(input: string, language: 'flow' | 'typescript'): any {
4747
// Extract the first line to quickly check for custom test directives
4848
if (language === 'flow') {
4949
return HermesParser.parse(input, {
@@ -181,9 +181,9 @@ function getFunctionIdentifier(
181181
}
182182

183183
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
184-
const results = new Map<string, PrintedCompilerPipelineValue[]>();
184+
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
185185
const error = new CompilerError();
186-
const upsert = (result: PrintedCompilerPipelineValue) => {
186+
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
187187
const entry = results.get(result.name);
188188
if (Array.isArray(entry)) {
189189
entry.push(result);
@@ -273,13 +273,17 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
273273
}
274274
}
275275
} catch (err) {
276-
// error might be an invariant violation or other runtime error
277-
// (i.e. object shape that is not CompilerError)
276+
/**
277+
* error might be an invariant violation or other runtime error
278+
* (i.e. object shape that is not CompilerError)
279+
*/
278280
if (err instanceof CompilerError && err.details.length > 0) {
279281
error.details.push(...err.details);
280282
} else {
281-
// Handle unexpected failures by logging (to get a stack trace)
282-
// and reporting
283+
/**
284+
* Handle unexpected failures by logging (to get a stack trace)
285+
* and reporting
286+
*/
283287
console.error(err);
284288
error.details.push(
285289
new CompilerErrorDetail({
@@ -297,7 +301,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
297301
return [{kind: 'ok', results}, language];
298302
}
299303

300-
export default function Editor() {
304+
export default function Editor(): JSX.Element {
301305
const store = useStore();
302306
const deferredStore = useDeferredValue(store);
303307
const dispatchStore = useStoreDispatch();

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

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,17 @@ import {useEffect, useState} from 'react';
1515
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
1616
import {useStore, useStoreDispatch} from '../StoreContext';
1717
import {monacoOptions} from './monacoOptions';
18-
// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
19-
// @ts-ignore
18+
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
2019
import React$Types from '../../node_modules/@types/react/index.d.ts';
2120

2221
loader.config({monaco});
2322

2423
type Props = {
25-
errors: CompilerErrorDetail[];
24+
errors: Array<CompilerErrorDetail>;
2625
language: 'flow' | 'typescript';
2726
};
2827

29-
export default function Input({errors, language}: Props) {
28+
export default function Input({errors, language}: Props): JSX.Element {
3029
const [monaco, setMonaco] = useState<Monaco | null>(null);
3130
const store = useStore();
3231
const dispatchStore = useStoreDispatch();
@@ -38,18 +37,19 @@ export default function Input({errors, language}: Props) {
3837
const model = monaco.editor.getModel(uri);
3938
invariant(model, 'Model must exist for the selected input file.');
4039
renderReactCompilerMarkers({monaco, model, details: errors});
41-
// N.B. that `tabSize` is a model property, not an editor property.
42-
// So, the tab size has to be set per model.
40+
/**
41+
* N.B. that `tabSize` is a model property, not an editor property.
42+
* So, the tab size has to be set per model.
43+
*/
4344
model.updateOptions({tabSize: 2});
4445
}, [monaco, errors]);
4546

46-
const flowDiagnosticDisable = [
47-
7028 /* unused label */, 6133 /* var declared but not read */,
48-
];
4947
useEffect(() => {
50-
// Ignore "can only be used in TypeScript files." errors, since
51-
// we want to support syntax highlighting for Flow (*.js) files
52-
// and Flow is not a built-in language.
48+
/**
49+
* Ignore "can only be used in TypeScript files." errors, since
50+
* we want to support syntax highlighting for Flow (*.js) files
51+
* and Flow is not a built-in language.
52+
*/
5353
if (!monaco) return;
5454
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
5555
diagnosticCodesToIgnore: [
@@ -64,15 +64,17 @@ export default function Input({errors, language}: Props) {
6464
8011,
6565
8012,
6666
8013,
67-
...(language === 'flow' ? flowDiagnosticDisable : []),
67+
...(language === 'flow'
68+
? [7028 /* unused label */, 6133 /* var declared but not read */]
69+
: []),
6870
],
6971
noSemanticValidation: true,
7072
// Monaco can't validate Flow component syntax
7173
noSyntaxValidation: language === 'flow',
7274
});
7375
}, [monaco, language]);
7476

75-
const handleChange = (value: string | undefined) => {
77+
const handleChange: (value: string | undefined) => void = value => {
7678
if (!value) return;
7779

7880
dispatchStore({
@@ -83,7 +85,10 @@ export default function Input({errors, language}: Props) {
8385
});
8486
};
8587

86-
const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
88+
const handleMount: (
89+
_: editor.IStandaloneCodeEditor,
90+
monaco: Monaco,
91+
) => void = (_, monaco) => {
8792
setMonaco(monaco);
8893

8994
const tscOptions = {
@@ -111,10 +116,12 @@ export default function Input({errors, language}: Props) {
111116
monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib);
112117
monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib);
113118

114-
// Remeasure the font in case the custom font is loaded only after
115-
// Monaco Editor is mounted.
116-
// N.B. that this applies also to the output editor as it seems
117-
// Monaco Editor instances share the same font config.
119+
/**
120+
* Remeasure the font in case the custom font is loaded only after
121+
* Monaco Editor is mounted.
122+
* N.B. that this applies also to the output editor as it seems
123+
* Monaco Editor instances share the same font config.
124+
*/
118125
document.fonts.ready.then(() => {
119126
monaco.editor.remeasureFonts();
120127
});
@@ -125,14 +132,18 @@ export default function Input({errors, language}: Props) {
125132
<Resizable
126133
minWidth={650}
127134
enable={{right: true}}
128-
// Restrict MonacoEditor's height, since the config autoLayout:true
129-
// will grow the editor to fit within parent element
135+
/**
136+
* Restrict MonacoEditor's height, since the config autoLayout:true
137+
* will grow the editor to fit within parent element
138+
*/
130139
className="!h-[calc(100vh_-_3.5rem)]">
131140
<MonacoEditor
132141
path={'index.js'}
133-
// .js and .jsx files are specified to be TS so that Monaco can actually
134-
// check their syntax using its TS language service. They are still JS files
135-
// due to their extensions, so TS language features don't work.
142+
/**
143+
* .js and .jsx files are specified to be TS so that Monaco can actually
144+
* check their syntax using its TS language service. They are still JS files
145+
* due to their extensions, so TS language features don't work.
146+
*/
136147
language={'javascript'}
137148
value={store.source}
138149
onMount={handleMount}

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {type CompilerError} from 'babel-plugin-react-compiler/src';
1717
import parserBabel from 'prettier/plugins/babel';
1818
import * as prettierPluginEstree from 'prettier/plugins/estree';
1919
import * as prettier from 'prettier/standalone';
20-
import {memo, useEffect, useState} from 'react';
20+
import {memo, ReactNode, useEffect, useState} from 'react';
2121
import {type Store} from '../../lib/stores';
2222
import TabbedWindow from '../TabbedWindow';
2323
import {monacoOptions} from './monacoOptions';
@@ -42,10 +42,10 @@ export type PrintedCompilerPipelineValue =
4242
| {kind: 'debug'; name: string; fnName: string | null; value: string};
4343

4444
export type CompilerOutput =
45-
| {kind: 'ok'; results: Map<string, PrintedCompilerPipelineValue[]>}
45+
| {kind: 'ok'; results: Map<string, Array<PrintedCompilerPipelineValue>>}
4646
| {
4747
kind: 'err';
48-
results: Map<string, PrintedCompilerPipelineValue[]>;
48+
results: Map<string, Array<PrintedCompilerPipelineValue>>;
4949
error: CompilerError;
5050
};
5151

@@ -54,7 +54,10 @@ type Props = {
5454
compilerOutput: CompilerOutput;
5555
};
5656

57-
async function tabify(source: string, compilerOutput: CompilerOutput) {
57+
async function tabify(
58+
source: string,
59+
compilerOutput: CompilerOutput,
60+
): Promise<Map<string, ReactNode>> {
5861
const tabs = new Map<string, React.ReactNode>();
5962
const reorderedTabs = new Map<string, React.ReactNode>();
6063
const concattedResults = new Map<string, string>();
@@ -112,8 +115,10 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
112115
}
113116
// Ensure that JS and the JS source map come first
114117
if (topLevelFnDecls.length > 0) {
115-
// Make a synthetic Program so we can have a single AST with all the top level
116-
// FunctionDeclarations
118+
/**
119+
* Make a synthetic Program so we can have a single AST with all the top level
120+
* FunctionDeclarations
121+
*/
117122
const ast = t.program(topLevelFnDecls);
118123
const {code, sourceMapUrl} = await codegen(ast, source);
119124
reorderedTabs.set(
@@ -175,7 +180,7 @@ function getSourceMapUrl(code: string, map: string): string | null {
175180
)}`;
176181
}
177182

178-
function Output({store, compilerOutput}: Props) {
183+
function Output({store, compilerOutput}: Props): JSX.Element {
179184
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
180185
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
181186
() => new Map(),
@@ -236,11 +241,13 @@ function TextTabContent({
236241
output: string;
237242
diff: string | null;
238243
showInfoPanel: boolean;
239-
}) {
244+
}): JSX.Element {
240245
const [diffMode, setDiffMode] = useState(false);
241246
return (
242-
// Restrict MonacoEditor's height, since the config autoLayout:true
243-
// will grow the editor to fit within parent element
247+
/**
248+
* Restrict MonacoEditor's height, since the config autoLayout:true
249+
* will grow the editor to fit within parent element
250+
*/
244251
<div className="w-full h-monaco_small sm:h-monaco">
245252
{showInfoPanel ? (
246253
<div className="flex items-center gap-1 bg-amber-50 p-2">

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
import dynamic from 'next/dynamic';
99

10-
// monaco-editor is currently not compatible with ssr
11-
// https://github.com/vercel/next.js/issues/31692
10+
/**
11+
* monaco-editor is currently not compatible with ssr
12+
* https://github.com/vercel/next.js/issues/31692
13+
*/
1214
const Editor = dynamic(() => import('./EditorImpl'), {
1315
ssr: false,
1416
});

compiler/apps/playground/components/Header.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,26 @@ import {IconGitHub} from './Icons/IconGitHub';
1616
import Logo from './Logo';
1717
import {useStoreDispatch} from './StoreContext';
1818

19-
export default function Header() {
19+
export default function Header(): JSX.Element {
2020
const [showCheck, setShowCheck] = useState(false);
2121
const dispatchStore = useStoreDispatch();
2222
const {enqueueSnackbar, closeSnackbar} = useSnackbar();
2323

24-
const handleReset = () => {
24+
const handleReset: () => void = () => {
2525
if (confirm('Are you sure you want to reset the playground?')) {
26-
/*
27-
Close open snackbars if any. This is necessary because when displaying
28-
outputs (Preview or not), we only close previous snackbars if we received
29-
new messages, which is needed in order to display "Bad URL" or success
30-
messages when loading Playground for the first time. Otherwise, messages
31-
such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
32-
*/
26+
/**
27+
* Close open snackbars if any. This is necessary because when displaying
28+
* outputs (Preview or not), we only close previous snackbars if we received
29+
* new messages, which is needed in order to display "Bad URL" or success
30+
* messages when loading Playground for the first time. Otherwise, messages
31+
* such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
32+
*/
3333
closeSnackbar();
3434
dispatchStore({type: 'setStore', payload: {store: defaultStore}});
3535
}
3636
};
3737

38-
const handleShare = () => {
38+
const handleShare: () => void = () => {
3939
navigator.clipboard.writeText(location.href).then(() => {
4040
enqueueSnackbar('URL copied to clipboard');
4141
setShowCheck(true);

compiler/apps/playground/components/Logo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
// https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx
99

10-
export default function Logo(props: JSX.IntrinsicElements['svg']) {
10+
export default function Logo(props: JSX.IntrinsicElements['svg']): JSX.Element {
1111
return (
1212
<svg
1313
viewBox="0 0 410 369"

compiler/apps/playground/components/StoreContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
2929
/**
3030
* Make Store and dispatch function available to all sub-components in children.
3131
*/
32-
export function StoreProvider({children}: {children: ReactNode}) {
32+
export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
3333
const [store, dispatch] = useReducer(storeReducer, emptyStore);
3434

3535
return (

compiler/apps/playground/lib/createContext.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ import React from 'react';
2323
* Instead, it throws an error when `useContext` is not called within a
2424
* Provider with a value.
2525
*/
26-
export default function createContext<T>() {
26+
export default function createContext<T>(): {
27+
useContext: () => NonNullable<T>;
28+
Provider: React.Provider<T | null>;
29+
} {
2730
const context = React.createContext<T | null>(null);
2831

29-
function useContext() {
32+
function useContext(): NonNullable<T> {
3033
const c = React.useContext(context);
3134
if (!c)
3235
throw new Error('useContext must be within a Provider with a value');

compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ function mapReactCompilerDiagnosticToMonacoMarker(
4646
type ReactCompilerMarkerConfig = {
4747
monaco: Monaco;
4848
model: editor.ITextModel;
49-
details: CompilerErrorDetail[];
49+
details: Array<CompilerErrorDetail>;
5050
};
51-
let decorations: string[] = [];
51+
let decorations: Array<string> = [];
5252
export function renderReactCompilerMarkers({
5353
monaco,
5454
model,

0 commit comments

Comments
 (0)