Воссоздаём расширение для VS Code
surf_webПривет, меня зовут Лиза, Frontend-разработчик в Surf. Лучший способ усвоить что-то новое — применить знания на практике, создавая что-то интересное своими руками. В этой статье мы шаг за шагом воссоздадим популярное расширение для VS Code.
Я выбрала Pomodoro-таймер в качестве примера, потому что он обладает всеми необходимыми функциями для демонстрации процесса разработки расширений.
Что будет в нашем расширении
Наше расширение будет включать в себя:
Два основных экрана:
- Экран настроек.
- Экран самого таймера.
Интерактивные элементы:
- Поля ввода для установки длительности каждого этапа (рабочий интервал, короткий и длинный перерывы).
- Кнопки управления: старт, пауза, сброс.
- Визуализация прогресса таймера.
Удобное расположение: расширение будет интегрировано в сайдбар VS Code, как и встроенные панели.
Дополнительные функции:
- Уведомления — всплывающие сообщения в углу экрана о смене этапов.
- Звуковые сигналы — аудио-оповещения при переходе между этапами.
Гибкие настройки — возможность задать длительность для нескольких рабочих сессий, коротких перерывов и одного длинного перерыва после завершения цикла.
Визуальная концепция — минималистичный дизайн с четким индикатором прогресса (здесь можно добавить скриншот или более детальное описание интерфейса).
Как это будет работать
Настройка длительности
Пользователь сможет установить:
- Длительность рабочего интервала (например, 25 минут).
- Длительность короткого перерыва (например, 5 минут).
- Длительность длинного перерыва (например, 15 минут, после 4 рабочих циклов).
Автоматическое переключение
Таймер будет автоматически переключаться между этапами (работа, короткий перерыв, длинный перерыв).
Оповещения
VS Code будет уведомлять пользователя о смене этапов с помощью звука и всплывающих нотификаций.
План разработки
Разработка будет разбита на отдельные этапы, каждому из которых будет соответствовать отдельная ветка на GitHub. Названия веток будут соответствовать формату: part_n.
Приступим к работе.
Ветка part_1
Для начала работы над расширением мы воспользуемся генератором yo code. На данном этапе мы сосредоточимся на исследовании функциональности, поэтому отбросим бандлинг.
Ответим на вопросы генератора следующим образом:
- What type of extension do you want to create? — New Extension (TypeScript)
- Which bundler to use? — unbundled
Размещение расширения — сайдбар VS Code
Наше приложение будет располагаться в сайдбаре VS Code, что обеспечит удобный доступ к функциям Pomodoro-таймера. Мы будем использовать Primary Sidebar для максимальной интеграции в пользовательский интерфейс.
- Для размещения нашего пользовательского интерфейса в сайдбаре мы будем использовать Webview. Оно позволяет отображать веб-контент (HTML, CSS, JavaScript) внутри VS Code, что дает нам полную гибкость в создании дизайна и интерактивных элементов.
- Рекомендую ознакомиться с официальным руководством по дизайну сайдбаров, чтобы наше расширение выглядело и ощущалось как нативное.
Интеграция Webview в сайдбар
Для успешного размещения Webview в сайдбаре нам потребуется создать специальный класс для управления его состоянием и содержимым.
Мы создадим класс SidebarProvider, который будет отвечать за прокидывание Webview в сайдбар. Файл для этого класса будет располагаться рядом с extension.ts.
import * as vscode from 'vscode';
export class SidebarProvider implements vscode.WebviewViewProvider {
private _view?: vscode.WebviewView;
// Здесь будет храниться ссылка на вебвью
// Этот метод вызывается, когда VS Code создает вебвью
public resolveWebviewView(webviewView: vscode.WebviewView) {
this._view = webviewView; // Сохраняем ссылку на вебвью
webviewView.webview.html = this._getHtmlForWebview();
//Загружаем HTML
}
// Генерируем HTML, который будет отображаться в вебвью (взято с официального источника для примера)
private _getHtmlForWebview() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cat Coding</title>
</head>
<body>
<img src="https://media.giphy.com/media/JIX9t2ozdTN9s/giphy.gif" width="300" />
</body>
</html>`;
}
}
Класс SidebarProvider является ключевым элементом для размещения нашего интерфейса в сайдбаре VS Code. Он реализует интерфейс vscode.WebviewViewProvider, предоставляемый API VS Code, что позволяет VS Code создавать и управлять нашим Webview.
Давайте разберем основные части этого класса:
_view?: vscode.WebviewView — эта приватная переменная будет хранить ссылку на текущий экземпляр WebviewView. Это важно, если нам понадобится взаимодействовать с Webview из других частей расширения (например, отправлять ему сообщения или обновлять его содержимое).
resolveWebviewView(webviewView: vscode.WebviewView) — это главный метод класса SidebarProvider. VS Code вызывает его, когда:
- Пользователь впервые открывает сайдбар, содержащий наше расширение.
- VS Code готов отрисовать Webview в сайдбаре.
Внутри этого метода мы сохраняем переданную ссылку на webviewView в нашей приватной переменной _view, а затем присваиваем HTML-содержимое свойству webviewView.webview.html, вызывая метод this._getHtmlForWebview().
_getHtmlForWebview() Этот приватный метод отвечает за генерацию HTML-кода, который будет отображаться внутри Webview. В текущем примере мы возвращаем простую HTML-страницу, содержащую GIF-анимацию «кота-программиста». Это базовый пример, который демонстрирует, как можно встроить любой веб-контент в Webview. В дальнейшем мы заменим его на полноценный интерфейс Pomodoro-таймера.
В данном случае это простая страница с гифкой работающего кота.
import * as vscode from 'vscode'; // Импортируем API VS Code
const { SidebarProvider } = require('./SidebarProvider');
// Подключаем класс SidebarProvider
export function activate(context: vscode.ExtensionContext) {
// Создаём экземпляр SidebarProvider и передаём ему
// URI расширения (чтобы вебвью могло загружать локальные файлы)
const sidebarProvider = new SidebarProvider(context.extensionUri);
// Регистрируем вебвью в сайдбаре:
// 'myExtension-sidebar' — это ID, который должен совпадать с package.json
context.subscriptions.push(
vscode.window.registerWebviewViewProvider('myExtension-sidebar', sidebarProvider)
);
}
При запуске расширения VS Code вызывает функцию activate. Внутри неё создаётся SidebarProvider, который будет управлять содержимым сайдбара.
Далее, чтобы всё заработало, нам нужно отредактировать секцию contributes в package.json. Сразу же добавим иконку для activityBar. Больше информации по иконкам тут.
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "myExtension-sidebar-view",
"title": "CatoDoro",
"icon": "resources/cat.png"
}
]
},
"views": {
"myExtension-sidebar-view": [
{
"type": "webview",
"id": "myExtension-sidebar",
"name": "CatoDoro",
"icon": "resources/cat.png"
}
]
},
"files": [
"out",
"resources"
],
"commands": []
}
viewsContainers — этот раздел добавляет новую вкладку в Activity Bar (левую панель VS Code):
- id — уникальный идентификатор для нашего контейнера.
- title — текст, который появится при наведении курсора на иконку.
- icon — путь к иконке (в данном случае, файл находится в resources/cat.png).
views — здесь определяется содержимое созданной вкладки:
- myExtension-sidebar-view — это ссылка на контейнер, который мы определили в viewsContainers.
- type: "webview" — указывает, что содержимое будет представлено как вебвью.
- name — название, которое будет отображаться для вкладки внутри самого сайдбара.
Теперь пришло время запустить нашу первую часть. Откройте файл extension.ts в редакторе и запустите его в режиме отладки, нажав F5.
Ура! В сайдбаре уже активно «клацает» котик.
Не будем уступать ему в стремлении и перейдём ко второй части.
Ветка part_2
Пока оставим в сайдбаре заглушку с картинкой, а сами займёмся разработкой Webview.
Как мы будем делать сборку? (Возможно, это можно будет обернуть в монорепозиторий, но сейчас это факультативно).
Важно отметить, что нам удобно писать код компонентами и управлять состоянием. Однако для Node.js нам потребуется чистый JavaScript после сборки.
Выбранный стек технологий:
- Preact + Vite — для быстрого старта (это лёгкий аналог React с похожим синтаксисом).
- Styled Components — чтобы сосредоточиться на логике, а не на многочисленных CSS-файлах.
- Day.js — для удобной работы со временем (таймеры, форматирование).
Это решение «в лоб» – позднее можно будет перейти на React + CSS-модули + date-fns без необходимости переписывать основную логику.
Главное сейчас — создать работающий прототип, а не идеальный код.
Webview будет размещено в отдельной папке под названием -webApp. Все зависимости, исходные файлы и результаты сборки будут храниться внутри этой папки.
Начнём с установки зависимостей. Preact + Vite проще установить через команду:
npm init preact
В рутовый tsconfig добавим exclude
"exclude": [
"webApp",
"node_modules"
]
Поправим vite.config.js
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';
export default defineConfig({
plugins: [preact()],
root: 'src',
build: {
// Папка для собранных файлов (относительно корня проекта)
outDir: '../dist',
manifest: true,
rollupOptions: {
input: 'src/index.html',
output: {
entryFileNames: `webview-build/[name].js`,
chunkFileNames: `webview-build/[name].js`,
assetFileNames: `webview-build/[name].[ext]`,
},
},
},
base: '',
});
При работе с расширением нас будет интересовать только билд Webview, с UI можно будет взаимодействовать в режиме разработки.
Далее нам необходимо предоставить расширению доступ к нашему Webview. Попрощаемся с картинкой котика.
И вернёмся в extension.ts. Мы «прокинем» в sidebarProvider путь к папке расширения (context.extensionUri).
const sidebarProvider = new SidebarProvider(context.extensionUri);
Этот параметр (context.extensionUri) нужен для нескольких целей:
- Доступ к файлам расширения
Он позволяет вебвью загружать локальные ресурсы (такие как HTML, CSS, JavaScript файлы, а также иконки) непосредственно из папки вашего расширения. - Безопасность
VS Code требует явного указания путей к этим ресурсам через метод asWebviewUri. Это сделано для повышения безопасности, предотвращая несанкционированный доступ к посторонним файлам вне директории расширения. - Генерация правильных URI
Этот параметр помогает преобразовать относительные пути (например, media/style.css) в полноценные абсолютные URI, которые корректно понимаются и обрабатываются внутри Webview.
В самом Webview потребуется внести немного больше изменений.
export class SidebarProvider implements vscode.WebviewViewProvider {
private _view?: vscode.WebviewView;
// Конструктор получает URI папки расширения
constructor(private readonly _extensionUri: vscode.Uri) {}
public resolveWebviewView(webviewView: vscode.WebviewView) {
this._view = webviewView;
// Настраиваем опции вебвью:
webviewView.webview.options = {
enableScripts: true, // Разрешаем выполнение JavaScript
localResourceRoots: [ // Указываем, откуда можно загружать ресурсы
this._extensionUri // Только из папки расширения
],
};
// Устанавливаем HTML-контент для вебвью
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
}
}
// Метод для получения HTML-контента
private _getHtmlForWebview(webview: vscode.Webview) {
// Формируем путь к HTML-файлу в папке расширения
const htmlPath = path.join(
this._extensionUri.fsPath,
'webApp',
'dist',
'index.html'
);
// Проверка существования файла
if (!fs.existsSync(htmlPath)) {
throw new Error(`HTML file not found at ${htmlPath}`);
}
// Читаем содержимое HTML-файла
let html = fs.readFileSync(htmlPath, 'utf8');
// Модифицируем HTML, добавляя базовый URL для корректной загрузки
ресурсов
html = html.replace(
'</head>',
`<base href="${webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'webApp', 'dist')
)}"/>
</head>`
);
return html; // Возвращаем модифицированный HTML
}
Давай разберём ключевые моменты:
Локальные ресурсы
- localResourceRoots — это важная опция, которая ограничивает доступ Webview только к файлам, расположенным в папке вашего расширения. Это мера безопасности.
- asWebviewUri — этот метод преобразует обычные файловые пути в специальные URI-адреса, которые понимает и корректно обрабатывает среда VS Code для вебвью.
Безопасность
- enableScripts: true — эта настройка явно разрешает выполнение JavaScript-кода внутри Webview. Без неё скрипты не будут работать.
- Все ресурсы загружаются только из разрешенных путей, что минимизирует риски безопасности.
Работа с HTML
- Базовый URL (<base href>) — крайне важен для корректной работы относительных путей к CSS и JavaScript файлам внутри вашего вебвью. Без него браузер не будет знать, откуда загружать эти ресурсы.
- HTML-файл вебвью читается напрямую из файловой системы вашего расширения.
Теперь пришло время запустить билд, а затем расширение в режиме отладки. Ура! У нас получилось добавить шаблонную страничку в сайдбар.
Ещё больше полезной информации ищи в нашем Telegram-канале.