Skip to content

Commit

Permalink
fix(module:code-editor): load Monaco only once (#7033)
Browse files Browse the repository at this point in the history
  • Loading branch information
arturovt authored Nov 4, 2021
1 parent 3277d22 commit e1eafec
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 15 deletions.
55 changes: 41 additions & 14 deletions components/code-editor/code-editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject, Observable, of as observableOf, Subject, Subscription } from 'rxjs';
import { BehaviorSubject, Observable, of, ReplaySubject, Subscription } from 'rxjs';
import { map, tap } from 'rxjs/operators';

import { CodeEditorConfig, NzConfigService } from 'ng-zorro-antd/core/config';
Expand All @@ -26,14 +26,22 @@ function tryTriggerFunc(fn?: (...args: NzSafeAny[]) => NzSafeAny): (...args: NzS
};
}

// Caretaker note: previously, these were `NzCodeEditorService` properties.
// They're kept as static variables because this will allow loading Monaco only once.
// This applies to micro frontend apps with multiple Angular apps or a single Angular app
// that can be bootstrapped and destroyed multiple times (e.g. using Webpack module federation).
// Root providers are re-initialized each time the app is bootstrapped. Platform providers aren't.
// We can't make the `NzCodeEditorService` to be a platform provider (`@Injectable({ providedIn: 'platform' })`)
// since it depends on other root providers.
const loaded$ = new ReplaySubject<boolean>(1);
let loadingStatus = NzCodeEditorLoadingStatus.UNLOAD;

@Injectable({
providedIn: 'root'
})
export class NzCodeEditorService implements OnDestroy {
private document: Document;
private firstEditorInitialized = false;
private loaded$ = new Subject<boolean>();
private loadingStatus = NzCodeEditorLoadingStatus.UNLOAD;
private option: JoinedEditorOptions = {};
private config: CodeEditorConfig;
private subscription: Subscription | null;
Expand Down Expand Up @@ -70,12 +78,12 @@ export class NzCodeEditorService implements OnDestroy {
}

requestToInit(): Observable<JoinedEditorOptions> {
if (this.loadingStatus === NzCodeEditorLoadingStatus.LOADED) {
if (loadingStatus === NzCodeEditorLoadingStatus.LOADED) {
this.onInit();
return observableOf(this.getLatestOption());
return of(this.getLatestOption());
}

if (this.loadingStatus === NzCodeEditorLoadingStatus.UNLOAD) {
if (loadingStatus === NzCodeEditorLoadingStatus.UNLOAD) {
if (this.config.useStaticLoading && typeof monaco === 'undefined') {
warn(
'You choose to use static loading but it seems that you forget ' +
Expand All @@ -87,7 +95,7 @@ export class NzCodeEditorService implements OnDestroy {
}
}

return this.loaded$.asObservable().pipe(
return loaded$.pipe(
tap(() => this.onInit()),
map(() => this.getLatestOption())
);
Expand All @@ -99,11 +107,11 @@ export class NzCodeEditorService implements OnDestroy {
return;
}

if (this.loadingStatus === NzCodeEditorLoadingStatus.LOADING) {
if (loadingStatus === NzCodeEditorLoadingStatus.LOADING) {
return;
}

this.loadingStatus = NzCodeEditorLoadingStatus.LOADING;
loadingStatus = NzCodeEditorLoadingStatus.LOADING;

const assetsRoot = this.config.assetsRoot;
const vs = assetsRoot ? `${assetsRoot}/vs` : 'assets/vs';
Expand All @@ -112,25 +120,44 @@ export class NzCodeEditorService implements OnDestroy {

loadScript.type = 'text/javascript';
loadScript.src = `${vs}/loader.js`;
loadScript.onload = () => {

const onLoad = (): void => {
cleanup();
windowAsAny.require.config({
paths: { vs }
});
windowAsAny.require(['vs/editor/editor.main'], () => {
this.onLoad();
});
};
loadScript.onerror = () => {

const onError = (): void => {
cleanup();
throw new Error(`${PREFIX} cannot load assets of monaco editor from source "${vs}".`);
};

const cleanup = (): void => {
// Caretaker note: we have to remove these listeners once the `<script>` is loaded successfully
// or not since the `onLoad` listener captures `this`, which will prevent the `NzCodeEditorService`
// from being garbage collected.
loadScript.removeEventListener('load', onLoad);
loadScript.removeEventListener('error', onError);
// We don't need to keep the `<script>` element within the `<body>` since JavaScript has
// been executed and Monaco is available globally. E.g. Webpack, always removes `<script>`
// elements after loading chunks (see its `LoadScriptRuntimeModule`).
this.document.documentElement.removeChild(loadScript);
};

loadScript.addEventListener('load', onLoad);
loadScript.addEventListener('error', onError);

this.document.documentElement.appendChild(loadScript);
}

private onLoad(): void {
this.loadingStatus = NzCodeEditorLoadingStatus.LOADED;
this.loaded$.next(true);
this.loaded$.complete();
loadingStatus = NzCodeEditorLoadingStatus.LOADED;
loaded$.next(true);
loaded$.complete();

tryTriggerFunc(this.config.onLoad)();
}
Expand Down
2 changes: 1 addition & 1 deletion components/code-editor/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type JoinedEditorOptions = EditorOptions | DiffEditorOptions;

export type NzEditorMode = 'normal' | 'diff';

export enum NzCodeEditorLoadingStatus {
export const enum NzCodeEditorLoadingStatus {
UNLOAD = 'unload',
LOADING = 'loading',
LOADED = 'LOADED'
Expand Down

0 comments on commit e1eafec

Please sign in to comment.