소스 편집기

소스 편집기는 GitLab에서 편집 경험을 제공합니다.

Monaco 에디터 주위에 짜여진 이 얇은 래퍼는 필요한 도우미와 추상화를 제공하며, Monaco를 확장하여 확장합니다. 여러 GitLab 기능에서 사용되며, 여기에는 다음이 포함됩니다:

소스 편집기를 사용해야 할 때

파일 내용을 편집해야 하는 경우에만 소스 편집기를 사용하세요.

소스 코드를 표시하는 것만 필요한 경우 BlobContent 컴포넌트를 사용하는 것을 고려하세요.

작업 중인 페이지가 이미 소스 편집기를 로드하고 있다면, 소스 편집기에서 읽기 전용 콘텐츠를 표시하는 것이 여전히 유효한 옵션입니다.

소스 편집기 사용 방법

소스 편집기는 프레임워크에 구애받지 않으며, Rails와 Vue를 포함한 모든 애플리케이션에서 사용할 수 있습니다. 통합을 돕기 위해, 우리는 전용 <source-editor> Vue 컴포넌트를 제공하지만, 소스 편집기의 통합은 일반적으로 간단합니다:

  1. 소스 편집기 가져오기:

    import SourceEditor from '~/editor/source_editor';
    
  2. 뷰를 위한 전역 에디터 초기화:

    const editor = new SourceEditor({
      // 편집기 옵션.
      // 모든 수락된 옵션 목록은
      // https://microsoft.github.io/monaco-editor/docs.html 에서 확인할 수 있습니다.
    });
    
  3. 편집기 인스턴스 생성:

    editor.createInstance({
      // 소스 편집기 구성 옵션.
    })
    

소스 편집기의 인스턴스는 다음 구성 옵션을 수용합니다:

옵션 필수? 설명
el true HTML 노드: 편집기를 렌더링할 요소입니다.
blobPath false String: 편집기에서 렌더링할 파일 이름으로, 해당 파일에 사용할 올바른 구문 강조기를 식별하는 데 사용됩니다. 실제 파일 이름이 알려지지 않거나 중요한 역할을 하지 않는 경우 *.js와 같은 와일드카드를 받아들일 수 있습니다.
blobContent false String: 편집기에 렌더링할 초기 콘텐츠입니다.
extensions false Array: 이 인스턴스에서 사용할 확장입니다.
blobGlobalId false String: 자동으로 생성된 속성입니다.
참고: 이 속성은 향후 사라질 수 있습니다. 무엇을 하고 있는지 모른다면 blobGlobalId를 전달하지 마세요.
에디터 옵션 false Object(s): 목록에 없는 모든 속성은 이 특정 인스턴스의 에디터 옵션으로 처리됩니다. 이 필드를 사용하여 인스턴스 수준에서 전역 에디터 옵션을 재정의하세요. 전체 에디터 옵션 색인을 사용할 수 있습니다.

API

편집기는 Monaco 에디터가 제공하는 동일한 공개 API를 사용하며, 인스턴스 수준에서 추가 기능을 제공합니다:

함수 인수 설명
updateModelLanguage path: String 인스턴스의 구문 강조를 전달된 path의 확장을 따르도록 업데이트합니다. 인스턴스 수준에서만 사용할 수 있습니다.
use 객체 배열 인스턴스에 적용할 확장의 배열입니다. 오직 객체의 배열만 수락합니다. 확장의 ES6 모듈은 use에 전달되기 전에 당신의 뷰나 컴포넌트에서 가져오고 해결되어야 합니다. 인스턴스 및 전역 편집기(모든 인스턴스) 수준 모두에서 사용 가능합니다.
Monaco 에디터 옵션 문서 보기 기본 Monaco 편집기 옵션입니다.

  1. 에디터의 로딩 상태.

    로딩 상태는 Source Editor에 내장되어 있으므로 스피너와 로더는 HTML에서 거의 필요하지 않습니다. 내장된 로딩 상태를 활용하려면, 에디터를 포함해야 하는 HTML 요소에 data-editor-loading 속성을 설정하세요. 부트스트래핑 시, Source Editor는 자동으로 로더를 표시합니다.

    Source Editor: 로딩 상태

  2. 파일 이름이 변경되면 구문 강조를 업데이트하세요.

    // fileNameEl은 파일 이름이 포함된 HTML 입력 요소입니다.
    fileNameEl.addEventListener('change', () => {
      this.editor.updateModelLanguage(fileNameEl.value);
    });
    
  3. 에디터의 내용을 가져옵니다.

    우리는 에디터의 모든 변경 사항에 대한 리스너를 설정할 수 있지만, 이는 빠르게 비싼 작업이 될 수 있습니다. 대신, 필요할 때 에디터의 내용을 가져오세요. 예를 들어, 폼 제출 시:

    form.addEventListener('submit', () => {
      my_content_variable = this.editor.getValue();
    });
    
  4. 성능

    Source Editor 자체는 매우 가볍지만, Monaco 에디터에 의존하기 때문에 무게가 추가됩니다. Source Editor를 뷰에 추가할 때마다, JavaScript 번들의 크기가 크게 증가하여 뷰의 로딩 성능에 영향을 미칩니다. 다음과 같은 경우에는 에디터를 필요할 때 가져오세요:

    • 뷰가 에디터가 필요한지 확실하지 않을 경우
    • 에디터가 뷰의 보조 요소일 경우

    필요할 때 Source Editor를 로드하는 것은 다른 모듈을 로드하는 방식과 같습니다:

    someActionFunction() {
      import(/* webpackChunkName: 'SourceEditor' */ '~/editor/source_editor').
        then(({ default: SourceEditor }) => {
          const editor = new SourceEditor();
          ...
        });
      ...
    }
    

확장

Source Editor는 전체 제품에 보편적이고 확장 가능한 편집 도구를 제공하며, 특정 그룹에 의존하지 않습니다. Source Editor의 핵심은 Create::Editor FE Team에 의해 소유되지만, 모든 그룹이 확장 기능을 소유할 수 있습니다—주요 기능 요소들. Source Editor 확장의 목표는 에디터의 핵심을 슬림하고 안정적으로 유지하는 것입니다. 필요한 모든 기능은 이 핵심에 확장으로 추가될 수 있습니다. 어떤 그룹도 Source Editor를 수정하거나 무시하는 것을 걱정하지 않고 새로운 편집 기능을 구축하고 소유할 수 있습니다.

확장 기능에서 다른 모듈에 의존할 수 있습니다. 이러한 구조는 필요할 때 종속성을 가져오므로 Source Editor의 핵심 크기를 작게 유지하는 데 도움이 됩니다.

구조적으로, Source Editor의 전체 구현은 다음 다이어그램으로 나타낼 수 있습니다:

%%{init: { "fontFamily": "GitLab Sans" }}%% graph TD; B[확장 1]---A[Source Editor] C[확장 2]---A[Source Editor] D[확장 3]---A[Source Editor] E[...]---A[Source Editor] F[확장 N]---A[Source Editor] A[Source Editor]---Z[Monaco]

확장은 JavaScript 객체를 내보내는 ES6 모듈입니다:

import { Position } from 'monaco-editor';

export default {
  navigateFileStart() {
    this.setPosition(new Position(1, 1));
  },
};

확장 기능의 함수에서 this는 현재 Source Editor 인스턴스를 참조합니다. 이 this를 사용하면, 특정 경우의 setPosition() 메서드와 같은 인스턴스의 전체 API에 접근할 수 있습니다.

기존 확장 사용하기

소스 편집기의 인스턴스에 확장을 추가하려면 다음 단계를 수행해야 합니다:

import SourceEditor from '~/editor/source_editor';
import MyExtension from '~/my_extension';

const editor = new SourceEditor().createInstance({
  ...
});
editor.use(MyExtension);

확장 만들기

첫 번째 소스 편집기 확장을 만들어 보겠습니다. 확장은 ES6 모듈로 기본 Object를 내보내어 소스 편집기의 기능을 확장하는 데 사용됩니다. 테스트로, 호출 시 편집기의 내용을 alert로 출력하는 새로운 기능을 소스 편집기에 확장하는 확장을 만들어 보겠습니다.

~/my_folder/my_fancy_extension.js:

export default {
  throwContentAtMe() {
    alert(this.getValue());
  },
};

코드 예제에서 this는 인스턴스를 참조합니다. 인스턴스를 참조함으로써, 저희는 완전한 기본 Monaco editor API에 접근할 수 있으며, 여기에는 getValue()와 같은 함수가 포함되어 있습니다.

이제 우리의 확장을 사용해 보겠습니다:

~/my_folder/component_bundle.js:

import SourceEditor from '~/editor/source_editor';
import MyFancyExtension from './my_fancy_extension';

const editor = new SourceEditor().createInstance({
  ...
});
editor.use(MyFancyExtension);
...
someButton.addEventListener('click', () => {
  editor.throwContentAtMe();
});

우선, 소스 편집기와 새로운 확장을 가져옵니다. 그런 다음 편집기와 그 인스턴스를 생성합니다. 기본적으로 소스 편집기에는 throwContentAtMe 메서드가 없습니다. 그러나 editor.use(MyFancyExtension) 라인은 해당 메서드를 우리 인스턴스에 추가합니다. 그 후에는 필요할 때마다 이를 사용할 수 있습니다. 이 경우, 어떤 이론적인 버튼이 클릭되었을 때 호출합니다.

이 스크립트는 someButton이 클릭될 때 편집기의 내용을 포함하는 alert를 생성합니다.

소스 편집기 새 확장의 결과

  1. 성능

    소스 편집기 자체와 마찬가지로, 어떤 확장도 뷰의 로딩 성능에 해를 끼치지 않도록 필요 시 로드할 수 있습니다:

    const EditorPromise = import(
      /* webpackChunkName: 'SourceEditor' */ '~/editor/source_editor'
    );
    const MarkdownExtensionPromise = import('~/editor/source_editor_markdown_ext');
    
    Promise.all([EditorPromise, MarkdownExtensionPromise])
      .then(([{ default: SourceEditor }, { default: MarkdownExtension }]) => {
        const editor = new SourceEditor().createInstance({
          ...
        });
        editor.use(MarkdownExtension);
      });
    
  2. 여러 확장 사용하기

    사용 방법은 use 메서드에 확장 배열을 전달하는 것입니다:

    editor.use([FileTemplateExtension, MyFancyExtension]);