Болі розробників. Чи можливо їх вирішити за допомогою VS Code extensions

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Привіт, мене звати Максим Слободяник, я фулстек-девелопер в Binary Studio. Я долучився до компанії після проходження Binary Studio Academy та з того часу півтора роки працюю над розробкою застосунку для моделювання баз даних.

У цій статті я спробую відповісти на питання, чи є хорошою ідея створення окремого VS Code розширення для вирішення «болей» девелоперів під час розробки та підтримки long-term проєктів на прикладі кейсу нашої команди.

Трохи про VS Code extensions

VSCode extensions — це потужний інструмент, який може додати майже будь-який функціонал до вашого середовища розробки. Основною мовою для їхньої розробки є TypeScript, проте розробка на JavaScript також можлива.

Extensions marketplace містить безліч розширень на будь-який смак. Починаючи з тих, що додають справді корисні інструменти, наприклад GitLense для більш зручної взаємодії з системою контролю версій, закінчуючи тими, що створили величезний хайп навколо теми розширень. Згадати хоча б Instagram подібні stories, або vsinder aka Tinder для розробників. Певен, що багато хто з вас бачив ці відео, а ще більше про це читали на різноманітних ресурсах. Ці приклади ілюструють зацікавленість розробників в інтеграції до VS Code функціоналу поширених інструментів та створення just for fun extensions, проте за час своїх пошуків я не зустрічав використання розширень для автоматизації задач конкретних команд.

Чому я замислився над розробкою специфічного інструменту

Щоб краще пояснити причини виникнення такої ідеї, мені варто надати вам трішки контексту про наш проєкт. Це кросплатформенний десктоп застосунок для моделювання баз даних, що використовує React.js та Electron під капотом.

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

Якщо говорити детальніше про самі плагіни, то в нашому випадку це окремі проєкти, що містять js-файли та конфіги застосунка і хостяться на GitHub. Зазвичай користувачі встановлюють плагіни за допомогою вбудованого в додаток маркетплейсу, проте їх також можна встановлювати мануально. Для цього необхідно завантажити файли з репозиторію та перенести їх до папки, що містить вже встановлені плагіни. Процес схожий на встановлення модів на деякі ігри.

На відміну від користувачів, які зазвичай встановлюють один-два плагіни, для розробки нам потрібно мати встановленими всі плагіни одночасно. Протягом дня розробник працює з декількома плагінами. Досить зручно відкривати папки потрібних плагінів у новому вікні редактора за допомогою «Open recent», проте нерідко потрібна папка відсутня у випадаючому списку і доводиться знаходити ту саму серед більш ніж 30 візуально однакових папок інших плагінів. Чи варто казати, що це нелегке заняття?

Інший кейс — це оновлення плагінів. Як я вже казав, наші юзери зазвичай не мають встановленими багатьох плагінів, тож кнопка «Update all» в маркетплейсі у нас також відсутня і маємо по одній кнопці «Update» для кожного плагіну. Через це оновлення хоча б 5 плагінів, кожне з яких займає від 5 до 15 секунд, після релізної хвилі стає тим ще заняттям.

Остання не дуже зручна річ, яку я помітив після декількох місяців роботи на проєкті, це тестування фіч або відтворення багів на конкретній версії плагіну. Під версією я маю на увазі не лише версії, що вже в релізі, а також і ті, що знаходяться на форках команди від основного репозиторію. До створення VS Code розширення, єдиним способом це зробити було зайти на сторінку репозиторію потрібного плагіну або його форку, зклонувати і перейти на потрібну гілку або ж просто завантажити файли потрібної версії і замінити ними існуючу. Іншими словами, не було зручного способу отримати потрібну версію для тестування в декілька кліків.

Власне, ці три кейси підштовхнули мене почати свій pet-project, який зараз перетворився в інструмент, який використовується всією командою.

Чому VS Code extension

Я почав з вибору технологій та форми рішення. Перша річ, яка спала мені на думку, було щось на зразок десктопного застосунку, проте для мене було важливим те, щоб інструмент був максимально доступним для розробника: завжди на відстані одного кліку та максимально інтегрований у вже звичний процес розробки. На жаль, десктопний застосунок в окремому вікні не міг задовольнити ці вимоги. Також мені хотілося, щоб інструмент був кросплатформенним. Хоча більшість команди використовують Ubuntu в якості робочої системи, деякі все ж користуються MacOS, водночас всі ми використовуємо VS Code у якості IDE.

Це був момент, коли я згадав про існування VS Code розширень. Вони здалися мені настільки інтегрованим у процес розробки та настільки потужним, що я вирішив створити MVP у вигляді розширення.

З чого почати розробку власного розширення

Я не знайшов кращого ресурсу для початку ознайомлення з розробкою VS Code розширення, ніж гайд на сайті VS Code. Він містить розділ під назвою Extension guides, в якому є багато прикладів використання VS Code API та інформацію про анатомію розширень. Я спробував різні API і зупинився на TreeView API, завдяки якому в extension відображаються плагіни та їхні контроли.

Щоб трішки зрозуміти, як працює VS Code extension та TreeView API, пропоную створити простеньке розширення, яке зможе відображати JSON-структуру. До речі, це буде спрощена версія браузера документації нашого розширення, про яку я розповім трішки пізніше. Ви можете знайти весь код на моєму GitHub. Якщо внутрішнє влаштування розширення — не те що вас цікавить, сміливо переходьте до розділу MVP.

Почнемо з JSON-структури, яку ми будемо відображати:

{
    "root": [
        {
            "title": "App configuration chapter",
            "link": "https://www.google.com.ua/",
            "links": [
                {
                    "title": "First link",
                    "link": "https://www.google.com.ua/"
                },
                {
                    "title": "Second link",
                    "link": "https://www.google.com.ua/"
                }
            ]
        },
        {
            "title": "App development chapter",
            "link": "https://www.google.com.ua/",
            "links": [
                {
                    "title": "Third link",
                    "link": "https://www.google.com.ua/"
                }
            ]
        }
    ]
}

Масив root містить розділи, які в свою чергу містять лінки на деякі ресурси.

Щоб додати tree view до вашого екстеншину, необхідно виконати три кроки: додати view до package.json, створити TreeDataProvider та зареєструвати його.

Спочатку дамо VS Code знати про існування View Container, використовуючи contributes.viewsContainers Contribution Point в package.json.

"viewsContainers": {
			"activitybar": [
				{
					"id": "simple-tree-view",
					"title": "Simple tree view",
					"icon": "resources/logo.svg"
				}
			]
		}

Після цього створимо view, використовуючи contributes.views Contribution Point в package.json.

"views": {
			"simple-tree-view": [ //viewsContainer views belog to
				{
					"id": "documentation-browser", 
					"name": "documentation browser 🔭",
					"when": "config.SimpleTreeView.showDocumentationBrowser"
				}
			]
		}

Наступним кроком прокинемо дані на зареєстровану view, щоб VSCode міг відобразити її у side bar розширення. Для цього реалізуємо TreeDataProvider. Він повинен реалізовувати два обов’язкових методи:

  • getChildren(element?: T): ProviderResult<T[]> — повертає дочірні елементи для переданого element, в нашому випадку лінку розділу. Якщо element відсутній, то потрібно повернути елементи найвищого рівня, в нашому випадку це розділи. Коли користувач відкриває view, на якій є Tree View, викликається метод getChildren без передачі в нього element. У випадку розкриття випадаючого списку елемента, знову викликається getChildren з передачею цього елемента як параметру element. Таким чином ми можемо або підтягнути необхідні дані для конкретного елемента зовні, або просто взяти їх з об’єкту батька як в нашому випадку.
  • getTreeItem(element: T): TreeItem | Thenable<TreeItem> — повертає UI відображення (TreeItem) елемента, що буде відображений на view.
export class DocumentationProvider implements TreeDataProvider<BasicTreeItem> {
		getTreeItem(element: Chapter): TreeItem {
        return element;
    }

    getChildren(element?: BasicTreeItem): Thenable<BasicTreeItem[]> {
        if (element instanceof Chapter) {
            return getChapterLinksItems(element.nestedLinks); //root element child (link)
        }
        return getChaptersItems(); //root element (chapter)
    }
}

Також нам потрібен сервіс, який би отримав дані конфігу зовні і перетворив їх на TreeItems.

export const getChapterLinksItems = async (nestedLinks: LinkNode[]): Promise<TreeItem[]> => {
	const links: Link[] = convertLinksItems(nestedLinks);
	return Promise.resolve(links);
};

export const getChaptersItems = async (): Promise<Chapter[]> => {
	try {
		const config = await getDocumentationConfig(); //fetching documentation config
		return Promise.resolve(convertChaptersItems(config.root));
	} catch (e) {
		window.showErrorMessage("Error occurred while fetching documentation config 🥵");
		return Promise.resolve([]);
	}
};

const convertChaptersItems = (chapters: ChapterNode[]): Chapter[] =>
	chapters.map(
		(chapter) =>
			new Chapter( //extends TreeItem
				chapter.title,
				chapter.link,
				chapter.links,
				TreeItemCollapsibleState.Collapsed //means that this tree item can have children
			)
	);

const convertLinksItems = (links: LinkNode[]): Link[] =>
	links.map((link) => 
		new Link( //extends TreeItem
			link.title, 
			link.link, 
			TreeItemCollapsibleState.None //means that this tree item can't have children
		)
	);

Наступним кроком ми зв’яжемо data provider з view в extension.ts.

export async function activate(context: ExtensionContext) {
	const documentationProvider = new DocumentationProvider();
	window.registerTreeDataProvider('documentation-browser', documentationProvider); //bind documentation-browser to documentationProvider
}

extension.ts — головний файл будь-якого розширення. Він містить view to provider bindings та реєстрації команд. Команди — функції, тригери виконання яких можна за допомогою контролів на UI, що ми зробимо в наступному кроці або ж виклику команд через ctrl+shift+P. Для обох варіантів команду потрібно зв’язати з тригером в package.json.

Після виконання попередніх кроків ми маємо таке відображення 🥳

Наостанок давайте додамо контрол, за допомогою якого ми б могли відкривати лінки з елементів в браузері.

Зробимо це в три кроки: додавання команди та визначення місця розташування контролу в package.json , реалізація бізнес-логіки команди в DocumentationProvider та зв’язка хендлера з командою в extension.ts.

"commands": [ // creating command
			{ 
				"command": "documentation-browser.openLink", //command name
				"title": "Go to documentation",
				"icon": {
					"light": "resources/light/goByLink.svg", //icon for the control (light theame)
					"dark": "resources/dark/goByLink.svg" //icon for the control (dark theame)
				}
			}
		],
		"menus": { // setting a place for created command
			"view/item/context": [
				{
					"command": "documentation-browser.openLink", //related command name
					"when": "view == documentation-browser", //will be visible only on documentation-browser view
					"group": "inline"
				}
			]
		}

Додамо хендлер.

openExternalURI(link: string) {
      env.openExternal(Uri.parse(link)); // opens link in your browser
}

Наостанок, зв’яжемо команду та хендлер в extension.ts.

export async function activate(context: ExtensionContext) {
	...
	registerCommands(documentationProvider);
}

const registerCommands = (documentationProvider: DocumentationProvider) => {
	commands.registerCommand('documentation-browser.openLink', node =>
		documentationProvider.openExternalURI(node.link)
	)
}

Тепер ми можемо відкривати лінк кожного елемента в браузері.

MVP

Тепер, коли ви маєте базове явлення про внутрішнє влаштування розширень, що використовують TreeView, пропоную перейти до вигляду нашого розширення v0.1.0 (MVP).

В ньому було реалізовано 6 основних фіч:

  • Відображення встановлених плагінів.
  • Можливість відкрити папку плагіну в новому вікні редактора (VS Code або Sublime).
  • Можливість відкрити папку, що містить папки всіх плагінів у новому вікні редактора (VS Code або Sublime).
  • Можливість оновити конкретний плагін до останньої версії.
  • Можливість оновлення всіх застарілих плагінів одним контролом.
  • Позначення застарілих плагінів (сірий кружечок).

Як ви вже могли здогадатися, плагіни відображені за допомогою Tree view. Я додав лого вендора у якості іконки елемента дерева для спрощення візуального пошуку потрібного плагіну.

Інша корисна фіча VS Code Tree view — можливість пошуку елементів з коробки.

Якщо говорити про UX, то контроли, що стосуються конкретного плагіну, розмістилися на елементах, що репрезентують плагіни, а ті, що стосуються всіх плагінів одночасно, розміщені поруч з заголовком plugin’s browser view. Також я додав налаштування, що дозволяють кожному девелоперу налаштувати вигляд розширення на свій смак та залишити лише ті елементи та контроли, які треба. Наприклад, на зображенні нижче мій варіант налаштування, де відсутні контроли, пов’язані з Sublime text.

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

У що перетворилося розширення зараз

MVP вирішило багато «болей» нашої команди та за декілька тижнів використання показало, що це досить зручно — менеджити плагіни за допомогою TreeView, яке завжди під рукою. Проте ми все ще мали невирішеними декілька проблем, описаних на початку цієї статті, тому я поступово додавав функціонал та релізив нові версії.

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

На щастя, безліч популярних розширень мають public репозиторії, наприклад, вже згаданий на початку GitLens. Проте ось також кілька корисних лінків на документацію.

VS Code API reference — збірник наявного API, можна завжди звернутися сюди і спробувати знайти необхідний методи для взаємодії з IDE або ОС. Contribution points reference — містить документацію по contribution points необхідних для конфігурування зовнішнього вигляду, key bindings, контролів, налаштувань розширення тощо.

Зараз, через 6 місяців після MVP, розширення має версію v0.5.1, функціонал значно розширився, а до розробки долучилося декілька тіммейтів. У найновішій версії ми додали більше функціональності до елементів плагінів. Тепер можна переглядати форки, гілки форків, відкриті пулреквести, теги та гілки репозиторію плагіна. Крім того, можливо встановити версію плагіну з будь-якого з цих джерел. Також за допомогою кнопки «OctoCat» на елементі автора форку, можливо зклонувати форк основного репозиторію. Іншими словами, потрібно просто форкнути основний репозиторій на GitHub, перемкнутися на вікно VS Code, та за допомогою однієї кнопки замінити встановлену версію плагіну готовим для розробки клоном.

Після вирішення відомих болей команди, я подумав, що б ще могло бути інтегрованим в розширення. Нещодавно ми почали розробку in-project документації, тож я вирішив додати її у вигляді documentation browser. У ньому ми зібрали корисні лінки з документації додатка та development guidelines. Структура документації зберігається у вигляді JSON-конфігурації, що хоститься на GitHub pages, тож можна додавати та змінювати лінки, не створюючи для цього нових релізів розширення.

Серед усього іншого VS Code API надає доступ до створення toast-сповіщень, які ми активно використовуємо для інформування про помилки, прогрес дії, нотифікацій про наявність оновлень плагінів на старті IDE та для надання опцій вибору під час оновлення або клонування плагінів.

Чи все так ідеально

Хоч з усього сказаного вище може здатися, що VS Code розширення має безмежні можливості для кастомізації, а його розробка — панацея для вирішення будь-яких проблем, насправді це не зовсім так.

Розширення все ще не мають повної підтримки API VS Code, а зміни, які реквестить ком’юніті, подовгу не знаходять своєї реалізації. Наприклад, ми хотіли додати лейбл з кількістю плагінів, що потребують оновлення на Activity bar, як це зроблено для Source Control. Проте, на жаль, така можливість для розробників розширень недоступна, а запит на додавання цього API був зроблений більше двох років тому.

Також попри можливість відмальовувати вебсторінки в окремій вкладці VS Code або на сайдбарі, документація радить перед цим дуже гарно подумати над можливістю перенести цей функціонал в окремий вебзастосунок, адже відмальовування таких сторінок є ресурсомістким.

Чи це вартувало того

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

Підсумовуючи, на мою думку, VS Code extensions чудово підходять для автоматизації нескладних задач, виклик яких відбувається за допомогою нативних контролів, що надає VS Code API.

Сподобалась стаття? Натискай «Подобається» внизу. Це допоможе автору виграти подарунок у програмі #ПишуНаDOU

👍ПодобаєтьсяСподобалось7
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Якісна технічна стаття. Дякую. Сам подумував написати розширення, але поки що не знаходжу часу. Додав до обраного, може колись ще повернусь до цієї ідеї.

А ведь где-то существует слесарь, который допилил напильником, напильник.

Класний приклад того, що якщо інженеру щось треба і цього ще ніхто до нього не зробив — він бере і робить :)

Напомните, с какого года пошла мода называть формошлепов инженерами ?
Я вот ДГУ закончил в 1994-от, прикладная математика, все дела, но как то неловко себя так называть.

Ну мені здається що формошльоп би не став про таке навіть думати. А тут людина має інструменти, має проблему і вирішує її. Чим не інженер?
Я дуже багато де чув зневажливе ставлення, що мовляв які то інженери. Але мені здається що не треба принижувати своїх здібностей і досягнень. Звичайно, коли ринок такий гарячий, дуже складно знайти інженера посеред формошльопів та крадошльопів (від акроніму CRUD), але вони є ;)

Підписатись на коментарі