Skip to content

Commit 39d92b1

Browse files
author
Ives van Hoorne
committed
Fix typescript modules losing context
1 parent 6d04832 commit 39d92b1

File tree

1 file changed

+57
-13
lines changed
  • src/app/components/sandbox/CodeEditor

1 file changed

+57
-13
lines changed

src/app/components/sandbox/CodeEditor/Monaco.js

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,23 @@ export default class CodeEditor extends React.Component<Props, State> {
319319
}
320320
};
321321

322+
disposeModel = (id: string) => {
323+
if (modelCache[id]) {
324+
try {
325+
if (modelCache[id].model) {
326+
modelCache[id].model.dispose();
327+
}
328+
if (modelCache[id].lib) {
329+
modelCache[id].lib.dispose();
330+
}
331+
332+
delete modelCache[id];
333+
} catch (e) {
334+
console.error(e);
335+
}
336+
}
337+
};
338+
322339
shouldComponentUpdate(nextProps: Props, nextState: State) {
323340
if (nextState.fuzzySearchEnabled !== this.state.fuzzySearchEnabled) {
324341
return true;
@@ -346,8 +363,7 @@ export default class CodeEditor extends React.Component<Props, State> {
346363
this.editor.setModel(null);
347364
}
348365

349-
modelCache[module.id].model.dispose();
350-
delete modelCache[module.id];
366+
this.disposeModel(module.id);
351367

352368
this.createModel(
353369
module,
@@ -367,8 +383,7 @@ export default class CodeEditor extends React.Component<Props, State> {
367383
Object.keys(modelCache).forEach(moduleId => {
368384
// This module got deleted, dispose it
369385
if (!nextProps.modules.find(m => m.id === moduleId)) {
370-
modelCache[moduleId].model.dispose();
371-
delete modelCache[moduleId];
386+
this.disposeModel(moduleId);
372387
}
373388
});
374389
}
@@ -420,8 +435,27 @@ export default class CodeEditor extends React.Component<Props, State> {
420435
if (nextId !== currentId && this.editor) {
421436
const pos = this.editor.getPosition();
422437
if (modelCache[currentId]) {
438+
const currentModule = this.props.modules.find(m => m.id === currentId);
439+
const path = getModulePath(
440+
this.props.modules,
441+
this.props.directories,
442+
currentId
443+
);
444+
423445
modelCache[currentId].cursorPos = pos;
446+
if (modelCache[currentId].lib) {
447+
// We let Monaco know what the latest code is of this file by removing
448+
// the old extraLib definition and defining a new one.
449+
modelCache[currentId].lib.dispose();
450+
modelCache[
451+
currentId
452+
].lib = this.monaco.languages.typescript.typescriptDefaults.addExtraLib(
453+
currentModule.code,
454+
`file://${path}`
455+
);
456+
}
424457
}
458+
425459
await this.openNewModel(nextId, nextTitle);
426460
this.editor.focus();
427461
}
@@ -441,7 +475,7 @@ export default class CodeEditor extends React.Component<Props, State> {
441475

442476
if (nextSandboxId !== currentSandboxId) {
443477
// Reset models, dispose old ones
444-
this.disposeModules();
478+
this.disposeModules(this.props.modules);
445479

446480
// Do in setTimeout, since disposeModules is async
447481
setTimeout(() => {
@@ -615,6 +649,9 @@ export default class CodeEditor extends React.Component<Props, State> {
615649
compilerDefaults
616650
);
617651

652+
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
653+
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
654+
618655
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
619656
noSemanticValidation: false,
620657
noSyntaxValidation: !hasNativeTypescript,
@@ -691,32 +728,30 @@ export default class CodeEditor extends React.Component<Props, State> {
691728
);
692729
};
693730

694-
disposeModules = () => {
731+
disposeModules = (modules: Array<Module>) => {
695732
if (this.editor) {
696733
this.editor.setModel(null);
697734
}
698735

699736
if (this.monaco) {
700-
this.monaco.editor.getModels().forEach(model => {
701-
model.dispose();
737+
modules.forEach(m => {
738+
this.disposeModel(m.id);
702739
});
703740
}
704741

705742
modelCache = {};
706743
};
707744

708-
initializeModules = (modules = this.props.modules) =>
709-
Promise.all(
710-
modules.reverse().map(module => this.createModel(module, modules))
711-
);
745+
initializeModules = (modules: Array<Module> = this.props.modules) =>
746+
Promise.all(modules.map(module => this.createModel(module, modules)));
712747

713748
resizeEditor = () => {
714749
this.editor.layout();
715750
};
716751

717752
componentWillUnmount() {
718753
window.removeEventListener('resize', this.resizeEditor);
719-
this.disposeModules();
754+
this.disposeModules(this.props.modules);
720755
if (this.editor) {
721756
this.editor.dispose();
722757
}
@@ -740,6 +775,14 @@ export default class CodeEditor extends React.Component<Props, State> {
740775
// Remove the first slash, as this will otherwise create errors in monaco
741776
const path = getModulePath(modules, directories, module.id);
742777

778+
// We need to add this as a lib specifically to Monaco, because Monaco
779+
// tends to lose type definitions if you don't touch a file for a while.
780+
// Related issue: https://github.com/Microsoft/monaco-editor/issues/461
781+
const lib = this.monaco.languages.typescript.typescriptDefaults.addExtraLib(
782+
module.code,
783+
`file://${path}`
784+
);
785+
743786
const mode = await this.getMode(module.title);
744787
const model = this.monaco.editor.createModel(
745788
module.code,
@@ -755,6 +798,7 @@ export default class CodeEditor extends React.Component<Props, State> {
755798
cursorPos: null,
756799
};
757800
modelCache[module.id].model = model;
801+
modelCache[module.id].lib = lib;
758802

759803
return model;
760804
};

0 commit comments

Comments
 (0)