Перейти к содержимому
Поиск
Разработка

CodeMirror 6: редактор кода в браузере — мастерская и 9 примеров

Встраиваем редактор в веб-сервис: JavaScript, HTML, JSON, поиск и замена, подсказки, темы и сравнение версий. Девять живых задач и готовый проект с исходниками.

Александр Некрасов
CodeMirror — редактор кода внутри сервиса: подсветка, поиск и проверка JSON

В административной панели нужно поправить JSON-настройки, в CMS — отредактировать HTML-блок, в сервисе рассылок — найти переменную во всех строках шаблона. Обычное текстовое поле позволит ввести символы, но не поможет увидеть структуру, обнаружить лишнюю запятую, сравнить две версии или удобно отменить изменения.

CodeMirror превращает такую часть интерфейса в полноценный редактор текста и кода. В этой статье используем современную ветку 6: девять живых сценариев, мини-мастерскую с русским интерфейсом, реальные подсказки, JSON-диагностику, историю, поиск и сравнение версий. Все примеры можно изменить прямо на странице и скачать. Пользовательский код не выполняется, а документы не отправляются на сервер.

Где нужен редактор внутри веб-сервиса

Редактор кода полезен не только в онлайн-песочнице для программистов. В CRM он может показывать шаблоны сообщений и правила интеграции. В панели разработчика — пример запроса, конфигурацию вебхука или JSON-ответ. В CMS — HTML-разметку отдельного блока. В системе мониторинга — конфигурацию уведомлений, которую сотрудник должен прочитать и сравнить с предыдущей версией.

В одних ситуациях пользователю разрешено менять документ, в других нужно только удобно читать и копировать. Иногда достаточно подсветки, иногда требуется проверка синтаксиса, поиск по десяткам строк и осмысленное подтверждение изменений. Хороший интерфейс выбирает инструменты под задачу, а не показывает все возможные кнопки одновременно.

В нашей мастерской каждая задача начинается с небольшого самостоятельного документа. Можно пройти путь от простого редактирования JavaScript до проверки изменения конфигурации, а затем открыть собственный файл. Такой набор показывает, как один компонент используется в разных частях продукта, сохраняя общие правила: понятное состояние, отмена, предсказуемое сохранение и объяснение ошибок.

CodeMirror, textarea и визуальный редактор

Textarea остаётся хорошим вариантом для короткого комментария или небольшого фрагмента текста. Она проста, привычна браузеру и не требует отдельной системы расширений. CodeMirror становится полезнее, когда сама структура документа важна: нужны номера строк, подсветка, сворачивание блоков, выделение совпадений, подсказки и история редакторских операций.

При этом CodeMirror не является визуальным конструктором страницы. HTML в нём остаётся исходным текстом. Если сотрудник должен собирать статью кнопками «заголовок», «список», «изображение», нужен другой тип редактора. Можно сочетать визуальный режим и исходник, но тогда придётся определить, какие изменения допускаются и как синхронизируются представления.

Также библиотека не становится средой исполнения только потому, что подсвечивает JavaScript. В наших примерах нет eval, запуска команд, предпросмотра пользовательского HTML или отправки текста в серверный интерпретатор. Это осознанная граница: редактор помогает подготовить документ, а использование этого документа — отдельная функция приложения со своими правилами.

Мини-мастерская: девять рабочих сценариев

Начните с JavaScript: измените число, отмените правку и повторите её. Затем откройте JSON и удалите лишнюю запятую. В поиске замените три вхождения переменной на имя, а в сравнении версий попробуйте принять и отклонить изменения. Все эти действия выполняют настоящие модули CodeMirror, а не отдельная имитация поверх текстового поля.

Тема и мягкий перенос применяются к текущему редактору без потери текста и истории. При переходе между задачами черновики остаются в памяти вкладки. Можно вернуться к предыдущему сценарию и продолжить. Кнопка «Вернуть пример» начинает выбранную задачу заново, а обновление страницы удаляет временные черновики — поэтому важный результат нужно скачать.

На узком экране мастерская перестраивается в одну рабочую колонку. Для долгого редактирования удобнее открыть её отдельно на весь экран. Внизу расположен стартовый код выбранного сценария, а полный проект содержит интерфейс, исходники, тесты и инструкции для Windows и Linux.

Версия 6 и независимые версии модулей

В CodeMirror 6 функциональность разделена между пакетами. Базовый пакет codemirror предоставляет удобную начальную конфигурацию, а состояние, отображение, команды, языки, поиск и другие возможности имеют отдельные версии. Поэтому номер базового пакета не описывает версию каждой части готового редактора.

В проекте закреплены codemirror 6.0.2, state 6.7.6, view 6.43.15, search 6.7.2, autocomplete 6.20.4, lint 6.9.7 и merge 6.12.3. Языковые пакеты также указаны явно в package.json. Все транзитивные зависимости зафиксированы в package-lock.json, а сборка создаёт список реально включённых runtime-пакетов и их лицензий.

Не переносите без проверки примеры для CodeMirror 5: способы конфигурации и обновления редактора отличаются. В шестой ветке важны состояние, транзакции и расширения. Для повторения материала достаточно установить зависимости из lock-файла; разбираться во всей внутренней архитектуре до первого результата не требуется.

Подробнее: документация CodeMirror 6.

Из чего состоит минимальный редактор

EditorState хранит документ и его конфигурацию, а EditorView связывает состояние с интерфейсом. Изменения передаются через транзакции. Расширения добавляют языковую поддержку, поиск, тему, историю, диагностику и другие возможности. BasicSetup объединяет распространённые инструменты, чтобы не собирать минимальный редактор по одной детали.

В проекте создание состояния вынесено в editor.js, а интерфейсные кнопки находятся в app.js. Учебные документы и ограничения хранятся отдельно в catalog.js. Такое разделение позволяет проверить поведение библиотек без полной страницы и затем использовать тот же редактор в другой оболочке.

Собранный app.js обслуживается локально вместе со страницей. Внешний CDN не нужен. Для собственного интерфейса вы можете начать с небольшого набора расширений, а затем добавлять только то, что требуется пользователю. Чем яснее назначение поля, тем легче объяснить его возможности и ограничения.

import { basicSetup, EditorView } from 'codemirror';
import { javascript } from '@codemirror/lang-javascript';

const view = new EditorView({
  parent: document.querySelector('#editor'),
  doc: 'const project = "Мастерская";\n',
  extensions: [basicSetup, javascript()]
});

Сценарий 1: JavaScript и безопасный эксперимент

Первый пример показывает учебное правило скидки. Измените возвращаемое число, добавьте комментарий или переместите курсор внутрь функции. Подсветка помогает различать конструкции, парные скобки упрощают ввод, а история позволяет отменить и повторить действие. Это уже заметно удобнее обычного поля с несколькими десятками строк.

При этом изменение числа не пересчитывает скидку в реальном магазине. Документ служит примером редактирования. Если вы хотите добавить выполнение, нужна отдельная архитектура с изоляцией, лимитами времени и памяти, управлением зависимостями и ясным разграничением доступа. Сам редактор не предоставляет такую защиту автоматически.

В интерфейсе полезно прямо обозначить эту границу: «редактирование правила», «проверка синтаксиса», «тестовый запуск» и «применение к данным» — разные операции. Не стоит показывать зелёную кнопку успеха только потому, что текст удалось вставить. В мастерской результатом является изменённый документ, который можно скопировать или скачать.

view.dispatch({
  changes: { from: 0, insert: '// Учебное правило\n' },
  userEvent: 'input'
});

// Текущий документ:
const text = view.state.doc.toString();

Сценарий 2: HTML-блок для CMS

HTML-редактор пригодится там, где пользователь осознанно работает с разметкой: редактирует небольшой шаблон, пример для документации или технический блок в CMS. Языковая поддержка помогает ориентироваться в тегах и атрибутах, но не превращает произвольный фрагмент в безопасный контент.

В мастерской измените заголовок карточки услуги и добавьте пункт списка. Разметка остаётся текстом внутри CodeMirror. Она не назначается через innerHTML и не открывается в iframe. Поэтому вставленный script не выполняется, а изображение из пользовательского тега не начинает автоматически загружаться на страницу.

Если продукту нужен HTML-предпросмотр, проектируйте его отдельно. В зависимости от задачи потребуются очистка допустимой разметки, изолированный iframe, ограничения источников и запрет выполнения скриптов. Для редакционного текста часто лучше разрешённый набор блоков, чем произвольный HTML. Здесь мы изучаем удобный редактор исходника и не подменяем этим правила публикации контента.

import { html } from '@codemirror/lang-html';
const htmlEditor = new EditorView({
  parent: document.querySelector('#editor'),
  doc: '<section><h2>Услуга</h2></section>',
  extensions: [basicSetup, html()]
});

Сценарий 3: JSON с настоящей диагностикой

В JSON-примере намеренно оставлена лишняя запятая в массиве. Проверка использует jsonParseLinter из официального языкового пакета: он вызывает JSON.parse и превращает синтаксическую ошибку в диагностику редактора. Удалите запятую или нажмите «Исправить пример», чтобы увидеть корректное состояние.

Кнопка исправления заменяет учебный документ заранее подготовленным образцом. Это не универсальный механизм ремонта JSON. Для собственного текста сначала прочитайте сообщение и исправьте структуру. Форматирование доступно только синтаксически корректному документу; при ошибке исходник сохраняется.

Есть тонкость с позицией: разные браузеры возвращают разный текст SyntaxError. Если движок не указал распознаваемую позицию, библиотека использует начало документа. Мастерская не выдаёт этот fallback за ошибку в первой строке: она сообщает, что точное место неизвестно, и предлагает перейти к началу. Кроме того, синтаксический успех не подтверждает соответствие JSON Schema или правилам вашего приложения.

import { json, jsonParseLinter } from '@codemirror/lang-json';
import { linter, lintGutter } from '@codemirror/lint';

const extensions = [
  basicSetup,
  json(),
  linter(jsonParseLinter(), { delay: 200 }),
  lintGutter()
];

Подробнее: подключение диагностики.

Форматирование JSON без незаметной потери значений

Самый короткий форматтер выглядит как JSON.parse с последующим JSON.stringify. Но такая операция проходит через значения JavaScript: большое целое может потерять точность, запись числа изменится, а повторяющиеся ключи сведутся к результату разбора объекта. Для редактора конфигурации это может оказаться неожиданным изменением содержимого.

В нашей мастерской JSON.parse используется только для проверки синтаксиса. Затем форматтер перестраивает пробелы и переводы строк по исходным токенам. Запись больших чисел, экспоненты, строковых escape-последовательностей и повторяющиеся ключи сохраняются. Это не означает, что дублирующиеся ключи полезны или разрешены вашим API: их допустимость проверяется отдельно.

Форматирование тоже ограничено по объёму. Короткий документ с глубокой вложенностью после добавления отступов способен сильно разрастись. Поэтому код проверяет накопленный размер и число строк в процессе построения результата. Если предел превышен, операция прекращается, исходник остаётся на месте, а интерфейс показывает причину вместо ложного сообщения об успехе.

import { prettyJSON } from './catalog.js';

try {
  const formatted = prettyJSON(view.state.doc.toString());
  view.dispatch({
    changes: { from: 0, to: view.state.doc.length, insert: formatted },
    userEvent: 'input'
  });
} catch (error) {
  // Покажите ошибку синтаксиса или превышения лимита.
  // Текущий документ пока не изменён.
}

Сценарий 4: поиск и замена в шаблоне

В шаблоне сообщения одна переменная может встречаться несколько раз. Ручная правка каждого вхождения занимает время и увеличивает риск пропуска. В примере поиска переменная client встречается трижды. Найдите её, замените на имя и проверьте число совпадений. После общей замены можно отменить действие одной командой.

Панель использует SearchQuery и команды CodeMirror. В мастерской поиск буквальный: фигурные скобки воспринимаются как обычные символы, а регулярные выражения не включены. Отдельный флаг управляет регистром. Такой набор проще объяснить пользователю, который редактирует шаблон сообщения, а не строит сложный поисковый запрос.

Для большого продукта полезно явно показывать область замены: текущий документ, выделенный фрагмент или несколько файлов. Здесь операция относится только к открытому документу. Перед массовыми изменениями давайте возможность увидеть совпадения, а после — отменить результат. Не называйте кнопку «заменить везде», если фактически речь только об одном текстовом поле.

import { SearchQuery, setSearchQuery, replaceAll } from '@codemirror/search';
const query = new SearchQuery({
  search: '{{client}}', replace: 'Анна',
  literal: true, regexp: false, caseSensitive: false
});
view.dispatch({ effects: setSearchQuery.of(query) });
replaceAll(view);

Сценарий 5: собственные подсказки для предметной области

В редакторе интеграции пользователю часто нужны не все возможные имена JavaScript, а небольшой набор команд конкретного сервиса. Можно предложить поля клиента, доступные события, параметры отчёта и заготовки вызовов. Такой словарь снижает количество опечаток и помогает изучить возможности интерфейса прямо во время работы.

В мастерской откройте подсказки в конце документа и выберите crmNotify. Это настоящее completion-меню CodeMirror: стрелки меняют выбор, Enter вставляет заготовку. У каждого варианта есть подпись и пояснение. Вставленный вызов остаётся текстом; уведомление не отправляется и задача в CRM не создаётся.

В реальном приложении список можно строить из разрешённых пользователю возможностей. Но подсказки не заменяют авторизацию: отсутствие команды в меню не защищает сервер от вручную написанного запроса. Если словарь приходит асинхронно, учитывайте отмену устаревшего ответа и изменения позиции курсора. Наш пример использует локальный небольшой список, чтобы поведение можно было повторить без внешнего API.

import { autocompletion } from '@codemirror/autocomplete';
function suggestions(context) {
  const word = context.matchBefore(/[A-Za-z_$][\w$]*/);
  if (!word && !context.explicit) return null;
  return {
    from: word ? word.from : context.pos,
    options: [{ label: 'crmClient', type: 'variable' }],
    validFor: /^[\w$]*$/
  };
}
const extension = autocompletion({ override: [suggestions] });

Подробнее: источники автодополнения.

Сценарий 6: документ только для чтения

Утверждённую конфигурацию иногда нужно показать сотруднику, не предлагая редактирование. При этом остаются полезными поиск, выделение, копирование, номера строк и скачивание копии. Read-only сценарий демонстрирует именно такой интерфейс: попытка ввода не изменяет документ, а инструменты чтения продолжают работать.

В CodeMirror настройки на уровне состояния и DOM различаются. Для этого примера используются readOnly и editable вместе, а содержимое остаётся фокусируемым через tabindex. Кнопки изменения также отключены, чтобы человек не нажимал недоступную операцию и не гадал, почему ничего не произошло.

Это интерфейсная политика, а не защита данных на сервере. Код приложения может программно передать новое состояние, а пользователь способен отправить собственный HTTP-запрос в ваш backend. Если конфигурацию нельзя менять без определённого права, сервер проверяет это независимо от редактора. На странице только для чтения полезно также показывать версию, время утверждения и автора изменения — эти данные должен предоставить ваш сервис.

import { EditorState } from '@codemirror/state';
const readonly = [
  EditorState.readOnly.of(true),
  EditorView.editable.of(false),
  EditorView.contentAttributes.of({ tabindex: '0' })
];

Подробнее: режим чтения и фокус.

Сценарий 7: тема и перенос без потери истории

Тёмное оформление и мягкий перенос относятся к рабочему месту пользователя. Они не должны менять документ или сбрасывать историю. В мастерской внесите правку, переключите тему, включите и выключите перенос, а затем нажмите отмену. Изменение текста останется частью той же истории редактирования.

Для таких настроек используется Compartment: часть набора расширений можно заменить транзакцией, не создавая новый EditorView. Это удобнее, чем уничтожать редактор при каждом переключении селектора. Сохраняются выделение, документ и остальные расширения, которые не требуется менять.

Мягкий перенос отличается от добавления символов новой строки. Он только меняет отображение длинной строки в доступной ширине. После скачивания файл сохраняет свои настоящие переводы LF. Это особенно важно для JSON, логов, URL и строковых литералов: пользователь должен понимать, где визуальный перенос, а где реальная граница строки в документе.

import { Compartment } from '@codemirror/state';
import { oneDark } from '@codemirror/theme-one-dark';
const theme = new Compartment();
const wrap = new Compartment();
// При создании: theme.of([]), wrap.of(EditorView.lineWrapping)
view.dispatch({ effects: theme.reconfigure(oneDark) });
view.dispatch({ effects: wrap.reconfigure([]) }); // без мягкого переноса

Подробнее: динамическая конфигурация.

Сценарий 8: сравнение и принятие изменений

При редактировании настроек полезно видеть не только новое значение, но и разницу с предыдущим. В мастерской используется unifiedMergeView: старые и новые фрагменты показаны в одном рабочем поле. Такой вариант занимает меньше ширины и удобнее двух параллельных колонок на телефоне.

Нажмите «К изменению», затем попробуйте отклонить фрагмент. В текущий документ вернётся прежний текст. Принятие имеет другой смысл: изменённый текст остаётся, а база сравнения обновляется, поэтому этот участок больше не считается отличием. В интерфейсе эти последствия описаны явно. Для нового опыта кнопка возврата примера восстанавливает исходную пару документов.

Это двухстороннее сравнение, а не Git-клиент и не полноценное разрешение конфликтов трёх версий. Если несколько сотрудников редактируют запись одновременно, серверу всё равно нужен номер версии или другой механизм контроля конфликтов. Перед сохранением можно показывать такой diff как часть подтверждения, но факт нажатия «принять» внутри браузера сам по себе ничего не публикует.

import { unifiedMergeView, acceptChunk, rejectChunk } from '@codemirror/merge';
const extension = unifiedMergeView({
  original: '{ "pageSize": 20 }',
  mergeControls: true
});
// Текущий документ может содержать новое pageSize=40.
// Команды работают с фрагментом под курсором:
acceptChunk(view); // обновить базу сравнения
// rejectChunk(view); // вернуть старый фрагмент в текущий документ

Подробнее: API сравнения документов.

Сценарий 9: локальный текстовый файл

Редактор может быть полезен даже без backend: открыть небольшую конфигурацию, поправить шаблон, сравнить текст или подготовить пример для документации. В мастерской файл читается через браузерный File API, а результат скачивается новой копией. Исходный файл на диске не перезаписывается.

Поддерживаются текстовые расширения TXT, JS, JSON, HTML, CSS, MD, CSV и LOG. Специализированные языковые пакеты подключены для JavaScript, HTML и JSON; остальные документы открываются обычным текстом. Это честнее, чем обещать полноценную поддержку всех языков только потому, что файл удалось прочитать.

Файл должен быть в UTF-8. Неподходящая кодировка, двоичное содержимое или превышение ограничений показывают ошибку и сохраняют текущую работу. Кнопка теста неверного файла позволяет проверить этот путь сразу на странице. Для собственного сервиса важен именно такой отказ: пользователь понимает причину и не теряет документ из-за случайного выбора.

const bytes = await file.arrayBuffer();
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
// В проекте до чтения проверяется file.size,
// после чтения — тип текста, длина и число строк.
const normalized = text.replace(/\r\n?/g, '\n');

Размер, кодировка и ограничения ввода

Мастерская принимает файлы до двухсот КиБ, ста тысяч позиций UTF-16 и пяти тысяч строк. Это выбранные границы учебного приложения, а не максимальные возможности CodeMirror. Лимит размера файла проверяется до чтения, а ограничения документа применяются также к обычному вводу и вставке через транзакции.

Количество позиций текста не равно весу UTF-8 файла. Кириллица и emoji занимают разное число байтов; некоторые видимые символы состоят из нескольких кодовых единиц. Статистика редактора показывает позиции, с которыми работает его модель. При интеграции не используйте это число как готовое доказательство соблюдения серверного ограничения байтов.

Крупные документы, очень длинные строки, тяжёлый анализ и большие сравнения нужно измерять отдельно. Даже хороший редактор можно перегрузить сторонним расширением, которое на каждое нажатие полностью разбирает огромный текст. Начинайте с разумного лимита и понятного сообщения, а затем расширяйте возможности по результатам реального использования.

Транзакции, история и сохранение черновиков

Изменение документа проходит через транзакцию. Это даёт общий механизм для ввода, замены, форматирования и программных правок. История редактора может отменять логические действия, а обработчик обновлений позволяет отслеживать, изменился ли текст или только выделение.

В нашей мастерской для каждой задачи сохраняется EditorState в памяти вкладки. Переключение между сценариями возвращает документ вместе с его историей. Тема и перенос обновляются отдельно. Возврат к исходному примеру намеренно создаёт новое состояние, чтобы пользователь получил чистый учебный опыт.

Важна и работа с жизненным циклом страницы. При восстановлении через браузерный back-forward cache нельзя уничтожать редактор так же, как при окончательном уходе: иначе вернётся пустой интерфейс. Проект учитывает persisted-события. Устаревший результат чтения файла также не получает права показать ошибку поверх уже выбранной другой задачи.

const listener = EditorView.updateListener.of(update => {
  if (update.docChanged) {
    const text = update.state.doc.toString();
    // Обновить признак «есть несохранённые изменения».
    // Отправку на сервер лучше планировать отдельно.
  }
});

Подсветка, линтер, схема и языковой сервер

Цветные ключевые слова помогают читать документ, но сами по себе не доказывают его корректность. Подсветка JavaScript не знает, существует ли функция в вашем приложении. HTML-поддержка не проверяет бизнес-правила CMS. JSON.parse подтверждает синтаксис, но не наличие обязательного поля и не допустимый диапазон значения.

Для разных уровней проверки нужны разные инструменты. Линтер ищет определённые ошибки и стилистические проблемы, JSON Schema описывает структуру данных, а языковой сервер может предоставлять более глубокую информацию о проекте. Подключение каждого инструмента добавляет стоимость и ограничения: вычисления, сетевые запросы, права доступа к коду и актуальность результата.

Начинайте с того, что помогает пользователю завершить конкретную задачу. Для небольших настроек часто достаточно синтаксиса и серверной проверки схемы после сохранения. Для сложного редактора кода понадобятся более богатые подсказки и диагностика. В любом случае интерфейс должен точно говорить, что именно проверено, вместо общего утверждения «код правильный».

Сохранение в PHP или Node.js приложении

В проекте нет сервера сохранения, но редактор легко становится частью существующей формы. Перед отправкой берётся текст из view.state.doc. Сервер получает документ, проверяет пользователя, права на конкретную запись, размер, формат и ожидаемую версию. После успешного ответа интерфейс показывает подтверждённую сервером версию.

Полезно различать три состояния: текст изменён, запрос отправлен, сервер подтвердил сохранение. Если сеть недоступна, черновик должен остаться доступным для повторной попытки. Если запись уже изменилась у другого сотрудника, нельзя молча перезаписывать её: покажите конфликт и предложите сравнить версии.

Ниже только пример точки интеграции с вашим API. Такого маршрута в мастерской нет. CSRF-токен и идентификаторы предоставляет приложение, а backend не должен принимать произвольный путь файла или исполнять полученный текст. Для справочного материала по работе с данными на PHP можно также использовать Composer, но конкретные зависимости обработчика выбираются под его реальную задачу.

const response = await fetch('/api/templates/42', {
  method: 'PUT', credentials: 'same-origin',
  headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
  body: JSON.stringify({
    expectedVersion: documentVersion,
    text: view.state.doc.toString()
  })
});
if (response.status === 409) throw new Error('Документ изменён другим пользователем');
if (!response.ok) throw new Error('Сохранение не подтверждено');

Безопасность: редактор не является песочницей исполнения

Текст в CodeMirror не должен автоматически становиться действием. Это особенно важно для административных панелей, где встречаются конфигурации, команды и HTML. Наш интерфейс отображает документы через редактор, сообщения — через textContent, а скачивание создаёт текстовый Blob. В нём нет пути от пользовательского ввода к eval или серверной команде.

Если позже добавляется выполнение, архитектура меняется существенно. Понадобятся изоляция процессов или среды, пределы ресурсов, контроль файловой системы и сети, очистка состояния и наблюдение за ошибками. Ограничение длины документа и read-only в браузере этих задач не решают.

Сама приватность локального редактора тоже имеет границы. Документы не отправляются нашим кодом, но страница должна быть доверенной: сторонние скрипты оболочки и расширения браузера не контролируются CodeMirror. Для чувствительных конфигураций не вставляйте реальные секреты в публичную демонстрацию; используйте собственный проверенный экземпляр и понятную политику обработки данных.

Клавиатура, мобильная ширина и понятные действия

Редактор встроен в обычную страницу, поэтому пользователь должен уметь из него выйти. В мастерской Tab не переназначен на вставку отступа: он переводит фокус к следующему элементу. Для выравнивания есть отдельная кнопка, а привычные сочетания отмены и вызова подсказок остаются доступными.

Поиск открывается кнопкой или Ctrl/Cmd+F и использует подписанные поля. Ошибки появляются в live region, а read-only документ остаётся фокусируемым. Основные надписи CodeMirror локализованы. Длинные сообщения и имена файлов переносятся, настройки на мобильной ширине располагаются под редактором.

Это не обещание полной доступности со всеми экранными дикторами и мобильными клавиатурами. Для продукта отдельно проверяйте физический телефон, IME, масштабирование, клавиатурную навигацию и работу с длинным документом. Узкая компоновка на компьютере помогает найти переполнение, но не воспроизводит все особенности сенсорного ввода.

Как запустить скачанный пример подключения

В мастерской есть два разных результата. «Скачать документ» сохраняет ваш текущий текст. «Скачать пример» сохраняет стартовую конфигурацию выбранного сценария с текущей темой, переносом и языком. Этот JavaScript содержит npm-импорты, поэтому его нужно собрать, а не просто открыть как самостоятельную веб-страницу.

Поместите пример в src/example.js внутри распакованного проекта. Для сценария подсказок рядом уже есть catalog.js. Создайте public/example.html с контейнером editor и подключением собранного example.js. После команды сборки откройте страницу через локальный сервер. Так скачанный фрагмент превращается в воспроизводимый минимальный опыт.

Для полноценного интерфейса берите весь проект: там есть обработка файлов, лимиты, кнопки, локализация и жизненный цикл. Стартовый пример намеренно короче и показывает подключение возможностей библиотеки. Он не копирует скрыто ваш пользовательский документ в образец кода.

<!doctype html><html lang="ru"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Мой редактор</title><div id="editor"></div>
<script type="module" src="example.js"></script></html>
npm exec -- esbuild src/example.js --bundle --format=esm --target=es2022 --outfile=public/example.js
npm start
# Адрес: http://127.0.0.1:8145/example.html

Запуск на Windows

Установите Node.js 24, скачайте исходники и распакуйте ZIP в отдельную рабочую папку. Для повторяемой установки используйте npm ci: версии уже зафиксированы. После сборки запустите тесты, затем локальный сервер и откройте адрес 127.0.0.1:8145.

В PowerShell можно использовать npm.cmd, чтобы не менять политику выполнения системы ради npm.ps1. Для остановки сервера нажмите Ctrl+C. Если порт занят, задайте другой через переменную PORT перед запуском. По умолчанию сервер слушает только loopback и не принимает загрузку документов.

Открывать index.html через file:// не нужно: модульному JavaScript требуется корректная HTTP-раздача. Для уже собранной public можно использовать установленный PHP и его встроенный сервер. В этом варианте PHP только отдаёт статические файлы; обработчика редактирования или сохранения в проекте нет.

Expand-Archive -LiteralPath .\codemirror-project.zip -DestinationPath .\codemirror-demo
Set-Location .\codemirror-demo\codemirror-project
node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd start
# Альтернатива для готовой public при установленном PHP:
# php -S 127.0.0.1:8145 -t public

Запуск на Linux и разница между сборкой и публикацией

На Linux действия похожи: подготовьте Node.js 24 из доверенного источника, распакуйте проект в каталог обычного пользователя и выполните установку, сборку и проверки. Для npm не требуются права root. Локальный сервер пригодится для разработки на той же машине или доступа через SSH-туннель.

Для постоянной публикации Node-процесс не обязателен. Папка public содержит готовую страницу и локальный bundle, которые можно обслуживать статическим веб-сервером. Это упрощает эксплуатацию: обновление редактора сводится к выпуску проверенной сборки, а не к поддержанию пользовательского исполнителя кода на сервере.

Проверьте Content-Type у app.js и доступность файлов лицензий и исходников. Модуль может не запускаться из-за неверного MIME, даже если сервер возвращает статус 200. В проекте используются расширения .js; не нужно полагаться на нестандартную настройку .mjs у конкретного хостинга.

mkdir -p "$HOME/codemirror-demo"
unzip codemirror-project.zip -d "$HOME/codemirror-demo"
cd "$HOME/codemirror-demo/codemirror-project"
npm ci --ignore-scripts
npm run build
npm test
npm start
# Во втором терминале:
curl -I http://127.0.0.1:8145/app.js
curl http://127.0.0.1:8145/health

Linux VPS: HTTPS, доступ и журналы

Для публичной мастерской подготовьте отдельный домен, настройте DNS и обслуживайте только public. В архиве есть Caddyfile.example с HTTPS-конфигурацией, запретом методов записи, заголовками и логом. Node.js может отсутствовать на VPS, если сборка уже подготовлена и проверена в другом окружении.

Если редактор предназначен сотрудникам, используйте авторизацию существующего приложения или basic_auth на уровне веб-сервера с хешем пароля. Пароли и конфигурации с секретами не помещаются в публичный каталог. Read-only внутри редактора не является заменой этой защиты. Если в будущем появится API, его права и ограничения проверяются отдельно.

Политика содержимого из архива запрещает сетевые обращения редактора, поскольку в нашей версии нет backend-сохранения. Для своего API её потребуется осмысленно изменить. Разрешение встроенных стилей необходимо оформлению CodeMirror. Для встраивания на другой origin настройте frame-ancestors конкретно под ваш сайт. Подробнее о веб-сервере — в практическом материале о Caddy.

editor.example.com {
    root * /srv/codemirror/current/public
    encode zstd gzip
    @write not method GET HEAD
    respond @write 405
    header X-Content-Type-Options nosniff
    file_server
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://editor.example.com/
sudo journalctl -u caddy --since '10 minutes ago'
# Подготовка каталогов, прав и отдельного access.log — в README.

Обновление, проверки и откат

Обновляйте зависимости в отдельной копии проекта. После изменения lock-файла выполните установку, сборку, аудит и тесты. Не подменяйте вручную случайный модуль внутри готового bundle: связанные части должны быть собраны из согласованной установки. В проекте сборщик автоматически собирает лицензии реально включённых пакетов.

Автоматические проверки используют настоящие библиотеки: парсеры, историю, Compartment, JSON-lint, SearchQuery, completion и принятие/отклонение merge. Отдельно проверяются файлы, лимиты, read-only, HTTP и жизненный цикл полной страницы в jsdom. Но jsdom не доказывает правильную мобильную компоновку, IME или системное скачивание — эти действия нужно проверять в браузере.

На VPS храните релизы в отдельных каталогах и переключайте ссылку current после проверки новой версии. Предыдущую сборку сохраняйте до завершения приёмки, чтобы можно было вернуть её тем же способом. Если используется CDN, обновляйте HTML и JavaScript согласованно. После публикации пройдите пользовательский путь, включая ошибку, отмену и сохранение файла, а не только открытие главной страницы.

Исходники и что развивать дальше

В архиве находятся готовая мастерская, исходные app.js/editor.js/catalog.js, точный lock-файл, тесты, полные сведения о лицензиях и инструкции для Windows и Linux. Собственный код и учебные документы распространяются по MIT. Для повторения не нужны внешние аккаунты, приватные файлы или платные сервисы.

Для внедрения выберите одну задачу: редактор JSON-настроек, шаблон сообщения или проверку изменений. Определите, что пользователь вправе менять, как проверяется документ и в какой момент сервер подтверждает сохранение. Затем добавляйте автосохранение, схемы, более умные подсказки и совместную работу, если они нужны вашему продукту.

CodeMirror даёт сильную основу для работы с текстом внутри веб-интерфейса. Практический результат зависит от окружения: понятных ошибок, сохранения черновика, правильных границ исполнения и предсказуемого подтверждения изменений. Именно эти элементы делают редактор полезной частью сервиса, которой удобно пользоваться каждый день.