在 Angular 13 中实现 Monaco 编辑器。

13

在 Angular 13 中实现 Monaco 编辑器的最佳选项是什么? 我看过 ngx-monaco-editor,但是最近更新是9个月前,并且升级到 Angular 12,那里的 Monaco 版本是0.20.0(11.02.2020),非常旧 :( 是否有另一种方法在 Angular 13 中使用它?


1
我正在寻找相同的东西。我尝试遵循这篇文章:https://ngohungphuc.wordpress.com/2019/01/08/integrate-monaco-editor-with-angular/,但是在window.require函数上出现了参数错误。 - Jiren
1
对我来说,它按照预期工作 - 只需按照此处的步骤 https://github.com/atularen/ngx-monaco-editor#readme,但担心功能支持。 - Tsvetelin
@jiren,请检查一下我的解决方案。也许这对你有用。 - moritz.vieli
@Tsvetelin,也许你可以将你的问题更改为适用于任何Angular版本的更一般性质的问题。 - moritz.vieli
5个回答

15

这是我解决问题的方式,受到atularen/ngx-monaco-editor的启发。但我也不想依赖这个库。可能会有更好的解决方案。

npm install monaco-editor

angular.json:

            "assets": [
              ...
              {
                "glob": "**/*",
                "input": "node_modules/monaco-editor",
                "output": "assets/monaco-editor"
              }
            ],

monaco-editor-service.ts:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class MonacoEditorService {
  loaded: boolean = false;

  public loadingFinished: Subject<void> = new Subject<void>();

  constructor() {}

  private finishLoading() {
    this.loaded = true;
    this.loadingFinished.next();
  }

  public load() {
    // load the assets

    const baseUrl = './assets' + '/monaco-editor/min/vs';

    if (typeof (<any>window).monaco === 'object') {
      this.finishLoading();
      return;
    }

    const onGotAmdLoader: any = () => {
      // load Monaco
      (<any>window).require.config({ paths: { vs: `${baseUrl}` } });
      (<any>window).require([`vs/editor/editor.main`], () => {
        this.finishLoading();
      });
    };

    // load AMD loader, if necessary
    if (!(<any>window).require) {
      const loaderScript: HTMLScriptElement = document.createElement('script');
      loaderScript.type = 'text/javascript';
      loaderScript.src = `${baseUrl}/loader.js`;
      loaderScript.addEventListener('load', onGotAmdLoader);
      document.body.appendChild(loaderScript);
    } else {
      onGotAmdLoader();
    }
  }
}

现在需要在编辑器使用前立即调用monacoEditorService.load()(在我的情况下,它是在app.component.ts的构造函数中调用的,以使编辑器始终可用并已预加载)。

现在,您可以根据需要创建编辑器,但请确保在Monaco尚未加载之前不要创建它们。像这样:

monaco-editor.component.ts

import ...

declare var monaco: any;

@Component({
  selector: 'app-monaco-editor',
  templateUrl: './monaco-editor.component.html',
  styleUrls: ['./monaco-editor.component.scss'],
})
export class MonacoEditorComponent implements OnInit, OnDestroy, AfterViewInit {
  public _editor: any;
  @ViewChild('editorContainer', { static: true }) _editorContainer: ElementRef;

  private initMonaco(): void {
    if(!this.monacoEditorService.loaded) {
      this.monacoEditorService.loadingFinished.pipe(first()).subscribe(() => {
        this.initMonaco();
      });
      return;
    }

    this._editor = monaco.editor.create(
      this._editorContainer.nativeElement,
      options
    );
  }

  ngAfterViewInit(): void {
    this.initMonaco();
  }

很可能有比使用布尔标志更优雅的解决方案和这个主题相关。

monaco-editor.component.html

确保组件中有一个像这样的 div:

<div class="editor-container" #editorContainer></div>

1
我一直在尝试实现这个功能,但一直无法使其正常工作。我几乎完全复制了该库(我使用了真实的类型而不是到处都用 any ),除了加载实际的 Monaco 代码的地方。最终我使用了 import * as monacoRuntime from 'monaco-editor'; export var monaco: typeof import('monaco-editor') = monacoRuntime; 以类型安全的方式加载它。但这完全破坏了 Monaco 的布局引擎。我不想依赖于_另一个_包来加载 Monaco。我认为唯一可接受的答案是不依赖第三方包。 - b4ux1t3
Monaco供应商提供的文件loader.js使用require.js(据我所知)动态地require Monaco需要的文件,这些文件作为单独的资源/下载提供。问题在于,这实际上背离了捆绑应用程序的目的,并且增加了另一个(隐式和不可管理的)依赖项(require.js)。并不是要过于消极,只是感觉任何解决方案如果不能导致一个单一的、连贯的Angular捆绑包,最多也只能算是不完整的解决方案。Monaco嵌入在VSCode中,这意味着必须有一种方法可以在不需要向后端发出单独请求的情况下使用它。 - b4ux1t3
@b4ux1t3 啊,现在我明白你的意思了。这是一个公正的观点,我同意。不确定如何解决这个问题。VSCode的Monaco编辑器似乎完全不同(甚至不基于JS?),所以也许他们没有那些问题。 - moritz.vieli
3
整体来说是个不错的解决方案!我只需要添加一些东西才能让它正常工作。在你的例子中,你从未调用 monacoEditorService.load(),这是必需的以启动加载。此外,initMonaco也需要被调用(当然)。编辑器出现了,但看起来很奇怪,因此还需要将 Monaco 样式添加到 angular.json 中。 - a-ctor
1
很棒的答案。要在Angular 14中使其工作,请遵循此链接https://github.com/angular/angular-cli/issues/23273。只需从package.json中删除依赖项,并通过assets []在angular.json中添加该包即可。 - rossco
显示剩余4条评论

9

在这里发布一个答案,使用自定义webpack配置和 Monaco Editor Webpack Loader Plugin 而不是第三方包装库。尚未在 Angular 15+ 中测试,但我不知道为什么这种方法不起作用。我将在接下来的几个月中将我们的应用程序迁移到v15,并在那时更新此帖子以确认该方法是否有效。

1)安装依赖

  • 仔细检查 Monaco 版本矩阵
  • npm i -D @angular-builders/custom-webpack monaco-editor-webpack-plugin style-loader css-loader

2)创建自定义 webpack 配置(基本)

有很多种方法可以做到这一点,但我选择了使用 TypeScript 并导出默认函数(这样我就可以记录整个配置)。我将其保存在根目录中,以便在 angular.json 中引用。

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
import * as webpack from 'webpack';

export default (config: webpack.Configuration) => {
  config?.plugins?.push(new MonacoWebpackPlugin());
  // Remove the existing css loader rule
  const cssRuleIdx = config?.module?.rules?.findIndex((rule: any) =>
    rule.test?.toString().includes(':css')
  );
  if (cssRuleIdx !== -1) {
    config?.module?.rules?.splice(cssRuleIdx!, 1);
  }
  config?.module?.rules?.push(
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
    },
    {
      test: /\.ttf$/,
      use: ['file-loader'],
    }
  );
  return config;
};

3) angular.json 修改

  • 修改 architect.build.builder 为使用 custom-webpack builder
  • 在 architect.build.builder.options 中添加 customWebpackConfig
  • 修改 architect.build.builder.options.styles,包含 monaco editor css
  • 更新 ENTIRE architect.serve 块为使用 custom-webpack builder
"my-application": {
  ...
  "architect": {
    "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      ...
      "options": {
        "customWebpackConfig": {
          "path": "./custom-webpack.config.ts"
        },
        ...
        "styles": [
          "node_modules/monaco-editor/min/vs/editor/editor.main.css", 
          "apps/my-application/src/styles.scss"
        ]
        ...
      }
      ...
    },
    "serve": {
      "builder": "@angular-builders/custom-webpack:dev-server",
      "options": {
        "browserTarget": "my-application:build:development"
      }
    },
    ...

4) 现在您可以创建一个编辑器组件

import * as monaco from 'monaco-editor';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'my-application-editor',
  template: `
    <div
      style="height:100%"
      #editorContainer
    ></div>
  `,
  styleUrls: ['./editor.component.scss'],
})
export class EditorComponent implements OnInit {
  @ViewChild('editorContainer', { static: true }) _editorContainer!: ElementRef;
  codeEditorInstance!: monaco.editor.IStandaloneCodeEditor;

  constructor() {}

  ngOnInit() {
    this.codeEditorInstance = monaco.editor.create(this._editorContainer.nativeElement, {
      theme: 'vs',
      wordWrap: 'on',
      wrappingIndent: 'indent',
      language: 'typescript',
      // minimap: { enabled: false },
      automaticLayout: true,
    });
  }

5) 奖励:优化

Webpack插件允许您通过删除您不使用的monaco部分来缩小最终捆绑包的大小。需要记住两件事:

  • 插件配置文档不是很好(试错一下才知道注释掉某些内容会意外地删除我们功能所需的关键部分)。
  • 根据文档, 您需要非常注意与monaco相关的所有导入语句。在我看来,它没有足够的调用注意力,但即使在组件或服务中只有一个import * as monaco from 'monaco-editor'也会包含整个库,从而抵消您摇树的努力。

这是我们为应用程序使用的内容(将配置对象传递给自定义webpack ts中的MonacoEditorWebpackPlugin):

new MonacoEditorWebpackPlugin({
  // a ton of languages are lazily loaded by default, but we dont use any of them
  languages: [],
  // we can disable features that we end up not needing/using
  features: [
    'accessibilityHelp',
    'anchorSelect',
    'bracketMatching',
    // 'browser',
    'caretOperations',
    'clipboard',
    // 'codeAction',
    // 'codelens',
    // 'colorPicker',
    // 'comment',
    'contextmenu',
    'copyPaste',
    'cursorUndo',
    // 'dnd',
    // 'documentSymbols',
    // 'dropIntoEditor',
    // 'find',
    // 'folding',
    // 'fontZoom',
    'format',
    // 'gotoError',
    // 'gotoLine',
    // 'gotoSymbol',
    'hover',
    // 'iPadShowKeyboard',
    // 'inPlaceReplace',
    'indentation',
    // 'inlayHints',
    'inlineCompletions',
    // 'inspectTokens',
    'lineSelection',
    'linesOperations',
    // 'linkedEditing',
    // 'links',
    // 'multicursor',
    // 'parameterHints',
    // 'quickCommand',
    // 'quickHelp',
    // 'quickOutline',
    // 'readOnlyMessage',
    // 'referenceSearch',
    // 'rename',
    'smartSelect',
    // 'snippet',
    'stickyScroll',
    // 'suggest',
    // 'toggleHighContrast',
    'toggleTabFocusMode',
    'tokenization',
    'unicodeHighlighter',
    // 'unusualLineTerminators',
    // 'viewportSemanticTokens',
    'wordHighlighter',
    'wordOperations',
    'wordPartOperations',
  ],
})

相关组件的更��内容如下:

  • 更新导入
// OLD
// import * as monaco from 'monaco-editor'
// NEW
import { editor, languages } from 'monaco-editor/esm/vs/editor/editor.api';

  • 更新编辑器创建和类型
// OLD
// codeEditorInstance!: monaco.editor.IStandaloneCodeEditor;
// this.codeEditorInstance = monaco.editor.create(...
// NEW
this.codeEditorInstance = editor.create(...
codeEditorInstance!: editor.IStandaloneCodeEditor;

6) 奖励:解决 Jest 单元测试问题

如果像我一样,你正在使用带有预配置 Jest 的 NX,你可能需要在 jest.config.js 中添加 transformIgnorePatterns 参考此答案

transformIgnorePatterns: ['node_modules/(?!monaco-editor/esm/.*)'],


1
我已经在Angular 15上测试了您的解决方案,并更新了建议的答案,以便能够运行它所需的微调。大部分更改已添加到自定义webpack构建器中。 - schankam
已成功集成并运行了monaco-yaml,但在codicon.ttf上出现错误,它使用文件路径访问codicon.ttf而不是引用URL(安全错误:http://localhost:4200/的内容可能无法加载或链接到file:///home/kprasad/kpworkspace/kp_17042023/angular-monaco-example/node_modules/monaco-editor/esm/vs/base/browser/ui/codicons/codicon/codicon.ttf)。存在问题的示例应用程序已推送到https://github.com/kprasad99/angular-monaco-example。 - Karthik Prasad
确认在Angular 16中,这个解决方案是有效的,但有两个注意事项:1)在运行时,加载codicon字体时会出现错误。似乎无法通过更改webpack规则配置来防止这种情况。2)如果您更改传递给new MonacoEditorWebpackPlugin({...})的配置(例如删除语言或功能),在构建或服务时间之前,您可能需要删除.angular/目录才能看到这些更改的反映。否则,这是一份令人惊叹且详尽的指南@chris-newman! - undefined
修复了由codicon.ttf引起的“不允许加载本地资源”的错误。请参见帖子顶部的更新@KarthikPrasad。 - undefined

3

Angular的Monaco编辑器

在项目目录中:

npm i --legacy-peer-deps ngx-monaco-editor
npm i --legacy-peer-deps monaco-editor

更新 angular.json 文件

"assets": [
   {
     "glob": "**/*",
     "input": "node_modules/monaco-editor",
     "output": "assets/monaco-editor"
   },
   ...
 ],

component.html

<ngx-monaco-editor [options]="codeEditorOptions" [(ngModel)]="code"></ngx-monaco-editor>

component.ts

Component {
    code: string = '';
    codeEditorOptions = {
      theme: 'vs-dark',
      language: 'json',
      automaticLayout: true
    };
    ...
}

module.ts

import {MonacoEditorModule} from 'ngx-monaco-editor';

...

@NgModule({
  ...
  imports: [
    ...,
    MonacoEditorModule.forRoot()
    ...
  ],
  ...
})

它对我起作用了 :)


1

0

我曾在旧版Angular中使用ngx-monaco-editor。尽管该库非常好,但它并不完全符合我想要使用monaco编辑器库的方式。因此,我为Angular 13编写了一种替代实现,并使用了最新版本的monaco-editor https://github.com/cisstech/nge


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接