Реалізація Code Editor (Monaco/CodeMirror) на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Code Editor (Monaco/CodeMirror) на сайті
Середня
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Code Editor (Monaco/CodeMirror) на сайті

Вбудований редактор коду потрібен в IDE-інструментах, навчальних платформах, конфігураторах, playground-сторінках та будь-яких SaaS де користувач пише скрипти або конфігурації. Підключити <textarea> — не варіант: нема підсвітки синтаксису, нема автодоповнення, нема нормальної роботи з відступами.

Monaco Editor vs CodeMirror 6

Monaco Editor — це VS Code у браузері. Повне автодоповнення TypeScript з type-checking, go-to-definition, find-all-references. Важить ~7 МБ в бандлі. Виправдан для серйозних IDE-like інтерфейсів.

CodeMirror 6 — модульний, важить від 50 КБ (лише те, що підключено). Швидше ініціалізується, краще працює на мобільних. Для більшості use-case — правильний вибір.

Інтеграція Monaco в React

npm install @monaco-editor/react

Пакет завантажує Monaco через CDN з web worker, не розбухує бандл:

import Editor, { OnMount, BeforeMount } from '@monaco-editor/react'
import * as monaco from 'monaco-editor'

interface CodeEditorProps {
  value: string
  onChange: (value: string) => void
  language?: string
  readOnly?: boolean
}

export function CodeEditor({ value, onChange, language = 'typescript', readOnly = false }: CodeEditorProps) {
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)

  const handleBeforeMount: BeforeMount = (monacoInstance) => {
    // Реєструємо кастомну мову або тему до монтування
    monacoInstance.editor.defineTheme('my-dark', {
      base: 'vs-dark',
      inherit: true,
      rules: [],
      colors: {
        'editor.background': '#0f172a',
        'editor.lineHighlightBackground': '#1e293b',
      },
    })
  }

  const handleMount: OnMount = (editor, monacoInstance) => {
    editorRef.current = editor

    // TypeScript/JavaScript — налаштування compiler options
    monacoInstance.languages.typescript.typescriptDefaults.setCompilerOptions({
      target: monacoInstance.languages.typescript.ScriptTarget.ES2020,
      moduleResolution: monacoInstance.languages.typescript.ModuleResolutionKind.NodeJs,
      strict: true,
    })

    // Додаємо type definitions для автодоповнення
    monacoInstance.languages.typescript.typescriptDefaults.addExtraLib(
      `declare module 'my-api' { export function query(sql: string): Promise<any[]> }`,
      'file:///node_modules/my-api/index.d.ts'
    )

    // Гарячі клавіші
    editor.addCommand(monacoInstance.KeyMod.CtrlCmd | monacoInstance.KeyCode.KeyS, () => {
      onSave?.(editor.getValue())
    })
  }

  return (
    <Editor
      height="400px"
      language={language}
      value={value}
      theme="my-dark"
      beforeMount={handleBeforeMount}
      onMount={handleMount}
      onChange={(val) => onChange(val ?? '')}
      options={{
        readOnly,
        minimap: { enabled: false },
        fontSize: 14,
        tabSize: 2,
        wordWrap: 'on',
        scrollBeyondLastLine: false,
        renderLineHighlight: 'line',
        padding: { top: 16, bottom: 16 },
      }}
    />
  )
}

Кілька файлів (multi-model)

Коли потрібно переключатися між файлами без втрати cursor position та undo-history:

function MultiFileEditor({ files }: { files: File[] }) {
  const [activeFile, setActiveFile] = useState(files[0].path)
  const modelsRef = useRef<Record<string, monaco.editor.ITextModel>>({})
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)

  const handleMount: OnMount = (editor, monacoInstance) => {
    editorRef.current = editor

    // Створюємо model для кожного файлу
    files.forEach((file) => {
      const uri = monacoInstance.Uri.parse(`file:///${file.path}`)
      modelsRef.current[file.path] = monacoInstance.editor.createModel(
        file.content,
        detectLanguage(file.path),
        uri
      )
    })

    editor.setModel(modelsRef.current[activeFile])
  }

  function switchFile(path: string) {
    setActiveFile(path)
    editorRef.current?.setModel(modelsRef.current[path])
  }

  return (
    <div>
      <div className="flex gap-1 border-b">
        {files.map((f) => (
          <button
            key={f.path}
            onClick={() => switchFile(f.path)}
            className={activeFile === f.path ? 'bg-gray-800 text-white' : ''}
          >
            {f.name}
          </button>
        ))}
      </div>
      <Editor onMount={handleMount} /* ... */ />
    </div>
  )
}

CodeMirror 6: більш легка альтернатива

npm install @codemirror/view @codemirror/state @codemirror/lang-javascript \
  @codemirror/lang-python @codemirror/lang-css \
  @codemirror/theme-one-dark @codemirror/basic-setup
import { useEffect, useRef } from 'react'
import { EditorView, basicSetup } from 'codemirror'
import { javascript } from '@codemirror/lang-javascript'
import { oneDark } from '@codemirror/theme-one-dark'
import { EditorState } from '@codemirror/state'

function CodeMirrorEditor({ value, onChange }: { value: string; onChange: (v: string) => void }) {
  const containerRef = useRef<HTMLDivElement>(null)
  const viewRef = useRef<EditorView | null>(null)

  useEffect(() => {
    if (!containerRef.current) return

    const updateListener = EditorView.updateListener.of((update) => {
      if (update.docChanged) {
        onChange(update.state.doc.toString())
      }
    })

    const view = new EditorView({
      state: EditorState.create({
        doc: value,
        extensions: [
          basicSetup,
          javascript({ typescript: true }),
          oneDark,
          updateListener,
          EditorView.lineWrapping,
        ],
      }),
      parent: containerRef.current,
    })

    viewRef.current = view
    return () => view.destroy()
  }, []) // Монтуємо один раз

  // Обновляємо значення ззовні без пересоздання редактора
  useEffect(() => {
    const view = viewRef.current
    if (!view) return
    const current = view.state.doc.toString()
    if (current !== value) {
      view.dispatch({
        changes: { from: 0, to: current.length, insert: value },
      })
    }
  }, [value])

  return <div ref={containerRef} className="border rounded overflow-hidden" />
}

Що робимо

Вибираємо редактор під задачу (Monaco для IDE-like, CodeMirror для компактних сценаріїв), налаштовуємо мови, тему під дизайн проекту, гарячі клавіші, валідацію. При необхідності підключаємо language server по WebSocket для повноцінного IntelliSense на бекенді.

Термін: базовий редактор з підсвіткою та автодоповненням — 1–2 дні. Мультифайловий редактор з LSP — 3–4 дні.