Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
[ghstack-poisoned]
  • Loading branch information
poteto committed Sep 26, 2024
1 parent d4f9477 commit a4c5c54
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 98 deletions.
6 changes: 5 additions & 1 deletion compiler/apps/playground/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

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

export default function RootLayout({children}: {children: React.ReactNode}) {
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}): JSX.Element {
'use no memo';
return (
<html lang="en">
Expand Down
2 changes: 1 addition & 1 deletion compiler/apps/playground/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {SnackbarProvider} from 'notistack';
import {Editor, Header, StoreProvider} from '../components';
import MessageSnackbar from '../components/Message';

export default function Hoot() {
export default function Page(): JSX.Element {
return (
<StoreProvider>
<SnackbarProvider
Expand Down
20 changes: 12 additions & 8 deletions compiler/apps/playground/components/Editor/EditorImpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';

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

function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, PrintedCompilerPipelineValue[]>();
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
const upsert = (result: PrintedCompilerPipelineValue) => {
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
entry.push(result);
Expand Down Expand Up @@ -273,13 +273,17 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
}
}
} catch (err) {
// error might be an invariant violation or other runtime error
// (i.e. object shape that is not CompilerError)
/**
* error might be an invariant violation or other runtime error
* (i.e. object shape that is not CompilerError)
*/
if (err instanceof CompilerError && err.details.length > 0) {
error.details.push(...err.details);
} else {
// Handle unexpected failures by logging (to get a stack trace)
// and reporting
/**
* Handle unexpected failures by logging (to get a stack trace)
* and reporting
*/
console.error(err);
error.details.push(
new CompilerErrorDetail({
Expand All @@ -297,7 +301,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
return [{kind: 'ok', results}, language];
}

export default function Editor() {
export default function Editor(): JSX.Element {
const store = useStore();
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();
Expand Down
59 changes: 35 additions & 24 deletions compiler/apps/playground/components/Editor/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ import {useEffect, useState} from 'react';
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
// @ts-ignore
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
import React$Types from '../../node_modules/@types/react/index.d.ts';

loader.config({monaco});

type Props = {
errors: CompilerErrorDetail[];
errors: Array<CompilerErrorDetail>;
language: 'flow' | 'typescript';
};

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

const flowDiagnosticDisable = [
7028 /* unused label */, 6133 /* var declared but not read */,
];
useEffect(() => {
// Ignore "can only be used in TypeScript files." errors, since
// we want to support syntax highlighting for Flow (*.js) files
// and Flow is not a built-in language.
/**
* Ignore "can only be used in TypeScript files." errors, since
* we want to support syntax highlighting for Flow (*.js) files
* and Flow is not a built-in language.
*/
if (!monaco) return;
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
diagnosticCodesToIgnore: [
Expand All @@ -64,15 +64,17 @@ export default function Input({errors, language}: Props) {
8011,
8012,
8013,
...(language === 'flow' ? flowDiagnosticDisable : []),
...(language === 'flow'
? [7028 /* unused label */, 6133 /* var declared but not read */]
: []),
],
noSemanticValidation: true,
// Monaco can't validate Flow component syntax
noSyntaxValidation: language === 'flow',
});
}, [monaco, language]);

const handleChange = (value: string | undefined) => {
const handleChange: (value: string | undefined) => void = value => {
if (!value) return;

dispatchStore({
Expand All @@ -83,7 +85,10 @@ export default function Input({errors, language}: Props) {
});
};

const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
const handleMount: (
_: editor.IStandaloneCodeEditor,
monaco: Monaco,
) => void = (_, monaco) => {
setMonaco(monaco);

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

// Remeasure the font in case the custom font is loaded only after
// Monaco Editor is mounted.
// N.B. that this applies also to the output editor as it seems
// Monaco Editor instances share the same font config.
/**
* Remeasure the font in case the custom font is loaded only after
* Monaco Editor is mounted.
* N.B. that this applies also to the output editor as it seems
* Monaco Editor instances share the same font config.
*/
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
Expand All @@ -125,14 +132,18 @@ export default function Input({errors, language}: Props) {
<Resizable
minWidth={650}
enable={{right: true}}
// Restrict MonacoEditor's height, since the config autoLayout:true
// will grow the editor to fit within parent element
/**
* Restrict MonacoEditor's height, since the config autoLayout:true
* will grow the editor to fit within parent element
*/
className="!h-[calc(100vh_-_3.5rem)]">
<MonacoEditor
path={'index.js'}
// .js and .jsx files are specified to be TS so that Monaco can actually
// check their syntax using its TS language service. They are still JS files
// due to their extensions, so TS language features don't work.
/**
* .js and .jsx files are specified to be TS so that Monaco can actually
* check their syntax using its TS language service. They are still JS files
* due to their extensions, so TS language features don't work.
*/
language={'javascript'}
value={store.source}
onMount={handleMount}
Expand Down
27 changes: 17 additions & 10 deletions compiler/apps/playground/components/Editor/Output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {type CompilerError} from 'babel-plugin-react-compiler/src';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
import {memo, useEffect, useState} from 'react';
import {memo, ReactNode, useEffect, useState} from 'react';
import {type Store} from '../../lib/stores';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
Expand All @@ -42,10 +42,10 @@ export type PrintedCompilerPipelineValue =
| {kind: 'debug'; name: string; fnName: string | null; value: string};

export type CompilerOutput =
| {kind: 'ok'; results: Map<string, PrintedCompilerPipelineValue[]>}
| {kind: 'ok'; results: Map<string, Array<PrintedCompilerPipelineValue>>}
| {
kind: 'err';
results: Map<string, PrintedCompilerPipelineValue[]>;
results: Map<string, Array<PrintedCompilerPipelineValue>>;
error: CompilerError;
};

Expand All @@ -54,7 +54,10 @@ type Props = {
compilerOutput: CompilerOutput;
};

async function tabify(source: string, compilerOutput: CompilerOutput) {
async function tabify(
source: string,
compilerOutput: CompilerOutput,
): Promise<Map<string, ReactNode>> {
const tabs = new Map<string, React.ReactNode>();
const reorderedTabs = new Map<string, React.ReactNode>();
const concattedResults = new Map<string, string>();
Expand Down Expand Up @@ -112,8 +115,10 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
}
// Ensure that JS and the JS source map come first
if (topLevelFnDecls.length > 0) {
// Make a synthetic Program so we can have a single AST with all the top level
// FunctionDeclarations
/**
* Make a synthetic Program so we can have a single AST with all the top level
* FunctionDeclarations
*/
const ast = t.program(topLevelFnDecls);
const {code, sourceMapUrl} = await codegen(ast, source);
reorderedTabs.set(
Expand Down Expand Up @@ -175,7 +180,7 @@ function getSourceMapUrl(code: string, map: string): string | null {
)}`;
}

function Output({store, compilerOutput}: Props) {
function Output({store, compilerOutput}: Props): JSX.Element {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
() => new Map(),
Expand Down Expand Up @@ -236,11 +241,13 @@ function TextTabContent({
output: string;
diff: string | null;
showInfoPanel: boolean;
}) {
}): JSX.Element {
const [diffMode, setDiffMode] = useState(false);
return (
// Restrict MonacoEditor's height, since the config autoLayout:true
// will grow the editor to fit within parent element
/**
* Restrict MonacoEditor's height, since the config autoLayout:true
* will grow the editor to fit within parent element
*/
<div className="w-full h-monaco_small sm:h-monaco">
{showInfoPanel ? (
<div className="flex items-center gap-1 bg-amber-50 p-2">
Expand Down
6 changes: 4 additions & 2 deletions compiler/apps/playground/components/Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

import dynamic from 'next/dynamic';

// monaco-editor is currently not compatible with ssr
// https://github.com/vercel/next.js/issues/31692
/**
* monaco-editor is currently not compatible with ssr
* https://github.com/vercel/next.js/issues/31692
*/
const Editor = dynamic(() => import('./EditorImpl'), {
ssr: false,
});
Expand Down
20 changes: 10 additions & 10 deletions compiler/apps/playground/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,26 @@ import {IconGitHub} from './Icons/IconGitHub';
import Logo from './Logo';
import {useStoreDispatch} from './StoreContext';

export default function Header() {
export default function Header(): JSX.Element {
const [showCheck, setShowCheck] = useState(false);
const dispatchStore = useStoreDispatch();
const {enqueueSnackbar, closeSnackbar} = useSnackbar();

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

const handleShare = () => {
const handleShare: () => void = () => {
navigator.clipboard.writeText(location.href).then(() => {
enqueueSnackbar('URL copied to clipboard');
setShowCheck(true);
Expand Down
2 changes: 1 addition & 1 deletion compiler/apps/playground/components/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

export default function Logo(props: JSX.IntrinsicElements['svg']) {
export default function Logo(props: JSX.IntrinsicElements['svg']): JSX.Element {
return (
<svg
viewBox="0 0 410 369"
Expand Down
2 changes: 1 addition & 1 deletion compiler/apps/playground/components/StoreContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
/**
* Make Store and dispatch function available to all sub-components in children.
*/
export function StoreProvider({children}: {children: ReactNode}) {
export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
const [store, dispatch] = useReducer(storeReducer, emptyStore);

return (
Expand Down
7 changes: 5 additions & 2 deletions compiler/apps/playground/lib/createContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ import React from 'react';
* Instead, it throws an error when `useContext` is not called within a
* Provider with a value.
*/
export default function createContext<T>() {
export default function createContext<T>(): {
useContext: () => NonNullable<T>;
Provider: React.Provider<T | null>;
} {
const context = React.createContext<T | null>(null);

function useContext() {
function useContext(): NonNullable<T> {
const c = React.useContext(context);
if (!c)
throw new Error('useContext must be within a Provider with a value');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ function mapReactCompilerDiagnosticToMonacoMarker(
type ReactCompilerMarkerConfig = {
monaco: Monaco;
model: editor.ITextModel;
details: CompilerErrorDetail[];
details: Array<CompilerErrorDetail>;
};
let decorations: string[] = [];
let decorations: Array<string> = [];
export function renderReactCompilerMarkers({
monaco,
model,
Expand Down
8 changes: 5 additions & 3 deletions compiler/apps/playground/lib/stores/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function decodeStore(hash: string): Store {
/**
* Serialize, encode, and save @param store to localStorage and update URL.
*/
export function saveStore(store: Store) {
export function saveStore(store: Store): void {
const hash = encodeStore(store);
localStorage.setItem('playgroundStore', hash);
history.replaceState({}, '', `#${hash}`);
Expand Down Expand Up @@ -56,8 +56,10 @@ export function initStoreFromUrlOrLocalStorage(): Store {
const encodedSourceFromLocal = localStorage.getItem('playgroundStore');
const encodedSource = encodedSourceFromUrl || encodedSourceFromLocal;

// No data in the URL and no data in the localStorage to fallback to.
// Initialize with the default store.
/**
* No data in the URL and no data in the localStorage to fallback to.
* Initialize with the default store.
*/
if (!encodedSource) return defaultStore;

const raw = decodeStore(encodedSource);
Expand Down
Loading

0 comments on commit a4c5c54

Please sign in to comment.