Skip to content

Commit e63c99a

Browse files
docs: Custom monaco build (#2297)
1 parent 61eb66d commit e63c99a

8 files changed

Lines changed: 261 additions & 184 deletions

File tree

apps/typegpu-docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
"lodash": "^4.17.21",
4646
"lucide-react": "^0.536.0",
4747
"lz-string": "^1.5.0",
48-
"monaco-editor": "^0.53.0",
4948
"morphcharts": "^1.3.2",
5049
"motion": "^12.23.24",
5150
"onnxruntime-web": "1.23.0-dev.20250917-21fbad8a65",
@@ -60,6 +59,7 @@
6059
"starlight-typedoc": "^0.21.5",
6160
"three": "catalog:example",
6261
"tinybench": "^3.1.0",
62+
"tsover-monaco-editor": "^0.55.1",
6363
"typedoc": "^0.28.17",
6464
"typedoc-plugin-markdown": "4.10.0",
6565
"typegpu": "workspace:*",
Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,50 @@
1-
import Editor, { type BeforeMount, type Monaco, type OnMount } from '@monaco-editor/react';
2-
import type { editor } from 'monaco-editor';
1+
import Editor, { type Monaco, type OnMount } from '@monaco-editor/react';
2+
import type { editor } from 'tsover-monaco-editor';
33
import { entries, filter, fromEntries, isTruthy, map, pipe } from 'remeda';
44
import { SANDBOX_MODULES } from '../utils/examples/sandboxModules.ts';
55
import type { ExampleCommonFile, ExampleSrcFile } from '../utils/examples/types.ts';
6-
import { tsCompilerOptions } from '../utils/liveEditor/embeddedTypeScript.ts';
6+
import {
7+
tsnotoverCompilerOptions,
8+
tsoverCompilerOptions,
9+
} from '../utils/liveEditor/embeddedTypeScript.ts';
710

8-
function handleEditorWillMount(monaco: Monaco) {
11+
// This setup is required for tsover to work, because monaco-react won't use custom monaco without `loader.config()`
12+
// Config docs: https://www.npmjs.com/package/@monaco-editor/react?activeTab=readme#loader-config
13+
// Integration docs: https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md
14+
import { loader } from '@monaco-editor/react';
15+
import * as monaco from 'tsover-monaco-editor';
16+
// oxlint-disable-next-line import/default
17+
import editorWorker from 'tsover-monaco-editor/esm/vs/editor/editor.worker?worker';
18+
// oxlint-disable-next-line import/default
19+
import jsonWorker from 'tsover-monaco-editor/esm/vs/language/json/json.worker?worker';
20+
// oxlint-disable-next-line import/default
21+
import cssWorker from 'tsover-monaco-editor/esm/vs/language/css/css.worker?worker';
22+
// oxlint-disable-next-line import/default
23+
import htmlWorker from 'tsover-monaco-editor/esm/vs/language/html/html.worker?worker';
24+
// oxlint-disable-next-line import/default
25+
import tsWorker from 'tsover-monaco-editor/esm/vs/language/typescript/ts.worker?worker';
26+
27+
self.MonacoEnvironment = {
28+
getWorker(_, label) {
29+
switch (label) {
30+
case 'json':
31+
return new jsonWorker();
32+
case 'css':
33+
return new cssWorker();
34+
case 'html':
35+
return new htmlWorker();
36+
case 'typescript':
37+
case 'javascript':
38+
return new tsWorker();
39+
default:
40+
return new editorWorker();
41+
}
42+
},
43+
};
44+
45+
loader.config({ monaco });
46+
47+
const handleEditorWillMount = (tsover: boolean) => (monaco: Monaco) => {
948
const tsDefaults = monaco?.languages.typescript.typescriptDefaults;
1049

1150
const reroutes = pipe(
@@ -34,10 +73,10 @@ function handleEditorWillMount(monaco: Monaco) {
3473
}
3574

3675
tsDefaults.setCompilerOptions({
37-
...tsCompilerOptions,
76+
...(tsover ? tsoverCompilerOptions : tsnotoverCompilerOptions),
3877
paths: reroutes,
3978
});
40-
}
79+
};
4180

4281
function handleEditorOnMount(editor: editor.IStandaloneCodeEditor) {
4382
// Folding regions in code automatically. Useful for code not strictly
@@ -46,45 +85,35 @@ function handleEditorOnMount(editor: editor.IStandaloneCodeEditor) {
4685
}
4786

4887
type Props = {
88+
language: 'typescript' | 'html';
89+
tsoverEnabled: boolean;
4990
file: ExampleSrcFile | ExampleCommonFile;
5091
shown: boolean;
5192
};
5293

53-
const createCodeEditorComponent =
54-
(language: 'typescript' | 'html', beforeMount?: BeforeMount, onMount?: OnMount) =>
55-
(props: Props) => {
56-
const { file, shown } = props;
57-
58-
// Monaco needs relative paths to work correctly and '../../common/file.ts' will not do
59-
const path =
60-
'common' in file
61-
? `common/${file.path}`
62-
: `${file.exampleKey.replace('--', '/')}/${file.path}`;
63-
64-
return (
65-
<div className={shown ? 'h-[calc(100%-7rem)] md:h-[calc(100%-3rem)]' : 'hidden'}>
66-
<Editor
67-
defaultLanguage={language}
68-
value={file.tsnotoverContent ?? file.content}
69-
path={path}
70-
beforeMount={beforeMount}
71-
onMount={onMount}
72-
options={{
73-
minimap: {
74-
enabled: false,
75-
},
76-
readOnly: true,
77-
domReadOnly: true,
78-
}}
79-
/>
80-
</div>
81-
);
82-
};
94+
export function CodeEditor(props: Props) {
95+
const { language, tsoverEnabled, file, shown } = props;
8396

84-
export const TsCodeEditor = createCodeEditorComponent(
85-
'typescript',
86-
handleEditorWillMount,
87-
handleEditorOnMount,
88-
);
97+
// Monaco needs relative paths to work correctly and '../../common/file.ts' will not do
98+
const path =
99+
'common' in file ? `common/${file.path}` : `${file.exampleKey.replace('--', '/')}/${file.path}`;
89100

90-
export const HtmlCodeEditor = createCodeEditorComponent('html');
101+
return (
102+
<div className={shown ? 'h-[calc(100%-7rem)] md:h-[calc(100%-3rem)]' : 'hidden'}>
103+
<Editor
104+
defaultLanguage={language}
105+
value={tsoverEnabled ? file.content : (file.tsnotoverContent ?? file.content)}
106+
path={path}
107+
beforeMount={language === 'typescript' ? handleEditorWillMount(tsoverEnabled) : undefined}
108+
onMount={language === 'typescript' ? (handleEditorOnMount as OnMount) : undefined}
109+
options={{
110+
minimap: {
111+
enabled: false,
112+
},
113+
readOnly: true,
114+
domReadOnly: true,
115+
}}
116+
/>
117+
</div>
118+
);
119+
}

apps/typegpu-docs/src/components/ControlPanel.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import {
88
type ExampleControlParam,
99
exampleControlsAtom,
1010
} from '../utils/examples/exampleControlAtom.ts';
11-
import { codeEditorShownAtom, menuShownAtom } from '../utils/examples/exampleViewStateAtoms.ts';
11+
import {
12+
codeEditorShownAtom,
13+
menuShownAtom,
14+
tsoverUsedAtom,
15+
} from '../utils/examples/exampleViewStateAtoms.ts';
1216
import { isGPUSupported } from '../utils/isGPUSupported.ts';
1317
import { Button } from './design/Button.tsx';
1418
import { ColorPicker } from './design/ColorPicker.tsx';
@@ -280,10 +284,12 @@ const unreachable = (_: never) => null;
280284
export function ControlPanel() {
281285
const [menuShowing, setMenuShowing] = useAtom(menuShownAtom);
282286
const [codeEditorShowing, setCodeEditorShowing] = useAtom(codeEditorShownAtom);
287+
const [tsoverUsed, setTsoverUsed] = useAtom(tsoverUsedAtom);
283288
const exampleControlParams = useAtomValue(exampleControlsAtom);
284289

285290
const showLeftMenuId = useId();
286291
const showCodeEditorId = useId();
292+
const tsoverUsedId = useId();
287293

288294
return (
289295
<div
@@ -318,6 +324,17 @@ export function ControlPanel() {
318324
onChange={(e) => setCodeEditorShowing(e.target.checked)}
319325
/>
320326
</label>
327+
<label
328+
htmlFor={tsoverUsedId}
329+
className="flex cursor-pointer items-center justify-between gap-3 text-sm"
330+
>
331+
<span>Use operator overloads</span>
332+
<Toggle
333+
id={tsoverUsedId}
334+
checked={tsoverUsed}
335+
onChange={(e) => setTsoverUsed(e.target.checked)}
336+
/>
337+
</label>
321338

322339
<hr className="my-0 box-border w-full border-tameplum-100 border-t" />
323340
</div>

apps/typegpu-docs/src/components/ExampleView.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import cs from 'classnames';
22
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
33
import { type RefObject, useEffect, useRef, useState } from 'react';
44
import { currentSnackbarAtom } from '../utils/examples/currentSnackbarAtom.ts';
5-
import { codeEditorShownAtom } from '../utils/examples/exampleViewStateAtoms.ts';
5+
import { codeEditorShownAtom, tsoverUsedAtom } from '../utils/examples/exampleViewStateAtoms.ts';
66
import { ExecutionCancelledError } from '../utils/examples/errors.ts';
77
import { exampleControlsAtom } from '../utils/examples/exampleControlAtom.ts';
88
import { executeExample } from '../utils/examples/exampleRunner.ts';
99
import type { ExampleState } from '../utils/examples/exampleState.ts';
1010
import type { Example, ExampleCommonFile, ExampleSrcFile } from '../utils/examples/types.ts';
1111
import { isGPUSupported } from '../utils/isGPUSupported.ts';
12-
import { HtmlCodeEditor, TsCodeEditor } from './CodeEditor.tsx';
12+
import { CodeEditor } from './CodeEditor.tsx';
1313
import { ControlPanel } from './ControlPanel.tsx';
1414
import { Button } from './design/Button.tsx';
1515
import { Snackbar } from './design/Snackbar.tsx';
@@ -70,6 +70,7 @@ export function ExampleView({ example, common }: Props) {
7070
const [currentFilePath, setCurrentFilePath] = useState<string>('index.ts');
7171

7272
const codeEditorShown = useAtomValue(codeEditorShownAtom);
73+
const tsoverUsed = useAtomValue(tsoverUsedAtom);
7374
const exampleHtmlRef = useRef<HTMLDivElement>(null);
7475

7576
const tsFiles = filterRelevantTsFiles(srcFiles, common);
@@ -140,10 +141,21 @@ export function ExampleView({ example, common }: Props) {
140141
</div>
141142
</div>
142143

143-
<HtmlCodeEditor shown={currentFilePath === 'index.html'} file={htmlFile} />
144+
<CodeEditor
145+
shown={currentFilePath === 'index.html'}
146+
file={htmlFile}
147+
language={'html'}
148+
tsoverEnabled={false}
149+
/>
144150

145151
{tsFiles.map((file) => (
146-
<TsCodeEditor key={file.path} shown={file.path === currentFilePath} file={file} />
152+
<CodeEditor
153+
key={file.path}
154+
shown={file.path === currentFilePath}
155+
language={'typescript'}
156+
tsoverEnabled={tsoverUsed}
157+
file={file}
158+
/>
147159
))}
148160
</div>
149161

apps/typegpu-docs/src/components/translator/lib/editorConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { entries, filter, fromEntries, isTruthy, map, pipe } from 'remeda';
22
import type { Monaco } from '@monaco-editor/react';
33
import { SANDBOX_MODULES } from '../../../utils/examples/sandboxModules.ts';
4-
import { tsCompilerOptions } from '../../../utils/liveEditor/embeddedTypeScript.ts';
4+
import { tsnotoverCompilerOptions } from '../../../utils/liveEditor/embeddedTypeScript.ts';
55

66
export const LANGUAGE_MAP: Record<string, string> = {
77
wgsl: 'wgsl',
@@ -63,7 +63,7 @@ export function setupMonacoEditor(monaco: Monaco) {
6363
}
6464

6565
tsDefaults.setCompilerOptions({
66-
...tsCompilerOptions,
66+
...tsnotoverCompilerOptions,
6767
paths: reroutes,
6868
});
6969
}

apps/typegpu-docs/src/utils/examples/exampleViewStateAtoms.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export const codeEditorShownAtom = atomWithStorage(
1111
storageOptions,
1212
);
1313

14+
export const tsoverUsedAtom = atomWithStorage('tsover-used', true, undefined, storageOptions);
15+
1416
export const experimentalExamplesShownAtom = atomWithStorage(
1517
'experimental-examples-shown',
1618
true,

apps/typegpu-docs/src/utils/liveEditor/embeddedTypeScript.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { languages } from 'monaco-editor';
1+
// @ts-nocheck -- language.typescript is deprecated, but its replacement is not yet exported
2+
import { languages } from 'tsover-monaco-editor';
23

3-
export const tsCompilerOptions: languages.typescript.CompilerOptions = {
4+
export const tsnotoverCompilerOptions: languages.typescript.CompilerOptions = {
45
target: languages.typescript.ScriptTarget.ESNext,
56
allowNonTsExtensions: true,
67
strict: true,
@@ -12,3 +13,8 @@ export const tsCompilerOptions: languages.typescript.CompilerOptions = {
1213
baseUrl: '.',
1314
lib: ['dom', 'es2021'],
1415
};
16+
17+
export const tsoverCompilerOptions: languages.typescript.CompilerOptions = {
18+
...tsnotoverCompilerOptions,
19+
lib: [...tsnotoverCompilerOptions.lib, 'tsover'],
20+
};

0 commit comments

Comments
 (0)