main
tsx 180 lines 5.31 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
9 import {
10 CompilerErrorDetail,
11 CompilerDiagnostic,
12 } from 'babel-plugin-react-compiler';
13 import invariant from 'invariant';
14 import type {editor} from 'monaco-editor';
15 import * as monaco from 'monaco-editor';
16 import {
17 useEffect,
18 useState,
19 unstable_ViewTransition as ViewTransition,
20 } from 'react';
21 import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
22 import {useStore, useStoreDispatch} from '../StoreContext';
23 import TabbedWindow from '../TabbedWindow';
24 import {monacoOptions} from './monacoOptions';
25 import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes';
26
27 // @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
28 import React$Types from '../../node_modules/@types/react/index.d.ts';
29
30 loader.config({monaco});
31
32 type Props = {
33 errors: Array<CompilerErrorDetail | CompilerDiagnostic>;
34 language: 'flow' | 'typescript';
35 };
36
37 export default function Input({errors, language}: Props): JSX.Element {
38 const [monaco, setMonaco] = useState<Monaco | null>(null);
39 const store = useStore();
40 const dispatchStore = useStoreDispatch();
41
42 // Set tab width to 2 spaces for the selected input file.
43 useEffect(() => {
44 if (!monaco) return;
45 const uri = monaco.Uri.parse(`file:///index.js`);
46 const model = monaco.editor.getModel(uri);
47 invariant(model, 'Model must exist for the selected input file.');
48 renderReactCompilerMarkers({
49 monaco,
50 model,
51 details: errors,
52 source: store.source,
53 });
54 }, [monaco, errors, store.source]);
55
56 useEffect(() => {
57 /**
58 * Ignore "can only be used in TypeScript files." errors, since
59 * we want to support syntax highlighting for Flow (*.js) files
60 * and Flow is not a built-in language.
61 */
62 if (!monaco) return;
63 monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
64 diagnosticCodesToIgnore: [
65 8002,
66 8003,
67 8004,
68 8005,
69 8006,
70 8008,
71 8009,
72 8010,
73 8011,
74 8012,
75 8013,
76 ...(language === 'flow'
77 ? [7028 /* unused label */, 6133 /* var declared but not read */]
78 : []),
79 ],
80 noSemanticValidation: true,
81 // Monaco can't validate Flow component syntax
82 noSyntaxValidation: language === 'flow',
83 });
84 }, [monaco, language]);
85
86 const handleChange: (value: string | undefined) => void = async value => {
87 if (!value) return;
88
89 dispatchStore({
90 type: 'updateSource',
91 payload: {
92 source: value,
93 },
94 });
95 };
96
97 const handleMount: (
98 _: editor.IStandaloneCodeEditor,
99 monaco: Monaco,
100 ) => void = (_, monaco) => {
101 if (typeof window !== 'undefined') {
102 window['__MONACO_LOADED__'] = true;
103 }
104 setMonaco(monaco);
105
106 const tscOptions = {
107 allowNonTsExtensions: true,
108 target: monaco.languages.typescript.ScriptTarget.ES2015,
109 moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
110 jsx: monaco.languages.typescript.JsxEmit.Preserve,
111 typeRoots: ['node_modules/@types'],
112 allowSyntheticDefaultImports: true,
113 };
114 monaco.languages.typescript.javascriptDefaults.setCompilerOptions(
115 tscOptions,
116 );
117 monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
118 ...tscOptions,
119 checkJs: true,
120 allowJs: true,
121 });
122
123 // Add React type declarations to Monaco
124 const reactLib = [
125 React$Types,
126 'file:///node_modules/@types/react/index.d.ts',
127 ] as [any, string];
128 monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib);
129 monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib);
130
131 /**
132 * Remeasure the font in case the custom font is loaded only after
133 * Monaco Editor is mounted.
134 * N.B. that this applies also to the output editor as it seems
135 * Monaco Editor instances share the same font config.
136 */
137 document.fonts.ready.then(() => {
138 monaco.editor.remeasureFonts();
139 });
140 };
141
142 const editorContent = (
143 <MonacoEditor
144 path={'index.js'}
145 /**
146 * .js and .jsx files are specified to be TS so that Monaco can actually
147 * check their syntax using its TS language service. They are still JS files
148 * due to their extensions, so TS language features don't work.
149 */
150 language={'javascript'}
151 value={store.source}
152 onMount={handleMount}
153 onChange={handleChange}
154 className="monaco-editor-input"
155 options={monacoOptions}
156 loading={''}
157 />
158 );
159
160 const tabs = new Map([['Input', editorContent]]);
161 const [activeTab, setActiveTab] = useState('Input');
162
163 return (
164 <ViewTransition
165 update={{
166 [CONFIG_PANEL_TRANSITION]: 'container',
167 default: 'none',
168 }}>
169 <div className="flex-1 min-w-[550px] sm:min-w-0">
170 <div className="flex flex-col h-full !h-[calc(100vh_-_3.5rem)] border-r border-gray-200">
171 <TabbedWindow
172 tabs={tabs}
173 activeTab={activeTab}
174 onTabChange={setActiveTab}
175 />
176 </div>
177 </div>
178 </ViewTransition>
179 );
180 }