Vue 3 환경에서 Markdown 및 Monaco 코드 에디터 통합하기

Markdown 에디터 구현 (ByteMD)

Vue 3 프로젝트에서 높은 성능과 확장성을 가진 Markdown 에디터를 구현하기 위해 ByteMD를 활용할 수 있습니다. ByteMD는 ByteDance에서 관리하는 라이브러리로, 다양한 플러그인을 통해 기능을 확장하기 용이합니다.

패키지 설치

에디터 본체와 더불어 GFM(GitHub Flavored Markdown) 지원 및 코드 하이라이팅을 위한 플러그인을 함께 설치합니다.

npm install @bytemd/vue-next @bytemd/plugin-gfm @bytemd/plugin-highlight

에디터 컴포넌트 작성

ByteMD를 래핑하여 재사용 가능한 MarkdownEditor.vue 컴포넌트를 생성합니다.

<template>
  <Editor
    :value="content"
    :plugins="enabledPlugins"
    :mode="viewMode"
    @change="updateContent"
  />
</template>

<script setup lang="ts">
import { ref, defineProps, withDefaults } from 'vue';
import { Editor } from '@bytemd/vue-next';
import gfm from '@bytemd/plugin-gfm';
import highlight from '@bytemd/plugin-highlight';
import 'bytemd/dist/index.css';
import 'highlight.js/styles/github.css';

interface EditorProps {
  content: string;
  viewMode?: 'split' | 'tab' | 'fullscreen';
  onUpdate: (val: string) => void;
}

const props = withDefaults(defineProps<EditorProps>(), {
  content: '',
  viewMode: 'split'
});

const enabledPlugins = [
  gfm(),
  highlight(),
];

const updateContent = (v: string) => {
  props.onUpdate(v);
};
</script>

Monaco Editor를 이용한 코드 편집기 구축

VS Code의 핵심 엔진인 Monaco Editor를 사용하여 강력한 코드 편집 환경을 제공할 수 있습니다.

의존성 및 환경 설정

Monaco Editor를 설치하고, Webpack 환경에서 워커 설정을 간소화하기 위해 플러그인을 추가합니다.

npm install monaco-editor monaco-editor-webpack-plugin

vue.config.js 파일에 Monaco Editor 설정을 추가하여 모든 언어 기능을 활성화합니다.

const { defineConfig } = require("@vue/cli-service");
const MonacoEditorPlugin = require("monaco-editor-webpack-plugin");

module.exports = defineConfig({
  transpileDependencies: true,
  chainWebpack(config) {
    config.plugin("monaco").use(new MonacoEditorPlugin());
  },
});

코드 에디터 컴포넌트 구현

반응형 이슈를 방지하기 위해 toRaw를 사용하여 Monaco 인스턴스를 관리합니다.

<template>
  <div ref="editorContainer" class="code-container" style="height: 450px; border: 1px solid #ddd" />
</template>

<script setup lang="ts">
import * as monaco from 'monaco-editor';
import { onMounted, ref, toRaw, onBeforeUnmount } from 'vue';

interface CodeProps {
  initialCode: string;
  language?: string;
  onCodeChange: (value: string) => void;
}

const props = withDefaults(defineProps<CodeProps>(), {
  initialCode: '',
  language: 'javascript',
});

const editorContainer = ref<HTMLElement | null>(null);
let instance: monaco.editor.IStandaloneCodeEditor | null = null;

onMounted(() => {
  if (editorContainer.value) {
    instance = monaco.editor.create(editorContainer.value, {
      value: props.initialCode,
      language: props.language,
      theme: 'vs-dark',
      automaticLayout: true,
      minimap: { enabled: true },
      fontSize: 14,
    });

    instance.onDidChangeModelContent(() => {
      const currentVal = toRaw(instance)?.getValue();
      if (currentVal !== undefined) {
        props.onCodeChange(currentVal);
      }
    });
  }
});

onBeforeUnmount(() => {
  instance?.dispose();
});
</script>

통합 사용 예시

위에서 정의한 두 컴포넌트를 부모 컴포넌트에서 조합하여 데이터 흐름을 제어합니다.

<template>
  <div class="editor-workspace">
    <section>
      <h2>문서 편집기</h2>
      <MarkdownEditor :content="markdownText" :onUpdate="handleMdChange" />
    </section>

    <section style="margin-top: 40px;">
      <h2>소스 코드 편집기</h2>
      <CodeEditor :initialCode="sourceCode" language="typescript" :onCodeChange="handleCodeChange" />
    </section>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import MarkdownEditor from './components/MarkdownEditor.vue';
import CodeEditor from './components/CodeEditor.vue';

const markdownText = ref('# Hello Markdown');
const sourceCode = ref('const greeting = "Hello World";');

const handleMdChange = (val: string) => {
  markdownText.value = val;
};

const handleCodeChange = (val: string) => {
  sourceCode.value = val;
};
</script>

태그: Vue.js markdown Monaco Editor ByteMD TypeScript

8월 18일 19:54에 게시됨