Всем привет! Меня зовут Андрей, я работаю в БФТ-Холдинге на должности ведущий инженер-разработчик и занимаюсь frontend-разработкой.

В статье подробно поговорим о самом быстром форматере кода. Подробно покажем, как интегрировать форматер в любой проект, настроим форматирование по сохранению в редакторах кода VSCode/VSCodium и посоревнуемся в скорости форматирования с prettier. На фоне хайпа нейросетей я решил разбавить статью изображениями форматера в представлении различных нейросетей. Надеюсь, будет интересно, погнали!

Несмотря на то, что статья с акцентом на frontend, а приведённые настройки конфигурации и примеры кода относятся к JavaScript, TypeScript, Vue или React, статья будет полезна всем разработчикам, так как форматер не ограничивается форматированием вышеуказанного.

Неформатированный код от ruDALL-E. Модель Kandinsky 2.1
Неформатированный код от ruDALL-E. Модель Kandinsky 2.1

План статьи

Погружение

Кратко расскажу о себе. За плечами у меня более 5 лет опыта коммерческой разработки, где большую часть времени я работал в качестве Fullstack-разработчика. Мой основной стек — Vue. В свободное время я делаю вклад в open source и веду активности в профильных сообществах.

В БФТ-Холдинге я разрабатываю четыре проекта, в два из которых удалось внедрить архитектурную методологию Feature-Sliced Design. Основной стек проектов — Quasar 2 + Pinia и, конечно, TypeScript.

Так как я разрабатываю frontend, то начнём погружение в плоскости JavaScript (TypeScript). JavaScript не ограничивает вас в правилах табуляции, пробелах, переносах строк, именовании переменных и т.д. при написании программного кода. Иными словами, человек может писать в своём неповторимом стиле, не следуя какому-то определённому code style. И для проекта, в котором работает только он один, скорее всего, это не вызывает никаких проблем и никак не влияет на скорость выполнения задач, но это не точно. А если в проекте задействована целая команда разработки, начинаются трудности. Например, с чтением и пониманием кодовой базы.

И так сойдёт :)
И так сойдёт :)

Есть объективные причины, почему необходимо придерживаться единого code style при разработке, и поэтому компании следят, чтобы разработка кодовой базы велась в соответствии с принятыми стандартами. На Хабр есть отличная статья, в которой подробно раскрывается эта тема.

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

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

Dprint от Midjourney
Dprint от Midjourney

И вот мы плавно подобрались к форматерам. Сегодня на рынке доминирует prettier, так как он имеет большой набор плагинов, что позволяет ему форматировать большое количество форматов файлов. Также есть менее известные форматеры, такие как jscodeshift (форматирует по codemode скриптам), js-beautify, jsfmt, tsfmt и dprint. Это только те форматеры, которые поддерживаются или активно разрабатываются, и мы можем установить их в качестве dev-зависимости. Помимо этого, есть большое количество расширений для различных IDE и редакторов кода.

Если я забыл упомянуть какой-либо ещё живой форматер, пожалуйста, напишите об этом в комментариях.

Знакомьтесь, dprint

Dprint — это open source форматер кода, написанный на языке Rust. Форматирование выполняется очень быстро за счёт своего собственного алгоритма форматирования и WebAssembly-плагинов. Из коробки доступны плагины для форматирования javascript и typescript, json, markdown, toml и dockerfile. Плагины для форматирования javascript и typescript, json, markdown встроены в Deno cli. На момент написания статьи осуществляется поддержка process-плагинов — плагинов-обёрток (prettier, roslyn (C#/VB), exec). Плагины и конфигурации к ним можно подключать как локально, так и из удаленного источника. Написаны расширения для  VSCode/VSCodium и IDE от компании JetBrains.

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

Также, нельзя не отметить дружелюбный CLI и отличную документацию.

Dprint от ruDALL-E. Модель Kandinsky 2.1
Dprint от ruDALL-E. Модель Kandinsky 2.1

Говоря о dprint, нельзя не упомянуть автора инструмента. Это David Sherret, разработчик из Канады, который занимается программированием уже более 17 лет. В настоящее время он работает в компании Deno, до этого разрабатывал ПО в области медицины. Dprint не единственный его проект, он также разработал статический анализатор и генератор кода (ts-morph) и TypeScript AST Viewer.

Интеграция в проект

В качестве проекта создадим простенький react-проект при помощи Vite. Так как статья не про то, как создавать проекты с Vite, просто приведу команды с минимальными комментариями:

/* создаём проект */
yarn create vite react-dprint --template react-ts

/* Переходим в директорию и ставим пакеты */
cd react-dprint && yarn

/* Запускаем, проверяя работоспособность */
yarn dev

Готовый проект со всеми настройками можно пощупать, клонировав его из репозитория

Отлично — проект готов! Для того, чтобы dprint начал нам что-то форматировать, его необходимо установить и создать правильную конфигурацию. Dprint предлагает целых девять вариантов установки:

  • Shell (Mac, Linux, WSL)

  • Windows Installer

  • Powershell (Windows)

  • Scoop (Windows)

  • Homebrew (Mac)

  • Cargo

  • Deno

  • npm

  • asdf-vm (asdf-dprint)

Устанавливаем: npm install dprint --save-dev.

Как говорилось ранее, у dprint дружелюбный CLI, поэтому пишем npx dprint --help и получаем отличное руководство для старта. Разберём основные команды:

  • init — создание конфигурационного файла для текущей директории

  • output-resolved-config — напечатать все доступные конфигурации для выбранных плагинов

  • check — поиск неформатированных файлов

  • fmt — форматировать

Создаём конфигурационный файл командой npx dprint init. CLI предложит нам выбор из списка нативных плагинов:

Выбор после выполнения команды dprint init
Выбор после выполнения команды dprint init

Для примера выберем следующие плагины:

 * dprint-plugin-typescript
 * dprint-plugin-json
 * dprint-plugin-markdown

Нажимая пробел, выбираем плагины: dprint-plugin-typescript, dprint-plugin-json, dprint-plugin-markdown. Заглянем в бережно созданный для нас конфигурационный файл:

  • Секции typescript, json и markdown предназначены для указания правил к форматированию

  • includes — указываем какие директории и типы файлов необходимо отформатировать

  • excludes — указываем, что не надо форматировать

  • plugins — пути к плагинам

Это не все настройки, но для старта их более чем достаточно. Теперь мы можем добавить команды в секцию scripts файла package.json.

Добавим команды для форматирования в секцию scripts:

"scripts": {
  "dev": "vite",
  "build": "tsc && vite build",
  "preview": "vite preview",
  "format": "npx dprint fmt",
  "format:diff": "npx dprint fmt --diff"
}

Но на практике вы будете редко запускать форматирование через NPM (исключение — CI), поэтому настроим форматирование по сохранению файла в VSCode/VSCodium. Для этого необходимо в корне проекта создать (если отсутствует) директорию .vscode, в ней создать (если отсутствует) файл settings.json и положить туда следующее содержимое:

{
  "[typescript]": {
    "editor.defaultFormatter": "dprint.dprint",
    "editor.formatOnSave": true
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "dprint.dprint",
    "editor.formatOnSave": true
  },
  "[json]": {
    "editor.defaultFormatter": "dprint.dprint",
    "editor.formatOnSave": true
  }
}

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

Приятной работы! Ну а мы продолжим.

Dprint от Stable diffusion
Dprint от Stable diffusion
Dprint от Stable diffusion
Dprint от Stable diffusion
Dprint от Stable diffusion
Dprint от Stable diffusion

Соревнования в скорости форматирования

Теперь, когда мы познакомились с форматером, настало время соревнований! В качестве плацдарма возьмём репозиторий фреймворка Quasar 2, а соревноваться в скорости будем c prettier.

Также необходимо сказать о параметрах тачки, на которой мы будем устраивать соревнования:

Система и параметры тачки
Система и параметры тачки

Скрипты будем запускать, используя WSL 2.

Подготовка

Создадим две директории с клоном репозитория Quasar 2. В первой директории настроим dprint, а во второй prettier. Также необходимо создать скрипт для каждой из директорий, который запустит форматирование, измерит время форматирования и подсчитает количество изменённых файлов.

В директории с форматером dprint

Скрипт:

# /bin/bash

echo "dprint:"

START=$(date + %s%N)
npx dprint fmt --incremental=false
END=$(date + %s%N)

milliseconds=$((($END - $START)/1000000))

seconds=$(($milliseconds/1000))

minutes=$(($seconds/1000))

echo "Количество миллисекунд: $milliseconds"
echo "Количество секунд: $seconds"
echo "Количество минут: $minutes"

echo "Количество затронутых файлов:"

git diff --shortstat | tail -n1

Мы используем флаг incremental со значением false для того, чтобы отключить пропуск тех файлов, которые не были изменены с момента последнего форматирования (на всякий пожарный).

Конфигурационный файл (dprint.json):

{
  "typescript": {
    "indentWidth": 2,
    "lineWidth": 120,
    "quoteStyle": "alwaysDouble",
    "semiColons": "always",
    "trailingCommas": "never"
  },
  "includes": ["**/*.{ts,tsx,js,jsx,cjs,mjs}"],
  "excludes": [
    "**/node_modules",
    "**/templates",
    "**/test"
  ],
  "plugins": [
    "https://plugins.dprint.dev/typescript-0.75.0.wasm"
  ]
}

В директории с форматером prettier

Скрипт, измеряющий скорость форматирования в директории с prettier, ничем не отличается, кроме заголовка (echo "prettier:") вызова самого форматера:

npx prettier --write --loglevel=silent (--loglevel=silent — отключаем вывод логов). В отличие от dprint, который использует только один конфигурационный файл, для prettier необходим конфигурационный файл и ignore-файл.

Конфигурационный файл (.prettierrc.json):

{
  "semi": true,
  "singleQuote": false,
  "printWidth": 120,
  "trailingComma": "none"
}

ignore-файл (.prettierignore):

**/node_modules
**/templates
**/test
**/*.vue
**/*.md
**/*.json
**/*.xml
**/*.yml
**/*.css
**/*.html
**/*.prettierrc

Форматируем

Внимательный читатель заметил, что форматировать мы будет только javascript-файлы и typescript-файлы. В процессе форматирования мы будем заменять одинарные кавычки на двойные.

Замечание: перед запуском скриптов каждый репозиторий отформатирован соответствующим форматером! На момент произведения замеров в директориях содержалось 25132 файла (не считая конфигурационных файлов форматеров) и 3560 директорий. Также были удалены те файлы, на которых спотыкались форматеры.

Скриптиус форматиус
Скриптиус форматиус

Результаты форматирования двух форматеров представлены в таблице ниже:

prettier

dprint

Количество изменённых файлов

1103

1103

Затраченное время(в секундах)

41

5

Как видим, dprint справился с задачей не просто быстро, а очень быстро и быстрее prettier почти в 9 раз. Если повторить такой сценарий, то значения времени будут не сильно отличаться. У dprint значения варьировались от 5 секунд до 7, а у prettier от 41 до 46 секунд.

И ещё раз посоревнуемся

У dprint есть потрясающий плагин-обёртка для prettier, который по заявлениям автора выполняет форматирование быстрее, чем сам prettier:

Цитата: Additionally it's much faster. This plugin will format files in parallel and you can take advantage of the speed of dprint's incremental formatting if enabled.

Подготовка

Чтобы это проверить, также создадим две директории и в каждую из них клонируем один большой проект из проектов компании, написанный на Vue. Скрипты для запуска форматирования будем использовать из предыдущего сравнения. А вот конфигурационные файлы форматеров потерпят изменения.

В директории с форматером dprint

Конфигурационный файл (dprint.json):

{
  "prettier": {
    "singleQuote": true,
    "tabWidth": 2,
    "vueIndentScriptAndStyle": true
  },
  "includes": ["**/*.vue"],
  "excludes": [
    "**/node_modules",
    "**/templates",
    "**/test"
  ],
  "plugins": [
    "https://plugins.dprint.dev/prettier-0.13.0.json@dc5d12b7c1bf1a4683eff317c2c87350e75a5a3dfcc127f3d5628931bfb534b1"
  ]
}

В директории с форматером prettier

Конфигурационный файл (.prettierrc.json):

{
  "singleQuote": true,
  "tabWidth": 2,
  "vueIndentScriptAndStyle": true
}

ignore-файл (.prettierignore):

**/node_modules
**/templates
**/test
**/*.vue
**/*.md
**/*.json
**/*.xml
**/*.yml
**/*.css
**/*.html
**/*.ts
**/*.tsx
**/*.jsx
**/*.js
**/*.prettierrc

Форматируем

Тест на внимательность: если вам кажется, что у вас дежавю, то советую вам отдохнуть. А пока следите за цифрами

В процессе форматирования мы будем заменять одинарные кавычки на двойные в файлах .vue.

Замечание: перед запуском скриптов каждый репозиторий отформатирован соответствующим форматером! На момент произведения замеров в директориях содержалось 1029 файлов (не считая конфигурационных файлов форматеров) и 305 директорий. Также были удалены те файлы, на которых спотыкались форматеры.

Скриптиус форматиус
Скриптиус форматиус

Результаты форматирования двух форматеров представлены в таблице ниже:

prettier

dprint

Количество изменённых файлов

151

151

Затраченное время(в секундах)

11

6

Магия
Магия

Итоги

Сегодня мы познакомились с замечательным инструментом для форматирования. Увидели, как просто и быстро его можно интегрировать в любой проект. А благодаря плагину-обёртке над prettier можно ускорить форматирование, просто перенеся правила в конфигурационный файл dprint.json. Давайте теперь подведём итоги в виде плюсов и минусов данного форматера.

Плюсы:

  • Open source (MIT license)

  • Быстрый, как говорилось ранее, за счёт своего собственного алгоритма форматирования и WebAssembly-плагинов

  • Поддержка плагинов-обёрток (process-плагины), например, prettier

  • Подключение конфигураций как локально, так и из удалённого источника

  • Управление многопоточностью

  • CI

  • Отличный DX (developer experience), а именно: дружелюбный и функциональный CLI, отличная документация на официальном сайте и наличие playground.

Минусы:

  • Отсутствует стабильная версия

  • Нет нативной поддержки Vue, Svelte и других форматов

  • Недостаточный набор правил форматирования. Например, для javascript нет замены var на let или const и поддержки вложенных тернарных операторов (скоро появится)

  • Небольшое сообщество и всё отсюда вытекающее.

На этом всё. Надеюсь, вы попробуете в работе данный инструмент. Буду рад любым комментариям, и спасибо за уделённое время.

Комментарии (12)


  1. NeoCode
    30.05.2023 07:54
    +2

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


    1. novoselov
      30.05.2023 07:54
      +1

      При этом ваш мозг подсознательно успевает обработать картинку и синхронизировать с ней свое представление о реальности. В итоге в голове у вас остается примерно это


      1. doox911 Автор
        30.05.2023 07:54

        Потрясающий ролик! Спасибо)


  1. Cheater
    30.05.2023 07:54

    Самый быстрый форматер кода

    соревноваться в скорости будем c prettier.

    С одним из самых медленных форматировщиков, написанных на js))))
    Погоняйтесь с jsonformat что ли на json, или с rustfmt для Rust, а затем пишите кликбейтные заголовки про самый быстрый форматировщик.

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

    за счёт своего собственного алгоритма форматирования

    Универсального для всех языков и вызываемых парсеров? Как это работает?


    1. doox911 Автор
      30.05.2023 07:54

      Большое спасибо за комментарий. Prettier был выбран в виду его большой популярности (я не пишу на Rust). А по поводу других форматеров - отличная идея. Надо будет попробовать)


    1. domix32
      30.05.2023 07:54

      Подозреваю, что там TreeSitter прикручен. В итоге получается поток токенов который достаточно десериализовать в форматированный вывод.


      1. ris58h
        30.05.2023 07:54

        Tree-sitter вроде дерево на выходе даёт - в поток токенов надо самому превращать.


    1. doox911 Автор
      30.05.2023 07:54

      Погоняйтесь с jsonformat что ли на json, или с rustfmt для Rust

      В дополнение к предыдущему моему ответу. jsonformat, rustfmt - форматируют только один формат. На мой взгляд, сравнивать можно, но не совсем корректно. А для Rust был плагин обёртка, а сейчас предлагается использовать dprint-plugin-exec.

      Универсального для всех языков и вызываемых парсеров? Как это работает?

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


  1. Boomburum
    30.05.2023 07:54
    -1

    Эталонно оформленная публикация ????


    1. doox911 Автор
      30.05.2023 07:54

      Спасибо. Я только с лёту не смог разобраться как используя режим MD в редакторе вставлять картинки (путь до картинки относительный), так же как в редакторе без режима MD.


      1. Boomburum
        30.05.2023 07:54
        +1

        В md-режиме нужно вставлять прямые урлы до картинок, для этого можно использовать наш картиночный сервис, например. Вне md-режима - просто в меню вставки элементов (слева) жмём плюсик и выбираем "Изображение", а дальше выбираем его с диска.


  1. dedmagic
    30.05.2023 07:54
    +1

    для javascript нет замены var на let или const

    Это не входит в обязанности форматтеров, это ответственность линтеров.

    Форматтеры только добавляют/убирают пробелы и переводы строк, текст программы они не меняют.