mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Playground uses run() to automatically sync w compiler passes
Changes playground to use the modified `run()` function of the compiler, polling the generator and building up a Map of tabs automatically based on the passes that the compiler runs. This means tabs are always derived from the current state of the compiler and we can never forget to add a pass. <img width="1497" alt="Screen Shot 2023-01-10 at 11 06 03 AM" src="https://user-images.githubusercontent.com/6425824/211639442-da421f73-e19e-4b63-9f33-0ce5a68cceb7.png"> Note the inclusion of some recently added passes that weren't added to the playground — which was my fault but only bc i intended to ship this PR soon :-)
This commit is contained in:
@@ -9,59 +9,22 @@ import MonacoEditor from "@monaco-editor/react";
|
||||
import { HIR } from "babel-plugin-react-forget";
|
||||
import prettier from "prettier";
|
||||
import prettierParserBabel from "prettier/parser-babel";
|
||||
import { memo, useMemo } from "react";
|
||||
import { memo, useMemo, useState } from "react";
|
||||
import type { Store } from "../../lib/stores";
|
||||
import TabbedWindow, { TabTypes } from "../TabbedWindow";
|
||||
import TabbedWindow from "../TabbedWindow";
|
||||
import { monacoOptions } from "./monacoOptions";
|
||||
const {
|
||||
buildReactiveFunction,
|
||||
codegenReactiveFunction,
|
||||
eliminateRedundantPhi,
|
||||
enterSSA,
|
||||
Environment,
|
||||
flattenReactiveLoops,
|
||||
inferTypes,
|
||||
inferMutableRanges,
|
||||
inferReactiveScopes,
|
||||
inferReactiveScopeVariables,
|
||||
inferReferenceEffects,
|
||||
leaveSSA,
|
||||
lower,
|
||||
parseFunctions,
|
||||
printHIR,
|
||||
printReactiveFunction,
|
||||
propagateScopeDependencies,
|
||||
pruneUnusedLabels,
|
||||
pruneUnusedScopes,
|
||||
} = HIR;
|
||||
const { parseFunctions, printHIR, printReactiveFunction } = HIR;
|
||||
const MemoizedOutput = memo(Output);
|
||||
|
||||
export default MemoizedOutput;
|
||||
|
||||
type Props = {
|
||||
store: Store;
|
||||
tabsOpen: Map<TabTypes, boolean>;
|
||||
setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
|
||||
};
|
||||
|
||||
type CompilerOutput = {
|
||||
ssaOutput: string;
|
||||
hirOutput: string;
|
||||
eliminateRedundantPhiOutput: string;
|
||||
inferTypesOutput: string;
|
||||
inferReferenceEffectsOutput: string;
|
||||
inferMutableRangesOutput: string;
|
||||
inferReactiveScopeVariablesOutput: string;
|
||||
inferReactiveScopesOutput: string;
|
||||
reactiveFunctionOutput: string;
|
||||
leaveSSAOutput: string;
|
||||
codegenOutput: string;
|
||||
sourceMapUrl: string | null;
|
||||
};
|
||||
|
||||
type CompilerError = string;
|
||||
|
||||
function compile(source: string): CompilerOutput | CompilerError {
|
||||
function compile(source: string): Map<string, React.ReactNode> | CompilerError {
|
||||
try {
|
||||
const astFunctions = parseFunctions(source);
|
||||
if (astFunctions.length === 0) {
|
||||
@@ -70,82 +33,96 @@ function compile(source: string): CompilerOutput | CompilerError {
|
||||
|
||||
// TODO: Handle multiple functions
|
||||
const func = astFunctions[0];
|
||||
const env = new Environment();
|
||||
const ir = lower(func, env);
|
||||
|
||||
const hirOutput = printHIR(ir.body);
|
||||
|
||||
enterSSA(ir, env);
|
||||
const ssaOutput = printHIR(ir.body);
|
||||
|
||||
eliminateRedundantPhi(ir);
|
||||
const eliminateRedundantPhiOutput = printHIR(ir.body);
|
||||
|
||||
inferTypes(ir);
|
||||
const inferTypesOutput = printHIR(ir.body);
|
||||
|
||||
inferReferenceEffects(ir);
|
||||
const inferReferenceEffectsOutput = printHIR(ir.body);
|
||||
|
||||
inferMutableRanges(ir);
|
||||
const inferMutableRangesOutput = printHIR(ir.body);
|
||||
|
||||
leaveSSA(ir);
|
||||
const leaveSSAOutput = printHIR(ir.body);
|
||||
|
||||
inferReactiveScopeVariables(ir);
|
||||
const inferReactiveScopeVariablesOutput = printHIR(ir.body);
|
||||
|
||||
inferReactiveScopes(ir);
|
||||
const inferReactiveScopesOutput = printHIR(ir.body);
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(ir);
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
flattenReactiveLoops(reactiveFunction);
|
||||
propagateScopeDependencies(reactiveFunction);
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
const reactiveFunctionOutput = printReactiveFunction(reactiveFunction);
|
||||
|
||||
const ast = codegenReactiveFunction(reactiveFunction);
|
||||
const generated = generate(
|
||||
ast,
|
||||
{
|
||||
sourceMaps: true,
|
||||
sourceFileName: "input.js",
|
||||
},
|
||||
source
|
||||
);
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map)
|
||||
);
|
||||
const codegenOutput = prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: "babel",
|
||||
plugins: [prettierParserBabel],
|
||||
const tabs = new Map<string, React.ReactNode>();
|
||||
const generator = HIR.run(func);
|
||||
let ast;
|
||||
while (true) {
|
||||
const next = generator.next();
|
||||
if (next.done) {
|
||||
ast = next.value;
|
||||
break;
|
||||
}
|
||||
const result = next.value;
|
||||
switch (result.kind) {
|
||||
case "ast": {
|
||||
const { code } = codegen(result.value, source);
|
||||
tabs.set(
|
||||
result.name,
|
||||
<TextTabContent output={code}></TextTabContent>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "hir": {
|
||||
const text = printHIR(result.value.body);
|
||||
tabs.set(
|
||||
result.name,
|
||||
<TextTabContent output={text}></TextTabContent>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "reactive": {
|
||||
const text = printReactiveFunction(result.value);
|
||||
tabs.set(
|
||||
result.name,
|
||||
<TextTabContent output={text}></TextTabContent>
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error("Unexpected result kind");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure that JS and the JS source map come first
|
||||
const reorderedTabs = new Map();
|
||||
const { code, sourceMapUrl } = codegen(ast, source);
|
||||
reorderedTabs.set("JS", <TextTabContent output={code}></TextTabContent>);
|
||||
if (sourceMapUrl) {
|
||||
reorderedTabs.set(
|
||||
"SourceMap",
|
||||
<>
|
||||
<iframe
|
||||
src={sourceMapUrl}
|
||||
className="w-full h-96"
|
||||
title="Generated Code"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
tabs.forEach((tab, name) => {
|
||||
reorderedTabs.set(name, tab);
|
||||
});
|
||||
|
||||
return {
|
||||
hirOutput,
|
||||
ssaOutput,
|
||||
eliminateRedundantPhiOutput,
|
||||
inferTypesOutput,
|
||||
inferReferenceEffectsOutput,
|
||||
inferMutableRangesOutput,
|
||||
inferReactiveScopeVariablesOutput,
|
||||
inferReactiveScopesOutput,
|
||||
reactiveFunctionOutput,
|
||||
leaveSSAOutput,
|
||||
codegenOutput,
|
||||
sourceMapUrl,
|
||||
};
|
||||
return reorderedTabs;
|
||||
} catch (e: any) {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
function codegen(
|
||||
ast: any,
|
||||
source: string
|
||||
): { code: any; sourceMapUrl: string | null } {
|
||||
const generated = generate(
|
||||
ast,
|
||||
{ sourceMaps: true, sourceFileName: "input.js" },
|
||||
source
|
||||
);
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map)
|
||||
);
|
||||
const codegenOutput = prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: "babel",
|
||||
plugins: [prettierParserBabel],
|
||||
});
|
||||
return { code: codegenOutput, sourceMapUrl };
|
||||
}
|
||||
|
||||
// TODO(gsn: Update diagnostics ƒrom HIR output
|
||||
function Output({ store, setTabsOpen, tabsOpen }: Props) {
|
||||
function Output({ store }: Props) {
|
||||
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set());
|
||||
const compilerOutput = useMemo(() => compile(store.source), [store.source]);
|
||||
|
||||
if (typeof compilerOutput === "string") {
|
||||
@@ -158,66 +135,7 @@ function Output({ store, setTabsOpen, tabsOpen }: Props) {
|
||||
defaultTab="HIR"
|
||||
setTabsOpen={setTabsOpen}
|
||||
tabsOpen={tabsOpen}
|
||||
tabs={{
|
||||
JS: <TextTabContent output={compilerOutput.codegenOutput} />,
|
||||
HIR: (
|
||||
<TextTabContent output={compilerOutput.hirOutput}></TextTabContent>
|
||||
),
|
||||
SSA: (
|
||||
<TextTabContent output={compilerOutput.ssaOutput}></TextTabContent>
|
||||
),
|
||||
EliminateRedundantPhi: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.eliminateRedundantPhiOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
InferTypes: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.inferTypesOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
InferReferenceEffects: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.inferReferenceEffectsOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
InferMutableRanges: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.inferMutableRangesOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
LeaveSSA: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.leaveSSAOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
InferReactiveScopeVariables: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.inferReactiveScopeVariablesOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
InferReactiveScopes: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.inferReactiveScopesOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
ReactiveFunctions: (
|
||||
<TextTabContent
|
||||
output={compilerOutput.reactiveFunctionOutput}
|
||||
></TextTabContent>
|
||||
),
|
||||
SourceMap: (
|
||||
<>
|
||||
{compilerOutput.sourceMapUrl && (
|
||||
<iframe
|
||||
src={compilerOutput.sourceMapUrl}
|
||||
className="w-full h-96"
|
||||
title="Generated Code"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
tabs={compilerOutput}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import clsx from "clsx";
|
||||
import invariant from "invariant";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useDeferredValue, useState } from "react";
|
||||
import { useDeferredValue } from "react";
|
||||
import { useMountEffect } from "../../hooks";
|
||||
import { defaultStore } from "../../lib/defaultStore";
|
||||
import {
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
type Store,
|
||||
} from "../../lib/stores";
|
||||
import { useStore, useStoreDispatch } from "../StoreContext";
|
||||
import { TabTypes } from "../TabbedWindow";
|
||||
import Input from "./Input";
|
||||
import Output from "./Output";
|
||||
|
||||
@@ -28,7 +27,6 @@ export default function Editor() {
|
||||
const deferredStore = useDeferredValue(store);
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [tabsOpen, setTabsOpen] = useState<Map<TabTypes, boolean>>(new Map());
|
||||
|
||||
useMountEffect(() => {
|
||||
let mountStore: Store;
|
||||
@@ -62,11 +60,7 @@ export default function Editor() {
|
||||
<Input />
|
||||
</div>
|
||||
<div className={clsx("flex sm:flex")}>
|
||||
<Output
|
||||
tabsOpen={tabsOpen}
|
||||
setTabsOpen={setTabsOpen}
|
||||
store={deferredStore}
|
||||
/>
|
||||
<Output store={deferredStore} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -7,35 +7,21 @@
|
||||
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
export type TabTypes =
|
||||
| "HIR"
|
||||
| "SSA"
|
||||
| "EliminateRedundantPhi"
|
||||
| "InferTypes"
|
||||
| "InferReferenceEffects"
|
||||
| "InferMutableRanges"
|
||||
| "InferReactiveScopeVariables"
|
||||
| "InferReactiveScopes"
|
||||
| "ReactiveFunctions"
|
||||
| "LeaveSSA"
|
||||
| "JS"
|
||||
| "SourceMap";
|
||||
|
||||
type TabsRecord = Record<TabTypes, React.ReactNode>;
|
||||
type TabsRecord = Map<string, React.ReactNode>;
|
||||
|
||||
export default function TabbedWindow(props: {
|
||||
defaultTab: string | null;
|
||||
tabs: TabsRecord;
|
||||
tabsOpen: Map<TabTypes, boolean>;
|
||||
setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
|
||||
tabsOpen: Set<string>;
|
||||
setTabsOpen: (newTab: Set<string>) => void;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div className="flex flex-row h-full">
|
||||
{Object.keys(props.tabs).map((name, index, all) => {
|
||||
{Array.from(props.tabs.keys()).map((name) => {
|
||||
return (
|
||||
<TabbedWindowItem
|
||||
name={name as TabTypes}
|
||||
key={index}
|
||||
name={name}
|
||||
key={name}
|
||||
tabs={props.tabs}
|
||||
tabsOpen={props.tabsOpen}
|
||||
setTabsOpen={props.setTabsOpen}
|
||||
@@ -52,16 +38,20 @@ function TabbedWindowItem({
|
||||
tabsOpen,
|
||||
setTabsOpen,
|
||||
}: {
|
||||
name: TabTypes;
|
||||
name: string;
|
||||
tabs: TabsRecord;
|
||||
tabsOpen: Map<TabTypes, boolean>;
|
||||
setTabsOpen: (newTab: Map<TabTypes, boolean>) => void;
|
||||
tabsOpen: Set<string>;
|
||||
setTabsOpen: (newTab: Set<string>) => void;
|
||||
}): React.ReactElement {
|
||||
const isShow = tabsOpen.get(name) ?? false;
|
||||
const isShow = tabsOpen.has(name);
|
||||
|
||||
const toggleTabs = useCallback(() => {
|
||||
const nextState = new Map(tabsOpen);
|
||||
nextState.set(name, !isShow);
|
||||
const nextState = new Set(tabsOpen);
|
||||
if (nextState.has(name)) {
|
||||
nextState.delete(name);
|
||||
} else {
|
||||
nextState.add(name);
|
||||
}
|
||||
setTabsOpen(nextState);
|
||||
}, [tabsOpen, name, isShow, setTabsOpen]);
|
||||
|
||||
@@ -75,7 +65,7 @@ function TabbedWindowItem({
|
||||
>
|
||||
- {name}
|
||||
</h2>
|
||||
{tabs[name]}
|
||||
{tabs.get(name) ?? <div>No output for {name}</div>}
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative items-center h-full px-4 py-8 align-middle border-r border-grey-200">
|
||||
|
||||
Reference in New Issue
Block a user